/* Copyright (C) 2024 BeamMP Ltd., BeamMP team and contributors. Licensed under AGPL-3.0 (or later), see . SPDX-License-Identifier: AGPL-3.0-or-later */ #include "Http.h" #include #include #include #include #include #include #include #include #include #include #include #include #include static size_t CurlWriteCallback(void* contents, size_t size, size_t nmemb, void* userp) { std::string* Result = reinterpret_cast(userp); std::string NewContents(reinterpret_cast(contents), size * nmemb); *Result += NewContents; return size * nmemb; } bool HTTP::isDownload = false; std::string HTTP::Get(const std::string& IP) { std::string Ret; static thread_local CURL* curl = curl_easy_init(); if (curl) { CURLcode res; char errbuf[CURL_ERROR_SIZE]; curl_easy_setopt(curl, CURLOPT_URL, IP.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 120); // seconds curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); errbuf[0] = 0; res = curl_easy_perform(curl); if (res != CURLE_OK) { error("GET to " + IP + " failed: " + std::string(curl_easy_strerror(res))); error("Curl error: " + std::string(errbuf)); return ""; } } else { error("Curl easy init failed"); return ""; } return Ret; } std::string HTTP::Post(const std::string& IP, const std::string& Fields) { std::string Ret; static thread_local CURL* curl = curl_easy_init(); if (curl) { CURLcode res; char errbuf[CURL_ERROR_SIZE]; curl_easy_setopt(curl, CURLOPT_URL, IP.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&Ret); curl_easy_setopt(curl, CURLOPT_POST, 1); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, Fields.c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, Fields.size()); struct curl_slist* list = nullptr; list = curl_slist_append(list, "Content-Type: application/json"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 120); // seconds curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf); errbuf[0] = 0; res = curl_easy_perform(curl); curl_slist_free_all(list); if (res != CURLE_OK) { error("POST to " + IP + " failed: " + std::string(curl_easy_strerror(res))); error("Curl error: " + std::string(errbuf)); return ""; } } else { error("Curl easy init failed"); return ""; } return Ret; } bool HTTP::Download(const std::string& IP, const beammp_fs_string& Path, const std::string& Hash) { static std::mutex Lock; std::scoped_lock Guard(Lock); info("Downloading an update (this may take a while)"); std::string Ret = Get(IP); if (Ret.empty()) { error("Download failed"); return false; } std::string RetHash = Utils::GetSha256HashReallyFast(Ret, Path); debug("Return hash: " + RetHash); debug("Expected hash: " + Hash); if (RetHash != Hash) { error("Downloaded file hash does not match expected hash"); return false; } std::ofstream File(Path, std::ios::binary); if (File.is_open()) { File << Ret; File.close(); info("Download Complete!"); } else { error(beammp_wide("Failed to open file directory: ") + Path); return false; } return true; } void set_headers(httplib::Response& res) { res.set_header("Access-Control-Allow-Origin", "*"); res.set_header("Access-Control-Request-Method", "POST, OPTIONS, GET"); res.set_header("Access-Control-Request-Headers", "X-API-Version"); } void HTTP::StartProxy() { std::thread proxy([&]() { httplib::Server HTTPProxy; httplib::Headers headers = { { "User-Agent", "BeamMP-Launcher/" + GetVer() + GetPatch() }, { "Accept", "*/*" } }; httplib::Client backend("https://backend.beammp.com"); httplib::Client forum("https://forum.beammp.com"); const std::string pattern = ".*"; auto handle_request = [&](const httplib::Request& req, httplib::Response& res) { set_headers(res); if (req.has_header("X-BMP-Authentication")) { headers.emplace("X-BMP-Authentication", PrivateKey); } if (req.has_header("X-API-Version")) { headers.emplace("X-API-Version", req.get_header_value("X-API-Version")); } const std::vector path = Utils::Split(req.path, "/"); httplib::Result cli_res; const std::string method = req.method; std::string host = ""; if (!path.empty()) host = path[0]; if (host == "backend") { std::string remaining_path = req.path.substr(std::strlen("/backend")); if (method == "GET") cli_res = backend.Get(remaining_path, headers); else if (method == "POST") cli_res = backend.Post(remaining_path, headers); } else if (host == "avatar") { bool error = false; std::string username; std::string avatar_size = "100"; if (path.size() > 1) { username = path[1]; } else { error = true; } if (path.size() > 2) { try { if (std::stoi(path[2]) > 0) avatar_size = path[2]; } catch (std::exception&) { } } httplib::Result summary_res; if (!error) { summary_res = forum.Get("/u/" + username + ".json", headers); if (!summary_res || summary_res->status != 200) { error = true; } } if (!error) { try { nlohmann::json d = nlohmann::json::parse(summary_res->body, nullptr, false); // can fail with parse_error auto user = d.at("user"); // can fail with out_of_range auto avatar_link_json = user.at("avatar_template"); // can fail with out_of_range auto avatar_link = avatar_link_json.get(); size_t start_pos = avatar_link.find("{size}"); if (start_pos != std::string::npos) avatar_link.replace(start_pos, std::strlen("{size}"), avatar_size); cli_res = forum.Get(avatar_link, headers); } catch (std::exception&) { error = true; } } if (error) { cli_res = forum.Get("/user_avatar/forum.beammp.com/user/0/0.png", headers); } } else { res.set_content("Host not found", "text/plain"); return; } if (cli_res) { res.set_content(cli_res->body, cli_res->get_header_value("Content-Type")); } else { res.set_content(to_string(cli_res.error()), "text/plain"); } }; HTTPProxy.Get(pattern, [&](const httplib::Request& req, httplib::Response& res) { handle_request(req, res); }); HTTPProxy.Post(pattern, [&](const httplib::Request& req, httplib::Response& res) { handle_request(req, res); }); ProxyPort = HTTPProxy.bind_to_any_port("127.0.0.1"); debug("HTTP Proxy listening on port " + std::to_string(ProxyPort)); HTTPProxy.listen_after_bind(); }); proxy.detach(); }