summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Stigge <stigge@antcom.de>2018-01-05 21:03:31 +0100
committerRoland Stigge <stigge@antcom.de>2018-01-05 21:03:31 +0100
commitf939d19e9bcb0cc0cf048aa0c8037f1f9c5a8c8b (patch)
treec656bd35facfbf57959a9e5c3a6fcad0cdd6df0b
Initial commit, basic working webbox (WIP), see TODO
-rw-r--r--Makefile12
-rw-r--r--TODO22
-rw-r--r--apache/localhost.conf47
-rw-r--r--html/index.html57
-rw-r--r--html/webbox.css138
-rw-r--r--html/webbox.js446
-rw-r--r--src/Makefile17
-rw-r--r--src/webbox.cpp333
8 files changed, 1072 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..cae9e7c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+all:
+ make -C src
+
+install: all
+ sudo service apache2 stop
+ sudo cp -r html/* /var/www/webbox/
+ sudo mkdir -p /usr/lib/webbox
+ sudo cp src/query /usr/lib/webbox/
+ sudo service apache2 start
+
+clean:
+ make -C src clean
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..ba7f43c
--- /dev/null
+++ b/TODO
@@ -0,0 +1,22 @@
+Prio 1 (for next version)
+======
+
+Icons: menu, file, directory
+debian/
+implement TBD features
+handle spaces/umlauts
+download zip
+Upload bug
+Refresh
+
+Prio 2 (for future versions)
+======
+
+gallery
+chromecast
+player
+handle writability
+register GET/POST functions
+handle too small window
+sandclock during operations
+i18n
diff --git a/apache/localhost.conf b/apache/localhost.conf
new file mode 100644
index 0000000..160dbe3
--- /dev/null
+++ b/apache/localhost.conf
@@ -0,0 +1,47 @@
+<VirtualHost *:80>
+ # The ServerName directive sets the request scheme, hostname and port that
+ # the server uses to identify itself. This is used when creating
+ # redirection URLs. In the context of virtual hosts, the ServerName
+ # specifies what hostname must appear in the request's Host: header to
+ # match this virtual host. For the default virtual host (this file) this
+ # value is not decisive as it is used as a last resort host regardless.
+ # However, you must set it for any further virtual host explicitly.
+ ServerName localhost
+
+ ServerAdmin webmaster@localhost
+ DocumentRoot /var/www/webbox
+
+ FcgidInitialEnv WEBBOX_PATH /home/stigge/testbox
+ FcgidInitialEnv WEBBOX_NAME Testbox
+ FcgidMaxRequestLen 100000000
+
+ ScriptAlias /bin/ /usr/lib/webbox/
+ <Directory "/usr/lib/webbox">
+ SetHandler fcgid-script
+
+ AllowOverride None
+ Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
+ # Order allow,deny
+ # Allow from all
+ Require all granted
+ </Directory>
+
+
+ # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
+ # error, crit, alert, emerg.
+ # It is also possible to configure the loglevel for particular
+ # modules, e.g.
+ #LogLevel info ssl:warn
+
+ ErrorLog ${APACHE_LOG_DIR}/error.log
+ CustomLog ${APACHE_LOG_DIR}/access.log combined
+
+ # For most configuration files from conf-available/, which are
+ # enabled or disabled at a global level, it is possible to
+ # include a line for only one particular virtual host. For example the
+ # following line enables the CGI configuration for this host only
+ # after it has been globally disabled with "a2disconf".
+ #Include conf-available/serve-cgi-bin.conf
+</VirtualHost>
+
+# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
diff --git a/html/index.html b/html/index.html
new file mode 100644
index 0000000..023b8bc
--- /dev/null
+++ b/html/index.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Webbox</title>
+ <link rel="stylesheet" type="text/css" href="webbox.css"/>
+ <script src="webbox.js"></script>
+ </head>
+ <body onload="initMainpage();">
+ <div>
+ <h1 class="title"></h1>
+ <input id="uploadfile" type="file" onchange="onUploadFile();" hidden/>
+ <table class="menu">
+ <tr>
+ <td class="firsttd"></td>
+ <td class="entry" onclick="createDir();">New folder</td>
+ <td class="entry" onclick="download();">Download</td>
+ <td class="entry" onclick="upload();">Upload</td>
+ <td class="entry" onclick="deleteItem();">Delete</td>
+ <td class="entry" onclick="move();">Move</td>
+ <td class="entry" onclick="info();">Info</td>
+ <td class="entry" onclick="selectAll();">Select All</td>
+ </tr>
+ </table>
+ </div>
+ <div id="list">
+ </div>
+
+ <div id="debug1">
+ </div>
+
+ <div class="greyout fullscreen" id="greyout" hidden>
+ </div>
+
+ <div class="dialogwindow" id="dialogwindow" hidden>
+ <div id="dialog" class="dialog">
+ </div>
+ <button class="button leftbutton" id="cancelbutton">Cancel</button>
+ <button class="button rightbutton" id="okbutton">OK</button>
+ </div>
+
+ <div id="create-dir-dialog" hidden>
+ New folder:<br>
+ <input type="text" id="newdir" class="textinput"></input>
+ </div>
+
+ <div id="download-zip-dialog" hidden>
+ Download multiple files as ZIP?<br>
+ </div>
+
+ <a download="webbox-download.zip" id="download-a" hidden/>
+
+ <div class="footer">
+ </div>
+ </body>
+</html>
+
diff --git a/html/webbox.css b/html/webbox.css
new file mode 100644
index 0000000..39551e0
--- /dev/null
+++ b/html/webbox.css
@@ -0,0 +1,138 @@
+div, td, h1 {
+ font-family: "sans-serif";
+}
+
+.title {
+ font-size: 16pt;
+}
+
+.greyout {
+ opacity: 0.7;
+ background-color: #FFFFFF;
+ z-index: 9; /* behind dialog window which is 10 */
+}
+
+.button {
+ background-color: #303060;
+ border: none;
+ color: white;
+ padding: 18px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ margin: 2px 2px;
+ border-radius: 6px;
+}
+
+.leftbutton {
+ position: absolute;
+ left: 20px;
+ bottom: 20px;
+}
+
+.rightbutton {
+ position: absolute;
+ right: 20px;
+ bottom: 20px;
+}
+
+.dialogwindow {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ width: 400px;
+ height: 400px;
+ margin-top: -200px;
+ margin-left: -200px;
+ background-color: #FFFFFF;
+ opacity: 1;
+ z-index: 10;
+ border-width: 3px;
+ border-style: solid;
+ border-color: #808080;
+ padding: 10pt;
+}
+
+.dialog {
+ /*width: 100%;*/
+}
+
+.textinput {
+ width: calc(100% - 20px);
+}
+
+.fullscreen {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: 0;
+}
+
+div.footer {
+ font-size: 8pt;
+ padding-top: 30pt;
+ text-align: center;
+}
+
+table.menu {
+ width: 100%;
+ border: 0; /* 1px solid #000000; */
+ border-collapse: collapse;
+ margin: 0;
+ padding: 0;
+ table-layout: fixed;
+}
+
+table.menu tr {
+ border: 0; /* 1px solid #000000; */
+ margin: 0;
+ padding: 0;
+}
+
+table.menu td.firsttd {
+ width: 100%;
+ border: 0; /* 1px solid #000000; */
+ color: #FFFFFF;
+ background-color: #404070;
+ margin: 0;
+ padding: 0 5px 0 5px;
+}
+
+table.menu td.entry {
+ width: 60px;
+ border: 0; /* 1px solid #000000; */
+ color: #FFFFFF;
+ background-color: #404070;
+ padding: 9px;
+ margin: 0;
+ cursor: pointer;
+}
+
+table.list {
+ width: 100%;
+ border: 0; /* 1px solid #000000; */
+ cursor: pointer;
+ background-color: #FFFFFF;
+}
+
+table.list tr {
+ background-color: #FFFFFF;
+}
+
+table.list tr.selectedrow {
+ background-color: #CCCCFF;
+}
+
+table.list td.type {
+ width: 50px;
+ border: 0; /* 1px solid #000000; */
+}
+
+table.list td.name {
+ width: 100%;
+ border: 0; /* 1px solid #000000; */
+ color: #0000FF;
+}
diff --git a/html/webbox.js b/html/webbox.js
new file mode 100644
index 0000000..3bcbf8d
--- /dev/null
+++ b/html/webbox.js
@@ -0,0 +1,446 @@
+var currentDir = "/";
+var listElements;
+var numberOfSelectedRows = 0;
+
+function loadContents(dir) {
+ numberOfSelectedRows = 0;
+
+ var xhr = new XMLHttpRequest();
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState != 4 || this.status != 200) {
+ return;
+ }
+
+ var list = xhr.responseXML;
+ listElements = list.getElementsByTagName("listentry");
+
+ var result = "<table class=\"list\">";
+
+ if (listElements.length == 0) {
+ result += "<tr><td class=\"type\"></td><td class=\"name\">(empty)</td></tr>";
+ } else {
+ for (var i = 0; i < listElements.length; i++) {
+ var type = listElements[i].getAttribute("type");
+
+ result += "<tr " +
+ "onmousedown=\"entryMouseDown('" + listElements[i].childNodes[0].nodeValue + "')\" " +
+ "onmouseup=\"entryMouseUp('" + listElements[i].childNodes[0].nodeValue + "')\"" +
+ "><td class=\"type\">" + type + "</td><td class=\"name\">" + listElements[i].childNodes[0].nodeValue + "</td></tr>";
+ }
+ }
+
+ result += "</table>"
+
+ listElement = document.getElementById("list");
+
+ listElement.innerHTML = result;
+
+ }
+
+ xhr.open("GET", "/bin/query" + currentDir + "?command=list", true);
+ xhr.send();
+}
+
+// return list of file names
+function getFileList() {
+ var result = [];
+
+ for (var i = 0; i < listElements.length; i++) {
+ result.push(listElements[i].childNodes[0].nodeValue);
+ }
+
+ return result;
+}
+
+function getFileType(filename) {
+ for (var i = 0; i < listElements.length; i++) {
+ if (listElements[i].childNodes[0].nodeValue == filename) {
+ return listElements[i].getAttribute("type");
+ }
+ }
+ return "";
+}
+
+function getRow(filename) {
+ var list = document.getElementById("list");
+ var rows = list.getElementsByTagName("tr");
+
+ for (var i = 0; i < rows.length; i++) {
+ var nameElement = rows[i].getElementsByClassName("name")[0];
+ if (nameElement.childNodes[0].nodeValue == filename) {
+ return rows[i];
+ }
+ }
+ return "";
+}
+
+// return list of files
+function getSelectedFiles() {
+ var result = [];
+ var list = document.getElementById("list");
+ var rows = list.getElementsByTagName("tr");
+
+ for (var i = 0; i < rows.length; i++) {
+ if (rows[i].classList.contains("selectedrow")) {
+ result.push(rows[i].getElementsByClassName("name")[0].childNodes[0].nodeValue);
+ }
+ }
+
+ return result;
+}
+
+/*
+var debugc = 0;
+
+function debugf() {
+ debug1 = document.getElementById("debug1");
+ debug1.innerHTML = debugc;
+ debugc++;
+}
+*/
+
+// As long as this is 1, the mouse was pressed less than 1 second ago
+var mouseShortFlag = 0;
+var mouseTimeout = undefined;
+
+function getSelection(filename) {
+ var row = getRow(filename);
+
+ if (row.classList.contains("selectedrow")) {
+ return true;
+ }
+ return false;
+}
+
+function clearSelection(filename) {
+ var row = getRow(filename);
+
+ if (row.classList.contains("selectedrow")) {
+ row.classList.remove("selectedrow");
+ numberOfSelectedRows--;
+ }
+}
+
+function setSelection(filename) {
+ var row = getRow(filename);
+
+ if (!row.classList.contains("selectedrow")) {
+ row.classList.add("selectedrow");
+ numberOfSelectedRows++;
+ }
+}
+
+function toggleSelection(filename) {
+ var row = getRow(filename);
+
+ if (row.classList.contains("selectedrow")) {
+ row.classList.remove("selectedrow");
+ numberOfSelectedRows--;
+ } else {
+ row.classList.add("selectedrow");
+ numberOfSelectedRows++;
+ }
+}
+
+function mouseTimeoutFunction(filename) {
+ mouseShortFlag = 0;
+ toggleSelection(filename);
+}
+
+function entryMouseDown(filename) {
+ if (mouseTimeout !== undefined) {
+ clearTimeout(mouseTimeout);
+ }
+
+ if (numberOfSelectedRows > 0) {
+ toggleSelection(filename);
+ } else {
+ mouseShortFlag = 1;
+ mouseTimeout = setTimeout(function(){ mouseTimeoutFunction(filename); }, 1000);
+ }
+}
+
+function entryMouseUp(filename) {
+ if (mouseTimeout !== undefined) {
+ clearTimeout(mouseTimeout);
+ }
+
+ // short click: download / change dir
+ if (mouseShortFlag) {
+ var type = getFileType(filename);
+ if (type == "file") {
+ download(filename);
+ } else if (type == "dir") {
+ if (filename == "..") {
+ if (!currentDir.includes("/")) {
+ alert("Bad path " + currentDir + " for " + filename);
+ return;
+ }
+ currentDir = currentDir.substr(0, currentDir.lastIndexOf("/"));
+
+ if (currentDir == "") {
+ currentDir = "/";
+ }
+
+ setCurrentDir(currentDir);
+ return;
+ }
+
+ if (!currentDir.endsWith("/")) {
+ currentDir += "/";
+ }
+ setCurrentDir(currentDir + filename);
+ }
+ }
+
+ mouseShortFlag = 0;
+}
+
+function showDialog() {
+ document.getElementById("greyout").style.display = 'block';
+ document.getElementById("dialogwindow").style.display = 'block';
+}
+
+function hideDialog() {
+ document.getElementById("greyout").style.display = 'none';
+ document.getElementById("dialogwindow").style.display = 'none';
+}
+
+function initMainpage() {
+ setCurrentDir("/");
+
+ // default action for "Cancel" button: hide dialog window
+ document.getElementById("cancelbutton").onclick = hideDialog;
+
+ // on Escape, hide dialog window
+ document.onkeydown = function(evt) {
+ if (evt.key == "Escape") {
+ hideDialog();
+ }
+ };
+
+ // load title
+ var xhrTitle = new XMLHttpRequest();
+
+ xhrTitle.onreadystatechange = function() {
+ if (this.readyState != 4 || this.status != 200) {
+ return;
+ }
+ document.getElementsByClassName("title")[0].innerHTML = xhrTitle.responseText;
+ }
+
+ xhrTitle.open("GET", "/bin/query?command=title", true);
+ xhrTitle.send();
+
+ // load footer
+ var xhrFooter = new XMLHttpRequest();
+
+ xhrFooter.onreadystatechange = function() {
+ if (this.readyState != 4 || this.status != 200) {
+ return;
+ }
+ document.getElementsByClassName("footer")[0].innerHTML = xhrFooter.responseText;
+ }
+
+ xhrFooter.open("GET", "/bin/query?command=version", true);
+ xhrFooter.send();
+}
+
+function setCurrentDir(newDir) {
+ currentDir = newDir;
+ loadContents(newDir);
+
+ var menu = document.getElementsByClassName("menu")[0];
+ var firsttd = menu.getElementsByClassName("firsttd")[0];
+ firsttd.innerHTML = newDir;
+}
+
+function download(filename) {
+ if (filename === undefined) { // download selection as ZIP
+ showDialog();
+
+ var files = getSelectedFiles();
+ if (files.length == 0) {
+ document.getElementById("dialog").innerHTML = "No files selected.";
+ document.getElementById("okbutton").onclick = hideDialog;
+ return;
+ }
+
+ document.getElementById("dialog").innerHTML = document.getElementById("download-zip-dialog").innerHTML;
+
+ document.getElementById("okbutton").onclick = function() {
+ hideDialog();
+
+ // send info request for files
+ var xhr = new XMLHttpRequest();
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState != 4 || this.status != 200) {
+ return;
+ }
+
+ var a = document.getElementById("download-a");
+ var file = new Blob([this.response]);
+ a.href = window.URL.createObjectURL(file);
+ a.click();
+ }
+
+ var files = getSelectedFiles();
+ var parser = new DOMParser();
+ var xmlDocument = parser.parseFromString("<files></files>", "text/xml");
+ var filesElement = xmlDocument.getElementsByTagName("files")[0];
+
+ for (var i = 0; i < files.length; i++) {
+ var fileElement = xmlDocument.createElement("file");
+ fileElement.appendChild(document.createTextNode(files[i]));
+ filesElement.appendChild(fileElement);
+ }
+
+ xhr.open("POST", "/bin/query" + currentDir + "?command=download-zip", true);
+ xhr.setRequestHeader("Content-type", "text/xml");
+ xhr.send(xmlDocument);
+ }
+ } else {
+ var dir = currentDir;
+ if (dir != "/") {
+ dir += "/"
+ }
+ document.location.href = "/bin/query" + dir + filename;
+ }
+}
+
+function createDir() {
+ showDialog();
+
+ // hide dialog when done
+ document.getElementById("okbutton").onclick = function() {
+ // send new folder request for directory
+ var xhr = new XMLHttpRequest();
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState != 4 || this.status != 200) {
+ return;
+ }
+
+ document.getElementById("dialog").innerHTML = xhr.responseText;
+ document.getElementById("okbutton").onclick = hideDialog;
+ loadContents(currentDir); // load new file list with new dir
+ }
+
+ var parser = new DOMParser();
+ var xmlDocument = parser.parseFromString("<dirname></dirname>", "text/xml");
+ var dirElement = xmlDocument.getElementsByTagName("dirname")[0];
+
+ dirElement.appendChild(document.createTextNode(document.getElementById("newdir").value));
+
+ xhr.open("POST", "/bin/query" + currentDir + "?command=newdir", true);
+ xhr.setRequestHeader("Content-type", "text/xml");
+ xhr.send(xmlDocument);
+ }
+
+ document.getElementById("dialog").innerHTML = document.getElementById("create-dir-dialog").innerHTML;
+}
+
+function upload() {
+ var uploadfile = document.getElementById("uploadfile");
+
+ uploadfile.click();
+}
+
+function onUploadFile() {
+ var xhr = new XMLHttpRequest();
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState != 4 || this.status != 200) {
+ return;
+ }
+
+ if (xhr.responseText == "OK") {
+ alert("Upload successful.");
+ loadContents(currentDir); // load new file list with uploaded file
+ } else {
+ alert("Error: " + xhr.responseText);
+ }
+ }
+
+ var uploadfile = document.getElementById("uploadfile");
+ var formData = new FormData();
+ formData.append("uploadfile", uploadfile.files[0]);
+
+ xhr.open("POST", "/bin/query" + currentDir + "?command=upload", true);
+ xhr.send(formData);
+}
+
+function deleteItem() {
+ alert("TBD");
+}
+
+function move() {
+ alert("TBD");
+}
+
+// File info: date, size, type
+function info() {
+ showDialog();
+ document.getElementById("cancelbutton").style.display = "none"; // hide "cancel" button in info dialog, only provide "OK"
+
+ // hide dialog when done
+ document.getElementById("okbutton").onclick = function() {
+ hideDialog();
+ document.getElementById("cancelbutton").style.display = "block";
+ }
+
+ var files = getSelectedFiles();
+ if (files.length == 0) {
+ document.getElementById("dialog").innerHTML = "No files selected.";
+ return;
+ }
+
+ // send info request for files
+ var xhr = new XMLHttpRequest();
+
+ xhr.onreadystatechange = function() {
+ if (this.readyState != 4 || this.status != 200) {
+ return;
+ }
+
+ document.getElementById("dialog").innerHTML = xhr.responseText;
+ }
+
+ var parser = new DOMParser();
+ var xmlDocument = parser.parseFromString("<files></files>", "text/xml");
+ var filesElement = xmlDocument.getElementsByTagName("files")[0];
+
+ for (var i = 0; i < files.length; i++) {
+ var fileElement = xmlDocument.createElement("file");
+ fileElement.appendChild(document.createTextNode(files[i]));
+ filesElement.appendChild(fileElement);
+ }
+
+ xhr.open("POST", "/bin/query" + currentDir + "?command=info", true);
+ xhr.setRequestHeader("Content-type", "text/xml");
+ xhr.send(xmlDocument);
+}
+
+// select all files, except if all are selected, unselect all
+function selectAll() {
+ var files = getFileList();
+
+ var allSelected = true;
+
+ for (var i = 0; i < files.length; i++) {
+ if (getSelection(files[i]) == false) {
+ allSelected = false;
+ }
+ }
+
+ for (var i = 0; i < files.length; i++) {
+ if (allSelected) {
+ clearSelection(files[i]);
+ } else {
+ setSelection(files[i]);
+ }
+ }
+}
+
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..e960f67
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,17 @@
+TARGET=query
+CPPFLAGS=-Wall -O2 -fPIC -I/usr/include/x86_64-linux-gnu/qt5 -I/usr/include/x86_64-linux-gnu/qt5/QtCore
+LDFLAGS=-Wall -O2 -fPIC -lstdc++ -lfcgi -lQt5Core
+OBJS=webbox.o
+
+all: $(TARGET)
+
+$(TARGET): $(OBJS)
+ gcc $(LDFLAGS) -o $@ $^
+
+%.o: %.cpp
+ gcc $(CPPFLAGS) -c -o $@ $<
+
+clean:
+ -rm -rf $(TARGET) $(OBJS)
+
+.PHONY: clean all
diff --git a/src/webbox.cpp b/src/webbox.cpp
new file mode 100644
index 0000000..ab65529
--- /dev/null
+++ b/src/webbox.cpp
@@ -0,0 +1,333 @@
+#include <fcgiapp.h>
+
+#include <QString>
+#include <QStringList>
+#include <QHash>
+#include <QDir>
+#include <QFileInfo>
+#include <QXmlStreamReader>
+#include <QDateTime>
+
+#define PROGRAMVERSION "1.0"
+#define BUFSIZE 1000000
+
+// supported httpStatusCode:
+// 400 Bad Request
+// 403 Forbidden
+// 404 Not Found
+// 500 Internal Server Error
+// message: additional message
+QString httpError(int httpStatusCode, QString message) {
+ QString description;
+
+ switch(httpStatusCode) {
+ case 400:
+ description = "Bad Request";
+ break;
+ case 403:
+ description = "Forbidden";
+ break;
+ case 404:
+ description = "Not Found";
+ break;
+ case 500:
+ description = "Internal Server Error";
+ break;
+ default:
+ message = QString("Bad error code: %1, message: %2").arg(httpStatusCode).arg(message);
+ httpStatusCode = 500;
+ description = "Internal Server Error";
+ }
+ return QString("Status: %1 %2\r\nContent-Type: text/html\r\n\r\n<html><body><h1>%1 %2</h1><p>%3</p></body></html>\r\n").arg(httpStatusCode).arg(description).arg(message);
+}
+
+int main(int argc, char* argv[]) {
+ int result = FCGX_Init();
+ if (result != 0) {
+ return 1; // error on init
+ }
+
+ FCGX_Request request;
+
+ if (FCGX_InitRequest(&request, 0, 0) != 0) {
+ return 1; // error on init
+ }
+
+ int count = 0;
+
+ while (FCGX_Accept_r(&request) == 0) {
+
+ count++;
+
+ QString queryString(FCGX_GetParam("QUERY_STRING", request.envp));
+
+ // URL parameters
+ QStringList paramList = queryString.split("&");
+ QHash<QString, QString> paramHash;
+
+ foreach(QString keyValue, paramList) {
+ QStringList keyValueList = keyValue.split("=");
+ if (keyValueList.size() == 2) {
+ QString key = keyValueList[0];
+ QString value = keyValueList[1];
+ paramHash[key] = value;
+ }
+ }
+
+ QString command = paramHash["command"];
+
+ // process environment
+ QString webboxPath(getenv("WEBBOX_PATH"));
+
+ // FastCGI request environment
+ QString pathInfo(FCGX_GetParam("PATH_INFO", request.envp));
+ if (pathInfo == "") {
+ pathInfo = "/";
+ }
+ if (pathInfo.contains("..")) {
+ FCGX_PutS(httpError(403, QString("Bad path: %1").arg(pathInfo)).toUtf8().data(), request.out);
+ continue;
+ }
+
+ QString path = webboxPath + pathInfo;
+
+ if (command == "diag") {
+ FCGX_PutS("Content-Type: text/html\r\n\r\n", request.out);
+
+ FCGX_PutS("<html><head><title>Params</title></head><body>\r\n", request.out);
+
+ FCGX_PutS(QString("Request no. %1<br/><br/>\r\n").arg(count).toUtf8().data(), request.out);
+
+ char** tmp = request.envp;
+
+ while (*tmp) {
+ FCGX_PutS(QString("%1<br/>\r\n").arg(*tmp).toUtf8().data(), request.out);
+ tmp++;
+ }
+
+ FCGX_PutS(QString("<br/>WEBBOX_PATH=%1<br/>\r\n").arg(getenv("WEBBOX_PATH")).toUtf8().data(), request.out);
+
+ FCGX_PutS("</body></html>\r\n", request.out);
+ } else
+ if (command == "list") {
+ FCGX_PutS("Content-Type: text/xml\r\n\r\n", request.out);
+
+ FCGX_PutS("<list>\r\n", request.out);
+
+ QDir dir(path);
+ QFileInfoList dirEntryList = dir.entryInfoList(QDir::NoDot | QDir::AllEntries, QDir::DirsFirst | QDir::Name);
+ foreach(QFileInfo i, dirEntryList) {
+ if (pathInfo != "/" || i.fileName() != "..") { // skip on ".." in "/"
+ FCGX_PutS(QString("<listentry type=\"%1\">%2</listentry>\r\n").arg(i.isDir() ? "dir" : "file").arg(i.fileName()).toUtf8().data(), request.out);
+ }
+ }
+ FCGX_PutS("</list>\r\n", request.out);
+ } else if (command == "title") {
+ FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out);
+ FCGX_PutS(getenv("WEBBOX_NAME"), request.out);
+ } else if (command == "version") {
+ FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out);
+ FCGX_PutS(QString("webbox %1<br/>(C) 2017 <a href=\"https://www.reichwein.it/\">Reichwein.IT</a>\r\n").arg(PROGRAMVERSION).toUtf8().data(), request.out);
+ } else if (command == "newdir") { // POST!
+ QString contentLengthString(FCGX_GetParam("CONTENT_LENGTH", request.envp));
+ bool ok;
+ int contentLength = contentLengthString.toInt(&ok);
+
+ FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out);
+ if (!ok) {
+ FCGX_PutS(QString("Bad content length").toUtf8().data(), request.out);
+ } else {
+ QByteArray content(contentLength, 0);
+
+ int result = FCGX_GetStr(content.data(), content.size(), request.in);
+ if (result != content.size()) {
+ FCGX_PutS(QString("Read error (%1/%2)").arg(result).arg(content.size()).toUtf8().data(), request.out);
+ } else {
+ QXmlStreamReader xml(content);
+
+ while (!xml.atEnd()) {
+ while (xml.readNextStartElement()) {
+ if (xml.name() == "dirname") {
+ QString dirname = xml.readElementText();
+ QDir dir(path);
+ if (dir.mkdir(dirname)) {
+ FCGX_PutS("Successfully created directory", request.out);
+ } else {
+ FCGX_PutS("Error creating directory", request.out);
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (command == "info") { // POST!
+ QString contentLengthString(FCGX_GetParam("CONTENT_LENGTH", request.envp));
+ bool ok;
+ int contentLength = contentLengthString.toInt(&ok);
+
+ FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out);
+ if (!ok) {
+ FCGX_PutS(QString("Bad content length").toUtf8().data(), request.out);
+ } else {
+ QByteArray content(contentLength, 0);
+
+ int result = FCGX_GetStr(content.data(), content.size(), request.in);
+ if (result != content.size()) {
+ FCGX_PutS(QString("Read error (%1/%2)").arg(result).arg(content.size()).toUtf8().data(), request.out);
+ } else {
+ QXmlStreamReader xml(content);
+
+ while (!xml.atEnd()) {
+ while (xml.readNextStartElement()) {
+ if (xml.name() == "files") {
+ while (xml.readNextStartElement()) {
+ if (xml.name() == "file") {
+ QString filename = xml.readElementText();
+ QFileInfo fileInfo(path + "/" + filename);
+ qint64 size = fileInfo.size();
+ QString date = fileInfo.lastModified().toString();
+ QString type = fileInfo.isDir() ? "directory" : "file";
+ FCGX_PutS(QString("%1, %2 bytes, %3 (%4)<br>")
+ .arg(filename)
+ .arg(size)
+ .arg(date)
+ .arg(type).toUtf8().data(), request.out);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } else if (command == "download-zip") { // POST!
+ QString contentLengthString(FCGX_GetParam("CONTENT_LENGTH", request.envp));
+ bool ok;
+ int contentLength = contentLengthString.toInt(&ok);
+
+ if (!ok) {
+ FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out);
+ FCGX_PutS(QString("Bad content length").toUtf8().data(), request.out);
+ } else {
+ QByteArray content(contentLength, 0);
+
+ int result = FCGX_GetStr(content.data(), content.size(), request.in);
+ if (result != content.size()) {
+ FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out);
+ FCGX_PutS(QString("Read error (%1/%2)").arg(result).arg(content.size()).toUtf8().data(), request.out);
+ } else {
+ QXmlStreamReader xml(content);
+
+ QByteArray zipData;
+
+ while (!xml.atEnd()) {
+ while (xml.readNextStartElement()) {
+ if (xml.name() == "files") {
+ while (xml.readNextStartElement()) {
+ if (xml.name() == "file") {
+ QString filename = xml.readElementText();
+
+ zipData.append(filename.toUtf8()); // TBD
+ }
+ }
+ }
+ }
+ }
+
+ FCGX_PutS(QString("Content-Disposition: attachment; filename=\"%1\"\r\n").arg("webbox-download.zip").toUtf8().data(), request.out);
+ FCGX_PutS("Content-Type: application/octet-stream\r\n\r\n", request.out);
+
+ FCGX_PutStr(zipData.data(), zipData.size(), request.out);
+ }
+ }
+ } else if (command == "upload") { // POST!
+ QString contentLengthString(FCGX_GetParam("CONTENT_LENGTH", request.envp));
+ bool ok;
+ int contentLength = contentLengthString.toInt(&ok);
+ FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out);
+ if (!ok) {
+ FCGX_PutS(QString("Bad content length").toUtf8().data(), request.out);
+ } else {
+ QByteArray content(contentLength, 0);
+
+ int result = FCGX_GetStr(content.data(), content.size(), request.in);
+ if (result != content.size()) {
+ FCGX_PutS(QString("Read error (%1/%2)").arg(result).arg(content.size()).toUtf8().data(), request.out);
+ } else {
+ QString contentType(FCGX_GetParam("CONTENT_TYPE", request.envp));
+
+ if (!contentType.contains("boundary=--")) {
+ FCGX_PutS(QString("No boundary defined").toUtf8().data(), request.out);
+ } else {
+ QByteArray boundary = contentType.split("boundary=--")[1].toUtf8();
+ int boundaryCount = content.count(boundary);
+ if (boundaryCount != 2) {
+ FCGX_PutS(QString("Bad boundary number found: %1").arg(boundaryCount).toUtf8().data(), request.out);
+ } else {
+ int start = content.indexOf(boundary) + boundary.size();
+ int end = content.indexOf(boundary, start);
+
+ content = content.mid(start, end - start);
+
+ // Read filename
+ start = content.indexOf("filename=\"");
+ if (start == -1) {
+ FCGX_PutS(QString("Error reading filename / start").toUtf8().data(), request.out);
+ } else {
+ start += QString("filename=\"").size();
+
+ end = content.indexOf("\"", start);
+ if (end == -1) {
+ FCGX_PutS(QString("Error reading filename / end").toUtf8().data(), request.out);
+ } else {
+ QString filename = content.mid(start, end - start);
+
+ if (filename.size() < 1) {
+ FCGX_PutS(QString("Bad filename").toUtf8().data(), request.out);
+ } else {
+ // Remove header
+ start = content.indexOf("\r\n\r\n");
+ if (start == -1) {
+ FCGX_PutS(QString("Error removing upload header").toUtf8().data(), request.out);
+ } else {
+
+ content = content.mid(start + QString("\r\n\r\n").size());
+
+ QFile file(path + "/" + filename);
+ if (!file.open(QIODevice::WriteOnly)) {
+ FCGX_PutS(QString("Error opening file").toUtf8().data(), request.out);
+ } else {
+ qint64 written = file.write(content);
+ if (written != content.size()) {
+ FCGX_PutS(QString("Error writing file").toUtf8().data(), request.out);
+ } else {
+ FCGX_PutS("OK", request.out);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } else { // default: download
+ QFile file(path);
+ if (file.open(QIODevice::ReadOnly)) {
+ QFileInfo fileInfo(path);
+ FCGX_PutS(QString("Content-Disposition: attachment; filename=\"%1\"\r\n").arg(fileInfo.fileName()).toUtf8().data(), request.out);
+ FCGX_PutS("Content-Type: application/octet-stream\r\n\r\n", request.out);
+
+ while (!file.atEnd()) {
+ QByteArray ba = file.read(BUFSIZE);
+ FCGX_PutStr(ba.data(), ba.size(), request.out);
+ }
+ } else {
+ FCGX_PutS(httpError(500, QString("Bad file: %1").arg(pathInfo)).toUtf8().data(), request.out);
+ }
+ }
+ }
+
+ return 0;
+}
+