diff options
| author | Roland Reichwein <mail@reichwein.it> | 2020-04-12 22:20:33 +0200 | 
|---|---|---|
| committer | Roland Reichwein <mail@reichwein.it> | 2020-04-12 22:20:33 +0200 | 
| commit | 4732dc63657f4c6fc342f7674f7dc7c666b293dc (patch) | |
| tree | da91a5dbbd62982284435d252dd89ac963952ee9 | |
| parent | 3f778eecc705990598f1033e6245522f42e2fcb5 (diff) | |
webbox (WIP)
| -rw-r--r-- | Makefile | 5 | ||||
| -rw-r--r-- | README.txt | 28 | ||||
| -rw-r--r-- | TODO | 2 | ||||
| -rw-r--r-- | debian/webserver.docs | 1 | ||||
| -rw-r--r-- | plugins/static-files/static-files.cpp | 2 | ||||
| -rw-r--r-- | plugins/webbox/Makefile | 1 | ||||
| -rw-r--r-- | plugins/webbox/stringutil.cpp (renamed from stringutil.cpp) | 0 | ||||
| -rw-r--r-- | plugins/webbox/stringutil.h (renamed from stringutil.h) | 0 | ||||
| -rw-r--r-- | plugins/webbox/webbox.cpp | 586 | ||||
| -rw-r--r-- | plugins/webbox/webbox.h | 7 | ||||
| -rw-r--r-- | response.cpp | 7 | ||||
| -rw-r--r-- | test-webserver.cpp | 35 | ||||
| -rw-r--r-- | webserver.conf | 10 | ||||
| -rw-r--r-- | webserver.cpp | 8 | 
14 files changed, 351 insertions, 341 deletions
@@ -1,7 +1,7 @@  DISTROS=debian10  VERSION=$(shell dpkg-parsechangelog --show-field Version)  PROJECTNAME=webserver -PLUGINS=static-files #webbox # weblog cgi fcgi +PLUGINS=static-files# webbox # weblog cgi fcgi  CXX=clang++-10 @@ -65,8 +65,7 @@ PROGSRC=\      plugin.cpp \      privileges.cpp \      response.cpp \ -    server.cpp \ -    stringutil.cpp +    server.cpp  TESTSRC=\      test-webserver.cpp \ @@ -1,14 +1,26 @@ -Edit /etc/webserver.conf +Features +-------- -Enable in Debian: +* Support for IPv4 and IPv6 +* Virtual servers +* Plugin interface -systemctl enable webserver.service -Start: +Configuration +------------- -systemctl start webserver +* Edit /etc/webserver.conf -Status: +* Enable in Debian: -systemctl status webserver -or /var/log/syslog +  # systemctl enable webserver.service + +* Start: + +  # systemctl start webserver + +* Query Status: + +  # systemctl status webserver + +  or observe /var/log/syslog @@ -1,5 +1,5 @@  Certbot: https://certbot.eff.org/lets-encrypt/debianbuster-other -Webbox +Webbox: html, minify  Request properties: Remote Address, e.g. [::1]:8081 -> ipv6 / ipv4  Speed up DocRoot, use string_view diff --git a/debian/webserver.docs b/debian/webserver.docs new file mode 100644 index 0000000..71dfd5b --- /dev/null +++ b/debian/webserver.docs @@ -0,0 +1 @@ +README.txt diff --git a/plugins/static-files/static-files.cpp b/plugins/static-files/static-files.cpp index ad85f22..b137cb8 100644 --- a/plugins/static-files/static-files.cpp +++ b/plugins/static-files/static-files.cpp @@ -72,7 +72,7 @@ std::string static_files_plugin::generate_page(    if (method != "GET" && method != "HEAD")     return HttpStatus("400", "Unknown HTTP method", SetResponseHeader); -  // Request path must be absolute and not contain "..". +  // Request path must not contain "..".    std::string rel_target{GetRequestParam("rel_target")};    if (rel_target.find("..") != std::string::npos) {     std::string target{GetRequestParam("target")}; diff --git a/plugins/webbox/Makefile b/plugins/webbox/Makefile index e16171d..2ddec0e 100644 --- a/plugins/webbox/Makefile +++ b/plugins/webbox/Makefile @@ -58,6 +58,7 @@ endif  PROGSRC=\      file.cpp \ +    stringutil.cpp \      webbox.cpp  TESTSRC=\ diff --git a/stringutil.cpp b/plugins/webbox/stringutil.cpp index f87fa00..f87fa00 100644 --- a/stringutil.cpp +++ b/plugins/webbox/stringutil.cpp diff --git a/stringutil.h b/plugins/webbox/stringutil.h index 5110e2e..5110e2e 100644 --- a/stringutil.h +++ b/plugins/webbox/stringutil.h diff --git a/plugins/webbox/webbox.cpp b/plugins/webbox/webbox.cpp index 5d3f64c..363df6c 100644 --- a/plugins/webbox/webbox.cpp +++ b/plugins/webbox/webbox.cpp @@ -1,15 +1,25 @@  #include "webbox.h" +#include "stringutil.h" +  #include <boost/algorithm/string/replace.hpp> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/xml_parser.hpp> +#include <filesystem>  #include <iostream>  #include <string>  #include <unordered_map>  using namespace std::string_literals; +namespace fs = std::filesystem; +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; + };   unordered_map<std::string> status_map {    { "400", "Bad Request"}, @@ -18,11 +28,57 @@ namespace {    { "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); +   } +  } + } +  + 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") +  { +  } +}; +  // Used to return errors by generating response page and HTTP status code -std::string HttpStatus(std::string status, std::string message, std::function<plugin_interface_setter_type>& SetResponseHeader) +std::string HttpStatus(std::string status, std::string message, CommandParameters& commandParameters)  { - SetResponseHeader("status", status); - SetResponseHeader("content_type", "text/html"); + commandParameters.m_SetResponseHeader("status", status); + commandParameters.m_SetResponseHeader("content_type", "text/html");   auto it{status_map.find(status)};   std::string description{"(Unknown)"}; @@ -32,169 +88,134 @@ std::string HttpStatus(std::string status, std::string message, std::function<pl   return "<html><body><h1>"s + status + " "s + description + "</h1><p>"s + message + "</p></body></html>";  } - -struct CommandParameters { -  std::function<std::string(const std::string& key)>& GetServerParam; -  std::function<std::string(const std::string& key)>& GetRequestParam; // request including body (POST...) -  std::function<void(const std::string& key, const std::string& value)>& SetResponseHeader; // to be added to result string -	FCGX_Request request; // the request - -	QUrlQuery urlQuery; // derived from request -	QHash<QString, QString> paramHash; // derived from urlQuery - -	int count; // request count for this instance - -	// Webbox parameters, constant and set globally in Web server config -	QString webboxPath; -	QString webboxName; -	bool webboxReadOnly; -}; - -class Command { -	public: -		// call interface -		void execute(CommandParameters& p) { -			// check if this webbox is writable and enforce this -			if (p.webboxReadOnly && m_isWriteCommand) { -				printHttpError(400, QString("Webbox is Read-Only"), p); -				return; -			} - -			// check for correct method GET/POST -			QString requestMethod(FCGX_GetParam("REQUEST_METHOD", p.request.envp)); -			if (requestMethod != m_requestMethod) { -				printHttpError(403, QString("Bad request method"), p); -				return; -			} - -			// Set parameters from FastCGI request environment -			m_pathInfo = FCGX_GetParam("PATH_INFO", p.request.envp); -			if (m_pathInfo == "") { -				m_pathInfo = "/"; -			} -			if (m_pathInfo.contains("..")) { -				printHttpError(403, QString("Bad path: %1"), p); -				return; -			} - -			m_path = p.webboxPath + m_pathInfo; - -			this->start(p); -		} - -		QString getCommandName() { -			return m_commandName; -		} - -	protected: -		// helper function for writing http error -		void printHttpError(int httpStatusCode, QString message, CommandParameters& p) { -			FCGX_PutS(httpError(httpStatusCode, message).toUtf8().data(), p.request.out); -		} -		 -		// implemented in implementation classes -		virtual void start(CommandParameters& p) = 0; - -		// Implementation class constants -		QString m_commandName; -		QString m_requestMethod; -		bool m_isWriteCommand; // if true, command must be prevented if p.webboxReadOnly - -		// calculated during start of execute() -		QString m_pathInfo; // path inside webbox, derived from request -		QString m_path; // complete path +class Command +{ +public: + // call interface + std::string execute(CommandParameters& p) + { +  // check if this webbox is writable and enforce this +  if (p.webboxReadOnly && m_isWriteCommand) { +   return HttpStatus("400", "Webbox is Read-Only", p); +  } + +  // check for correct method GET/POST +  std::string requestMethod{p.m_GetRequestParam("method")}; +  if (requestMethod != m_requestMethod) { +   return HttpStatus("403", "Bad request method", p); +  } + +  // Set parameters from FastCGI request environment +  m_pathInfo = p.m_GetRequestParam("rel_path"); +  if (m_pathInfo == "") { +   m_pathInfo = "/"; +  } +  if (m_pathInfo.find("..") != m_pathInfo.npos) { +   return HttpStatus("403", "Bad path: "s + m_pathInfo, p); +  } + +  m_path = p.webboxPath + m_pathInfo; + +  return this->start(p); + } + + std::string getCommandName() + { +  return m_commandName; + } + +protected: + // implemented in implementation classes + virtual std::string start(CommandParameters& p) = 0; + + // Implementation class constants + std::string m_commandName; + std::string m_requestMethod; + bool m_isWriteCommand; // if true, command must be prevented if p.webboxReadOnly + + // calculated during start of execute() + std::string m_pathInfo; // path inside webbox, derived from request + std::string m_path; // complete path  }; -class GetCommand: public Command { -	public: -		GetCommand() { -			m_requestMethod = "GET"; -		} +class GetCommand: public Command +{ +public: + GetCommand() { +  m_requestMethod = "GET"; + }  }; -class PostCommand: public Command { -	public: -		PostCommand() { -			m_requestMethod = "POST"; -		} - -	protected: -		// prepare POST handler implementation: read m_contentLength and m_content -		// needs to be called at beginning of post implementations start() -		// returns true on success -		bool readContent(CommandParameters& p) { -			QString contentLengthString(FCGX_GetParam("CONTENT_LENGTH", p.request.envp)); -			bool ok; -			m_contentLength = contentLengthString.toInt(&ok); - -			if (!ok) { -				printHttpError(400, QString("Bad content length"), p); -				return false; -			} else { -				m_content.resize(m_contentLength); -				 -				int result = FCGX_GetStr(m_content.data(), m_content.size(), p.request.in); -				if (result != m_content.size()) { -					printHttpError(400, QString("Read error (%1/%2)").arg(result).arg(m_content.size()), p); -					return false; -				} -			} -			return true; -		} - -		int m_contentLength; -		QByteArray m_content; +class PostCommand: public Command +{ +public: + PostCommand() + { +  m_requestMethod = "POST"; + } + +protected: + // prepare POST handler implementation: read m_contentLength and m_content + // needs to be called at beginning of post implementations start() + // returns true on success + void readContent(CommandParameters& p) + { +  m_content = p.m_GetRequestParam("body"); +  m_contentLength = m_content.size(); + } + + int m_contentLength; + std::string m_content;  }; -class DiagCommand: public GetCommand { -	public: -		DiagCommand() { -			m_commandName = "diag"; -			m_isWriteCommand = false; -		} - -	protected: -		virtual void start(CommandParameters& p) { -			QString serverName(FCGX_GetParam("SERVER_NAME", p.request.envp)); -			// provide diag only on "localhost" -			if (serverName != "localhost") { -				printHttpError(403, QString("Command not available"), p); -				return; -			} +class DiagCommand: public GetCommand +{ +public: + DiagCommand() + { +  m_commandName = "diag"; +  m_isWriteCommand = false; + } -			FCGX_PutS("Content-Type: text/html\r\n\r\n", p.request.out); +protected: + virtual std::string start(CommandParameters& p) + { +  std::string serverName(p.m_GetRequestParam("host")); +   +  // provide diag only on "localhost" +  if (serverName != "localhost") +   throw std::runtime_error("Command not available"); -			FCGX_PutS("<html><head><title>Params</title></head><body>\r\n", p.request.out); +  p.m_SetRequestParam("content_type", "text/html"); -			FCGX_PutS(QString("Request no. %1<br/><br/>\r\n").arg(p.count).toUtf8().data(), p.request.out); +  std::string result {"<html><head><title>Params</title></head><body>\r\n"}; -			char** tmp = p.request.envp; +  result += "WEBBOX_PATH="s + p.webboxPath +  "<br/>\r\n"s; -			while (*tmp) { -				FCGX_PutS(QString("%1<br/>\r\n").arg(*tmp).toUtf8().data(), p.request.out); -				tmp++; -			} -				 -			FCGX_PutS(QString("<br/>WEBBOX_PATH=%1<br/>\r\n").arg(p.webboxPath).toUtf8().data(), p.request.out); +  result += "<br/>URL Query="s + p.m_GetRequestParam("rel_target") + "<br/>\r\n";; -			FCGX_PutS(QString("<br/>URL Query=%1<br/>\r\n").arg(p.urlQuery.toString()).toUtf8().data(), p.request.out); +  result += "</body></html>\r\n"; -			 -			FCGX_PutS("</body></html>\r\n", p.request.out); -		} +  return result; + }  }; -class ListCommand: public GetCommand { -	public: -		ListCommand() { -			m_commandName = "list"; -			m_isWriteCommand = false; -		} - -	protected: -		virtual void start(CommandParameters& p) { -			FCGX_PutS("Content-Type: text/xml\r\n\r\n", p.request.out); - +class ListCommand: public GetCommand +{ +public: + ListCommand() + { +  m_commandName = "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); @@ -227,7 +248,7 @@ class ServerInfoCommand: public GetCommand {  		}  	protected: -		virtual void start(CommandParameters& p) { +		virtual std::string start(CommandParameters& p) {  			FCGX_PutS("Content-Type: text/xml\r\n\r\n", p.request.out);  			QByteArray xmlData; @@ -253,9 +274,9 @@ class VersionCommand: public GetCommand {  		}  	protected: -		virtual void start(CommandParameters& p) { +		virtual std::string start(CommandParameters& p) {  			FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); -			FCGX_PutS(QString("webbox %1<br/>(C) 2018 <a href=\"https://www.reichwein.it/\">Reichwein.IT</a>\r\n").arg(PROGRAMVERSION).toUtf8().data(), 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);  		}  }; @@ -267,9 +288,8 @@ class NewDirCommand: public PostCommand {  		}  	protected: -		virtual void start(CommandParameters& p) { -			if (!readContent(p)) -				return; +		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); @@ -277,7 +297,7 @@ class NewDirCommand: public PostCommand {  			while (!xml.atEnd()) {  				while (xml.readNextStartElement()) {  					if (xml.name() == "dirname") { -						QString dirname = xml.readElementText(); +						std::string dirname = xml.readElementText();  						QDir dir(m_path);  						if (dir.mkdir(dirname)) {  							FCGX_PutS("Successfully created directory", p.request.out); @@ -298,9 +318,8 @@ class InfoCommand: public PostCommand {  		}  	protected: -		virtual void start(CommandParameters& p) { -			if (!readContent(p)) -				return; +		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); @@ -310,16 +329,16 @@ class InfoCommand: public PostCommand {  					if (xml.name() == "files") {  						while (xml.readNextStartElement()) {  							if (xml.name() == "file") { -								QString filename = xml.readElementText(); +								std::string filename = xml.readElementText();  								QFileInfo fileInfo(m_path + "/" + filename);  								qint64 size = fileInfo.size(); -								QString date = fileInfo.lastModified().toString(); +								std::string date = fileInfo.lastModified().toString();  								if (fileInfo.isDir()) { -									FCGX_PutS(QString("%1, %2 (folder)<br>") +									FCGX_PutS(std::string("%1, %2 (folder)<br>")  											.arg(filename)  											.arg(date).toUtf8().data(), p.request.out);  								} else { -									FCGX_PutS(QString("%1, %2 bytes, %3 (file)<br>") +									FCGX_PutS(std::string("%1, %2 bytes, %3 (file)<br>")  											.arg(filename)  											.arg(size)  											.arg(date).toUtf8().data(), p.request.out); @@ -339,19 +358,18 @@ class DownloadZipCommand: public PostCommand {  		}  	protected: -		virtual void start(CommandParameters& p) { -			if (!readContent(p)) -				return; +		virtual std::string start(CommandParameters& p) { +			readContent(p);  			QXmlStreamReader xml(m_content);  			QByteArray zipData; -			QStringList argumentList; +			std::stringList argumentList;  			QTemporaryFile tempfile(QDir::tempPath() + "/webboxXXXXXX.zip");  			tempfile.open();  			QFileInfo fileInfo(tempfile); -			QString tempfilePath = fileInfo.absolutePath(); -			QString tempfileName = fileInfo.fileName(); +			std::string tempfilePath = fileInfo.absolutePath(); +			std::string tempfileName = fileInfo.fileName();  			tempfile.close();  			tempfile.remove(); @@ -363,7 +381,7 @@ class DownloadZipCommand: public PostCommand {  					if (xml.name() == "files") {  						while (xml.readNextStartElement()) {  							if (xml.name() == "file") { -								QString filename = xml.readElementText(); +								std::string filename = xml.readElementText();  								argumentList.append(filename); // add parts  							} @@ -379,7 +397,7 @@ class DownloadZipCommand: public PostCommand {  			process.start();  			process.waitForFinished(); -			QString debugText = process.readAll(); +			std::string debugText = process.readAll();  			process.setReadChannel(QProcess::StandardError);  			debugText += process.readAll(); @@ -387,7 +405,7 @@ class DownloadZipCommand: public PostCommand {  			    process.exitCode() != 0 ||  			    process.exitStatus() != QProcess::NormalExit)  			{ -				printHttpError(500, QString("Error running process: %1 %2 %3 %4 %5 %6 %7"). +				printHttpError(500, std::string("Error running process: %1 %2 %3 %4 %5 %6 %7").  					  arg(process.state()).  					  arg(process.exitCode()).  					  arg(process.exitStatus()). @@ -399,11 +417,11 @@ class DownloadZipCommand: public PostCommand {  				QFile tempfile(tempfilePath + "/" + tempfileName);  				if (!tempfile.open(QIODevice::ReadOnly)) { -					printHttpError(500, QString("Error reading file"), p); +					printHttpError(500, std::string("Error reading file"), p);  				} else {  					zipData = tempfile.readAll(); -					FCGX_PutS(QString("Content-Disposition: attachment; filename=\"%1\"\r\n").arg("webbox-download.zip").toUtf8().data(), p.request.out); +					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); @@ -423,34 +441,33 @@ class DeleteCommand: public PostCommand {  		}  	protected: -		virtual void start(CommandParameters& p) { -			if (!readContent(p)) -				return; +		virtual std::string start(CommandParameters& p) { +			readContent(p);  			QXmlStreamReader xml(m_content); -			QString response = ""; +			std::string response = "";  			while (!xml.atEnd()) {  				while (xml.readNextStartElement()) {  					if (xml.name() == "files") {  						while (xml.readNextStartElement()) {  							if (xml.name() == "file") { -								QString filename = xml.readElementText(); +								std::string filename = xml.readElementText();  								QFileInfo fileInfo(m_path + "/" + filename);  								if (fileInfo.isDir()) {  									QDir dir(m_path);  									if (!dir.rmdir(filename)) { -										response += QString("Error on removing directory %1<br/>").arg(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 += QString("Error on removing file %1<br/>").arg(filename); +										response += std::string("Error on removing file %1<br/>").arg(filename);  									}  								} else { -									response += QString("Error: %1 is neither file nor directory.<br/>").arg(filename); +									response += std::string("Error: %1 is neither file nor directory.<br/>").arg(filename);  								}  							}  						} @@ -475,14 +492,13 @@ class MoveCommand: public PostCommand {  		}  	protected: -		virtual void start(CommandParameters& p) { -			if (!readContent(p)) -				return; +		virtual std::string start(CommandParameters& p) { +			readContent(p);  			QXmlStreamReader xml(m_content); -			QString response = ""; -			QString targetDir; +			std::string response = ""; +			std::string targetDir;  			while (!xml.atEnd()) {  				while (xml.readNextStartElement()) { @@ -491,21 +507,21 @@ class MoveCommand: public PostCommand {  							if (xml.name() == "target") {  								targetDir = xml.readElementText();  							} else if (xml.name() == "file") { -								QString filename = xml.readElementText(); +								std::string filename = xml.readElementText();  								QFileInfo fileInfo(m_path + "/" + filename);  								if (fileInfo.isDir()) {  									QDir dir(m_path);  									if (!dir.rename(filename, targetDir + "/" + filename)) { -										response += QString("Error moving directory %1<br/>").arg(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 += QString("Error on moving file %1<br/>").arg(filename); +										response += std::string("Error on moving file %1<br/>").arg(filename);  									}  								} else { -									response += QString("Error: %1 is neither file nor directory.<br/>").arg(filename); +									response += std::string("Error: %1 is neither file nor directory.<br/>").arg(filename);  								}  							}  						} @@ -530,14 +546,13 @@ class RenameCommand: public PostCommand {  		}  	protected: -		virtual void start(CommandParameters& p) { -			if (!readContent(p)) -				return; +		virtual std::string start(CommandParameters& p) { +			readContent(p);  			QXmlStreamReader xml(m_content); -			QString oldname; -			QString newname; +			std::string oldname; +			std::string newname;  			while (!xml.atEnd()) {  				while (xml.readNextStartElement()) { @@ -555,9 +570,9 @@ class RenameCommand: public PostCommand {  			}  			QDir dir(m_path); -			QString response; +			std::string response;  			if (!dir.rename(oldname, newname)) { -				response = QString("Error renaming %1 to %2<br/>").arg(oldname).arg(newname); +				response = std::string("Error renaming %1 to %2<br/>").arg(oldname).arg(newname);  			} else {  				response = "OK";  			} @@ -575,21 +590,20 @@ class UploadCommand: public PostCommand {  		}  	protected: -		virtual void start(CommandParameters& p) { -			if (!readContent(p)) -				return; +		virtual std::string start(CommandParameters& p) { +			readContent(p);  			FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); -			QString contentType(FCGX_GetParam("CONTENT_TYPE", p.request.envp)); +			std::string contentType(FCGX_GetParam("CONTENT_TYPE", p.request.envp)); -			QString separator("boundary="); +			std::string separator("boundary=");  			if (!contentType.contains(separator)) { -				FCGX_PutS(QString("No boundary defined").toUtf8().data(), p.request.out); +				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(QString("Bad boundary number found: %1").arg(boundaryCount).toUtf8().data(), p.request.out); +					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(); @@ -605,34 +619,34 @@ class UploadCommand: public PostCommand {  						// Read filename  						start = filecontent.indexOf("filename=\"");  						if (start == -1) { -							FCGX_PutS(QString("Error reading filename / start").toUtf8().data(), p.request.out); +							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(QString("Error reading filename / end").toUtf8().data(), p.request.out); +								FCGX_PutS(std::string("Error reading filename / end").toUtf8().data(), p.request.out);  							} else { -								QString filename = QString::fromUtf8(filecontent.mid(start, end - start)); +								std::string filename = std::string::fromUtf8(filecontent.mid(start, end - start));  								if (filename.size() < 1) { -									FCGX_PutS(QString("Bad filename").toUtf8().data(), p.request.out); +									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(QString("Error removing upload header").toUtf8().data(), p.request.out); +										FCGX_PutS(std::string("Error removing upload header").toUtf8().data(), p.request.out);  									} else { -										filecontent = filecontent.mid(start + QString("\r\n\r\n").toUtf8().size()); +										filecontent = filecontent.mid(start + std::string("\r\n\r\n").toUtf8().size());  										QFile file(m_path + "/" + filename);  										if (!file.open(QIODevice::WriteOnly)) { -											FCGX_PutS(QString("Error opening file").toUtf8().data(), p.request.out); +											FCGX_PutS(std::string("Error opening file").toUtf8().data(), p.request.out);  										} else {  											qint64 written = file.write(filecontent);  											if (written != filecontent.size()) { -												FCGX_PutS(QString("Error writing file").toUtf8().data(), p.request.out); +												FCGX_PutS(std::string("Error writing file").toUtf8().data(), p.request.out);  											}  										}  									} @@ -654,11 +668,11 @@ class DownloadCommand: public GetCommand {  		}  	protected: -		virtual void start(CommandParameters& p) { +		virtual std::string start(CommandParameters& p) {  			QFile file(m_path);  			if (file.open(QIODevice::ReadOnly)) {  				QFileInfo fileInfo(m_path); -				FCGX_PutS(QString("Content-Disposition: attachment; filename=\"%1\"\r\n").arg(fileInfo.fileName()).toUtf8().data(), p.request.out); +				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()) { @@ -666,116 +680,11 @@ class DownloadCommand: public GetCommand {  					FCGX_PutStr(ba.data(), ba.size(), p.request.out);  				}  			} else { -				FCGX_PutS(httpError(500, QString("Bad file: %1").arg(m_pathInfo)).toUtf8().data(), p.request.out); +				FCGX_PutS(httpError(500, std::string("Bad file: %1").arg(m_pathInfo)).toUtf8().data(), p.request.out);  			}  		}  }; -// Hash of commands for fast access -class Commands: public QHash<QString, Command*> { -	public: -		void registerCommand(Command& command) { -			(*this)[command.getCommandName()] = &command; -		} -}; - -void initlocale() { -	if (setenv("LC_ALL", "UTF-8", 1)) { -		exit(1); -	} -} - -int main(int argc, char* argv[]) { -	initlocale(); - -	int result = FCGX_Init(); -	if (result != 0) { -		return 1; // error on init -	} - -	CommandParameters commandParameters; - -	FCGX_Request& request = commandParameters.request; - -	if (FCGX_InitRequest(&request, 0, 0) != 0) { -		return 1; // error on init -	} - -	commandParameters.count = 0; - -	// values constant to this instance: -	commandParameters.webboxPath = getenv("WEBBOX_PATH"); -	commandParameters.webboxName = getenv("WEBBOX_NAME"); -	char* WEBBOX_READONLY = getenv("WEBBOX_READONLY"); -	commandParameters.webboxReadOnly = ((WEBBOX_READONLY != NULL) && !strcmp(WEBBOX_READONLY, "On")); - -	Commands commands; - -	DiagCommand diagCommand; -	commands.registerCommand(diagCommand); - -	ListCommand listCommand; -	commands.registerCommand(listCommand); - -	ServerInfoCommand serverInfoCommand; -	commands.registerCommand(serverInfoCommand); - -	VersionCommand versionCommand; -	commands.registerCommand(versionCommand); - -	NewDirCommand newDirCommand; -	commands.registerCommand(newDirCommand); - -	InfoCommand infoCommand; -	commands.registerCommand(infoCommand); - -	DownloadZipCommand downloadZipCommand; -	commands.registerCommand(downloadZipCommand); -	 -	DeleteCommand deleteCommand; -	commands.registerCommand(deleteCommand); -	 -	MoveCommand moveCommand; -	commands.registerCommand(moveCommand); -	 -	RenameCommand renameCommand; -	commands.registerCommand(renameCommand); -	 -	UploadCommand uploadCommand; -	commands.registerCommand(uploadCommand); -	 -	DownloadCommand downloadCommand; -	commands.registerCommand(downloadCommand); -	 -	while (FCGX_Accept_r(&request) == 0) { - -		commandParameters.count++; - -		QString queryString(FCGX_GetParam("QUERY_STRING", request.envp)); - -		// URL parameters -		commandParameters.urlQuery.setQuery(queryString); - -		QList<QPair<QString, QString> > items = commandParameters.urlQuery.queryItems(); -		commandParameters.paramHash.clear(); - -		for (int i = 0; i < items.size(); i++) { -			commandParameters.paramHash[items[i].first] = items[i].second; -		} - -		QString commandName = commandParameters.paramHash["command"]; -		if (commands.contains(commandName)) { -			Command* command = commands[commandName]; - -			command->execute(commandParameters); -		} else { -			FCGX_PutS(httpError(400, QString("Bad command: %1").arg(commandName)).toUtf8().data(), request.out); -		} -	} - -	return 0; -} -  } // anonymous namespace  std::string webbox_plugin::name() @@ -786,6 +695,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>());  }  webbox_plugin::~webbox_plugin() @@ -799,5 +720,22 @@ std::string webbox_plugin::generate_page(    std::function<void(const std::string& key, const std::string& value)>& SetResponseHeader // to be added to result string  )  { - return "Webbox"; + CommandParameters commandParameters(GetServerParam, GetRequestParam, SetResponseHeader); + + auto it {commandParameters.paramHash.find("command")}; + if (it != commandParameters.paramHash.end()) { +  std::string& commandName{it->second}; + +  auto commands_it{commands.find(commandName)}; +  if (commands_it != commands.end()) { +   try { +    return commands_it->second.execute(commandParameters); +   } catch (const std::exception& ex) { +    return HttpStatus("500", "Processing command: "s + commandName, commandParameters); +   } +  } else +   return HttpStatus("400", "Bad command: "s + commandName, commandParameters); + } else +  return HttpStatus("400", "No command specified"s, commandParameters);  } + diff --git a/plugins/webbox/webbox.h b/plugins/webbox/webbox.h index d6d26c1..e2644f3 100644 --- a/plugins/webbox/webbox.h +++ b/plugins/webbox/webbox.h @@ -2,8 +2,15 @@  #include "../../plugin_interface.h" +#include <memory> +#include <string> +#include <unordered_map> +  class webbox_plugin: public webserver_plugin_interface   { +private: + std::unordered_map<std::string, std::shared_ptr<Command>> m_commands; +  public:   webbox_plugin();   ~webbox_plugin(); diff --git a/response.cpp b/response.cpp index 1ee8932..ffd72b6 100644 --- a/response.cpp +++ b/response.cpp @@ -78,6 +78,8 @@ std::unordered_map<std::string, std::function<std::string(RequestContext&)>> Get   {"body", [](RequestContext& req_ctx) { return req_ctx.GetReq().body(); }}, + {"content_length", [](RequestContext& req_ctx) { return std::to_string(req_ctx.GetReq().body().size()); }}, +   {"method", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq().method_string()};}},  }; @@ -100,10 +102,11 @@ std::string GetRequestParam(const std::string& key, RequestContext& req_ctx)   }   // third, look up req parameters + // contains: host   {    try {     return std::string{req_ctx.GetReq()[key]}; -  } catch(...){ +  } catch(...) {     // not found    }   } @@ -202,6 +205,8 @@ response_type generate_response(request_type& req, Server& server)    return res;   } catch(const std::out_of_range& ex) {    return HttpStatus("400", "Bad request: Host "s + std::string{req["host"]} + ":"s + std::string{req.target()} + " unknown"s, res); + } catch(const std::exception& ex) { +  return HttpStatus("400", "Bad request: "s + std::string{ex.what()}, res);   }  } diff --git a/test-webserver.cpp b/test-webserver.cpp index baf8b3f..1b2c043 100644 --- a/test-webserver.cpp +++ b/test-webserver.cpp @@ -1,6 +1,41 @@  #include "gmock/gmock.h"  #include "gtest/gtest.h" +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/xml_parser.hpp> + +#include <sstream> +#include <string> + +using namespace std::string_literals; +namespace pt = boost::property_tree; + +TEST(property_tree, put) +{ + pt::ptree p; +  + pt::ptree entry; + entry.put_value("name1.txt"); + entry.put("<xmlattr>.type", "file1"); + + p.push_back(pt::ptree::value_type("listentry", entry)); + + entry.put_value("name2.txt"); + entry.put("<xmlattr>.type", "file2"); + + p.push_back(pt::ptree::value_type("listentry", entry)); +  + pt::ptree list; + list.push_back(pt::ptree::value_type("list", p)); + + std::stringstream ss; + + pt::xml_parser::write_xml(ss, list /*, pt::xml_parser::xml_writer_make_settings<std::string>(' ', 1)*/); + + EXPECT_EQ(ss.str(), "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<list><listentry type=\"file1\">name1.txt</listentry><listentry type=\"file2\">name2.txt</listentry></list>"); +} + +  int main(int argc, char* argv[]) {   ::testing::InitGoogleMock(&argc, argv);   return RUN_ALL_TESTS(); diff --git a/webserver.conf b/webserver.conf index e3731bc..365e015 100644 --- a/webserver.conf +++ b/webserver.conf @@ -21,12 +21,16 @@      <plugin>static-files</plugin>      <target>/home/ernie/homepage/test</target>     </path> -   <!--     <path requested="/webbox"> +    <plugin>static-files</plugin> +    <target>/home/ernie/webbox/html</target> +   </path> +   <path requested="/webbox/bin">      <plugin>webbox</plugin> -    <target>/var/lib/webbox</target> +    <target>/home/ernie/testbox</target> +    <WEBBOX_NAME>Testbox1</WEBBOX_NAME> +    <WEBBOX_READONLY>0</WEBBOX_READONLY>     </path> -   -->     <certpath>/home/ernie/code/webserver/fullchain.pem</certpath>     <keypath>/home/ernie/code/webserver/privkey.pem</keypath>    </site> diff --git a/webserver.cpp b/webserver.cpp index 51dc6a7..3e312f4 100644 --- a/webserver.cpp +++ b/webserver.cpp @@ -13,8 +13,16 @@ void usage()   std::cout << "usage: webserver [-c <configuration-filename>]" << std::endl;  } +void initlocale() { + if (setenv("LC_ALL", "UTF-8", 1)) { +  exit(1); + } +} +  int main(int argc, char* argv[])  { + initlocale(); +   std::string config_filename;   if (!(argc == 1 || argc == 3)) {  | 
