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();  | 
