diff options
author | Roland Reichwein <mail@reichwein.it> | 2020-04-13 16:16:06 +0200 |
---|---|---|
committer | Roland Reichwein <mail@reichwein.it> | 2020-04-13 16:16:06 +0200 |
commit | 5b3022c4a0e81ff23ce4ebc2ec7b03e32f7a719e (patch) | |
tree | 3f58cc9b9a161e89d0d8e341473714a2acf5ed08 /plugins/webbox | |
parent | 4732dc63657f4c6fc342f7674f7dc7c666b293dc (diff) |
webbox (WIP)
Diffstat (limited to 'plugins/webbox')
-rw-r--r-- | plugins/webbox/webbox.cpp | 1098 | ||||
-rw-r--r-- | plugins/webbox/webbox.h | 4 |
2 files changed, 575 insertions, 527 deletions
diff --git a/plugins/webbox/webbox.cpp b/plugins/webbox/webbox.cpp index 363df6c..78be007 100644 --- a/plugins/webbox/webbox.cpp +++ b/plugins/webbox/webbox.cpp @@ -1,14 +1,22 @@ #include "webbox.h" +#include "file.h" #include "stringutil.h" +#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/replace.hpp> +#include <boost/algorithm/string/split.hpp> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/xml_parser.hpp> +#include <chrono> +#include <cstdio> +#include <cstdlib> +#include <ctime> #include <filesystem> #include <iostream> #include <string> +#include <sstream> #include <unordered_map> using namespace std::string_literals; @@ -17,76 +25,104 @@ namespace pt = boost::property_tree; namespace { - void registerCommand(std::unordered_map<std::string, std::shared_ptr<Command>>& commands, std::shared_ptr<Command> command) { - commands[command.getCommandName()] = command; + static const std::string PROGRAMVERSION{"Webbox 2.0"}; + static const std::string DOWNLOAD_FILENAME{"webbox-download.zip"}; + + class Tempfile + { + fs::path m_path; + + public: + fs::path GetPath() const + { + return m_path; + } + + Tempfile() { + try { + m_path = std::string{tmpnam(NULL)}; + } catch (const std::exception& ex) { + throw std::runtime_error("Tempfile error: "s + ex.what()); + } + } + + ~Tempfile() { + try { + fs::remove_all(m_path); + } catch (const std::exception& ex) { + std::cerr << "Warning: Couldn't remove temporary file " << m_path << std::endl; + } + } }; - unordered_map<std::string> status_map { + std::unordered_map<std::string, std::string> status_map { { "400", "Bad Request"}, { "403", "Forbidden" }, { "404", "Not Found" }, { "505", "Internal Server Error" }, }; -std::unordered_map<std::string, std::string> ParseQueryString(std::string s) -{ - std::unordered_map<std::string, std::string> result; - - size_t pos = s.find('?'); - if (pos != s.npos) { - auto list {split(s.substr(pos), "&")}; - for (auto i: list) { - pos = i.find('='); - if (pos != i.npos) { - result[i.substr(0, pos)] = i.substr(pos + 1); + std::unordered_map<std::string, std::string> ParseQueryString(std::string s) + { + std::unordered_map<std::string, std::string> result; + + size_t pos = s.find('?'); + if (pos != s.npos) { + auto list {split(s.substr(pos), "&")}; + for (auto i: list) { + pos = i.find('='); + if (pos != i.npos) { + result[i.substr(0, pos)] = i.substr(pos + 1); + } } } + + return result; } - - return result; -} -struct CommandParameters -{ - std::function<std::string(const std::string& key)>& m_GetServerParam; - std::function<std::string(const std::string& key)>& m_GetRequestParam; // request including body (POST...) - std::function<void(const std::string& key, const std::string& value)>& m_SetResponseHeader; // to be added to result string - - std::unordered_map<std::string, std::string> paramHash; - - std::string webboxPath; - std::string webboxName; - bool webboxReadOnly; - - CommandParameters( - std::function<std::string(const std::string& key)>& GetServerParam, - std::function<std::string(const std::string& key)>& GetRequestParam, - std::function<void(const std::string& key, const std::string& value)>& SetResponseHeader - ) - : m_GetServerParam(GetServerParam) - , m_GetRequestParam(GetRequestParam) - , m_SetResponseHeader(SetResponseHeader) - , paramHash(ParseQueryString(GetRequestParam("rel_target"))) // rel_target contains query string - , webboxPath(m_GetRequestParam("doc_root")) - , webboxName(m_GetRequestParam("WEBBOX_NAME")) - , webboxReadOnly(m_GetRequestParam("WEBBOX_READONLY") == "1") - { - } -}; + struct CommandParameters + { + std::function<std::string(const std::string& key)>& m_GetServerParam; + std::function<std::string(const std::string& key)>& m_GetRequestParam; // request including body (POST...) + std::function<void(const std::string& key, const std::string& value)>& m_SetResponseHeader; // to be added to result string + + std::unordered_map<std::string, std::string> paramHash; + + std::string webboxPath; + std::string webboxName; + bool webboxReadOnly; + + CommandParameters( + std::function<std::string(const std::string& key)>& GetServerParam, + std::function<std::string(const std::string& key)>& GetRequestParam, + std::function<void(const std::string& key, const std::string& value)>& SetResponseHeader + ) + : m_GetServerParam(GetServerParam) + , m_GetRequestParam(GetRequestParam) + , m_SetResponseHeader(SetResponseHeader) + , paramHash(ParseQueryString(GetRequestParam("rel_target"))) // rel_target contains query string + , webboxPath(m_GetRequestParam("doc_root")) + , webboxName(m_GetRequestParam("WEBBOX_NAME")) + , webboxReadOnly(m_GetRequestParam("WEBBOX_READONLY") == "1") + { + } + }; -// Used to return errors by generating response page and HTTP status code -std::string HttpStatus(std::string status, std::string message, CommandParameters& commandParameters) -{ - commandParameters.m_SetResponseHeader("status", status); - commandParameters.m_SetResponseHeader("content_type", "text/html"); + // Used to return errors by generating response page and HTTP status code + std::string HttpStatus(std::string status, std::string message, CommandParameters& commandParameters) + { + commandParameters.m_SetResponseHeader("status", status); + commandParameters.m_SetResponseHeader("content_type", "text/html"); - auto it{status_map.find(status)}; - std::string description{"(Unknown)"}; - if (it != status_map.end()) - description = it->second; + auto it{status_map.find(status)}; + std::string description{"(Unknown)"}; + if (it != status_map.end()) + description = it->second; - return "<html><body><h1>"s + status + " "s + description + "</h1><p>"s + message + "</p></body></html>"; -} + return "<html><body><h1>"s + status + " "s + description + "</h1><p>"s + message + "</p></body></html>"; + } + +} // anonymous namespace class Command { @@ -106,7 +142,7 @@ public: } // Set parameters from FastCGI request environment - m_pathInfo = p.m_GetRequestParam("rel_path"); + m_pathInfo = p.m_GetRequestParam("rel_target"); if (m_pathInfo == "") { m_pathInfo = "/"; } @@ -124,6 +160,8 @@ public: return m_commandName; } + virtual ~Command() = 0; + protected: // implemented in implementation classes virtual std::string start(CommandParameters& p) = 0; @@ -135,13 +173,14 @@ protected: // calculated during start of execute() std::string m_pathInfo; // path inside webbox, derived from request - std::string m_path; // complete path + std::string m_path; // complete path, TODO: fs::path }; class GetCommand: public Command { public: - GetCommand() { + GetCommand() + { m_requestMethod = "GET"; } }; @@ -186,7 +225,7 @@ protected: if (serverName != "localhost") throw std::runtime_error("Command not available"); - p.m_SetRequestParam("content_type", "text/html"); + p.m_SetResponseHeader("content_type", "text/html"); std::string result {"<html><head><title>Params</title></head><body>\r\n"}; @@ -205,487 +244,487 @@ class ListCommand: public GetCommand public: ListCommand() { - m_commandName = "list"; + m_commandName = "list"; // TODO: possible in initializer list? m_isWriteCommand = false; } protected: - virtual std::string start(CommandParameters& p) { - FCGX_PutS("Content-Type: text/xml\r\n\r\n", p.request.out); - - fs::directory_iterator dir(m_path); - for (auto it& - pt::ptree - QDir dir(m_path); - QFileInfoList dirEntryList = dir.entryInfoList(QDir::NoDot | QDir::AllEntries, QDir::DirsFirst | QDir::Name | QDir::IgnoreCase); - - QByteArray xmlData; - QXmlStreamWriter xmlWriter(&xmlData); - xmlWriter.writeStartDocument(); - xmlWriter.writeStartElement("list"); - foreach(QFileInfo i, dirEntryList) { - if (m_pathInfo != "/" || i.fileName() != "..") { // skip on ".." in "/" - xmlWriter.writeStartElement("listentry"); - xmlWriter.writeAttribute("type", i.isDir() ? "dir" : "file"); - xmlWriter.writeCharacters(i.fileName()); - xmlWriter.writeEndElement(); - } - } - xmlWriter.writeEndElement(); // list - xmlWriter.writeEndDocument(); - FCGX_PutS(xmlData.data(), p.request.out); - } + virtual std::string start(CommandParameters& p) + { + p.m_SetResponseHeader("content_type", "text/xml"); + + pt::ptree tree; + pt::ptree list; + pt::ptree entry; + + if (m_pathInfo != ""s) { // Add ".." if not in top directory of this webbox + entry.put_value(".."); + entry.put("<xmlattr>.type", "dir"); + list.push_back(pt::ptree::value_type("listentry", entry)); + } + + fs::directory_iterator dir(m_path); + + for (auto& dir_entry: dir) { + if (dir_entry.is_regular_file() || dir_entry.is_directory()) { + entry.put_value(dir_entry.path().filename()); + entry.put("<xmlattr>.type", dir_entry.is_directory() ? "dir" : "file"); + list.push_back(pt::ptree::value_type("listentry", entry)); + } + } + tree.push_back(pt::ptree::value_type("list", list)); + + std::stringstream ss; + + pt::xml_parser::write_xml(ss, tree /*, pt::xml_parser::xml_writer_make_settings<std::string>(' ', 1)*/); + + return ss.str(); + } }; // Retrieve from Server: // Title // ReadOnly flag -class ServerInfoCommand: public GetCommand { - public: - ServerInfoCommand() { - m_commandName = "server-info"; - m_isWriteCommand = false; - } - - protected: - virtual std::string start(CommandParameters& p) { - FCGX_PutS("Content-Type: text/xml\r\n\r\n", p.request.out); - - QByteArray xmlData; - QXmlStreamWriter xmlWriter(&xmlData); - xmlWriter.writeStartDocument(); - xmlWriter.writeStartElement("serverinfo"); - - xmlWriter.writeTextElement("title", p.webboxName); - - xmlWriter.writeTextElement("readonly", p.webboxReadOnly ? "1" : "0"); - - xmlWriter.writeEndElement(); // serverinfo - xmlWriter.writeEndDocument(); - FCGX_PutS(xmlData.data(), p.request.out); - } -}; +class ServerInfoCommand: public GetCommand +{ +public: + ServerInfoCommand() + { + m_commandName = "server-info"; + m_isWriteCommand = false; + } -class VersionCommand: public GetCommand { - public: - VersionCommand() { - m_commandName = "version"; - m_isWriteCommand = false; - } - - protected: - virtual std::string start(CommandParameters& p) { - FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); - FCGX_PutS(std::string("webbox %1<br/>(C) 2018 <a href=\"https://www.reichwein.it/\">Reichwein.IT</a>\r\n").arg(PROGRAMVERSION).toUtf8().data(), p.request.out); - } +protected: + virtual std::string start(CommandParameters& p) + { + p.m_SetResponseHeader("content_type", "text/xml"); + + pt::ptree tree; + tree.put("serverinfo.title", p.webboxName); + tree.put("serverinfo.readonly", p.webboxReadOnly ? "1" : "0"); + std::stringstream ss; + pt::xml_parser::write_xml(ss, tree); + return ss.str(); + } }; -class NewDirCommand: public PostCommand { - public: - NewDirCommand() { - m_commandName = "newdir"; - m_isWriteCommand = true; - } - - protected: - virtual std::string start(CommandParameters& p) { - readContent(p); - - FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); - QXmlStreamReader xml(m_content); - - while (!xml.atEnd()) { - while (xml.readNextStartElement()) { - if (xml.name() == "dirname") { - std::string dirname = xml.readElementText(); - QDir dir(m_path); - if (dir.mkdir(dirname)) { - FCGX_PutS("Successfully created directory", p.request.out); - } else { - FCGX_PutS("Error creating directory", p.request.out); - } - } - } - } - } +class VersionCommand: public GetCommand +{ +public: + VersionCommand() + { + m_commandName = "version"; + m_isWriteCommand = false; + } + +protected: + virtual std::string start(CommandParameters& p) + { + p.m_SetResponseHeader("content_type", "text/plain"); + return PROGRAMVERSION + "<br/>(C) 2020 <a href=\"https://www.reichwein.it/\">Reichwein.IT</a>"; + } }; -class InfoCommand: public PostCommand { - public: - InfoCommand() { - m_commandName = "info"; - m_isWriteCommand = false; - } - - protected: - virtual std::string start(CommandParameters& p) { - readContent(p); - - FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); - QXmlStreamReader xml(m_content); - - while (!xml.atEnd()) { - while (xml.readNextStartElement()) { - if (xml.name() == "files") { - while (xml.readNextStartElement()) { - if (xml.name() == "file") { - std::string filename = xml.readElementText(); - QFileInfo fileInfo(m_path + "/" + filename); - qint64 size = fileInfo.size(); - std::string date = fileInfo.lastModified().toString(); - if (fileInfo.isDir()) { - FCGX_PutS(std::string("%1, %2 (folder)<br>") - .arg(filename) - .arg(date).toUtf8().data(), p.request.out); - } else { - FCGX_PutS(std::string("%1, %2 bytes, %3 (file)<br>") - .arg(filename) - .arg(size) - .arg(date).toUtf8().data(), p.request.out); - } - } - } - } - } - } - } +class NewDirCommand: public PostCommand +{ +public: + NewDirCommand() + { + m_commandName = "newdir"; + m_isWriteCommand = true; + } + +protected: + virtual std::string start(CommandParameters& p) + { + readContent(p); + + p.m_SetResponseHeader("content_type", "text/plain"); + + pt::ptree tree; + pt::read_xml(m_content, tree, pt::xml_parser::no_comments | pt::xml_parser::trim_whitespace); + + std::string dirname = tree.get<std::string>("dirname"); + + try { + if (fs::create_directory(fs::path(m_path) / dirname)) + return "Successfully created directory"; + else + return "Error creating directory"; + } catch (const std::exception& ex) { + return "Error creating directory: "s + ex.what(); + } + } }; -class DownloadZipCommand: public PostCommand { - public: - DownloadZipCommand() { - m_commandName = "download-zip"; - } - - protected: - virtual std::string start(CommandParameters& p) { - readContent(p); - - QXmlStreamReader xml(m_content); - - QByteArray zipData; - std::stringList argumentList; - QTemporaryFile tempfile(QDir::tempPath() + "/webboxXXXXXX.zip"); - tempfile.open(); - QFileInfo fileInfo(tempfile); - std::string tempfilePath = fileInfo.absolutePath(); - std::string tempfileName = fileInfo.fileName(); - tempfile.close(); - tempfile.remove(); - - argumentList << "-r"; // recursive packing - argumentList << tempfilePath + "/" + tempfileName; // zip filename - - while (!xml.atEnd()) { - while (xml.readNextStartElement()) { - if (xml.name() == "files") { - while (xml.readNextStartElement()) { - if (xml.name() == "file") { - std::string filename = xml.readElementText(); - - argumentList.append(filename); // add parts - } - } - } - } - } - - QProcess process; - process.setWorkingDirectory(m_path); - process.setProgram("/usr/bin/zip"); - process.setArguments(argumentList); - process.start(); - process.waitForFinished(); - - std::string debugText = process.readAll(); - process.setReadChannel(QProcess::StandardError); - debugText += process.readAll(); - - if (process.state() != QProcess::NotRunning || - process.exitCode() != 0 || - process.exitStatus() != QProcess::NormalExit) - { - printHttpError(500, std::string("Error running process: %1 %2 %3 %4 %5 %6 %7"). - arg(process.state()). - arg(process.exitCode()). - arg(process.exitStatus()). - arg(tempfilePath). - arg(tempfileName). - arg(argumentList[0]). - arg(debugText), p); - } else { - - QFile tempfile(tempfilePath + "/" + tempfileName); - if (!tempfile.open(QIODevice::ReadOnly)) { - printHttpError(500, std::string("Error reading file"), p); - } else { - zipData = tempfile.readAll(); - - FCGX_PutS(std::string("Content-Disposition: attachment; filename=\"%1\"\r\n").arg("webbox-download.zip").toUtf8().data(), p.request.out); - FCGX_PutS("Content-Type: application/octet-stream\r\n\r\n", p.request.out); - - FCGX_PutStr(zipData.data(), zipData.size(), p.request.out); - } - - tempfile.close(); - tempfile.remove(); - } - } +class InfoCommand: public PostCommand +{ +public: + InfoCommand() + { + m_commandName = "info"; + m_isWriteCommand = false; + } + +protected: + virtual std::string start(CommandParameters& p) + { + readContent(p); + + std::string result; + + p.m_SetResponseHeader("content_type", "text/plain"); + + pt::ptree tree; + pt::read_xml(m_content, tree, pt::xml_parser::no_comments | pt::xml_parser::trim_whitespace); + + try { + auto elements {tree.get_child("files")}; + for (const auto& element: elements) { + if (element.first == "file"s) { + std::string filename{element.second.data()}; + fs::path path {fs::path(m_path) / filename}; + + auto filesize {fs::file_size(path)}; + + fs::file_time_type ftime {fs::last_write_time(path)}; + auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(ftime - fs::file_time_type::clock::now() + + std::chrono::system_clock::now()); + std::time_t cftime = std::chrono::system_clock::to_time_t(sctp); + std::string last_write_time {std::asctime(std::localtime(&cftime))}; + + if (fs::is_directory(path)) { + result += filename + ", "s + last_write_time + " (folder)<br>"s; + } else { + result += filename + ", "s + std::to_string(filesize) + " bytes, "s + last_write_time + " (file)<br>"s; + } + + } else { + result += "Bad element: "s + element.first + ". Expected file.<br>"s; + } + } + } catch (const std::exception& ex) { + return "Bad request: "s + ex.what(); + } + + return result; + } }; -class DeleteCommand: public PostCommand { - public: - DeleteCommand() { - m_commandName = "delete"; - m_isWriteCommand = true; - } - - protected: - virtual std::string start(CommandParameters& p) { - readContent(p); - - QXmlStreamReader xml(m_content); - - std::string response = ""; - - while (!xml.atEnd()) { - while (xml.readNextStartElement()) { - if (xml.name() == "files") { - while (xml.readNextStartElement()) { - if (xml.name() == "file") { - std::string filename = xml.readElementText(); - - QFileInfo fileInfo(m_path + "/" + filename); - if (fileInfo.isDir()) { - QDir dir(m_path); - if (!dir.rmdir(filename)) { - response += std::string("Error on removing directory %1<br/>").arg(filename); - } - } else if (fileInfo.isFile()) { - QFile file(m_path + "/" + filename); - if (!file.remove()) { - response += std::string("Error on removing file %1<br/>").arg(filename); - } - } else { - response += std::string("Error: %1 is neither file nor directory.<br/>").arg(filename); - } - } - } - } - } - } - - if (response == "") { - response = "OK"; - } - - FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); - FCGX_PutS(response.toUtf8().data(), p.request.out); - } +class DownloadZipCommand: public PostCommand +{ +public: + DownloadZipCommand() + { + m_commandName = "download-zip"; + } + +protected: + virtual std::string start(CommandParameters& p) + { + // Get file list + std::string arglist; + + readContent(p); + + pt::ptree tree; + pt::read_xml(m_content, tree, pt::xml_parser::no_comments | pt::xml_parser::trim_whitespace); + + try { + auto elements {tree.get_child("files")}; + for (const auto& element: elements) { + if (element.first == "file"s) { + std::string filename{element.second.data()}; + + arglist += " \""s + filename + "\""; + } + } + } catch (const std::exception& ex) { + return HttpStatus("500", "Reading file list: "s + ex.what(), p); + } + + if (arglist.size() == 0) + return HttpStatus("400", "No files found", p); + + try { + fs::current_path(m_path); + } catch (const std::exception& ex) { + return HttpStatus("500", "Change path error: "s + ex.what(), p); + } + + Tempfile tempfile; // guards this path, removing file afterwards via RAII + + arglist = "/usr/bin/zip -r "s + tempfile.GetPath().string() + " "s + arglist; + + int system_result {system(arglist.c_str())}; + if (system_result != 0) { + return HttpStatus("500", "Error from system(zip): "s + std::to_string(system_result), p); + } + + try { + std::string zipData{File::getFile(tempfile.GetPath())}; + p.m_SetResponseHeader("content_type", "application/octet-stream"); + p.m_SetResponseHeader("content_disposition", "attachment; filename=\""s + DOWNLOAD_FILENAME + "\""); + return zipData; + + } catch (const std::exception& ex) { + return HttpStatus("500", "Tempfile read error: "s + ex.what(), p); + } + + } }; -class MoveCommand: public PostCommand { - public: - MoveCommand() { - m_commandName = "move"; - m_isWriteCommand = true; - } - - protected: - virtual std::string start(CommandParameters& p) { - readContent(p); - - QXmlStreamReader xml(m_content); - - std::string response = ""; - std::string targetDir; - - while (!xml.atEnd()) { - while (xml.readNextStartElement()) { - if (xml.name() == "request") { - while (xml.readNextStartElement()) { - if (xml.name() == "target") { - targetDir = xml.readElementText(); - } else if (xml.name() == "file") { - std::string filename = xml.readElementText(); - - QFileInfo fileInfo(m_path + "/" + filename); - if (fileInfo.isDir()) { - QDir dir(m_path); - if (!dir.rename(filename, targetDir + "/" + filename)) { - response += std::string("Error moving directory %1<br/>").arg(filename); - } - } else if (fileInfo.isFile()) { - QFile file(m_path + "/" + filename); - if (!file.rename(m_path + "/" + targetDir + "/" + filename)) { - response += std::string("Error on moving file %1<br/>").arg(filename); - } - } else { - response += std::string("Error: %1 is neither file nor directory.<br/>").arg(filename); - } - } - } - } - } - } - - if (response == "") { - response = "OK"; - } - - FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); - FCGX_PutS(response.toUtf8().data(), p.request.out); - } +class DeleteCommand: public PostCommand +{ +public: + DeleteCommand() + { + m_commandName = "delete"; + m_isWriteCommand = true; + } + +protected: + virtual std::string start(CommandParameters& p) + { + std::string result{}; + readContent(p); + + pt::ptree tree; + pt::read_xml(m_content, tree, pt::xml_parser::no_comments | pt::xml_parser::trim_whitespace); + + try { + auto elements {tree.get_child("files")}; + for (const auto& element: elements) { + if (element.first == "file"s) { + std::string filename{element.second.data()}; + + fs::path path{fs::path(m_path) / filename}; + + if (fs::is_directory(path)) { + try { + fs::remove_all(path); + } catch (const std::exception& ex) { + result += "Error on removing directory "s + filename + "<br/>"s; + } + } else + if (fs::is_regular_file(path)) { + try { + fs::remove(path); + } catch (const std::exception& ex) { + result += "Error on removing file "s + filename + "<br/>"s; + } + } else { + result += "Error: "s + filename + " is neither file nor directory.<br/>"s; + } + } + } + } catch (const std::exception& ex) { + return HttpStatus("500", "Reading file list: "s + ex.what(), p); + } + + if (result.empty()) { + result = "OK"; + } + + p.m_SetResponseHeader("content_type", "text/plain"); + return result; + } }; -class RenameCommand: public PostCommand { - public: - RenameCommand() { - m_commandName = "rename"; - m_isWriteCommand = true; - } - - protected: - virtual std::string start(CommandParameters& p) { - readContent(p); - - QXmlStreamReader xml(m_content); - - std::string oldname; - std::string newname; - - while (!xml.atEnd()) { - while (xml.readNextStartElement()) { - if (xml.name() == "request") { - while (xml.readNextStartElement()) { - if (xml.name() == "oldname") { - oldname = xml.readElementText(); - } else - if (xml.name() == "newname") { - newname = xml.readElementText(); - } - } - } - } - } - - QDir dir(m_path); - std::string response; - if (!dir.rename(oldname, newname)) { - response = std::string("Error renaming %1 to %2<br/>").arg(oldname).arg(newname); - } else { - response = "OK"; - } - - FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); - FCGX_PutS(response.toUtf8().data(), p.request.out); - } +class MoveCommand: public PostCommand +{ +public: + MoveCommand() + { + m_commandName = "move"; + m_isWriteCommand = true; + } + +protected: + virtual std::string start(CommandParameters& p) + { + std::string result{}; + fs::path targetDir{}; + + readContent(p); + + pt::ptree tree; + pt::read_xml(m_content, tree, pt::xml_parser::no_comments | pt::xml_parser::trim_whitespace); + + try { + auto elements {tree.get_child("request")}; + for (const auto& element: elements) { + if (element.first == "target") { + targetDir = fs::path{m_path} / element.second.data(); + } else if (element.first == "file") { + std::string filename{element.second.data()}; + fs::path old_path{fs::path{m_path} / filename}; + fs::path new_path{targetDir / filename}; + try { + fs::rename(old_path, new_path); + } catch (const std::exception& ex) { + result += "Error moving "s + filename + ": "s + ex.what() + "<br>"s; + } + } else { + result += "Unknown element: "s + element.first + "<br>"s; + } + } + } catch (const std::exception& ex) { + return HttpStatus("500", "Reading file list: "s + ex.what(), p); + } + + if (result.empty()) { + result = "OK"; + } + + p.m_SetResponseHeader("content_type", "text/plain"); + return result; + } }; -class UploadCommand: public PostCommand { - public: - UploadCommand() { - m_commandName = "upload"; - m_isWriteCommand = true; - } - - protected: - virtual std::string start(CommandParameters& p) { - readContent(p); - - FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); - std::string contentType(FCGX_GetParam("CONTENT_TYPE", p.request.envp)); - - std::string separator("boundary="); - if (!contentType.contains(separator)) { - FCGX_PutS(std::string("No boundary defined").toUtf8().data(), p.request.out); - } else { - QByteArray boundary = QByteArray("--") + contentType.split(separator)[1].toUtf8(); - int boundaryCount = m_content.count(boundary); - if (boundaryCount < 2) { - FCGX_PutS(std::string("Bad boundary number found: %1").arg(boundaryCount).toUtf8().data(), p.request.out); - } else { - while (true) { - int start = m_content.indexOf(boundary) + boundary.size(); - int end = m_content.indexOf(QByteArray("\r\n") + boundary, start); - - if (end == -1) { // no further boundary found: all handled. - break; - } - - QByteArray filecontent = m_content.mid(start, end - start); - int nextBoundaryIndex = end; - - // Read filename - start = filecontent.indexOf("filename=\""); - if (start == -1) { - FCGX_PutS(std::string("Error reading filename / start").toUtf8().data(), p.request.out); - } else { - start += QByteArray("filename=\"").size(); - - end = filecontent.indexOf(QByteArray("\""), start); - if (end == -1) { - FCGX_PutS(std::string("Error reading filename / end").toUtf8().data(), p.request.out); - } else { - std::string filename = std::string::fromUtf8(filecontent.mid(start, end - start)); - - if (filename.size() < 1) { - FCGX_PutS(std::string("Bad filename").toUtf8().data(), p.request.out); - } else { - // Remove header - start = filecontent.indexOf(QByteArray("\r\n\r\n")); - if (start == -1) { - FCGX_PutS(std::string("Error removing upload header").toUtf8().data(), p.request.out); - } else { - - filecontent = filecontent.mid(start + std::string("\r\n\r\n").toUtf8().size()); - - QFile file(m_path + "/" + filename); - if (!file.open(QIODevice::WriteOnly)) { - FCGX_PutS(std::string("Error opening file").toUtf8().data(), p.request.out); - } else { - qint64 written = file.write(filecontent); - if (written != filecontent.size()) { - FCGX_PutS(std::string("Error writing file").toUtf8().data(), p.request.out); - } - } - } - } - } - } - m_content.remove(0, nextBoundaryIndex); - } - } - } - } +class RenameCommand: public PostCommand +{ +public: + RenameCommand() + { + m_commandName = "rename"; + m_isWriteCommand = true; + } + +protected: + virtual std::string start(CommandParameters& p) + { + std::string result{}; + + readContent(p); + + pt::ptree tree; + pt::read_xml(m_content, tree, pt::xml_parser::no_comments | pt::xml_parser::trim_whitespace); + + std::string oldname{tree.get<std::string>("request.oldname")}; + std::string newname{tree.get<std::string>("request.newname")}; + + fs::path oldpath{fs::path(m_path) / oldname}; + fs::path newpath{fs::path(m_path) / newname}; + + try { + fs::rename(oldpath, newpath); + result = "OK"s; + } catch (const std::exception& ex) { + result = "Error renaming "s + oldname + " to " + newname + "<br/>"s; + } + + p.m_SetResponseHeader("content_type", "text/plain"); + return result; + } }; -class DownloadCommand: public GetCommand { - public: - DownloadCommand() { - m_commandName = ""; // default command w/o explict "command=" query argument - m_isWriteCommand = false; - } - - protected: - virtual std::string start(CommandParameters& p) { - QFile file(m_path); - if (file.open(QIODevice::ReadOnly)) { - QFileInfo fileInfo(m_path); - FCGX_PutS(std::string("Content-Disposition: attachment; filename=\"%1\"\r\n").arg(fileInfo.fileName()).toUtf8().data(), p.request.out); - FCGX_PutS("Content-Type: application/octet-stream\r\n\r\n", p.request.out); - - while (!file.atEnd()) { - QByteArray ba = File::getFile(); - FCGX_PutStr(ba.data(), ba.size(), p.request.out); - } - } else { - FCGX_PutS(httpError(500, std::string("Bad file: %1").arg(m_pathInfo)).toUtf8().data(), p.request.out); - } - } +class UploadCommand: public PostCommand +{ +public: + UploadCommand() + { + m_commandName = "upload"; + m_isWriteCommand = true; + } + +protected: + virtual std::string start(CommandParameters& p) + { + std::string result; + readContent(p); + + p.m_SetResponseHeader("content_type", "text/plain"); + + std::string contentType{p.m_GetRequestParam("content_type")}; + + std::string separator("boundary="); + size_t pos {contentType.find(separator)}; + if (pos == contentType.npos) { + result += "No boundary defined"; + } else { + std::string boundary = "--"s + contentType.substr(pos + separator.size()); + std::vector<std::string> occurences; + boost::algorithm::find_all(occurences, m_content, boundary); + size_t boundaryCount = occurences.size(); + if (boundaryCount < 2) { + result += "Bad boundary number found: "s + std::to_string(boundaryCount); + } else { + while (true) { + size_t start {m_content.find(boundary) + boundary.size()}; + size_t end { m_content.find("\r\n"s + boundary, start)}; + + if (end == m_content.npos) // no further boundary found: all handled. + break; + + std::string filecontent { m_content.substr(start, end - start) }; + size_t nextBoundaryIndex = end; + + // Read filename + start = filecontent.find("filename=\""); + if (start == filecontent.npos) { + result += "Error reading filename / start"; + } else { + start += "filename=\""s.size(); + + end = filecontent.find("\""s, start); + if (end == filecontent.npos) { + result += "Error reading filename / end"; + } else { + std::string filename {filecontent.substr(start, end - start)}; + + if (filename.size() < 1) { + result += "Bad filename"; + } else { + // Remove header + start = filecontent.find("\r\n\r\n"); + if (start == filecontent.npos) { + result += "Error removing upload header"; + } else { + filecontent = filecontent.substr(start + "\r\n\r\n"s.size()); + + fs::path path{ fs::path{m_path} / filename}; + try { + File::setFile(path, filecontent); + } catch (const std::exception& ex) { + result += "Error writing to file "s + filename; + } + } + } + } + } + m_content.erase(0, nextBoundaryIndex); + } + } + } + return result; + } }; -} // anonymous namespace +class DownloadCommand: public GetCommand +{ +public: + DownloadCommand() + { + m_commandName = ""; // default command w/o explict "command=" query argument + m_isWriteCommand = false; + } + +protected: + virtual std::string start(CommandParameters& p) + { + try { + std::string result{File::getFile(m_path)}; + + p.m_SetResponseHeader("content_disposition", "attachment; filename=\""s + fs::path{m_path}.filename().string() + "\""s); + p.m_SetResponseHeader("content_type", "application/octet-stream"); + + return result; + } catch (const std::exception& ex) { + return HttpStatus("500", "Bad file: "s + fs::path{m_path}.filename().string(), p); + } + } +}; std::string webbox_plugin::name() { @@ -695,18 +734,18 @@ std::string webbox_plugin::name() webbox_plugin::webbox_plugin() { //std::cout << "Plugin constructor" << std::endl; - registerCommand(m_commands, std::make_shared<DiagCommand>()); - registerCommand(m_commands, std::make_shared<ListCommand>()); - registerCommand(m_commands, std::make_shared<ServerInfoCommand>()); - registerCommand(m_commands, std::make_shared<VersionCommand>()); - registerCommand(m_commands, std::make_shared<NewDirCommand>()); - registerCommand(m_commands, std::make_shared<InfoCommand>()); - registerCommand(m_commands, std::make_shared<DownloadZipCommand>()); - registerCommand(m_commands, std::make_shared<DeleteCommand>()); - registerCommand(m_commands, std::make_shared<MoveCommand>()); - registerCommand(m_commands, std::make_shared<RenameCommand>()); - registerCommand(m_commands, std::make_shared<UploadCommand>()); - registerCommand(m_commands, std::make_shared<DownloadCommand>()); + registerCommand(std::make_shared<DiagCommand>()); + registerCommand(std::make_shared<ListCommand>()); + registerCommand(std::make_shared<ServerInfoCommand>()); + registerCommand(std::make_shared<VersionCommand>()); + registerCommand(std::make_shared<NewDirCommand>()); + registerCommand(std::make_shared<InfoCommand>()); + registerCommand(std::make_shared<DownloadZipCommand>()); + registerCommand(std::make_shared<DeleteCommand>()); + registerCommand(std::make_shared<MoveCommand>()); + registerCommand(std::make_shared<RenameCommand>()); + registerCommand(std::make_shared<UploadCommand>()); + registerCommand(std::make_shared<DownloadCommand>()); } webbox_plugin::~webbox_plugin() @@ -726,10 +765,10 @@ std::string webbox_plugin::generate_page( if (it != commandParameters.paramHash.end()) { std::string& commandName{it->second}; - auto commands_it{commands.find(commandName)}; - if (commands_it != commands.end()) { + auto commands_it{m_commands.find(commandName)}; + if (commands_it != m_commands.end()) { try { - return commands_it->second.execute(commandParameters); + return commands_it->second->execute(commandParameters); } catch (const std::exception& ex) { return HttpStatus("500", "Processing command: "s + commandName, commandParameters); } @@ -739,3 +778,8 @@ std::string webbox_plugin::generate_page( return HttpStatus("400", "No command specified"s, commandParameters); } +void webbox_plugin::registerCommand(std::shared_ptr<Command> command) +{ + m_commands[command->getCommandName()] = command; +}; + diff --git a/plugins/webbox/webbox.h b/plugins/webbox/webbox.h index e2644f3..dd2fb93 100644 --- a/plugins/webbox/webbox.h +++ b/plugins/webbox/webbox.h @@ -6,11 +6,15 @@ #include <string> #include <unordered_map> +class Command; + class webbox_plugin: public webserver_plugin_interface { private: std::unordered_map<std::string, std::shared_ptr<Command>> m_commands; + void registerCommand(std::shared_ptr<Command>); + public: webbox_plugin(); ~webbox_plugin(); |