#include #include #include #include #include #include #include #include #include #include #include #define PROGRAMVERSION "1.0" #define BUFSIZE 1000000 // XML special characters: // < : < // > : > // & : & // " : " // ' : ' // // here:replace & QString escapeXML(QString s) { s.replace("&", "&"); return s; } // revert escapeXML(); QString unescapeXML(QString s) { s.replace("&", "&"); return s; } // 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

%1 %2

%3

\r\n").arg(httpStatusCode).arg(description).arg(message); } 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 } 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 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 0 // only for debugging if (command == "diag") { FCGX_PutS("Content-Type: text/html\r\n\r\n", request.out); FCGX_PutS("Params\r\n", request.out); FCGX_PutS(QString("Request no. %1

\r\n").arg(count).toUtf8().data(), request.out); char** tmp = request.envp; while (*tmp) { FCGX_PutS(QString("%1
\r\n").arg(*tmp).toUtf8().data(), request.out); tmp++; } FCGX_PutS(QString("
WEBBOX_PATH=%1
\r\n").arg(getenv("WEBBOX_PATH")).toUtf8().data(), request.out); FCGX_PutS("\r\n", request.out); } else #endif if (command == "list") { FCGX_PutS("Content-Type: text/xml\r\n\r\n", request.out); QDir dir(path); QFileInfoList dirEntryList = dir.entryInfoList(QDir::NoDot | QDir::AllEntries, QDir::DirsFirst | QDir::Name | QDir::IgnoreCase); QByteArray xmlData; QXmlStreamWriter xmlWriter(&xmlData); xmlWriter.writeStartDocument(); xmlWriter.writeStartElement("list"); foreach(QFileInfo i, dirEntryList) { if (pathInfo != "/" || i.fileName() != "..") { // skip on ".." in "/" xmlWriter.writeStartElement("listentry"); xmlWriter.writeAttribute("type", i.isDir() ? "dir" : "file"); xmlWriter.writeCharacters(i.fileName()); xmlWriter.writeEndElement(); } } xmlWriter.writeEndElement(); // list xmlWriter.writeEndDocument(); FCGX_PutS(xmlData.data(), 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
(C) 2017 Reichwein.IT\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)
") .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; QStringList argumentList; QTemporaryFile tempfile(QDir::tempPath() + "/webboxXXXXXX.zip"); tempfile.open(); QFileInfo fileInfo(tempfile); QString tempfilePath = fileInfo.absolutePath(); QString tempfileName = fileInfo.fileName(); tempfile.close(); tempfile.remove(); argumentList << "-r"; // recursive packing argumentList << tempfilePath + "/" + tempfileName; // zip filename while (!xml.atEnd()) { while (xml.readNextStartElement()) { if (xml.name() == "files") { while (xml.readNextStartElement()) { if (xml.name() == "file") { QString filename = xml.readElementText(); argumentList.append(filename); // add parts } } } } } QProcess process; process.setWorkingDirectory(path); process.setProgram("/usr/bin/zip"); process.setArguments(argumentList); process.start(); process.waitForFinished(); QString debugText = process.readAll(); process.setReadChannel(QProcess::StandardError); debugText += process.readAll(); if (process.state() != QProcess::NotRunning || process.exitCode() != 0 || process.exitStatus() != QProcess::NormalExit) { FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); FCGX_PutS(QString("Error running process: %1 %2 %3 %4 %5 %6 %7"). arg(process.state()). arg(process.exitCode()). arg(process.exitStatus()). arg(tempfilePath). arg(tempfileName). arg(argumentList[0]). arg(debugText).toUtf8().data(), request.out); } else { QFile tempfile(tempfilePath + "/" + tempfileName); if (!tempfile.open(QIODevice::ReadOnly)) { FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); FCGX_PutS(QString("Error reading file").toUtf8().data(), request.out); } else { zipData = tempfile.readAll(); 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); } tempfile.close(); tempfile.remove(); } } } } else if (command == "delete") { // 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); QString response = ""; 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); if (fileInfo.isDir()) { QDir dir(path); if (!dir.rmdir(filename)) { response += QString("Error on removing directory %1
").arg(filename); } } else if (fileInfo.isFile()) { QFile file(path + "/" + filename); if (!file.remove()) { response += QString("Error on removing file %1
").arg(filename); } } else { response += QString("Error: %1 is neither file nor directory.
").arg(filename); } } } } } } if (response == "") { response = "OK"; } FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); FCGX_PutS(response.toUtf8().data(), request.out); } } } else if (command == "move") { // 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); QString response = ""; QString targetDir; while (!xml.atEnd()) { while (xml.readNextStartElement()) { if (xml.name() == "request") { while (xml.readNextStartElement()) { if (xml.name() == "target") { targetDir = xml.readElementText(); } else if (xml.name() == "file") { QString filename = xml.readElementText(); QFileInfo fileInfo(path + "/" + filename); if (fileInfo.isDir()) { QDir dir(path); if (!dir.rename(filename, targetDir + "/" + filename)) { response += QString("Error moving directory %1
").arg(filename); } } else if (fileInfo.isFile()) { QFile file(path + "/" + filename); if (!file.rename(path + "/" + targetDir + "/" + filename)) { response += QString("Error on moving file %1
").arg(filename); } } else { response += QString("Error: %1 is neither file nor directory.
").arg(filename); } } } } } } if (response == "") { response = "OK"; } FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); FCGX_PutS(response.toUtf8().data(), 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)); QString separator("boundary="); if (!contentType.contains(separator)) { FCGX_PutS(QString("No boundary defined").toUtf8().data(), request.out); } else { QByteArray boundary = QByteArray("--") + contentType.split(separator)[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(QByteArray("\r\n") + 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 += QByteArray("filename=\"").size(); end = content.indexOf(QByteArray("\""), start); if (end == -1) { FCGX_PutS(QString("Error reading filename / end").toUtf8().data(), request.out); } else { QString filename = QString::fromUtf8(content.mid(start, end - start)); if (filename.size() < 1) { FCGX_PutS(QString("Bad filename").toUtf8().data(), request.out); } else { // Remove header start = content.indexOf(QByteArray("\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").toUtf8().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; }