#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "file.h" #include "qrcode.h" namespace pt = boost::property_tree; using namespace std::string_literals; namespace fs = std::filesystem; namespace { uint32_t checksum32(const std::string& s) { uint32_t result{0}; for (int i = 0; i < s.size(); i++) { result = ((result >> 1) | ((result & 1) << 31)) ^ (s[i] & 0xFF); } return result & 0x7FFFFFFF; } fs::path data_path; std::string generate_id() { static std::random_device r; static std::default_random_engine e1(r()); static std::uniform_int_distribution uniform_dist(0, 15); // limit tries for (int j = 0; j < 100000; j++) { std::string result; for (int i = 0; i < 6; i++) { char c{static_cast('0' + uniform_dist(e1))}; if (c > '9') c = c - '9' + 'a'; result.push_back(c); } fs::path path{data_path / result}; if (!fs::exists(path)) return result; } return "endofcodes"; } void setFileById(const std::string& data, const std::string& id) { fs::path path{data_path / id}; // skip if it doesn't exists if (data.empty() && !fs::exists(path)) return; // clean up immediately, if appropriate if (data.empty()) { fs::remove(path); return; } File::setFile(path, data); } std::string getFileById(const std::string& id) { fs::path path{data_path / id}; if (!fs::exists(path)) return {}; return File::getFile(path); } } int main(void) { Config config; data_path = config.getDataPath(); Magick::InitializeMagick(NULL); // for qrcode.cpp 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 { 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")}; setFileById(data, id); 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 {getFileById(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::cout << "Checksum JS: " << checksum_s << std::endl; //std::cout << "Checksum C++: " << checksum32(filedata) << std::endl; std::string filedata {getFileById(id)}; if (checksum != 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(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 {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()); } } return 0; }