#include "whiteboard.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libreichwein/file.h" #include "config.h" #include "qrcode.h" #include "storage.h" namespace pt = boost::property_tree; using namespace std::string_literals; namespace fs = std::filesystem; namespace { void usage() { std::cout << "Usage: \n" " whiteboard [-c]\n" "\n" "Options:\n" " -c : Cleanup database according to timeout rules (config: maxage)\n" "\n" "Without options, whiteboard will be started as FCGI application" << std::endl; } } // namespace Whiteboard::Whiteboard(): m_config(), m_storage(m_config) { } // contents of cleanup thread; looping void Whiteboard::storage_cleanup() { while(true) { { std::lock_guard lock(m_storage_mutex); m_storage.cleanup(); } std::this_thread::sleep_for(std::chrono::minutes(10)); } } // the actual main() for testability int Whiteboard::run(int argc, char* argv[]) { if (argc == 2) { if (argv[1] == "-h"s || argv[1] == "-?"s) { usage(); exit(0); } else if (argv[1] == "-c"s) { m_storage.cleanup(); exit(0); } } std::thread storage_cleanup_thread(std::bind(&Whiteboard::storage_cleanup, this)); QRCode::init(); int result = FCGX_Init(); if (result != 0) { // error on init fprintf(stderr, "Error: FCGX_Init()\n"); return 1; } result = FCGX_IsCGI(); if (result) { fprintf(stderr, "Error: No FCGI environment available.\n"); return 1; } FCGX_Request request; result = FCGX_InitRequest(&request, 0, 0); if (result != 0) { fprintf(stderr, "Error: FCGX_InitRequest()\n"); return 1; } while (FCGX_Accept_r(&request) >= 0) { try { std::lock_guard lock(m_storage_mutex); char* method = FCGX_GetParam("REQUEST_METHOD", request.envp); // POST for server actions, changes if (!strcmp(method, "POST")) { size_t contentLength { std::stoul(FCGX_GetParam("CONTENT_LENGTH", request.envp)) }; std::string postData(contentLength, '\0'); // contentLength number of bytes, initialize with 0 if (FCGX_GetStr(postData.data(), contentLength, request.in) != static_cast(contentLength)) { throw std::runtime_error("Bad data read: Content length mismatch.\r\n"); } // postData contains POST data std::string contentType(FCGX_GetParam("CONTENT_TYPE", request.envp)); std::string xmlData = postData; // default: interpret whole POST data as xml request pt::ptree xml; std::istringstream ss{xmlData}; pt::xml_parser::read_xml(ss, xml); std::string command {xml.get("request.command")}; if (command == "modify") { std::string id {xml.get("request.id")}; std::string data {xml.get("request.data")}; m_storage.setDocument(id, data); FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); } else if (command == "getfile") { std::string id {xml.get("request.id")}; std::string filedata {m_storage.getDocument(id)}; if (filedata.size() > 30000000) throw std::runtime_error("File too big"); FCGX_PutS("Content-Type: application/octet-stream\r\n", request.out); FCGX_FPrintF(request.out, "Content-Length: %d\r\n\r\n", filedata.size()); FCGX_PutStr(filedata.c_str(), filedata.size(), request.out); } else if (command == "checkupdate") { std::string id {xml.get("request.id")}; std::string checksum_s {xml.get("request.checksum")}; uint32_t checksum{static_cast(stoul(checksum_s))}; std::string filedata {m_storage.getDocument(id)}; if (checksum != m_storage.checksum32(filedata)) { //std::cout << "Sending change..." << std::endl; FCGX_PutS("Content-Type: application/octet-stream\r\n", request.out); FCGX_FPrintF(request.out, "Content-Length: %d\r\n\r\n", filedata.size()); FCGX_PutStr(filedata.c_str(), filedata.size(), request.out); } else { //std::cout << "No change..." << std::endl; FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); FCGX_PutS("No change.\r\n", request.out); } } else if (command == "newid") { FCGX_PutS("Content-Type: text/plain\r\n\r\n", request.out); FCGX_PutS(m_storage.generate_id().c_str(), request.out); } else if (command == "qrcode") { std::string url{xml.get("request.url")}; if (url.size() > 1000) throw std::runtime_error("URL too big"); std::string pngdata {QRCode::getQRCode(url)}; FCGX_PutS("Content-Type: image/png\r\n", request.out); FCGX_FPrintF(request.out, "Content-Length: %d\r\n\r\n", pngdata.size()); FCGX_PutStr(pngdata.c_str(), pngdata.size(), request.out); } else { throw std::runtime_error("Bad command: "s + command); } } else { throw std::runtime_error("Unsupported method.\r\n"); } } catch (const std::runtime_error& ex) { FCGX_PutS("Status: 500 Internal Server Error\r\n", request.out); FCGX_PutS("Content-Type: text/html\r\n\r\n", request.out); FCGX_FPrintF(request.out, "Error: %s\r\n", ex.what()); } catch (const std::exception& ex) { FCGX_PutS("Status: 500 Internal Server Error\r\n", request.out); FCGX_PutS("Content-Type: text/html\r\n\r\n", request.out); FCGX_FPrintF(request.out, "Unknown exception: %s\r\n", ex.what()); } } storage_cleanup_thread.join(); return 0; }