12#include "../../stdafx.h"
13#include "../../debug.h"
14#include "../../fileio_func.h"
16#include "../../thread.h"
17#include "../network_internal.h"
23#include <condition_variable>
28#include "../../safeguards.h"
32static constexpr std::initializer_list<std::string_view> _certificate_files = {
33 "/etc/ssl/certs/ca-certificates.crt"sv,
34 "/etc/pki/tls/certs/ca-bundle.crt"sv,
35 "/etc/ssl/ca-bundle.pem"sv,
36 "/etc/pki/tls/cacert.pem"sv,
37 "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"sv,
38 "/etc/ssl/cert.pem"sv,
41static constexpr std::initializer_list<std::string_view> _certificate_directories = {
43 "/etc/pki/tls/certs"sv,
44 "/system/etc/security/cacerts"sv,
48static std::vector<HTTPThreadSafeCallback *> _http_callbacks;
49static std::vector<HTTPThreadSafeCallback *> _new_http_callbacks;
50static std::mutex _http_callback_mutex;
51static std::mutex _new_http_callback_mutex;
68 std::lock_guard<std::mutex>
lock(_new_http_callback_mutex);
69 _new_http_callbacks.push_back(&this->callback);
74 std::lock_guard<std::mutex>
lock(_http_callback_mutex);
75 _http_callbacks.erase(std::remove(_http_callbacks.begin(), _http_callbacks.end(), &this->callback), _http_callbacks.end());
78 const std::string
uri;
83static std::thread _http_thread;
84static std::atomic<bool> _http_thread_exit =
false;
85static std::queue<std::unique_ptr<NetworkHTTPRequest>> _http_requests;
86static std::mutex _http_mutex;
87static std::condition_variable _http_cv;
89static std::string _http_ca_file =
"";
90static std::string _http_ca_path =
"";
96 if (_http_ca_file.empty() && _http_ca_path.empty()) {
102 std::lock_guard<std::mutex>
lock(_http_mutex);
103 _http_requests.push(std::make_unique<NetworkHTTPRequest>(uri, callback, std::move(data)));
104 _http_cv.notify_one();
109 std::lock_guard<std::mutex>
lock(_http_callback_mutex);
112 std::lock_guard<std::mutex> lock_new(_new_http_callback_mutex);
113 if (!_new_http_callbacks.empty()) {
115 _http_callbacks.insert(_http_callbacks.end(), _new_http_callbacks.begin(), _new_http_callbacks.end());
116 _new_http_callbacks.clear();
120 for (
auto &callback : _http_callbacks) {
121 callback->HandleQueue();
125void CurlSetOption(CURL *curl,
auto option,
auto value)
127 CURLcode res = curl_easy_setopt(curl, option, value);
128 if (res != CURLE_OK) {
129 Debug(net, 0,
"Could not execute curl_easy_setopt for {} [{}]", option, res);
135 CURL *curl = curl_easy_init();
136 assert(curl !=
nullptr);
139 std::unique_lock<std::mutex>
lock(_http_mutex);
142 while (_http_requests.empty() && !_http_thread_exit) {
145 if (_http_thread_exit)
break;
147 std::unique_ptr<NetworkHTTPRequest> request = std::move(_http_requests.front());
148 _http_requests.pop();
154 curl_easy_reset(curl);
155 curl_slist *headers =
nullptr;
157 if (_debug_net_level >= 5) {
158 CurlSetOption(curl, CURLOPT_VERBOSE, 1L);
162 std::string user_agent = fmt::format(
"OpenTTD/{}", GetNetworkRevisionString());
163 CurlSetOption(curl, CURLOPT_USERAGENT, user_agent.c_str());
164 CurlSetOption(curl, CURLOPT_FOLLOWLOCATION, 1L);
165 CurlSetOption(curl, CURLOPT_MAXREDIRS, 5L);
169 CurlSetOption(curl, CURLOPT_CAINFO, _http_ca_file.empty() ?
nullptr : _http_ca_file.c_str());
170 CurlSetOption(curl, CURLOPT_CAPATH, _http_ca_path.empty() ?
nullptr : _http_ca_path.c_str());
172 CurlSetOption(curl, CURLOPT_SSL_VERIFYHOST, 2);
173 CurlSetOption(curl, CURLOPT_SSL_VERIFYPEER,
true);
176 CurlSetOption(curl, CURLOPT_CONNECTTIMEOUT, 10L);
179 CurlSetOption(curl, CURLOPT_BUFFERSIZE, 100L * 1024L);
182 CurlSetOption(curl, CURLOPT_FAILONERROR, 1L);
185 if (!request->data.empty()) {
187 if (request->data.starts_with(
"{")) {
188 headers = curl_slist_append(headers,
"Content-Type: application/json");
190 headers = curl_slist_append(headers,
"Content-Type: application/x-www-form-urlencoded");
193 CurlSetOption(curl, CURLOPT_POST, 1L);
194 CurlSetOption(curl, CURLOPT_POSTFIELDS, request->data.c_str());
195 CurlSetOption(curl, CURLOPT_HTTPHEADER, headers);
197 CurlSetOption(curl, CURLOPT_URL, request->uri.c_str());
200 CurlSetOption(curl, CURLOPT_WRITEFUNCTION, +[](
char *ptr,
size_t size,
size_t nmemb,
void *userdata) ->
size_t {
201 Debug(net, 6,
"HTTP callback: {} bytes", size * nmemb);
205 std::unique_ptr<char[]> buffer = std::make_unique<char[]>(size * nmemb);
206 std::copy_n(ptr, size * nmemb, buffer.get());
211 CurlSetOption(curl, CURLOPT_WRITEDATA, &request->callback);
217 CurlSetOption(curl, CURLOPT_NOPROGRESS, 0L);
218 CurlSetOption(curl, CURLOPT_XFERINFOFUNCTION, +[](
void *userdata, curl_off_t , curl_off_t , curl_off_t , curl_off_t ) ->
int {
220 return (callback->cancelled || _http_thread_exit) ? 1 : 0;
222 CurlSetOption(curl, CURLOPT_XFERINFODATA, &request->callback);
225 CURLcode res = curl_easy_perform(curl);
227 curl_slist_free_all(headers);
229 if (res == CURLE_OK) {
230 Debug(net, 1,
"HTTP request succeeded");
231 request->callback.OnReceiveData(
nullptr, 0);
233 long status_code = 0;
234 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code);
237 Debug(net, (request->callback.cancelled || _http_thread_exit || status_code == HTTP_429_TOO_MANY_REQUESTS) ? 1 : 0,
"HTTP request failed: status_code: {}, error: {}", status_code, curl_easy_strerror(res));
238 request->callback.OnFailure();
242 request->callback.WaitTillEmptyOrCondition([]() ->
bool {
243 return _http_thread_exit;
247 curl_easy_cleanup(curl);
252 curl_global_init(CURL_GLOBAL_DEFAULT);
258 for (
auto &ca_file : _certificate_files) {
260 _http_ca_file = ca_file;
264 if (_http_ca_file.empty()) {
265 for (
auto &ca_path : _certificate_directories) {
267 _http_ca_path = ca_path;
272 Debug(net, 3,
"Using certificate file: {}", _http_ca_file.empty() ?
"none" : _http_ca_file);
273 Debug(net, 3,
"Using certificate path: {}", _http_ca_path.empty() ?
"none" : _http_ca_path);
276 if (_http_ca_file.empty() && _http_ca_path.empty()) {
277 Debug(net, 0,
"No certificate files or directories found, HTTPS will not work!");
281 _http_thread_exit =
false;
287 _http_thread_exit =
true;
294 std::lock_guard<std::mutex>
lock(_http_mutex);
295 _http_cv.notify_one();
298 if (_http_thread.joinable()) {
302 curl_global_cleanup();
Converts a HTTPCallback to a Thread-Safe variant.
void OnReceiveData(std::unique_ptr< char[]> data, size_t length)
Similar to HTTPCallback::OnReceiveData, but thread-safe.
const std::string data
Data to send, if any.
~NetworkHTTPRequest()
Destructor of the HTTP request.
NetworkHTTPRequest(std::string_view uri, HTTPCallback *callback, std::string &&data)
Create a new HTTP request.
HTTPThreadSafeCallback callback
Callback to send data back on.
const std::string uri
URI to connect to.
static void Connect(std::string_view uri, HTTPCallback *callback, std::string &&data="")
Connect to the given URI.
static void HTTPReceive()
Do the receiving for all HTTP connections.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
bool FileExists(std::string_view filename)
Test whether the given filename exists.
Basic functions to send and receive HTTP packets.
void NetworkHTTPInitialize()
Initialize the HTTP socket handler.
void NetworkHTTPUninitialize()
Uninitialize the HTTP socket handler.
Shared functions for implementations of HTTP requests.
Callback for when the HTTP handler has something to tell us.
virtual void OnFailure()=0
An error has occurred and the connection has been closed.
bool StartNewThread(std::thread *thr, std::string_view name, TFn &&_Fx, TArgs &&... _Ax)
Start a new thread.
std::mutex lock
synchronization for playback status fields