Rewrote Frontend

This commit is contained in:
2022-09-03 23:32:20 +02:00
parent 0939525cf3
commit 16876e090d
98 changed files with 4995 additions and 1757 deletions

9
backend/.idea/cmake.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeSharedSettings">
<configurations>
<configuration PROFILE_NAME="Debug" ENABLED="true" CONFIG_NAME="Debug" GENERATION_OPTIONS="-G Ninja --toolchain C:\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_INSTALL_PREFIX=./cmake_install" />
<configuration PROFILE_NAME="Release" ENABLED="true" CONFIG_NAME="Release" GENERATION_OPTIONS="-G Ninja --toolchain C:\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_INSTALL_PREFIX=./cmake_install" />
</configurations>
</component>
</project>

View File

@@ -1,18 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="sqlite.db" uuid="6e8086dd-b853-422e-b48a-7c96a2403352">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/old_backend/sqlite.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="sqlite.db [2]" uuid="788293bd-abec-4b6b-a13e-26da21cb36dd">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/run/sqlite.db</jdbc-url>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../run/sqlite.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>

View File

@@ -1,4 +1,10 @@
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.21)
if (WIN32 AND (NOT VCPKG_TARGET_TRIPLET))
set(VCPKG_TARGET_TRIPLET x64-windows-static)
endif (WIN32 AND (NOT VCPKG_TARGET_TRIPLET))
#set(VCPKG_LIBRARY_LINKAGE static)
project(backend)
set(CMAKE_CXX_STANDARD 20)
@@ -13,13 +19,6 @@ add_executable(backend
src/db/db.h
src/db/db.cpp
src/db/model/Inode.cc
src/db/model/Inode.h
src/db/model/Tokens.cc
src/db/model/Tokens.h
src/db/model/User.cc
src/db/model/User.h
src/controllers/controllers.h
src/controllers/admin.cpp
src/controllers/fs.cpp
@@ -32,14 +31,23 @@ add_executable(backend
src/filters/filters.h
src/filters/filters.cpp
model/Inode.cc
model/Inode.h
model/Tokens.cc
model/Tokens.h
model/User.cc
model/User.h
SMTPMail-drogon-master/SMTPMail.cc
)
find_package(Drogon CONFIG REQUIRED)
find_package(mailio CONFIG REQUIRED)
find_package(lodepng CONFIG REQUIRED)
find_package(OpenSSL REQUIRED)
find_package(OpenCV CONFIG REQUIRED)
find_package(kubazip CONFIG REQUIRED)
find_path(JWT_CPP_INCLUDE_DIRS "jwt-cpp/base.h")
find_path(BOTAN_INCLUDE_DIRS "botan/botan.h")
find_path(QR_INCLUDE_DIRS "qrcodegen.hpp")
@@ -48,6 +56,10 @@ find_library(QR_LIBRARY nayuki-qr-code-generator)
target_include_directories(backend PRIVATE
src
model
shl
SMTPMail-drogon-master
${OpenCV_INCLUDE_DIRS}
${JWT_CPP_INCLUDE_DIRS}
${BOTAN_INCLUDE_DIRS}
${QR_INCLUDE_DIRS}
@@ -55,14 +67,17 @@ target_include_directories(backend PRIVATE
target_link_libraries(backend
Drogon::Drogon
mailio
lodepng
OpenSSL::SSL
kubazip::kubazip
${OpenCV_LIBS}
${BOTAN_LIBRARY}
${QR_LIBRARY}
)
install(TARGETS backend)
set_property(TARGET backend PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
install(TARGETS backend RUNTIME_DEPENDENCY_SET backend_deps DESTINATION .)
install(RUNTIME_DEPENDENCY_SET backend_deps)
if(NOT MSVC)
target_compile_options(backend PRIVATE
@@ -74,5 +89,6 @@ else()
endif(NOT MSVC)
if(WIN32)
target_link_libraries(backend iphlpapi)
target_compile_definitions(backend PRIVATE NOMINMAX _WIN32_WINNT=0x0A00)
endif()

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 ihmc3jn09hk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,79 @@
# SMTPMail-drogon
Simple Mail for the Drogon framework.
It is made as a plugin for the [drogon](https://github.com/an-tao/drogon) framework.
It can be included into the drogon build with little
modification of the class declaration.
## Updates
- **[ 13-06-2022 ] Fixed vulnerability issues reported by [Sam](https://snoopysecurity.github.io/about).**
- [ 13-09-2021 ] Added [HTML content support](https://github.com/ihmc3jn09hk/SMTPMail-drogon/pull/1).
- [ 23-12-2020 ] Added DNS support.
## Acknowledgement
* The implementation takes SMTPClient for Qt from [kelvins](https://github.com/kelvins/SMTPClient) as reference.
* There requires a delay SSL encryption from the Tcp-socket (named TcpClient in trantor/drogon) and the major
author of drogon [reponsed](https://github.com/an-tao/drogon/issues/346) quickly.
## Usage
Download to the plugin directory of the target drogon app, E.g. ~/drogon-app/plugins
```bash
$ git clone https://github.com/ihmc3jn09hk/SMTPMail-drogon.git
$ cp SMTPMail-drogon/SMTPMail.* ~/drogon-app/plugins
```
* _Be aware of add the plugin into the config.json. Set the "name" field to "SMTPMail"_
Add the reference header and get the plugin from the app(), E.g.
```c++
...
#include "../plugins/SMTPMail.h"
...
//Inside some function, E.g. A controller function.
...
//Send an email
auto *smtpmailPtr = app().getPlugin<SMTPMail>();
auto id = smtpmailPtr->sendEmail(
"127.0.0.1", //The server IP/DNS
587, //The port
"mailer@something.com", //Who send the email
"receiver@otherthing.com", //Send to whom
"Testing SMTPMail Function", //Email Subject/Title
"Hello from drogon plugin", //Content
"mailer@something.com", //Login user
"123456", //User password
false //Is HTML content
);
...
//Or get noted when email is sent
...
void callback(const std::string &msg)
{
LOG_INFO << msg; /*Output e.g. "EMail sent. ID : 96ESERVDDFH17588ECF0C7B00326E3"*/
/*Do whatever you like*/
}
...
auto *smtpmailPtr = app().getPlugin<SMTPMail>();
auto id = smtpmailPtr->sendEmail(
"127.0.0.1", //The server IP/DNS
587, //The port
"mailer@something.com", //Who send the email
"receiver@otherthing.com", //Send to whom
"Testing SMTPMail Function", //Email Subject/Title
"Hello from drogon plugin", //Content
"mailer@something.com", //Login user
"123456", //User password
false, //Is HTML content
callback //Callback
);
```
```bash
$ cd ~/drogon-app/build
$ make
```
## Licence
* Feel free to use, thanks to open-source.
* For the sake of concern on commercial usage, a simple licence is included in each of the files.

View File

@@ -0,0 +1,400 @@
/**
*
* SMTPMail.cc *
*
* This plugin is for SMTP mail delivery for the Drogon web-framework.
Implementation
* reference from the project "SMTPClient" with Qt5 by kelvins. Please check out
* https://github.com/kelvins/SMTPClient.
Feel free to use the code. For the sake of any concern, the following licence is
attached.
Copyright 2020 ihmc3jn09hk
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "SMTPMail.h"
#include <drogon/HttpAppFramework.h>
#include <drogon/utils/Utilities.h>
#include <trantor/net/EventLoopThread.h>
#include <trantor/net/TcpClient.h>
using namespace drogon;
using namespace trantor;
struct EMail {
enum states {
Init,
HandShake,
Tls,
Auth,
User,
Pass,
Mail,
Rcpt,
Data,
Body,
Quit,
Close
};
std::string m_from;
std::string m_to;
std::string m_subject;
std::string m_content;
std::string m_user;
std::string m_passwd;
states m_status;
std::string m_uuid;
bool m_isHTML{false};
std::shared_ptr<trantor::TcpClient> m_socket;
EMail(std::string from, std::string to, std::string subject,
std::string content, std::string user, std::string passwd, bool isHTML,
std::shared_ptr<trantor::TcpClient> socket)
: m_from(std::move(from)), m_to(std::move(to)),
m_subject(std::move(subject)), m_content(std::move(content)),
m_user(std::move(user)), m_passwd(std::move(passwd)),
m_socket(std::move(socket)), m_isHTML(isHTML),
m_uuid(drogon::utils::getUuid()) {
m_status = Init;
}
~EMail() = default;
static std::unordered_map<std::string, std::shared_ptr<EMail>>
m_emails; // Container for processing emails
};
std::unordered_map<std::string, std::shared_ptr<EMail>> EMail::m_emails;
void SMTPMail::initAndStart(const Json::Value &config) {
/// Initialize and start the plugin
LOG_INFO << "SMTPMail initialized and started";
}
void SMTPMail::shutdown() {
/// Shutdown the plugin
LOG_INFO << "STMPMail shutdown";
}
void messagesHandle(const trantor::TcpConnectionPtr &connPtr,
trantor::MsgBuffer *msg,
const std::shared_ptr<EMail> &email,
const std::function<void(const std::string &msg)> &cb) {
std::string receivedMsg;
while (msg->readableBytes() > 0) {
std::string buf(msg->peek(), msg->readableBytes());
receivedMsg.append(buf);
// LOG_INFO << buf;
msg->retrieveAll();
}
LOG_TRACE << "receive: " << receivedMsg;
std::string responseCode(receivedMsg.begin(), receivedMsg.begin() + 3);
// std::string responseMsg(receivedMsg.begin() + 4, receivedMsg.end());
if (email->m_status == EMail::Init && responseCode == "220") {
std::string outMsg;
trantor::MsgBuffer out;
outMsg.append("EHLO smtpclient.qw");
outMsg.append("\r\n");
out.append(outMsg.data(), outMsg.size());
connPtr->send(std::move(out));
email->m_status = EMail::HandShake;
} else if (email->m_status == EMail::HandShake && responseCode == "220") {
std::string outMsg;
trantor::MsgBuffer out;
outMsg.append("EHLO smtpclient.qw");
outMsg.append("\r\n");
out.append(outMsg.data(), outMsg.size());
connPtr->startClientEncryption(
[connPtr, out]() {
// LOG_TRACE << "SSL established";
connPtr->send(out);
},
false, false);
email->m_status = EMail::Auth;
} else if (email->m_status == EMail::HandShake && responseCode == "250") {
std::string outMsg;
trantor::MsgBuffer out;
outMsg.append("STARTTLS");
outMsg.append("\r\n");
out.append(outMsg.data(), outMsg.size());
connPtr->send(std::move(out));
email->m_status = EMail::HandShake;
} else if (email->m_status == EMail::Auth && responseCode == "250") {
trantor::MsgBuffer out;
std::string outMsg;
outMsg.append("AUTH LOGIN");
outMsg.append("\r\n");
out.append(outMsg.data(), outMsg.size());
connPtr->send(std::move(out));
email->m_status = EMail::User;
} else if (email->m_status == EMail::User && responseCode == "334") {
trantor::MsgBuffer out;
std::string outMsg;
std::string secret(email->m_user);
// outMsg.append(base64_encode(reinterpret_cast<const unsigned
// char*>(secret.c_str()), secret.length()));
outMsg.append(drogon::utils::base64Encode(
reinterpret_cast<const unsigned char *>(secret.c_str()),
secret.length()));
outMsg.append("\r\n");
out.append(outMsg.data(), outMsg.size());
connPtr->send(std::move(out));
email->m_status = EMail::Pass;
} else if (email->m_status == EMail::Pass && responseCode == "334") {
trantor::MsgBuffer out;
std::string outMsg;
std::string secret(email->m_passwd);
outMsg.append(drogon::utils::base64Encode(
reinterpret_cast<const unsigned char *>(secret.c_str()),
secret.length()));
outMsg.append("\r\n");
out.append(outMsg.data(), outMsg.size());
connPtr->send(std::move(out));
email->m_status = EMail::Mail;
} else if (email->m_status == EMail::Mail && responseCode == "235") {
trantor::MsgBuffer out;
std::string outMsg;
outMsg.append("MAIL FROM:<");
outMsg.append(email->m_from);
outMsg.append(">\r\n");
out.append(outMsg.data(), outMsg.size());
connPtr->send(std::move(out));
email->m_status = EMail::Rcpt;
} else if (email->m_status == EMail::Rcpt && responseCode == "250") {
trantor::MsgBuffer out;
std::string outMsg;
outMsg.append("RCPT TO:<");
outMsg.append(email->m_to);
outMsg.append(">\r\n");
out.append(outMsg.data(), outMsg.size());
connPtr->send(std::move(out));
email->m_status = EMail::Data;
} else if (email->m_status == EMail::Data && responseCode == "250") {
trantor::MsgBuffer out;
std::string outMsg;
outMsg.append("DATA");
outMsg.append("\r\n");
out.append(outMsg.data(), outMsg.size());
connPtr->send(std::move(out));
email->m_status = EMail::Body;
} else if (email->m_status == EMail::Body && responseCode == "354") {
trantor::MsgBuffer out;
std::string outMsg;
std::time_t t = std::time(nullptr);
char buf[100];
std::strftime(buf, 100, "%a, %d %b %Y %T %z", std::localtime(&t));
outMsg.append("To: " + email->m_to + "\r\n");
outMsg.append("From: " + email->m_from + "\r\n");
outMsg.append("Date: " + std::string(buf) + "\r\n");
if (email->m_isHTML) {
outMsg.append("Content-Type: text/html;\r\n");
}
outMsg.append("Subject: " + email->m_subject + "\r\n\r\n");
outMsg.append(email->m_content);
outMsg.append("\r\n.\r\n");
out.append(outMsg.data(), outMsg.size());
connPtr->send(std::move(out));
email->m_status = EMail::Quit;
} else if (email->m_status == EMail::Quit && responseCode == "250") {
trantor::MsgBuffer out;
std::string outMsg;
outMsg.append("QUIT");
outMsg.append("\r\n");
out.append(outMsg.data(), outMsg.size());
connPtr->send(std::move(out));
email->m_status = EMail::Close;
} else if (email->m_status == EMail::Close) {
/*Callback here for succeed delivery is probable*/
cb("EMail sent. ID : " + email->m_uuid);
return;
} else {
email->m_status = EMail::Close;
/*Callback here for notification is probable*/
cb(receivedMsg);
}
}
std::string
SMTPMail::sendEmail(const std::string &mailServer, const uint16_t &port,
const std::string &from, const std::string &to,
const std::string &subject, const std::string &content,
const std::string &user, const std::string &passwd,
bool isHTML,
const std::function<void(const std::string &)> &cb) {
if (mailServer.empty() || from.empty() || to.empty() || subject.empty() ||
user.empty() || passwd.empty()) {
LOG_WARN << "Invalid input(s) - "
<< "\nServer : " << mailServer << "\nPort : " << port
<< "\nfrom : " << from << "\nto : " << to
<< "\nsubject : " << subject << "\nuser : " << user
<< "\npasswd : " << passwd;
return {};
}
static auto hasLineBreak = [](const std::string &msg) {
if (std::string::npos != msg.find_first_of("\n") ||
std::string::npos != msg.find_first_of("\r")) {
return true;
}
return false;
};
if (hasLineBreak(from)) {
LOG_WARN << "Invalid \"FROM\" data : " << from;
return {};
}
if (hasLineBreak(to)) {
LOG_WARN << "Invalid \"TO\" data : " << to;
return {};
}
if (hasLineBreak(subject)) {
LOG_WARN << "Invalid \"SUBJECT\" data : " << subject.data();
return {};
}
LOG_TRACE << "New TcpClient : " << mailServer << ":" << port;
// Create the email
auto email = std::make_shared<EMail>(from, to, subject, content, user, passwd,
isHTML, nullptr);
auto resolver = app().getResolver();
resolver->resolve(
mailServer, [email, port, cb](const trantor::InetAddress &addr) {
constexpr size_t defaultLoopIdA = 10;
constexpr size_t defaultLoopIdB = 9;
auto loopA = app().getIOLoop(defaultLoopIdA);
auto loopB = app().getIOLoop(defaultLoopIdB);
if ( loopA == loopB ) {
LOG_WARN << "Please provide at least 2 threads for this plugin";
return;
}
auto loop = loopA->isInLoopThread() ? loopB : loopA;
assert(loop); // Should never be null
trantor::InetAddress addr_(addr.toIp(), port, false);
auto tcpSocket =
std::make_shared<trantor::TcpClient>(loop, addr_, "SMTPMail");
email->m_socket = tcpSocket;
std::weak_ptr<EMail> email_wptr = email;
EMail::m_emails.emplace(email->m_uuid,
email); // Assuming there is no uuid collision
tcpSocket->setConnectionCallback(
[email_wptr](const trantor::TcpConnectionPtr &connPtr) {
auto email_ptr = email_wptr.lock();
if (!email_ptr) {
LOG_WARN << "EMail pointer gone";
return;
}
if (connPtr->connected()) {
// send request;
LOG_TRACE << "Connection established!";
} else {
LOG_TRACE << "Connection disconnect";
EMail::m_emails.erase(
email_ptr->m_uuid); // Remove the email in list
// thisPtr->onError(std::string("ReqResult::NetworkFailure"));
}
});
tcpSocket->setConnectionErrorCallback([email_wptr]() {
auto email_ptr = email_wptr.lock();
if (!email_ptr) {
LOG_ERROR << "EMail pointer gone";
return;
}
// can't connect to server
LOG_ERROR << "Bad Server address";
EMail::m_emails.erase(email_ptr->m_uuid); // Remove the email in list
// thisPtr->onError(std::string("ReqResult::BadServerAddress"));
});
auto cb_(cb ? cb : [](const std::string &msg) {
LOG_INFO << "Default email callback : " << msg;
});
tcpSocket->setMessageCallback(
[email_wptr, cb_](const trantor::TcpConnectionPtr &connPtr,
trantor::MsgBuffer *msg) {
auto email_ptr = email_wptr.lock();
if (!email_ptr) {
LOG_ERROR << "EMail pointer gone";
return;
}
// email->m_socket->disconnect();
messagesHandle(connPtr, msg, email_ptr, cb_);
});
tcpSocket->connect(); // Start trying to send the email
});
return email->m_uuid;
}

View File

@@ -0,0 +1,63 @@
/**
*
* SMTPMail.h
*
* This plugin is for SMTP mail delievery for the Drogon web-framework.
Implementation
* reference from the project "SMTPClient" with Qt5 by kelvins. Please check out
* https://github.com/kelvins/SMTPClient.
Copyright 2020 ihmc3jn09hk
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <drogon/plugins/Plugin.h>
class SMTPMail : public drogon::Plugin<SMTPMail> {
public:
SMTPMail() = default;
/// This method must be called by drogon to initialize and start the plugin.
/// It must be implemented by the user.
void initAndStart(const Json::Value &config) override;
/// This method must be called by drogon to shutdown the plugin.
/// It must be implemented by the user.
void shutdown() override;
/** Send an email
* return : An ID of the email.
*/
std::string sendEmail(
const std::string
&mailServer, // Mail server address/dns E.g. 127.0.0.1/smtp.mail.com
const uint16_t &port, // Port E.g. 587
const std::string &from, // Send from whom E.g. drogon@gmail.com
const std::string &to, // Reciever E.g. drogon@yahoo.com
const std::string &subject, // The email title/subject
const std::string &content, // The email content.
const std::string &user, // User (Usually same as "from")
const std::string &passwd, // Password
bool isHTML, // content type
const std::function<void(const std::string &)> &cb = {}
// The callback for email sent notification
);
};

View File

@@ -6,7 +6,7 @@
*/
#include "Inode.h"
#include <drogon/utils/Utilities.h>
#include "drogon/utils/Utilities.h"
#include <string>
using namespace drogon;
@@ -19,6 +19,7 @@ const std::string Inode::Cols::_name = "name";
const std::string Inode::Cols::_parent_id = "parent_id";
const std::string Inode::Cols::_owner_id = "owner_id";
const std::string Inode::Cols::_size = "size";
const std::string Inode::Cols::_has_preview = "has_preview";
const std::string Inode::primaryKeyName = "id";
const bool Inode::hasPrimaryKey = true;
const std::string Inode::tableName = "inode";
@@ -29,7 +30,8 @@ const std::vector<typename Inode::MetaData> Inode::metaData_={
{"name","std::string","text",0,0,0,0},
{"parent_id","uint64_t","integer",8,0,0,0},
{"owner_id","uint64_t","integer",8,0,0,1},
{"size","uint64_t","integer",8,0,0,0}
{"size","uint64_t","integer",8,0,0,0},
{"has_preview","uint64_t","integer",8,0,0,1}
};
const std::string &Inode::getColumnName(size_t index) noexcept(false)
{
@@ -64,11 +66,15 @@ Inode::Inode(const Row &r, const ssize_t indexOffset) noexcept
{
size_=std::make_shared<uint64_t>(r["size"].as<uint64_t>());
}
if(!r["has_preview"].isNull())
{
hasPreview_=std::make_shared<uint64_t>(r["has_preview"].as<uint64_t>());
}
}
else
{
size_t offset = (size_t)indexOffset;
if(offset + 6 > r.size())
if(offset + 7 > r.size())
{
LOG_FATAL << "Invalid SQL result for this model";
return;
@@ -104,13 +110,18 @@ Inode::Inode(const Row &r, const ssize_t indexOffset) noexcept
{
size_=std::make_shared<uint64_t>(r[index].as<uint64_t>());
}
index = offset + 6;
if(!r[index].isNull())
{
hasPreview_=std::make_shared<uint64_t>(r[index].as<uint64_t>());
}
}
}
Inode::Inode(const Json::Value &pJson, const std::vector<std::string> &pMasqueradingVector) noexcept(false)
{
if(pMasqueradingVector.size() != 6)
if(pMasqueradingVector.size() != 7)
{
LOG_ERROR << "Bad masquerading vector";
return;
@@ -163,6 +174,14 @@ Inode::Inode(const Json::Value &pJson, const std::vector<std::string> &pMasquera
size_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[5]].asUInt64());
}
}
if(!pMasqueradingVector[6].empty() && pJson.isMember(pMasqueradingVector[6]))
{
dirtyFlag_[6] = true;
if(!pJson[pMasqueradingVector[6]].isNull())
{
hasPreview_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[6]].asUInt64());
}
}
}
Inode::Inode(const Json::Value &pJson) noexcept(false)
@@ -215,12 +234,20 @@ Inode::Inode(const Json::Value &pJson) noexcept(false)
size_=std::make_shared<uint64_t>((uint64_t)pJson["size"].asUInt64());
}
}
if(pJson.isMember("has_preview"))
{
dirtyFlag_[6]=true;
if(!pJson["has_preview"].isNull())
{
hasPreview_=std::make_shared<uint64_t>((uint64_t)pJson["has_preview"].asUInt64());
}
}
}
void Inode::updateByMasqueradedJson(const Json::Value &pJson,
const std::vector<std::string> &pMasqueradingVector) noexcept(false)
{
if(pMasqueradingVector.size() != 6)
if(pMasqueradingVector.size() != 7)
{
LOG_ERROR << "Bad masquerading vector";
return;
@@ -272,6 +299,14 @@ void Inode::updateByMasqueradedJson(const Json::Value &pJson,
size_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[5]].asUInt64());
}
}
if(!pMasqueradingVector[6].empty() && pJson.isMember(pMasqueradingVector[6]))
{
dirtyFlag_[6] = true;
if(!pJson[pMasqueradingVector[6]].isNull())
{
hasPreview_=std::make_shared<uint64_t>((uint64_t)pJson[pMasqueradingVector[6]].asUInt64());
}
}
}
void Inode::updateByJson(const Json::Value &pJson) noexcept(false)
@@ -323,6 +358,14 @@ void Inode::updateByJson(const Json::Value &pJson) noexcept(false)
size_=std::make_shared<uint64_t>((uint64_t)pJson["size"].asUInt64());
}
}
if(pJson.isMember("has_preview"))
{
dirtyFlag_[6] = true;
if(!pJson["has_preview"].isNull())
{
hasPreview_=std::make_shared<uint64_t>((uint64_t)pJson["has_preview"].asUInt64());
}
}
}
const uint64_t &Inode::getValueOfId() const noexcept
@@ -452,6 +495,23 @@ void Inode::setSizeToNull() noexcept
dirtyFlag_[5] = true;
}
const uint64_t &Inode::getValueOfHasPreview() const noexcept
{
const static uint64_t defaultValue = uint64_t();
if(hasPreview_)
return *hasPreview_;
return defaultValue;
}
const std::shared_ptr<uint64_t> &Inode::getHasPreview() const noexcept
{
return hasPreview_;
}
void Inode::setHasPreview(const uint64_t &pHasPreview) noexcept
{
hasPreview_ = std::make_shared<uint64_t>(pHasPreview);
dirtyFlag_[6] = true;
}
void Inode::updateId(const uint64_t id)
{
id_ = std::make_shared<uint64_t>(id);
@@ -464,7 +524,8 @@ const std::vector<std::string> &Inode::insertColumns() noexcept
"name",
"parent_id",
"owner_id",
"size"
"size",
"has_preview"
};
return inCols;
}
@@ -526,6 +587,17 @@ void Inode::outputArgs(drogon::orm::internal::SqlBinder &binder) const
binder << nullptr;
}
}
if(dirtyFlag_[6])
{
if(getHasPreview())
{
binder << getValueOfHasPreview();
}
else
{
binder << nullptr;
}
}
}
const std::vector<std::string> Inode::updateColumns() const
@@ -551,6 +623,10 @@ const std::vector<std::string> Inode::updateColumns() const
{
ret.push_back(getColumnName(5));
}
if(dirtyFlag_[6])
{
ret.push_back(getColumnName(6));
}
return ret;
}
@@ -611,6 +687,17 @@ void Inode::updateArgs(drogon::orm::internal::SqlBinder &binder) const
binder << nullptr;
}
}
if(dirtyFlag_[6])
{
if(getHasPreview())
{
binder << getValueOfHasPreview();
}
else
{
binder << nullptr;
}
}
}
Json::Value Inode::toJson() const
{
@@ -663,6 +750,14 @@ Json::Value Inode::toJson() const
{
ret["size"]=Json::Value();
}
if(getHasPreview())
{
ret["has_preview"]=(Json::UInt64)getValueOfHasPreview();
}
else
{
ret["has_preview"]=Json::Value();
}
return ret;
}
@@ -670,7 +765,7 @@ Json::Value Inode::toMasqueradedJson(
const std::vector<std::string> &pMasqueradingVector) const
{
Json::Value ret;
if(pMasqueradingVector.size() == 6)
if(pMasqueradingVector.size() == 7)
{
if(!pMasqueradingVector[0].empty())
{
@@ -738,6 +833,17 @@ Json::Value Inode::toMasqueradedJson(
ret[pMasqueradingVector[5]]=Json::Value();
}
}
if(!pMasqueradingVector[6].empty())
{
if(getHasPreview())
{
ret[pMasqueradingVector[6]]=(Json::UInt64)getValueOfHasPreview();
}
else
{
ret[pMasqueradingVector[6]]=Json::Value();
}
}
return ret;
}
LOG_ERROR << "Masquerade failed";
@@ -789,6 +895,14 @@ Json::Value Inode::toMasqueradedJson(
{
ret["size"]=Json::Value();
}
if(getHasPreview())
{
ret["has_preview"]=(Json::UInt64)getValueOfHasPreview();
}
else
{
ret["has_preview"]=Json::Value();
}
return ret;
}
@@ -834,13 +948,23 @@ bool Inode::validateJsonForCreation(const Json::Value &pJson, std::string &err)
if(!validJsonOfField(5, "size", pJson["size"], err, true))
return false;
}
if(pJson.isMember("has_preview"))
{
if(!validJsonOfField(6, "has_preview", pJson["has_preview"], err, true))
return false;
}
else
{
err="The has_preview column cannot be null";
return false;
}
return true;
}
bool Inode::validateMasqueradedJsonForCreation(const Json::Value &pJson,
const std::vector<std::string> &pMasqueradingVector,
std::string &err)
{
if(pMasqueradingVector.size() != 6)
if(pMasqueradingVector.size() != 7)
{
err = "Bad masquerading vector";
return false;
@@ -904,6 +1028,19 @@ bool Inode::validateMasqueradedJsonForCreation(const Json::Value &pJson,
return false;
}
}
if(!pMasqueradingVector[6].empty())
{
if(pJson.isMember(pMasqueradingVector[6]))
{
if(!validJsonOfField(6, pMasqueradingVector[6], pJson[pMasqueradingVector[6]], err, true))
return false;
}
else
{
err="The " + pMasqueradingVector[6] + " column cannot be null";
return false;
}
}
}
catch(const Json::LogicError &e)
{
@@ -949,13 +1086,18 @@ bool Inode::validateJsonForUpdate(const Json::Value &pJson, std::string &err)
if(!validJsonOfField(5, "size", pJson["size"], err, false))
return false;
}
if(pJson.isMember("has_preview"))
{
if(!validJsonOfField(6, "has_preview", pJson["has_preview"], err, false))
return false;
}
return true;
}
bool Inode::validateMasqueradedJsonForUpdate(const Json::Value &pJson,
const std::vector<std::string> &pMasqueradingVector,
std::string &err)
{
if(pMasqueradingVector.size() != 6)
if(pMasqueradingVector.size() != 7)
{
err = "Bad masquerading vector";
return false;
@@ -996,6 +1138,11 @@ bool Inode::validateMasqueradedJsonForUpdate(const Json::Value &pJson,
if(!validJsonOfField(5, pMasqueradingVector[5], pJson[pMasqueradingVector[5]], err, false))
return false;
}
if(!pMasqueradingVector[6].empty() && pJson.isMember(pMasqueradingVector[6]))
{
if(!validJsonOfField(6, pMasqueradingVector[6], pJson[pMasqueradingVector[6]], err, false))
return false;
}
}
catch(const Json::LogicError &e)
{
@@ -1086,6 +1233,18 @@ bool Inode::validJsonOfField(size_t index,
return false;
}
break;
case 6:
if(pJson.isNull())
{
err="The " + fieldName + " column cannot be null";
return false;
}
if(!pJson.isUInt64())
{
err="Type error in the "+fieldName+" field";
return false;
}
break;
default:
err="Internal error in the server";
return false;

View File

@@ -6,17 +6,17 @@
*/
#pragma once
#include <drogon/orm/Result.h>
#include <drogon/orm/Row.h>
#include <drogon/orm/Field.h>
#include <drogon/orm/SqlBinder.h>
#include <drogon/orm/Mapper.h>
#include "drogon/orm/Result.h"
#include "drogon/orm/Row.h"
#include "drogon/orm/Field.h"
#include "drogon/orm/SqlBinder.h"
#include "drogon/orm/Mapper.h"
#ifdef __cpp_impl_coroutine
#include <drogon/orm/CoroMapper.h>
#endif
#include <trantor/utils/Date.h>
#include <trantor/utils/Logger.h>
#include <json/json.h>
#include "trantor/utils/Date.h"
#include "trantor/utils/Logger.h"
#include "json/json.h"
#include <string>
#include <memory>
#include <vector>
@@ -48,6 +48,7 @@ class Inode
static const std::string _parent_id;
static const std::string _owner_id;
static const std::string _size;
static const std::string _has_preview;
};
const static int primaryKeyNumber;
@@ -151,8 +152,16 @@ class Inode
void setSize(const uint64_t &pSize) noexcept;
void setSizeToNull() noexcept;
/** For column has_preview */
///Get the value of the column has_preview, returns the default value if the column is null
const uint64_t &getValueOfHasPreview() const noexcept;
///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null
const std::shared_ptr<uint64_t> &getHasPreview() const noexcept;
///Set the value of the column has_preview
void setHasPreview(const uint64_t &pHasPreview) noexcept;
static size_t getColumnNumber() noexcept { return 6; }
static size_t getColumnNumber() noexcept { return 7; }
static const std::string &getColumnName(size_t index) noexcept(false);
Json::Value toJson() const;
@@ -175,6 +184,7 @@ class Inode
std::shared_ptr<uint64_t> parentId_;
std::shared_ptr<uint64_t> ownerId_;
std::shared_ptr<uint64_t> size_;
std::shared_ptr<uint64_t> hasPreview_;
struct MetaData
{
const std::string colName_;
@@ -186,7 +196,7 @@ class Inode
const bool notNull_;
};
static const std::vector<MetaData> metaData_;
bool dirtyFlag_[6]={ false };
bool dirtyFlag_[7]={ false };
public:
static const std::string &sqlForFindingByPrimaryKey()
{
@@ -229,6 +239,11 @@ class Inode
sql += "size,";
++parametersCount;
}
if(dirtyFlag_[6])
{
sql += "has_preview,";
++parametersCount;
}
if(parametersCount > 0)
{
sql[sql.length()-1]=')';
@@ -261,6 +276,11 @@ class Inode
{
sql.append("?,");
}
if(dirtyFlag_[6])
{
sql.append("?,");
}
if(parametersCount > 0)
{

View File

@@ -6,7 +6,7 @@
*/
#include "Tokens.h"
#include <drogon/utils/Utilities.h>
#include "drogon/utils/Utilities.h"
#include <string>
using namespace drogon;

View File

@@ -6,17 +6,17 @@
*/
#pragma once
#include <drogon/orm/Result.h>
#include <drogon/orm/Row.h>
#include <drogon/orm/Field.h>
#include <drogon/orm/SqlBinder.h>
#include <drogon/orm/Mapper.h>
#include "drogon/orm/Result.h"
#include "drogon/orm/Row.h"
#include "drogon/orm/Field.h"
#include "drogon/orm/SqlBinder.h"
#include "drogon/orm/Mapper.h"
#ifdef __cpp_impl_coroutine
#include <drogon/orm/CoroMapper.h>
#endif
#include <trantor/utils/Date.h>
#include <trantor/utils/Logger.h>
#include <json/json.h>
#include "trantor/utils/Date.h"
#include "trantor/utils/Logger.h"
#include "json/json.h"
#include <string>
#include <memory>
#include <vector>

View File

@@ -6,7 +6,7 @@
*/
#include "User.h"
#include <drogon/utils/Utilities.h>
#include "drogon/utils/Utilities.h"
#include <string>
using namespace drogon;

View File

@@ -6,17 +6,17 @@
*/
#pragma once
#include <drogon/orm/Result.h>
#include <drogon/orm/Row.h>
#include <drogon/orm/Field.h>
#include <drogon/orm/SqlBinder.h>
#include <drogon/orm/Mapper.h>
#include "drogon/orm/Result.h"
#include "drogon/orm/Row.h"
#include "drogon/orm/Field.h"
#include "drogon/orm/SqlBinder.h"
#include "drogon/orm/Mapper.h"
#ifdef __cpp_impl_coroutine
#include <drogon/orm/CoroMapper.h>
#endif
#include <trantor/utils/Date.h>
#include <trantor/utils/Logger.h>
#include <json/json.h>
#include "trantor/utils/Date.h"
#include "trantor/utils/Logger.h"
#include "json/json.h"
#include <string>
#include <memory>
#include <vector>

View File

@@ -0,0 +1,68 @@
// Copyright (C) 2022 Andrei Avram
#ifndef MSD_CHANNEL_BLOCKING_ITERATOR_HPP_
#define MSD_CHANNEL_BLOCKING_ITERATOR_HPP_
#include <iterator>
#include <mutex>
namespace msd {
/**
* @brief An iterator that block the current thread,
* waiting to fetch elements from the channel.
*
* Used to implement channel range-based for loop.
*
* @tparam Channel Instance of channel.
*/
template <typename channel>
class blocking_iterator {
public:
using value_type = typename channel::value_type;
explicit blocking_iterator(channel& ch) : ch_{ch} {}
/**
* Advances to next element in the channel.
*/
blocking_iterator<channel> operator++() const noexcept { return *this; }
/**
* Returns an element from the channel.
*/
value_type operator*() const
{
value_type value;
value << ch_;
return value;
}
/**
* Makes iteration continue until the channel is closed and empty.
*/
bool operator!=(blocking_iterator<channel>) const
{
std::unique_lock<std::mutex> lock{ch_.mtx_};
ch_.waitBeforeRead(lock);
return !(ch_.closed() && ch_.empty());
}
private:
channel& ch_;
};
} // namespace msd
/**
* @brief Output iterator specialization
*/
template <typename T>
struct std::iterator_traits<msd::blocking_iterator<T>> {
using value_type = typename msd::blocking_iterator<T>::value_type;
using iterator_category = std::output_iterator_tag;
};
#endif // MSD_CHANNEL_BLOCKING_ITERATOR_HPP_

130
backend/shl/msd/channel.hpp Normal file
View File

@@ -0,0 +1,130 @@
// Copyright (C) 2022 Andrei Avram
#ifndef MSD_CHANNEL_HPP_
#define MSD_CHANNEL_HPP_
#include <atomic>
#include <condition_variable>
#include <cstdlib>
#include <mutex>
#include <queue>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include "blocking_iterator.hpp"
namespace msd {
#if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L))
#define NODISCARD [[nodiscard]]
#else
#define NODISCARD
#endif
namespace detail {
template <typename T>
struct remove_cvref {
using type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
};
template <typename T>
using remove_cvref_t = typename remove_cvref<T>::type;
} // namespace detail
/**
* @brief Exception thrown if trying to write on closed channel.
*/
class closed_channel : public std::runtime_error {
public:
explicit closed_channel(const char* msg) : std::runtime_error{msg} {}
};
/**
* @brief Thread-safe container for sharing data between threads.
*
* Implements a blocking input iterator.
*
* @tparam T The type of the elements.
*/
template <typename T>
class channel {
public:
using value_type = T;
using iterator = blocking_iterator<channel<T>>;
using size_type = std::size_t;
/**
* Creates a new channel.
*
* @param capacity Number of elements the channel can store before blocking.
*/
explicit constexpr channel(size_type capacity = 0);
/**
* Pushes an element into the channel.
*
* @throws closed_channel if channel is closed.
*/
template <typename Type>
friend void operator>>(Type&&, channel<detail::remove_cvref_t<Type>>&);
/**
* Pops an element from the channel.
*
* @tparam Type The type of the elements
*/
template <typename Type>
friend void operator<<(Type&, channel<Type>&);
/**
* Returns the number of elements in the channel.
*/
NODISCARD inline size_type constexpr size() const noexcept;
/**
* Returns true if there are no elements in channel.
*/
NODISCARD inline bool constexpr empty() const noexcept;
/**
* Closes the channel.
*/
inline void close() noexcept;
/**
* Returns true if the channel is closed.
*/
NODISCARD inline bool closed() const noexcept;
/**
* Iterator
*/
iterator begin() noexcept;
iterator end() noexcept;
/**
* Channel cannot be copied or moved.
*/
channel(const channel&) = delete;
channel& operator=(const channel&) = delete;
channel(channel&&) = delete;
channel& operator=(channel&&) = delete;
virtual ~channel() = default;
private:
const size_type cap_;
std::queue<T> queue_;
std::mutex mtx_;
std::condition_variable cnd_;
std::atomic<bool> is_closed_{false};
inline void waitBeforeRead(std::unique_lock<std::mutex>&);
friend class blocking_iterator<channel>;
};
#include "channel_impl.hpp"
} // namespace msd
#endif // MSD_CHANNEL_HPP_

View File

@@ -0,0 +1,87 @@
// Copyright (C) 2022 Andrei Avram
template <typename T>
constexpr channel<T>::channel(const size_type capacity) : cap_{capacity}
{
}
template <typename T>
void operator>>(T&& in, channel<detail::remove_cvref_t<T>>& ch)
{
if (ch.closed()) {
throw closed_channel{"cannot write on closed channel"};
}
std::unique_lock<std::mutex> lock{ch.mtx_};
if (ch.cap_ > 0 && ch.queue_.size() == ch.cap_) {
ch.cnd_.wait(lock, [&ch]() { return ch.queue_.size() < ch.cap_; });
}
ch.queue_.push(std::forward<T>(in));
ch.cnd_.notify_one();
}
template <typename T>
void operator<<(T& out, channel<T>& ch)
{
if (ch.closed() && ch.empty()) {
return;
}
{
std::unique_lock<std::mutex> lock{ch.mtx_};
ch.waitBeforeRead(lock);
if (ch.queue_.size() > 0) {
out = std::move(ch.queue_.front());
ch.queue_.pop();
}
}
ch.cnd_.notify_one();
}
template <typename T>
constexpr typename channel<T>::size_type channel<T>::size() const noexcept
{
return queue_.size();
}
template <typename T>
constexpr bool channel<T>::empty() const noexcept
{
return queue_.empty();
}
template <typename T>
void channel<T>::close() noexcept
{
is_closed_.store(true);
cnd_.notify_all();
}
template <typename T>
bool channel<T>::closed() const noexcept
{
return is_closed_.load();
}
template <typename T>
blocking_iterator<channel<T>> channel<T>::begin() noexcept
{
return blocking_iterator<channel<T>>{*this};
}
template <typename T>
blocking_iterator<channel<T>> channel<T>::end() noexcept
{
return blocking_iterator<channel<T>>{*this};
}
template <typename T>
void channel<T>::waitBeforeRead(std::unique_lock<std::mutex>& lock)
{
cnd_.wait(lock, [this] { return queue_.size() > 0 || closed(); });
}

View File

@@ -55,14 +55,15 @@ namespace api {
void admin::delete_user(req_type req, cbk_type cbk) {
Json::Value& json = *req->jsonObject();
msd::channel<std::string> chan;
try {
uint64_t user_id = dto::json_get<uint64_t>(json, "user").value();
db::MapperUser user_mapper(drogon::app().getDbClient());
auto user = user_mapper.findByPrimaryKey(user_id);
auth::revoke_all(user);
fs::delete_node(fs::get_node(user.getValueOfRootId()).value(), true);
user_mapper.deleteOne(user);
fs::delete_node(fs::get_node(user.getValueOfRootId()).value(), chan, true);
user_mapper.deleteOne(user);
cbk(dto::Responses::get_success_res());
} catch (const std::exception&) {
cbk(dto::Responses::get_badreq_res("Validation error"));

View File

@@ -5,7 +5,7 @@
#include <botan/base32.h>
#include <botan/base64.h>
#include <qrcodegen.hpp>
#include <lodepng.h>
#include <opencv2/opencv.hpp>
#include "controllers/controllers.h"
#include "db/db.h"
@@ -24,21 +24,14 @@ std::string create_totp_qrcode(const db::User& user, const std::string& b32_secr
const int mod_count = code.getSize();
const int row_size = qrcode_pixel_size * mod_count;
std::vector<uint8_t> secret, image, row;
row.reserve(row_size);
image.reserve(row_size * row_size);
cv::Mat image(mod_count, mod_count, CV_8UC1), scaled_image;
std::vector<uint8_t> image_encoded;
for (int y = 0; y < mod_count; y++) for (int x = 0; x < mod_count; x++)
image.at<uint8_t>(x, y) = code.getModule(x, y) ? 0 : 0xff;
cv::resize(image, scaled_image, cv::Size(), qrcode_pixel_size, qrcode_pixel_size, cv::INTER_NEAREST);
cv::imencode(".png", scaled_image, image_encoded);
for (int y = 0; y < mod_count; y++) {
row.clear();
for (int x = 0; x < mod_count; x++)
row.insert(row.end(), qrcode_pixel_size, code.getModule(x, y) ? 0 : 0xff);
for (int i = 0; i < qrcode_pixel_size; i++)
image.insert(image.end(), row.begin(), row.end());
}
lodepng::encode(secret, image, row_size, row_size, LCT_GREY, 8);
return "data:image/png;base64," + Botan::base64_encode(secret);
return "data:image/png;base64," + Botan::base64_encode(image_encoded);
}
namespace api {
@@ -64,7 +57,7 @@ namespace api {
std::string code = create_totp_qrcode(user, b32_secret);
cbk(dto::Responses::get_tfa_setup_res(b32_secret, code));
}
} catch (const std::exception&) {
} catch (const std::exception& e) {
cbk(dto::Responses::get_badreq_res("Validation error"));
}
}

View File

@@ -17,7 +17,7 @@
#include <jwt-cpp/traits/kazuho-picojson/traits.h>
#include <jwt-cpp/jwt.h>
#include <mailio/smtp.hpp>
#include <SMTPMail.h>
#include "controllers/controllers.h"
#include "db/db.h"
@@ -43,15 +43,17 @@ namespace api {
char totp[16];
std::snprintf(totp, 16, "%06d", Botan::TOTP(Botan::OctetString(totp_secret)).generate_totp(t));
mailio::message msg;
msg.from(mailio::mail_address("Fileserver", "fileserver@mattv.de"));
msg.add_recipient(mailio::mail_address(user.getValueOfName(), user.getValueOfName()));
msg.subject("Subject: Fileserver - Email 2fa code");
msg.content("Your code is: " + std::string(totp) +"\r\nIt is valid for 5 Minutes");
mailio::smtps conn("mail.mattv.de", 587);
conn.authenticate("no-reply@mattv.de", "noreplyLONGPASS123", mailio::smtps::auth_method_t::START_TLS);
conn.submit(msg);
drogon::app().getPlugin<SMTPMail>()->sendEmail(
"mail.mattv.de",
587,
"fileserver@mattv.de",
user.getValueOfName(),
"MFileserver - Email 2fa code",
"Your code is: " + std::string(totp) +"\r\nIt is valid for 5 Minutes",
"no-reply@mattv.de",
"noreplyLONGPASS123",
false
);
}
std::string auth::get_token(const db::User& user) {

View File

@@ -1,11 +1,11 @@
#ifndef BACKEND_CONTROLLERS_H
#define BACKEND_CONTROLLERS_H
#include <drogon/drogon.h>
#include <drogon/utils/coroutine.h>
#include <botan/rng.h>
#include <coroutine>
#include <variant>
#include <drogon/drogon.h>
#include <botan/rng.h>
#include <msd/channel.hpp>
#include "db/db.h"
using req_type = const drogon::HttpRequestPtr&;
@@ -86,14 +86,32 @@ public:
METHOD_ADD(fs::create_node_req<true>, "/createFile", drogon::Post, "Login");
METHOD_ADD(fs::delete_node_req, "/delete/{}", drogon::Post, "Login");
METHOD_ADD(fs::upload, "/upload/{}", drogon::Post, "Login");
METHOD_ADD(fs::create_zip, "/create_zip", drogon::Post, "Login");
METHOD_ADD(fs::download, "/download", drogon::Post, "Login");
METHOD_ADD(fs::download_multi, "/download_multi", drogon::Post, "Login");
METHOD_ADD(fs::download_preview, "/download_preview/{}", drogon::Get, "Login");
METHOD_ADD(fs::download_base64, "/download_base64/{}", drogon::Get, "Login");
METHOD_ADD(fs::get_type, "/get_type/{}", drogon::Get, "Login");
METHOD_LIST_END
enum class create_node_error {
INVALID_NAME,
INVALID_PARENT,
FILE_PARENT
};
struct mutex_stream {
std::stringstream ss;
std::mutex mutex;
bool done = false;
};
static std::optional<db::INode> get_node(uint64_t node);
static std::optional<db::INode> get_node_and_validate(const db::User& user, uint64_t node);
static std::vector<db::INode> get_children(const db::INode& parent);
static std::variant<db::INode, std::string> create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &parent, bool force = false);
static void delete_node(db::INode node, bool allow_root = false);
static std::variant<db::INode, fs::create_node_error, std::tuple<bool, uint64_t>>
create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &parent, bool force = false);
static void delete_node(db::INode node, msd::channel<std::string>& chan, bool allow_root = false);
void root(req_type, cbk_type);
@@ -102,7 +120,12 @@ public:
template<bool file> void create_node_req(req_type req, cbk_type cbk);
void delete_node_req(req_type, cbk_type, uint64_t node);
void upload(req_type, cbk_type, uint64_t node);
void create_zip(req_type, cbk_type);
void download(req_type, cbk_type);
void download_multi(req_type, cbk_type);
void download_preview(req_type, cbk_type, uint64_t node);
void download_base64(req_type, cbk_type, uint64_t node);
void get_type(req_type, cbk_type, uint64_t node);
};
class user : public drogon::HttpController<user> {

View File

@@ -3,12 +3,100 @@
#pragma ide diagnostic ignored "readability-convert-member-functions-to-static"
#include <filesystem>
#include <unordered_map>
#include <fstream>
#include <opencv2/opencv.hpp>
#include <botan/base64.h>
#include <trantor/net/EventLoopThread.h>
#include <zip/zip.h>
#include "controllers.h"
#include "dto/dto.h"
char windows_invalid_chars[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F<>:\"/\\|";
std::string generate_path(db::INode node) {
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types#common_image_file_types
const std::unordered_map<std::string, std::string> mime_type_map = {
{ ".apng" , "image/apng" },
{ ".avif" , "image/avif" },
{ ".bmp" , "image/bmp" },
{ ".gif" , "image/gif" },
{ ".jpg" , "image/jpeg" },
{ ".jpeg" , "image/jpeg" },
{ ".jfif" , "image/jpeg" },
{ ".pjpeg", "image/jpeg" },
{ ".pjp" , "image/jpeg" },
{ ".png" , "image/png" },
{ ".svg" , "image/svg" },
{ ".webp" , "image/webp" },
{ ".aac" , "audio/aac" },
{ ".flac" , "audio/flac" },
{ ".mp3" , "audio/mp3" },
{ ".m4a" , "audio/mp4" },
{ ".oga" , "audio/ogg" },
{ ".ogg" , "audio/ogg" },
{ ".wav" , "audio/wav" },
{ ".3gp" , "video/3gpp" },
{ ".mpg" , "video/mpeg" },
{ ".mpeg" , "video/mpeg" },
{ ".mp4" , "video/mp4" },
{ ".m4v" , "video/mp4" },
{ ".m4p" , "video/mp4" },
{ ".ogv" , "video/ogg" },
{ ".mov" , "video/quicktime" },
{ ".webm" , "video/webm" },
{ ".mkv" , "video/x-matroska" },
{ ".mk3d" , "video/x-matroska" },
{ ".mks" , "video/x-matroska" },
};
uint64_t next_temp_id = 0;
std::unordered_map<std::string, std::string> zip_to_temp_map;
std::unordered_map<std::string, std::tuple<std::string, uint64_t, uint64_t>> in_progress_zips;
trantor::EventLoop* get_zip_loop() {
static bool init_done = false;
static trantor::EventLoopThread loop("ZipEventLoop");
if (!init_done) {
init_done = true;
loop.run();
loop.getLoop()->runEvery(30*60, []{
for (const auto& entry : std::filesystem::directory_iterator("./temp")) {
if (!entry.is_regular_file()) continue;
const std::string file_name = "./temp/" + entry.path().filename().string();
const auto& progress_pos = std::find_if(in_progress_zips.begin(), in_progress_zips.end(),
[&file_name](const std::pair<std::string, std::tuple<std::string, uint64_t, uint64_t>>& entry) {
return std::get<0>(entry.second) == file_name;
}
);
if (progress_pos != in_progress_zips.end()) return;
const auto& zip_map_pos = std::find_if(zip_to_temp_map.begin(), zip_to_temp_map.end(),
[&file_name](const std::pair<std::string, std::string>& entry){
return entry.second == file_name;
}
);
if (zip_map_pos != zip_to_temp_map.end()) return;
std::filesystem::remove(entry.path());
}
});
}
return loop.getLoop();
}
trantor::EventLoop* get_delete_loop() {
static bool init_done = false;
static trantor::EventLoopThread loop("DeleteEventLoop");
if (!init_done) {
init_done = true;
loop.run();
}
return loop.getLoop();
}
void generate_path(db::INode node, std::string& str) {
db::MapperInode inode_mapper(drogon::app().getDbClient());
std::stack<db::INode> path;
path.push(node);
@@ -16,14 +104,101 @@ std::string generate_path(db::INode node) {
node = inode_mapper.findByPrimaryKey(node.getValueOfParentId());
path.push(node);
}
std::stringstream ss;
while (!path.empty()) {
const db::INode& seg = path.top();
ss << seg.getValueOfName();
if (seg.getValueOfIsFile() == 0) ss << '/';
str += seg.getValueOfName();
if (seg.getValueOfIsFile() == 0) str += "/";
path.pop();
}
return ss.str();
}
Json::Value generate_path(db::INode node) {
Json::Value segments = Json::Value(Json::ValueType::arrayValue);
db::MapperInode inode_mapper(drogon::app().getDbClient());
std::stack<db::INode> path;
path.push(node);
while (node.getParentId() != nullptr) {
node = inode_mapper.findByPrimaryKey(node.getValueOfParentId());
path.push(node);
}
while (!path.empty()) {
const db::INode& seg = path.top();
if (seg.getParentId() == nullptr) {
Json::Value json_seg;
json_seg["path"] = "/";
json_seg["node"] = seg.getValueOfId();
segments.append(json_seg);
} else {
Json::Value json_seg;
json_seg["path"] = seg.getValueOfName();
json_seg["node"] = seg.getValueOfId();
segments.append(json_seg);
if (seg.getValueOfIsFile() == 0) {
json_seg.removeMember("node");
json_seg["path"] = "/";
segments.append(json_seg);
}
}
path.pop();
}
Json::Value resp;
resp["segments"] = segments;
return resp;
}
uint64_t calc_total_size(const db::INode& base) {
uint64_t size = 0;
std::stack<db::INode> queue;
queue.push(base);
while (!queue.empty()) {
const db::INode& node = queue.top();
if (node.getValueOfIsFile() == 0) {
auto children = api::fs::get_children(node);
queue.pop();
for (const auto& child : children) {
if (child.getValueOfIsFile() == 0) queue.push(child);
else if (child.getSize()) size += child.getValueOfSize();
}
} else {
size += node.getValueOfSize();
queue.pop();
}
}
return size;
}
void add_to_zip(struct zip_t* zip, const std::string& key, const db::INode& node, const std::string& path) {
if (node.getValueOfIsFile() == 0) {
std::string new_path = path + node.getValueOfName() + "/";
zip_entry_opencasesensitive(zip, new_path.c_str());
zip_entry_close(zip);
auto children = api::fs::get_children(node);
for (const auto& child : children)
add_to_zip(zip, key, child, new_path);
} else {
zip_entry_opencasesensitive(zip, (path + node.getValueOfName()).c_str());
std::ifstream file("./files/" + std::to_string(node.getValueOfId()), std::ifstream::binary);
std::vector<char> buffer(64*1024);
while (!file.eof()) {
file.read(buffer.data(), (std::streamsize)buffer.size());
auto read = file.gcount();
zip_entry_write(zip, buffer.data(), read);
std::get<1>(in_progress_zips[key]) += read;
}
zip_entry_close(zip);
}
}
template<typename InputIt>
std::string join_string(InputIt first, InputIt last, const std::string& separator = ",") {
std::ostringstream result;
if (first != last) {
result << *first;
while (++first != last) {
result << separator << *first;
}
}
return result.str();
}
namespace api {
@@ -48,26 +223,31 @@ namespace api {
return inode_mapper.findBy(db::Criteria(db::INode::Cols::_parent_id, db::CompareOps::EQ, parent.getValueOfId()));
}
std::variant<db::INode, std::string> fs::create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &parent, bool force) {
std::variant<db::INode, fs::create_node_error, std::tuple<bool, uint64_t>>
fs::create_node(std::string name, const db::User& owner, bool file, const std::optional<uint64_t> &parent, bool force) {
// Stolen from https://github.com/boostorg/filesystem/blob/develop/src/portability.cpp
if (!force)
if (name.empty() || name[0] == ' ' || name.find_first_of(windows_invalid_chars, 0, sizeof(windows_invalid_chars)) != std::string::npos || *(name.end() - 1) == ' ' || *(name.end() - 1) == '.' || name == "." || name == "..")
return {"Invalid name"};
return {create_node_error::INVALID_NAME};
db::INode node;
node.setIsFile(file ? 1 : 0);
node.setName(name);
node.setOwnerId(owner.getValueOfId());
node.setHasPreview(0);
if (parent.has_value()) {
auto parent_node = get_node_and_validate(owner, *parent);
if (!parent_node.has_value())
return {"Invalid parent"};
return {create_node_error::INVALID_PARENT};
if (parent_node->getValueOfIsFile() != 0)
return {"Can't use file as parent"};
return {create_node_error::FILE_PARENT};
auto children = get_children(*parent_node);
for (const auto& child : children)
if (child.getValueOfName() == name)
return {"File/Folder already exists"};
return {std::make_tuple(
child.getValueOfIsFile() != 0,
child.getValueOfId()
)};
node.setParentId(*parent);
}
db::MapperInode inode_mapper(drogon::app().getDbClient());
@@ -75,18 +255,56 @@ namespace api {
return {node};
}
void fs::delete_node(db::INode node, bool allow_root) {
void fs::delete_node(db::INode node, msd::channel<std::string>& chan, bool allow_root) {
if (node.getValueOfParentId() == 0 && (!allow_root)) return;
if (node.getValueOfIsFile() == 0) {
auto children = get_children(node);
for (const auto& child : children) delete_node(child, false);
} else {
db::MapperInode inode_mapper(drogon::app().getDbClient());
const auto delete_file = [&chan, &inode_mapper](const db::INode& node) {
std::string entry = "Deleting ";
generate_path(node, entry);
entry >> chan;
std::filesystem::path p("./files");
p /= std::to_string(node.getValueOfId());
std::filesystem::remove(p);
if (node.getValueOfHasPreview() != 0)
std::filesystem::remove(p.string() + "_preview.png");
inode_mapper.deleteOne(node);
std::string(" Done\n") >> chan;
};
std::stack<db::INode> queue, files, folders;
if (node.getValueOfIsFile() == 0) queue.push(node);
else files.push(node);
while (!queue.empty()) {
while (!files.empty()) {
delete_file(files.top());
files.pop();
}
std::string entry = "Deleting ";
generate_path(queue.top(), entry);
entry += "\n";
entry >> chan;
auto children = get_children(queue.top());
folders.push(queue.top());
queue.pop();
for (const auto& child : children) {
if (child.getValueOfIsFile() == 0) queue.push(child);
else files.push(child);
}
}
while (!files.empty()) {
delete_file(files.top());
files.pop();
}
while (!folders.empty()) {
inode_mapper.deleteOne(folders.top());
folders.pop();
}
db::MapperInode inode_mapper(drogon::app().getDbClient());
inode_mapper.deleteOne(node);
}
void fs::root(req_type req, cbk_type cbk) {
@@ -98,23 +316,11 @@ namespace api {
db::User user = dto::get_user(req);
auto inode = get_node_and_validate(user, node);
if (!inode.has_value())
cbk(dto::Responses::get_badreq_res("Unknown node"));
else if (inode->getValueOfIsFile() == 0) {
std::vector<uint64_t> children;
for (const db::INode& child : get_children(*inode)) children.push_back(child.getValueOfId());
cbk(dto::Responses::get_node_folder_res(
inode->getValueOfId(),
inode->getValueOfName(),
inode->getParentId(),
children
));
} else
cbk(dto::Responses::get_node_file_res(
inode->getValueOfId(),
inode->getValueOfName(),
inode->getParentId(),
inode->getValueOfSize()
));
return cbk(dto::Responses::get_badreq_res("Unknown node"));
auto dto_node = dto::Responses::GetNodeEntry(*inode);
std::vector<dto::Responses::GetNodeEntry> children;
if (!dto_node.is_file) for (const db::INode& child : get_children(*inode)) children.emplace_back(child);
cbk(dto::Responses::get_node_res(dto_node, children));
}
void fs::path(req_type req, cbk_type cbk, uint64_t node) {
@@ -122,8 +328,10 @@ namespace api {
auto inode = get_node_and_validate(user, node);
if (!inode.has_value())
cbk(dto::Responses::get_badreq_res("Unknown node"));
else
cbk(dto::Responses::get_path_res( generate_path(*inode)));
else {
auto path = generate_path(*inode);
cbk(dto::Responses::get_success_res(path));
}
}
template<bool file>
@@ -135,10 +343,18 @@ namespace api {
std::string name = dto::json_get<std::string>(json, "name").value();
auto new_node = create_node(name, user, file, std::make_optional(parent));
if (std::holds_alternative<std::string>(new_node))
cbk(dto::Responses::get_badreq_res(std::get<std::string>(new_node)));
else
if (std::holds_alternative<db::INode>(new_node))
cbk(dto::Responses::get_new_node_res(std::get<db::INode>(new_node).getValueOfId()));
else if (std::holds_alternative<create_node_error>(new_node))
switch (std::get<create_node_error>(new_node)) {
case create_node_error::INVALID_NAME: return cbk(dto::Responses::get_badreq_res("Invalid name"));
case create_node_error::INVALID_PARENT: return cbk(dto::Responses::get_badreq_res("Invalid parent"));
case create_node_error::FILE_PARENT: return cbk(dto::Responses::get_badreq_res("Parent is file"));
}
else {
auto tuple = std::get<std::tuple<bool, uint64_t>>(new_node);
cbk(dto::Responses::get_node_exists_res(std::get<1>(tuple), std::get<0>(tuple)));
}
} catch (const std::exception&) {
cbk(dto::Responses::get_badreq_res("Validation error"));
}
@@ -152,12 +368,27 @@ namespace api {
else if (inode->getValueOfParentId() == 0)
cbk(dto::Responses::get_badreq_res("Can't delete root"));
else {
delete_node(*inode);
cbk(dto::Responses::get_success_res());
auto chan = std::make_shared<msd::channel<std::string>>();
std::string("Waiting in queue...\n") >> (*chan);
get_delete_loop()->queueInLoop([chan, inode=*inode]{
delete_node(inode, *chan);
chan->close();
});
cbk(drogon::HttpResponse::newStreamResponse([chan](char* buf, std::size_t size) -> std::size_t{
if (buf == nullptr) return 0;
if (chan->closed() && chan->empty()) return 0;
std::string buffer;
buffer << *chan;
if (buffer.empty()) return 0;
std::size_t read = std::min(size, buffer.size());
std::memcpy(buf, buffer.data(), read); // NOLINT(bugprone-not-null-terminated-result)
return read;
}));
}
}
void fs::upload(req_type req, cbk_type cbk, uint64_t node) {
constexpr int image_height = 256;
db::User user = dto::get_user(req);
auto inode = get_node_and_validate(user, node);
@@ -178,13 +409,73 @@ namespace api {
p /= std::to_string(inode->getValueOfId());
file.saveAs(p.string());
try {
if (file.fileLength() > 100 * 1024 * 1024) throw std::exception();
std::filesystem::path filename(inode->getValueOfName());
const std::string& mime = mime_type_map.at(filename.extension().string());
if (!mime.starts_with("image")) throw std::exception();
cv::_InputArray image_arr(file.fileData(), (int) file.fileLength());
cv::Mat image = cv::imdecode(image_arr, cv::IMREAD_COLOR);
if (!image.empty()) {
float h_ration = ((float) image_height) / ((float) image.rows);
cv::Mat preview;
cv::resize(image, preview, cv::Size((int) (((float) image.cols) * h_ration), image_height), 0, 0, cv::INTER_AREA);
cv::imwrite(p.string() + "_preview.png", preview);
inode->setHasPreview(1);
}
} catch (const std::exception&) {}
inode->setSize(file.fileLength());
db::MapperInode inode_mapper(drogon::app().getDbClient());
inode_mapper.update(*inode);
cbk(dto::Responses::get_success_res());
}
void fs::create_zip(req_type req, cbk_type cbk) {
db::User user = dto::get_user(req);
Json::Value& json = *req->jsonObject();
try {
if (!json.isMember("nodes")) throw std::exception();
Json::Value node_arr = json["nodes"];
if (!node_arr.isArray()) throw std::exception();
std::vector<uint64_t> node_ids;
for (const auto& node : node_arr)
node_ids.push_back(node.asUInt64());
std::vector<db::INode> nodes;
std::transform(node_ids.begin(), node_ids.end(), std::back_inserter(nodes), [&user](uint64_t node) {
return api::fs::get_node_and_validate(user, node).value();
});
std::string key = join_string(node_ids.begin(), node_ids.end());
if (zip_to_temp_map.contains(key)) return cbk(dto::Responses::get_create_zip_done_res());
if (in_progress_zips.contains(key)) {
auto progress = in_progress_zips.at(key);
return cbk(dto::Responses::get_create_zip_done_res(std::get<1>(progress), std::get<2>(progress)));
}
uint64_t size = 0;
for (const auto& node : nodes) size += calc_total_size(node);
std::string file_name = "./temp/fs_" + std::to_string(next_temp_id++) + ".zip";
in_progress_zips.emplace(key, std::make_tuple(file_name, 0, size));
get_zip_loop()->queueInLoop([key = std::move(key), nodes = std::move(nodes), file_name = std::move(file_name)]{
{
struct zip_t* zip = zip_open(file_name.c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'w');
for (const db::INode& node : nodes)
add_to_zip(zip, key, node, "");
zip_close(zip);
}
zip_to_temp_map.emplace(key, file_name);
in_progress_zips.erase(key);
});
return cbk(dto::Responses::get_create_zip_done_res(0, size));
} catch (const std::exception&) {
cbk(dto::Responses::get_badreq_res("Validation error"));
}
}
void fs::download(req_type req, cbk_type cbk) {
db::User user = dto::get_user(req);
@@ -193,19 +484,114 @@ namespace api {
cbk(dto::Responses::get_badreq_res("Invalid node"));
return;
}
auto inode = get_node_and_validate(user, *node_id);
auto inode = get_node_and_validate(user, *node_id);
if (!inode.has_value()) {
cbk(dto::Responses::get_badreq_res("Invalid node"));
return;
}
if (inode->getValueOfIsFile() != 0) {
std::filesystem::path p("./files");
p /= std::to_string(inode->getValueOfId());
cbk(drogon::HttpResponse::newFileResponse(
p.string(),
inode->getValueOfName()
));
} else {
try {
std::string key = std::to_string(inode->getValueOfId());
std::string file = zip_to_temp_map.at(key);
zip_to_temp_map.erase(key);
cbk(drogon::HttpResponse::newFileResponse(
file,
inode->getValueOfName() + ".zip"
));
} catch (const std::exception&) {
cbk(dto::Responses::get_badreq_res("Invalid node"));
}
}
}
void fs::download_multi(req_type req, cbk_type cbk) {
db::User user = dto::get_user(req);
auto node_ids_str = req->getOptionalParameter<std::string>("id");
if (!node_ids_str.has_value())
return cbk(dto::Responses::get_badreq_res("No nodes"));
std::stringstream node_ids_ss(*node_ids_str);
std::string temp;
try {
while (std::getline(node_ids_ss, temp, ','))
if (!get_node_and_validate(user, std::stoull(temp)).has_value()) throw std::exception();
std::string file = zip_to_temp_map.at(*node_ids_str);
zip_to_temp_map.erase(*node_ids_str);
cbk(drogon::HttpResponse::newFileResponse(
file,
"files.zip"
));
} catch (const std::exception&) {
cbk(dto::Responses::get_badreq_res("Invalid nodes"));
}
}
void fs::download_preview(req_type req, cbk_type cbk, uint64_t node) {
db::User user = dto::get_user(req);
auto inode = get_node_and_validate(user, node);
if (!inode.has_value())
return cbk(dto::Responses::get_badreq_res("Unknown node"));
if (inode->getValueOfHasPreview() == 0)
return cbk(dto::Responses::get_badreq_res("No preview"));
std::filesystem::path p("./files");
p /= std::to_string(inode->getValueOfId()) + "_preview.png";
std::ifstream file(p, std::ios::in | std::ios::binary);
std::vector<uint8_t> image((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
cbk(dto::Responses::get_download_base64_res("data:image/png;base64," + Botan::base64_encode(image)));
}
void fs::download_base64(req_type req, cbk_type cbk, uint64_t node) {
db::User user = dto::get_user(req);
auto inode = get_node_and_validate(user, node);
if (!inode.has_value())
return cbk(dto::Responses::get_badreq_res("Unknown node"));
std::filesystem::path p("./files"), name(inode->getValueOfName());
p /= std::to_string(inode->getValueOfId());
cbk(drogon::HttpResponse::newFileResponse(
p.string(),
inode->getValueOfName()
));
try {
std::string mime = mime_type_map.at(name.extension().string());
std::ifstream file(p, std::ios::in | std::ios::binary);
std::vector<uint8_t> content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
cbk(dto::Responses::get_download_base64_res("data:" + mime + ";base64," + Botan::base64_encode(content)));
} catch (const std::exception&) {
cbk(dto::Responses::get_badreq_res("Invalid file type"));
}
}
void fs::get_type(req_type req, cbk_type cbk, uint64_t node){
db::User user = dto::get_user(req);
auto inode = get_node_and_validate(user, node);
if (!inode.has_value())
return cbk(dto::Responses::get_badreq_res("Unknown node"));
std::filesystem::path p("./files"), name(inode->getValueOfName());
p /= std::to_string(inode->getValueOfId());
try {
cbk(dto::Responses::get_type_res(mime_type_map.at(name.extension().string())));
} catch (const std::exception&) {
cbk(dto::Responses::get_badreq_res("Invalid file type"));
}
}
}
#pragma clang diagnostic pop

View File

@@ -18,9 +18,10 @@ namespace api {
void user::delete_user(req_type req, cbk_type cbk) {
db::MapperUser user_mapper(drogon::app().getDbClient());
msd::channel<std::string> chan;
db::User user = dto::get_user(req);
auth::revoke_all(user);
fs::delete_node((fs::get_node(user.getValueOfRootId())).value(), true);
fs::delete_node((fs::get_node(user.getValueOfRootId())).value(), chan, true);
user_mapper.deleteOne(user);
cbk(dto::Responses::get_success_res());

View File

@@ -6,9 +6,9 @@
#include <drogon/utils/coroutine.h>
#include <drogon/drogon.h>
#include "model/Inode.h"
#include "model/Tokens.h"
#include "model/User.h"
#include "Inode.h"
#include "Tokens.h"
#include "User.h"
const std::string jwt_secret = "CUM";

View File

@@ -30,6 +30,16 @@ namespace dto {
db::UserRole role;
};
struct GetNodeEntry {
explicit GetNodeEntry(const db::INode& node) : id(node.getValueOfId()), name(node.getValueOfName()), is_file(node.getValueOfIsFile() != 0), has_preview(node.getValueOfHasPreview() != 0), parent(node.getParentId()) {
if (node.getValueOfIsFile() != 0) size = node.getValueOfSize();
}
uint64_t id, size;
std::string name;
bool is_file, has_preview;
std::shared_ptr<uint64_t> parent;
};
drogon::HttpResponsePtr get_error_res(drogon::HttpStatusCode, const std::string &msg);
drogon::HttpResponsePtr get_success_res();
drogon::HttpResponsePtr get_success_res(Json::Value &);
@@ -46,10 +56,13 @@ namespace dto {
drogon::HttpResponsePtr get_admin_users_res(const std::vector<GetUsersEntry>& users);
drogon::HttpResponsePtr get_root_res(uint64_t root);
drogon::HttpResponsePtr get_node_folder_res(uint64_t id, const std::string& name, const std::shared_ptr<uint64_t>& parent, const std::vector<uint64_t>& children);
drogon::HttpResponsePtr get_node_file_res(uint64_t id, const std::string& name, const std::shared_ptr<uint64_t>& parent, uint64_t size);
drogon::HttpResponsePtr get_path_res(const std::string& path);
drogon::HttpResponsePtr get_node_res(const GetNodeEntry& node, const std::vector<GetNodeEntry>& children);
drogon::HttpResponsePtr get_new_node_res(uint64_t id);
drogon::HttpResponsePtr get_node_exists_res(uint64_t id, bool file);
drogon::HttpResponsePtr get_download_base64_res(const std::string& data);
drogon::HttpResponsePtr get_type_res(const std::string& type);
drogon::HttpResponsePtr get_create_zip_done_res();
drogon::HttpResponsePtr get_create_zip_done_res(uint64_t progress, uint64_t total);
}
}

View File

@@ -63,30 +63,24 @@ namespace dto::Responses {
return get_success_res(json);
}
drogon::HttpResponsePtr get_node_folder_res(uint64_t id, const std::string &name, const std::shared_ptr<uint64_t> &parent, const std::vector<uint64_t> &children) {
Json::Value parse_node(const GetNodeEntry& node) {
Json::Value json;
json["id"] = id;
json["name"] = name;
json["isFile"] = false;
json["parent"] = (parent != nullptr) ? *parent : Json::Value::nullSingleton();
for (uint64_t child : children)
json["children"].append(child);
return get_success_res(json);
json["id"] = node.id;
json["name"] = node.name;
json["isFile"] = node.is_file;
json["preview"] = node.has_preview;
json["parent"] = (node.parent != nullptr) ? *node.parent : Json::Value::nullSingleton();
if (node.is_file) json["size"] = node.size;
return json;
}
drogon::HttpResponsePtr get_node_file_res(uint64_t id, const std::string &name, const std::shared_ptr<uint64_t> &parent, uint64_t size) {
Json::Value json;
json["id"] = id;
json["name"] = name;
json["isFile"] = true;
json["parent"] = (parent != nullptr) ? *parent : Json::Value::nullSingleton();
json["size"] = size;
return get_success_res(json);
}
drogon::HttpResponsePtr get_path_res(const std::string& path) {
Json::Value json;
json["path"] = path;
drogon::HttpResponsePtr get_node_res(const GetNodeEntry& node, const std::vector<GetNodeEntry>& children) {
Json::Value json = parse_node(node);
if (!node.is_file) {
json["children"] = Json::Value(Json::arrayValue);
for (const GetNodeEntry& child : children)
json["children"].append(parse_node(child));
}
return get_success_res(json);
}
@@ -95,4 +89,38 @@ namespace dto::Responses {
json["id"] = id;
return get_success_res(json);
}
drogon::HttpResponsePtr get_node_exists_res(uint64_t id, bool file) {
Json::Value json;
json["id"] = id;
json["exists"] = true;
json["isFile"] = file;
return get_success_res(json);
}
drogon::HttpResponsePtr get_download_base64_res(const std::string &data) {
Json::Value json;
json["data"] = data;
return get_success_res(json);
}
drogon::HttpResponsePtr get_type_res(const std::string &type) {
Json::Value json;
json["type"] = type;
return get_success_res(json);
}
drogon::HttpResponsePtr get_create_zip_done_res() {
Json::Value json;
json["done"] = true;
return get_success_res(json);
}
drogon::HttpResponsePtr get_create_zip_done_res(uint64_t progress, uint64_t total) {
Json::Value json;
json["done"] = false;
json["progress"] = progress;
json["total"] = total;
return get_success_res(json);
}
}

View File

@@ -16,7 +16,7 @@ void cleanup_tokens(db::MapperToken& mapper) {
void Login::doFilter(const drogon::HttpRequestPtr& req, drogon::FilterCallback&& cb, drogon::FilterChainCallback&& ccb) {
std::string token_str;
if (req->path() == "/api/fs/download") {
if (req->path() == "/api/fs/download" || req->path() == "/api/fs/download_multi") {
token_str = req->getParameter("jwtToken");
} else {
std::string auth_header = req->getHeader("Authorization");

View File

@@ -13,6 +13,9 @@ void cleanup() {
std::cout << "Cleanup up uploads...";
std::filesystem::remove_all("uploads");
std::cout << " [Done]" << std::endl;
std::cout << "Removing temp folder..." << std::flush;
std::filesystem::remove_all("temp");
std::cout << " [Done]" << std::endl;
std::cout << "Goodbye!" << std::endl;
}
@@ -47,6 +50,15 @@ int main(int argc, char* argv[]) {
std::filesystem::create_directory("logs");
std::cout << " [Done]" << std::endl;
}
if (std::filesystem::exists("temp")) {
std::cout << "Removing existing temp folder..." << std::flush;
std::filesystem::remove_all("temp");
std::cout << " [Done]" << std::endl;
}
std::cout << "Creating temp folder..." << std::flush;
std::filesystem::create_directory("temp");
std::cout << " [Done]" << std::endl;
auto* loop = drogon::app().getLoop();
loop->queueInLoop([]{
@@ -76,7 +88,8 @@ int main(int argc, char* argv[]) {
" 'name' TEXT,\n"
" 'parent_id' INTEGER,\n"
" 'owner_id' INTEGER NOT NULL,\n"
" 'size' INTEGER\n"
" 'size' INTEGER,\n"
" 'has_preview' INTEGER NOT NULL\n"
")");
std::cout << " [Done]" << std::endl;
std::cout << "Started!" << std::endl;
@@ -101,8 +114,12 @@ int main(int argc, char* argv[]) {
Json::Value access_logger;
access_logger["name"] = "drogon::plugin::AccessLogger";
Json::Value smtp_mail;
smtp_mail["name"] = "SMTPMail";
Json::Value config;
config["plugins"].append(access_logger);
config["plugins"].append(smtp_mail);
drogon::app()
.setClientMaxBodySize(std::numeric_limits<size_t>::max())
@@ -123,8 +140,10 @@ int main(int argc, char* argv[]) {
.setIntSignalHandler(cleanup)
.setTermSignalHandler(cleanup)
.addListener("0.0.0.0", 5678)
.setThreadNum(2);
.enableRelaunchOnError()
.addListener("0.0.0.0", 2345)
.setThreadNum(8);
std::cout << "Setup done!" << std::endl;
drogon::app().run();

View File

@@ -0,0 +1,15 @@
{
"default-registry": {
"kind": "git",
"repository": "https://github.com/microsoft/vcpkg.git",
"baseline": "927006b24c3a28dfd8aa0ec5f8ce43098480a7f1"
},
"registries": [
{
"kind": "filesystem",
"baseline": "default",
"path": "./vcpkg_reg",
"packages": [ "drogon" ]
}
]
}

View File

@@ -7,11 +7,15 @@
"name": "drogon",
"features": ["orm", "sqlite3"]
},
{
"name": "opencv4",
"default-features": false,
"features": ["tiff", "png", "jpeg", "webp", "openexr"]
},
"jwt-cpp",
"botan",
"mailio",
"nayuki-qr-code-generator",
"lodepng",
"openssl"
"openssl",
"kubazip"
]
}

View File

@@ -0,0 +1,13 @@
diff --git a/cmake/templates/DrogonConfig.cmake.in b/cmake/templates/DrogonConfig.cmake.in
index a21122a..6367259 100644
--- a/cmake/templates/DrogonConfig.cmake.in
+++ b/cmake/templates/DrogonConfig.cmake.in
@@ -19,7 +19,7 @@ find_dependency(UUID REQUIRED)
endif(NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" AND NOT WIN32)
find_dependency(ZLIB REQUIRED)
if(@pg_FOUND@)
-find_dependency(pg)
+find_dependency(PostgreSQL)
endif()
if(@SQLite3_FOUND@)
find_dependency(SQLite3)

View File

@@ -0,0 +1,61 @@
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO an-tao/drogon
REF v1.8.0
SHA512 a834d937e3719059223d9bf19d777dbc92eaf09c5c9c44b5a742bfefcbcd95a146a6568cef8c058050fb87e330f221434ffe784dfa29a49de12b031f86ab1a33
HEAD_REF master
PATCHES
vcpkg.patch
drogon_config.patch
)
vcpkg_check_features(
OUT_FEATURE_OPTIONS FEATURE_OPTIONS
FEATURES
ctl BUILD_CTL
mysql BUILD_MYSQL
orm BUILD_ORM
postgres BUILD_POSTGRESQL
postgres LIBPQ_BATCH_MODE
redis BUILD_REDIS
sqlite3 BUILD_SQLITE
)
string(COMPARE EQUAL "${VCPKG_LIBRARY_LINKAGE}" "dynamic" BUILD_DROGON_SHARED)
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}"
DISABLE_PARALLEL_CONFIGURE
OPTIONS
-DBUILD_SHARED_LIBS=${BUILD_DROGON_SHARED}
-DBUILD_EXAMPLES=OFF
-DCMAKE_DISABLE_FIND_PACKAGE_Boost=ON
${FEATURE_OPTIONS}
MAYBE_UNUSED_VARIABLES
CMAKE_DISABLE_FIND_PACKAGE_Boost
)
vcpkg_cmake_install(ADD_BIN_TO_PATH)
# Fix CMake files
vcpkg_cmake_config_fixup(CONFIG_PATH lib/cmake/Drogon)
vcpkg_fixup_pkgconfig()
# Copy drogon_ctl
if("ctl" IN_LIST FEATURES)
vcpkg_copy_tools(TOOL_NAMES drogon_ctl AUTO_CLEAN)
endif()
# Remove includes in debug
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/share")
if(VCPKG_LIBRARY_LINKAGE STREQUAL "static")
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/bin" "${CURRENT_PACKAGES_DIR}/debug/bin")
endif()
file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
file(INSTALL "${SOURCE_PATH}/LICENSE" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright)
# Copy pdb files
vcpkg_copy_pdbs()

View File

@@ -0,0 +1,4 @@
The package drogon provides CMake targets:
find_package(Drogon CONFIG REQUIRED)
target_link_libraries(main PRIVATE Drogon::Drogon)

View File

@@ -0,0 +1,92 @@
{
"name": "drogon",
"version-semver": "1.8.0",
"description": "A C++14/17 based HTTP web application framework running on Linux/macOS/Unix/Windows",
"homepage": "https://github.com/an-tao/drogon",
"documentation": "https://drogon.docsforge.com/master/overview/",
"license": "MIT",
"dependencies": [
"brotli",
"jsoncpp",
{
"name": "libuuid",
"platform": "!windows & !osx"
},
"trantor",
{
"name": "vcpkg-cmake",
"host": true
},
{
"name": "vcpkg-cmake-config",
"host": true
},
"zlib"
],
"features": {
"ctl": {
"description": "Build drogon_ctl tool."
},
"mysql": {
"description": "Support reading and writing from/to MySQL databases.",
"dependencies": [
{
"name": "drogon",
"features": [
"orm"
]
},
{
"name": "libmariadb",
"features": [
"iconv"
],
"platform": "osx"
},
{
"name": "libmariadb",
"platform": "!osx"
}
]
},
"orm": {
"description": "Build with object-relational mapping support."
},
"postgres": {
"description": "Support reading and writing from/to Postgres databases.",
"dependencies": [
{
"name": "drogon",
"features": [
"orm"
]
},
"libpq"
]
},
"redis": {
"description": "Support reading and writing from/to Redis databases.",
"dependencies": [
{
"name": "drogon",
"features": [
"orm"
]
},
"hiredis"
]
},
"sqlite3": {
"description": "Support reading and writing from/to SQLite databases.",
"dependencies": [
{
"name": "drogon",
"features": [
"orm"
]
},
"sqlite3"
]
}
}
}

View File

@@ -0,0 +1,53 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -120,9 +120,9 @@ if (WIN32)
PRIVATE $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/third_party/mman-win32>)
endif (WIN32)
-add_subdirectory(trantor)
+find_package(Trantor CONFIG REQUIRED)
-target_link_libraries(${PROJECT_NAME} PUBLIC trantor)
+target_link_libraries(${PROJECT_NAME} PUBLIC Trantor::Trantor)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
target_link_libraries(${PROJECT_NAME} PRIVATE network)
@@ -316,11 +316,10 @@ endif (NOT WIN32)
if (BUILD_POSTGRESQL)
# find postgres
- find_package(pg)
- if (pg_FOUND)
- message(STATUS "libpq inc path:" ${PG_INCLUDE_DIRS})
- message(STATUS "libpq lib:" ${PG_LIBRARIES})
- target_link_libraries(${PROJECT_NAME} PRIVATE pg_lib)
+ find_package(PostgreSQL REQUIRED)
+ if(PostgreSQL_FOUND)
+ set(pg_FOUND true)
+ target_link_libraries(${PROJECT_NAME} PRIVATE PostgreSQL::PostgreSQL)
set(DROGON_SOURCES
${DROGON_SOURCES}
orm_lib/src/postgresql_impl/PostgreSQLResultImpl.cc)
@@ -348,7 +348,7 @@ if (BUILD_POSTGRESQL)
${private_headers}
orm_lib/src/postgresql_impl/PgConnection.h)
endif (libpq_supports_batch)
- endif (pg_FOUND)
+ endif (PostgreSQL_FOUND)
endif (BUILD_POSTGRESQL)
if (BUILD_MYSQL)
diff --git a/drogon_ctl/CMakeLists.txt b/drogon_ctl/CMakeLists.txt
index 9f2f1e7..09871f8 100755
--- a/drogon_ctl/CMakeLists.txt
+++ b/drogon_ctl/CMakeLists.txt
@@ -19,7 +19,7 @@ add_executable(_drogon_ctl
target_link_libraries(_drogon_ctl ${PROJECT_NAME})
if (WIN32 AND BUILD_SHARED_LIBS)
set(DROGON_FILE $<TARGET_FILE:drogon>)
- set(TRANTOR_FILE $<TARGET_FILE:trantor>)
+ set(TRANTOR_FILE $<TARGET_FILE:Trantor::Trantor>)
add_custom_command(TARGET _drogon_ctl POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DCTL_FILE=${DROGON_FILE}

View File

@@ -0,0 +1,8 @@
{
"default": {
"drogon": {
"baseline": "1.8.0",
"port-version": 0
}
}
}

View File

@@ -0,0 +1,9 @@
{
"versions": [
{
"version-semver": "1.8.0",
"port-version": 0,
"path": "$/ports/drogon"
}
]
}