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; +} + | 
