diff --git a/Tubio/HttpServer.cpp b/Tubio/HttpServer.cpp index 8f50c9b..a2f576e 100644 --- a/Tubio/HttpServer.cpp +++ b/Tubio/HttpServer.cpp @@ -1,306 +1,307 @@ -#include "HttpServer.h" -#include "external_dependencies/leonetienne/stringtools/StringTools.h" - -using namespace Logging; -using namespace Rest; -using namespace JasonPP; - -HttpServer::HttpServer() -{ - pMgr = new mg_mgr(); - pNc = nullptr; - log = new Logger("HttpServer"); - - return; -} - -HttpServer::~HttpServer() -{ - delete pMgr; - delete log; - - log = nullptr; - pMgr = nullptr; - - return; -} - -void HttpServer::PostInit() -{ - isBootedSuccessfully = InitWebServer(); - - return; -} - -bool HttpServer::InitWebServer() -{ - mg_mgr_init(pMgr, NULL); - - log->cout << "Starting http-server on port " << XGConfig::httpServer.port << "..."; - log->Flush(); - - pNc = mg_bind(pMgr, XGConfig::httpServer.port.c_str(), this->EventHandler); - - if (pNc == NULL) - { - log->cout << log->Err() << "Failed to boot the http-server! - Unable to bind listener! (port: " << XGConfig::httpServer.port << ")"; - log->Flush(); - return false; - } - - mg_set_protocol_http_websocket(pNc); - frontend_serve_opts.document_root = XGConfig::httpServer.rootdir.c_str(); - frontend_serve_opts.enable_directory_listing = "no"; - - log->cout << "Started http-server successfully!"; - log->Flush(); - isBootedSuccessfully = true; - - return true; -} - -void HttpServer::Update() -{ - mg_mgr_poll(pMgr, XGConfig::httpServer.polling_rate); - - return; -} - -void HttpServer::ServeStringToConnection(struct mg_connection* c, std::string str, int httpStatusCode) -{ - mg_send_head(c, httpStatusCode, str.length(), "content-type: application/json\nAccess-Control-Allow-Origin: *"); - mg_printf(c, "%s", str.c_str()); - - return; -} - -void HttpServer::EventHandler(mg_connection* pNc, int ev, void* p) -{ - switch (ev) - { - case MG_EV_HTTP_REQUEST: - // Reset standby timer - XGControl::last_query_time = time(0); - - http_message* hpm = (http_message*)p; - std::string requestedUri = FixUnterminatedString(hpm->uri.p, hpm->uri.len); - - // Get clients ip address - std::string peer_addr; - { - char buf[32]; - mg_sock_addr_to_str(&pNc->sa, buf, sizeof(buf), MG_SOCK_STRINGIFY_IP); - peer_addr = buf; - } - - std::string denialReason; - if (IsConnectionAllowed(peer_addr, denialReason)) - { - try - { - if (requestedUri == "/api") - { - ProcessAPIRequest(pNc, ev, p, peer_addr); - } - else if (requestedUri.substr(0, 12) == "/downloadlog") - { - ServeDownloadLog(pNc, ev, p, requestedUri); - } - else if (requestedUri.substr(0, 9) == "/download") - { - ServeDownloadeableResource(pNc, ev, p, requestedUri); - } - else - { - // Just serve the files requested - mg_serve_http(pNc, (struct http_message*)p, frontend_serve_opts); - } - } - catch (std::exception& e) - { - Json j; - j.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::INTERNAL_SERVER_ERROR, e.what())); - ServeStringToConnection(pNc, j.Render(), (int)HTTP_STATUS_CODE::INTERNAL_SERVER_ERROR); - } - catch (...) - { - Json j; - j.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::INTERNAL_SERVER_ERROR, "Das not good")); - ServeStringToConnection(pNc, j.Render(), (int)HTTP_STATUS_CODE::INTERNAL_SERVER_ERROR); - } - - break; - } - else // Client is not allowed, serve error json - { - Json j; - j.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::UNAUTHORIZED, denialReason)); - ServeStringToConnection(pNc, j.Render(), (int)HTTP_STATUS_CODE::UNAUTHORIZED); - } - } - - return; -} - -std::string HttpServer::SanitizeString(std::string in) { - in = StringTools::Replace(in, '`', "\\\\`"); - in = StringTools::Replace(in, '|', "\\\\|"); - in = StringTools::Replace(in, '$', "\\\\$"); - in = StringTools::Replace(in, "&&", "\\\\&\\\\&"); - - return in; -} - -void HttpServer::ProcessAPIRequest(mg_connection* pNc, int ev, void* p, std::string peerAddress) -{ - // Get struct with http message informations - http_message* hpm = (http_message*)p; - - // Get the transmitted message body - std::string requestBodyRaw = FixUnterminatedString(hpm->body.p, hpm->body.len); - - // Sanitize it - requestBodyRaw = SanitizeString(requestBodyRaw); - - // Check for the body being valid json - if (IsJsonValid(requestBodyRaw)) - { - Json requestBody; - requestBody.Parse(requestBodyRaw); - - - - JsonBlock responseBody; - HTTP_STATUS_CODE returnCode; - RestQueryHandler::ProcessQuery(peerAddress, requestBody, responseBody, returnCode); - - Json response(responseBody); - ServeStringToConnection(pNc, response.Render(), (int)returnCode); - } - else // return error message for invalid json - { - Json errorJson; - errorJson.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::BAD_REQUEST, "Received json is fucked")); - ServeStringToConnection(pNc, errorJson.Render(), (int)HTTP_STATUS_CODE::BAD_REQUEST); - } - - return; -} - -void HttpServer::ServeDownloadeableResource(mg_connection* pNc, int ev, void* p, std::string uri) -{ - std::string fileId = uri.substr(10, uri.length() - 10); - - if (Downloader::DownloadManager::DoesTubioIDExist(fileId)) - { - Downloader::DownloadEntry& entry = Downloader::DownloadManager::GetDownloadEntryByTubioID(fileId); - - if (entry.status == Downloader::DOWNLOAD_STATUS::FINISHED) - { - std::stringstream ss; - std::string downloadedFilename = entry.title + (entry.mode == Downloader::DOWNLOAD_MODE::AUDIO ? ".mp3" : ".mp4"); - - ss << "Access-Control-Allow-Origin: *\nContent-Disposition: attachment; filename=\"" << downloadedFilename << "\"\nPragma: public\nCache-Control: must-revalidate, post-check=0, pre-check=0"; - mg_http_serve_file(pNc, (http_message*)p, entry.downloaded_filename.c_str(), mg_mk_str("application/octet-stream"), mg_mk_str(ss.str().c_str())); - } - else - { - Json j; - j.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::BAD_REQUEST, "File download not ready!")); - ServeStringToConnection(pNc, j.Render(), (int)HTTP_STATUS_CODE::BAD_REQUEST); - } - } - else - { - Json j; - j.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::BAD_REQUEST, "Invalid tubio id!")); - ServeStringToConnection(pNc, j.Render(), (int)HTTP_STATUS_CODE::BAD_REQUEST); - } - - return; -} - -void HttpServer::ServeDownloadLog(mg_connection* pNc, int ev, void* p, std::string uri) -{ - std::string fileId = uri.substr(13, uri.length() - 13); - - if (Downloader::DownloadManager::DoesTubioIDExist(fileId)) - { - Downloader::DownloadEntry& entry = Downloader::DownloadManager::GetDownloadEntryByTubioID(fileId); - - std::stringstream ss; - std::string logFilename = std::string("./dlcache/dlprogbuf/") + fileId + ".buf"; - - ss << "Access-Control-Allow-Origin: *\nContent-Type: text/plain; Cache-Control: must-revalidate, post-check=0, pre-check=0"; - mg_http_serve_file(pNc, (http_message*)p, logFilename.c_str(), mg_mk_str("text/plain"), mg_mk_str(ss.str().c_str())); - } - else - { - Json j; - std::stringstream ss; - j.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::BAD_REQUEST, "Invalid tubio id!")); - ServeStringToConnection(pNc, j.Render(), (int)HTTP_STATUS_CODE::BAD_REQUEST); - } - - return; -} - -bool HttpServer::IsConnectionAllowed(std::string peer_address, std::string& denialReason) -{ - // Localhost is always allowed! - if (peer_address == "127.0.0.1") return true; - - // Peer is not localhost, but only localhost is allowed! - else if (XGConfig::access.only_allow_localhost) - { - denialReason = "Only localhost allowed!"; - return false; - } - - // Let's check if the whitelist is active - else if (XGConfig::access.enable_whitelist) - { - // It is. Let's check if our peer is whitelisted - for (std::size_t i = 0; i < XGConfig::access.whitelist.size(); i++) - { - // Whitelist is enabled, and peer is whitelisted - if (XGConfig::access.whitelist[i] == peer_address) return true; - } - - // Whitelist is enabled, but peer is NOT whitelisted - denialReason = std::string("Not whitelisted! Ask your tubio administrator to whitelist '") + peer_address + "' in order to gain access."; - return false; - } - else // Whitelist is NOT enabled and only_allow_localhost is FALSE! - { - return true; - } - - // Should never come to this point - denialReason = "Access denied"; - return false; -} - -void HttpServer::OnExit() -{ - log->cout << "Shutting down http-server..."; - log->Flush(); - - mg_mgr_free(pMgr); - - return; -} - - -std::string HttpServer::FixUnterminatedString(const char* cstr, const std::size_t len) -{ - std::stringstream ss; - for (std::size_t i = 0; i < len; i++) - { - ss << *(cstr + i); - } - - return ss.str(); -} - -mg_serve_http_opts HttpServer::frontend_serve_opts; +#include "HttpServer.h" +#include "external_dependencies/leonetienne/stringtools/StringTools.h" + +using namespace Logging; +using namespace Rest; +using namespace JasonPP; + +HttpServer::HttpServer() +{ + pMgr = new mg_mgr(); + pNc = nullptr; + log = new Logger("HttpServer"); + + return; +} + +HttpServer::~HttpServer() +{ + delete pMgr; + delete log; + + log = nullptr; + pMgr = nullptr; + + return; +} + +void HttpServer::PostInit() +{ + isBootedSuccessfully = InitWebServer(); + + return; +} + +bool HttpServer::InitWebServer() +{ + mg_mgr_init(pMgr, NULL); + + log->cout << "Starting http-server on port " << XGConfig::httpServer.port << "..."; + log->Flush(); + + pNc = mg_bind(pMgr, XGConfig::httpServer.port.c_str(), this->EventHandler); + + if (pNc == NULL) + { + log->cout << log->Err() << "Failed to boot the http-server! - Unable to bind listener! (port: " << XGConfig::httpServer.port << ")"; + log->Flush(); + return false; + } + + mg_set_protocol_http_websocket(pNc); + frontend_serve_opts.document_root = XGConfig::httpServer.rootdir.c_str(); + frontend_serve_opts.enable_directory_listing = "no"; + + log->cout << "Started http-server successfully!"; + log->Flush(); + isBootedSuccessfully = true; + + return true; +} + +void HttpServer::Update() +{ + mg_mgr_poll(pMgr, XGConfig::httpServer.polling_rate); + + return; +} + +void HttpServer::ServeStringToConnection(struct mg_connection* c, std::string str, int httpStatusCode) +{ + mg_send_head(c, httpStatusCode, str.length(), "content-type: application/json\nAccess-Control-Allow-Origin: *"); + mg_printf(c, "%s", str.c_str()); + + return; +} + +void HttpServer::EventHandler(mg_connection* pNc, int ev, void* p) +{ + switch (ev) + { + case MG_EV_HTTP_REQUEST: + // Reset standby timer + XGControl::last_query_time = time(0); + + http_message* hpm = (http_message*)p; + std::string requestedUri = FixUnterminatedString(hpm->uri.p, hpm->uri.len); + + // Get clients ip address + std::string peer_addr; + { + char buf[32]; + mg_sock_addr_to_str(&pNc->sa, buf, sizeof(buf), MG_SOCK_STRINGIFY_IP); + peer_addr = buf; + } + + std::string denialReason; + if (IsConnectionAllowed(peer_addr, denialReason)) + { + try + { + if (requestedUri == "/api") + { + ProcessAPIRequest(pNc, ev, p, peer_addr); + } + else if (requestedUri.substr(0, 12) == "/downloadlog") + { + ServeDownloadLog(pNc, ev, p, requestedUri); + } + else if (requestedUri.substr(0, 9) == "/download") + { + ServeDownloadeableResource(pNc, ev, p, requestedUri); + } + else + { + // Just serve the files requested + mg_serve_http(pNc, (struct http_message*)p, frontend_serve_opts); + } + } + catch (std::exception& e) + { + Json j; + j.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::INTERNAL_SERVER_ERROR, e.what())); + ServeStringToConnection(pNc, j.Render(), (int)HTTP_STATUS_CODE::INTERNAL_SERVER_ERROR); + } + catch (...) + { + Json j; + j.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::INTERNAL_SERVER_ERROR, "Das not good")); + ServeStringToConnection(pNc, j.Render(), (int)HTTP_STATUS_CODE::INTERNAL_SERVER_ERROR); + } + + break; + } + else // Client is not allowed, serve error json + { + Json j; + j.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::UNAUTHORIZED, denialReason)); + ServeStringToConnection(pNc, j.Render(), (int)HTTP_STATUS_CODE::UNAUTHORIZED); + } + } + + return; +} + +std::string HttpServer::SanitizeString(std::string in) { + in = StringTools::Replace(in, '`', "\\\\`"); + in = StringTools::Replace(in, '|', "\\\\|"); + in = StringTools::Replace(in, '$', "\\\\$"); + in = StringTools::Replace(in, "&&", "\\\\&\\\\&"); + in = StringTools::Replace(in, ";", "\\\\;"); + + return in; +} + +void HttpServer::ProcessAPIRequest(mg_connection* pNc, int ev, void* p, std::string peerAddress) +{ + // Get struct with http message informations + http_message* hpm = (http_message*)p; + + // Get the transmitted message body + std::string requestBodyRaw = FixUnterminatedString(hpm->body.p, hpm->body.len); + + // Sanitize it + requestBodyRaw = SanitizeString(requestBodyRaw); + + // Check for the body being valid json + if (IsJsonValid(requestBodyRaw)) + { + Json requestBody; + requestBody.Parse(requestBodyRaw); + + + + JsonBlock responseBody; + HTTP_STATUS_CODE returnCode; + RestQueryHandler::ProcessQuery(peerAddress, requestBody, responseBody, returnCode); + + Json response(responseBody); + ServeStringToConnection(pNc, response.Render(), (int)returnCode); + } + else // return error message for invalid json + { + Json errorJson; + errorJson.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::BAD_REQUEST, "Received json is fucked")); + ServeStringToConnection(pNc, errorJson.Render(), (int)HTTP_STATUS_CODE::BAD_REQUEST); + } + + return; +} + +void HttpServer::ServeDownloadeableResource(mg_connection* pNc, int ev, void* p, std::string uri) +{ + std::string fileId = uri.substr(10, uri.length() - 10); + + if (Downloader::DownloadManager::DoesTubioIDExist(fileId)) + { + Downloader::DownloadEntry& entry = Downloader::DownloadManager::GetDownloadEntryByTubioID(fileId); + + if (entry.status == Downloader::DOWNLOAD_STATUS::FINISHED) + { + std::stringstream ss; + std::string downloadedFilename = entry.title + (entry.mode == Downloader::DOWNLOAD_MODE::AUDIO ? ".mp3" : ".mp4"); + + ss << "Access-Control-Allow-Origin: *\nContent-Disposition: attachment; filename=\"" << downloadedFilename << "\"\nPragma: public\nCache-Control: must-revalidate, post-check=0, pre-check=0"; + mg_http_serve_file(pNc, (http_message*)p, entry.downloaded_filename.c_str(), mg_mk_str("application/octet-stream"), mg_mk_str(ss.str().c_str())); + } + else + { + Json j; + j.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::BAD_REQUEST, "File download not ready!")); + ServeStringToConnection(pNc, j.Render(), (int)HTTP_STATUS_CODE::BAD_REQUEST); + } + } + else + { + Json j; + j.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::BAD_REQUEST, "Invalid tubio id!")); + ServeStringToConnection(pNc, j.Render(), (int)HTTP_STATUS_CODE::BAD_REQUEST); + } + + return; +} + +void HttpServer::ServeDownloadLog(mg_connection* pNc, int ev, void* p, std::string uri) +{ + std::string fileId = uri.substr(13, uri.length() - 13); + + if (Downloader::DownloadManager::DoesTubioIDExist(fileId)) + { + Downloader::DownloadEntry& entry = Downloader::DownloadManager::GetDownloadEntryByTubioID(fileId); + + std::stringstream ss; + std::string logFilename = std::string("./dlcache/dlprogbuf/") + fileId + ".buf"; + + ss << "Access-Control-Allow-Origin: *\nContent-Type: text/plain; Cache-Control: must-revalidate, post-check=0, pre-check=0"; + mg_http_serve_file(pNc, (http_message*)p, logFilename.c_str(), mg_mk_str("text/plain"), mg_mk_str(ss.str().c_str())); + } + else + { + Json j; + std::stringstream ss; + j.CloneFrom(RestResponseTemplates::GetByCode(HTTP_STATUS_CODE::BAD_REQUEST, "Invalid tubio id!")); + ServeStringToConnection(pNc, j.Render(), (int)HTTP_STATUS_CODE::BAD_REQUEST); + } + + return; +} + +bool HttpServer::IsConnectionAllowed(std::string peer_address, std::string& denialReason) +{ + // Localhost is always allowed! + if (peer_address == "127.0.0.1") return true; + + // Peer is not localhost, but only localhost is allowed! + else if (XGConfig::access.only_allow_localhost) + { + denialReason = "Only localhost allowed!"; + return false; + } + + // Let's check if the whitelist is active + else if (XGConfig::access.enable_whitelist) + { + // It is. Let's check if our peer is whitelisted + for (std::size_t i = 0; i < XGConfig::access.whitelist.size(); i++) + { + // Whitelist is enabled, and peer is whitelisted + if (XGConfig::access.whitelist[i] == peer_address) return true; + } + + // Whitelist is enabled, but peer is NOT whitelisted + denialReason = std::string("Not whitelisted! Ask your tubio administrator to whitelist '") + peer_address + "' in order to gain access."; + return false; + } + else // Whitelist is NOT enabled and only_allow_localhost is FALSE! + { + return true; + } + + // Should never come to this point + denialReason = "Access denied"; + return false; +} + +void HttpServer::OnExit() +{ + log->cout << "Shutting down http-server..."; + log->Flush(); + + mg_mgr_free(pMgr); + + return; +} + + +std::string HttpServer::FixUnterminatedString(const char* cstr, const std::size_t len) +{ + std::stringstream ss; + for (std::size_t i = 0; i < len; i++) + { + ss << *(cstr + i); + } + + return ss.str(); +} + +mg_serve_http_opts HttpServer::frontend_serve_opts;