diff options
-rw-r--r-- | Makefile | 12 | ||||
-rw-r--r-- | TODO | 22 | ||||
-rw-r--r-- | apache/localhost.conf | 47 | ||||
-rw-r--r-- | html/index.html | 57 | ||||
-rw-r--r-- | html/webbox.css | 138 | ||||
-rw-r--r-- | html/webbox.js | 446 | ||||
-rw-r--r-- | src/Makefile | 17 | ||||
-rw-r--r-- | src/webbox.cpp | 333 |
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 @@ -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; +} + |