#include "whiteboard.h" #include #include #include #include #include #include #include #include #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 [options]\n" "\n" "Options:\n" " -c : specify configuration file including path\n" " -C : clean up database according to timeout rules (config: maxage)\n" " -h : this help\n" "\n" "Without options, whiteboard will be started as websocket application" << std::endl; } } // namespace Whiteboard::Whiteboard() { } // contents of cleanup thread; looping void Whiteboard::storage_cleanup() { while(true) { { std::lock_guard lock(m_storage_mutex); if (!m_storage) throw std::runtime_error("Storage not initialized"); m_storage->cleanup(); } std::this_thread::sleep_for(std::chrono::minutes(10)); } } std::string Whiteboard::handle_request(const std::string& request) { try { std::lock_guard lock(m_storage_mutex); if (!m_storage) throw std::runtime_error("Storage not initialized"); pt::ptree xml; std::istringstream ss{request}; 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); return {}; } 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"); return filedata; } 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)) { return filedata; } else { return {}; } } else if (command == "newid") { return m_storage->generate_id(); } 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)}; return pngdata; } else { throw std::runtime_error("Bad command: "s + command); } } catch (const std::exception& ex) { return "Message handling error: "s + ex.what(); } } void Whiteboard::do_session(boost::asio::ip::tcp::socket socket) { try { // Construct the stream by moving in the socket boost::beast::websocket::stream ws{std::move(socket)}; // Set a decorator to change the Server of the handshake ws.set_option(boost::beast::websocket::stream_base::decorator( [](boost::beast::websocket::response_type& res) { res.set(boost::beast::http::field::server, std::string("Reichwein.IT Whiteboard")); })); boost::beast::http::request_parser parser; boost::beast::http::request req; boost::beast::flat_buffer buffer; boost::beast::http::read(ws.next_layer(), buffer, parser); req = parser.get(); ws.accept(req); while (true) { boost::beast::flat_buffer buffer; ws.read(buffer); ws.text(ws.got_text()); std::string data(boost::asio::buffers_begin(buffer.data()), boost::asio::buffers_end(buffer.data())); data = handle_request(data); buffer.consume(buffer.size()); boost::beast::ostream(buffer) << data; ws.write(buffer.data()); } } catch (boost::beast::system_error const& se) { // This indicates that the session was closed if (se.code() != boost::beast::websocket::error::closed) std::cerr << "Boost system_error in session: " << se.code().message() << std::endl; } catch (std::exception const& ex) { std::cerr << "Error in session: " << ex.what() << std::endl; } } // the actual main() for testability int Whiteboard::run(int argc, char* argv[]) { try { bool flag_cleanup{}; fs::path configFile; if (argc == 2) { if (argv[1] == "-h"s || argv[1] == "-?"s) { usage(); exit(0); } else if (argv[1] == "-C"s) { flag_cleanup = true; } } else if (argc == 3) { if (argv[1] == "-c"s) { configFile = argv[2]; } } if (configFile.empty()) m_config = std::make_unique(); else m_config = std::make_unique(configFile); m_storage = std::make_unique(*m_config); if (flag_cleanup) { m_storage->cleanup(); exit(0); } std::thread storage_cleanup_thread(std::bind(&Whiteboard::storage_cleanup, this)); QRCode::init(); auto const address = boost::asio::ip::make_address(m_config->getListenAddress()); auto const port = static_cast(m_config->getListenPort()); // The io_context is required for all I/O boost::asio::io_context ioc{m_config->getThreads()}; // The acceptor receives incoming connections boost::asio::ip::tcp::acceptor acceptor{ioc, {address, port}}; while (true) { // This will receive the new connection boost::asio::ip::tcp::socket socket{ioc}; // Block until we get a connection acceptor.accept(socket); // Launch the session, transferring ownership of the socket std::thread( &Whiteboard::do_session, this, std::move(socket)).detach(); } storage_cleanup_thread.join(); } catch (const std::exception& ex) { std::cerr << "Error: " << ex.what() << std::endl; } return 0; }