/* Minetest Copyright (C) 2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "httpfetch.h" #include "porting.h" // for sleep_ms(), get_sysinfo(), secure_rand_fill_buf() #include #include #include #include #include #include #include "network/socket.h" // for select() #include "threading/event.h" #include "config.h" #include "exceptions.h" #include "debug.h" #include "log.h" #include "util/container.h" #include "util/thread.h" #include "version.h" #include "settings.h" #include "noise.h" std::mutex g_httpfetch_mutex; std::map > g_httpfetch_results; PcgRandom g_callerid_randomness; HTTPFetchRequest::HTTPFetchRequest() : timeout(g_settings->getS32("curl_timeout")), connect_timeout(timeout), useragent(std::string(PROJECT_NAME_C "/") + g_version_hash + " (" + porting::get_sysinfo() + ")") { } static void httpfetch_deliver_result(const HTTPFetchResult &fetch_result) { unsigned long caller = fetch_result.caller; if (caller != HTTPFETCH_DISCARD) { MutexAutoLock lock(g_httpfetch_mutex); g_httpfetch_results[caller].push(fetch_result); } } static void httpfetch_request_clear(unsigned long caller); unsigned long httpfetch_caller_alloc() { MutexAutoLock lock(g_httpfetch_mutex); // Check each caller ID except HTTPFETCH_DISCARD const unsigned long discard = HTTPFETCH_DISCARD; for (unsigned long caller = discard + 1; caller != discard; ++caller) { std::map >::iterator it = g_httpfetch_results.find(caller); if (it == g_httpfetch_results.end()) { verbosestream << "httpfetch_caller_alloc: allocating " << caller << std::endl; // Access element to create it g_httpfetch_results[caller]; return caller; } } FATAL_ERROR("httpfetch_caller_alloc: ran out of caller IDs"); return discard; } unsigned long httpfetch_caller_alloc_secure() { MutexAutoLock lock(g_httpfetch_mutex); // Generate random caller IDs and make sure they're not // already used or equal to HTTPFETCH_DISCARD // Give up after 100 tries to prevent infinite loop u8 tries = 100; unsigned long caller; do { caller = (((u64) g_callerid_randomness.next()) << 32) | g_callerid_randomness.next(); if (--tries < 1) { FATAL_ERROR("httpfetch_caller_alloc_secure: ran out of caller IDs"); return HTTPFETCH_DISCARD; } } while (g_httpfetch_results.find(caller) != g_httpfetch_results.end()); verbosestream << "httpfetch_caller_alloc_secure: allocating " << caller << std::endl; // Access element to create it g_httpfetch_results[caller]; return caller; } void httpfetch_caller_free(unsigned long caller) { verbosestream<<"httpfetch_caller_free: freeing " < >::iterator it = g_httpfetch_results.find(caller); if (it == g_httpfetch_results.end()) return false; // Check that result queue is nonempty std::queue &caller_results = it->second; if (caller_results.empty()) return false; // Pop first result fetch_result = caller_results.front(); caller_results.pop(); return true; } #if USE_CURL #include /* USE_CURL is on: use cURL based httpfetch implementation */ static size_t httpfetch_writefunction( char *ptr, size_t size, size_t nmemb, void *userdata) { std::ostringstream *stream = (std::ostringstream*)userdata; size_t count = size * nmemb; stream->write(ptr, count); return count; } static size_t httpfetch_discardfunction( char *ptr, size_t size, size_t nmemb, void *userdata) { return size * nmemb; } class CurlHandlePool { std::list handles; public: CurlHandlePool() = default; ~CurlHandlePool() { for (std::list::iterator it = handles.begin(); it != handles.end(); ++it) { curl_easy_cleanup(*it); } } CURL * alloc() { CURL *curl; if (handles.empty()) { curl = curl_easy_init(); if (curl == NULL) { errorstream<<"curl_easy_init returned NULL"<alloc(); if (curl == NULL) { return; } // Set static cURL options curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 1); curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip"); std::string bind_address = g_settings->get("bind_address"); if (!bind_address.empty()) { curl_easy_setopt(curl, CURLOPT_INTERFACE, bind_address.c_str()); } #if LIBCURL_VERSION_NUM >= 0x071304 // Restrict protocols so that curl vulnerabilities in // other protocols don't affect us. // These settings were introduced in curl 7.19.4. long protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | CURLPROTO_FTPS; curl_easy_setopt(curl, CURLOPT_PROTOCOLS, protocols); curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, protocols); #endif // Set cURL options based on HTTPFetchRequest curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str()); curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, request.timeout); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, request.connect_timeout); if (!request.useragent.empty()) curl_easy_setopt(curl, CURLOPT_USERAGENT, request.useragent.c_str()); // Set up a write callback that writes to the // ostringstream ongoing->oss, unless the data // is to be discarded if (request.caller == HTTPFETCH_DISCARD) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, httpfetch_discardfunction); curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL); } else { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, httpfetch_writefunction); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &oss); } // Set POST (or GET) data if (request.post_fields.empty() && request.post_data.empty()) { curl_easy_setopt(curl, CURLOPT_HTTPGET, 1); } else if (request.multipart) { curl_httppost *last = NULL; for (StringMap::iterator it = request.post_fields.begin(); it != request.post_fields.end(); ++it) { curl_formadd(&post, &last, CURLFORM_NAMELENGTH, it->first.size(), CURLFORM_PTRNAME, it->first.c_str(), CURLFORM_CONTENTSLENGTH, it->second.size(), CURLFORM_PTRCONTENTS, it->second.c_str(), CURLFORM_END); } curl_easy_setopt(curl, CURLOPT_HTTPPOST, post); // request.post_fields must now *never* be // modified until CURLOPT_HTTPPOST is cleared } else if (request.post_data.empty()) { curl_easy_setopt(curl, CURLOPT_POST, 1); std::string str; for (auto &post_field : request.post_fields) { if (!str.empty()) str += "&"; str += urlencode(post_field.first); str += "="; str += urlencode(post_field.second); } curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, str.size()); curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, str.c_str()); } else { curl_easy_setopt(curl, CURLOPT_POST, 1); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, request.post_data.size()); curl_easy_setopt(curl, CURLOPT_POSlocal S if minetest.get_modpath("intllib") then S = intllib.Getter() else S = function(s,a,...)a={a,...}return s:gsub("@(%d+)",function(n)return a[tonumber(n)]end)end end advtrains.register_wagon("subway_wagon", { mesh="advtrains_subway_wagon.b3d", textures = {"advtrains_subway_wagon.png"}, drives_on={default=true}, max_speed=15, seats = { { name="Driver stand", attach_offset={x=0, y=10, z=0}, view_offset={x=0, y=0, z=0}, group="dstand", }, { name="1", attach_offset={x=-4, y=8, z=8}, view_offset={x=0, y=0, z=0}, group="pass", }, { name="2", attach_offset={x=4, y=8, z=8}, view_offset={x=0, y=0, z=0}, group="pass", }, { name="3", attach_offset={x=-4, y=8, z=-8}, view_offset={x=0, y=0, z=0}, group="pass", }, { name="4", attach_offset={x=4, y=8, z=-8}, view_offset={x=0, y=0, z=0}, group="pass", }, }, seat_groups = { dstand={ name = "Driver Stand", access_to = {"pass"}, require_doors_open=true, driving_ctrl_access=true, }, pass={ name = "Passenger area", access_to = {"dstand"}, require_doors_open=true, }, }, assign_to_seat_group = {"pass", "dstand"}, doors={ open={ [-1]={frames={x=0, y=20}, time=1}, [1]={frames={x=40, y=60}, time=1}, sound = "advtrains_subway_dopen", }, close={ [-1]={frames={x=20, y=40}, time=1}, [1]={frames={x=60, y=80}, time=1}, sound = "advtrains_subway_dclose", } }, door_entry={-1, 1}, visual_size = {x=1, y=1}, wagon_span=2, --collisionbox = {-1.0,-0.5,-1.8, 1.0,2.5,1.8}, collisionbox = {-1.0,-0.5,-1.0, 1.0,2.5,1.0}, is_locomotive=true, drops={"default:steelblock 4"}, horn_sound = "advtrains_subway_horn", custom_on_velocity_change = function(self, velocity, old_velocity, dtime) if not velocity or not old_velocity then return end if old_velocity == 0 and velocity > 0 then minetest.sound_play