OpenTTD Source 20260311-master-g511d3794ce
http_curl.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD 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.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "../../stdafx.h"
11#include "../../debug.h"
12#include "../../fileio_func.h"
13#include "../../rev.h"
14#include "../../thread.h"
15#include "../network_internal.h"
16
17#include "http.h"
18#include "http_shared.h"
19
20#include <atomic>
21#include <condition_variable>
22#include <curl/curl.h>
23#include <mutex>
24#include <queue>
25
26#include "../../safeguards.h"
27
28#if defined(UNIX)
30static constexpr std::initializer_list<std::string_view> _certificate_files = {
31 "/etc/ssl/certs/ca-certificates.crt"sv, // Debian/Ubuntu/Gentoo etc.
32 "/etc/pki/tls/certs/ca-bundle.crt"sv, // Fedora/RHEL 6
33 "/etc/ssl/ca-bundle.pem"sv, // OpenSUSE
34 "/etc/pki/tls/cacert.pem"sv, // OpenELEC
35 "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"sv, // CentOS/RHEL 7
36 "/etc/ssl/cert.pem"sv, // Alpine Linux
37};
39static constexpr std::initializer_list<std::string_view> _certificate_directories = {
40 "/etc/ssl/certs"sv, // SLES10/SLES11, https://golang.org/issue/12139
41 "/etc/pki/tls/certs"sv, // Fedora/RHEL
42 "/system/etc/security/cacerts"sv, // Android
43};
44#endif /* UNIX */
45
46static std::vector<HTTPThreadSafeCallback *> _http_callbacks;
47static std::vector<HTTPThreadSafeCallback *> _new_http_callbacks;
48static std::mutex _http_callback_mutex;
49static std::mutex _new_http_callback_mutex;
50
53public:
61 NetworkHTTPRequest(std::string_view uri, HTTPCallback *callback, std::string &&data) :
62 uri(uri),
64 data(std::move(data))
65 {
66 std::lock_guard<std::mutex> lock(_new_http_callback_mutex);
67 _new_http_callbacks.push_back(&this->callback);
68 }
69
72 {
73 std::lock_guard<std::mutex> lock(_http_callback_mutex);
74 _http_callbacks.erase(std::remove(_http_callbacks.begin(), _http_callbacks.end(), &this->callback), _http_callbacks.end());
75 }
76
77 const std::string uri;
79 const std::string data;
80};
81
82static std::thread _http_thread;
83static std::atomic<bool> _http_thread_exit = false;
84static std::queue<std::unique_ptr<NetworkHTTPRequest>> _http_requests;
85static std::mutex _http_mutex;
86static std::condition_variable _http_cv;
87#if defined(UNIX)
88static std::string _http_ca_file = "";
89static std::string _http_ca_path = "";
90#endif /* UNIX */
91
92
93/* static */ void NetworkHTTPSocketHandler::Connect(std::string_view uri, HTTPCallback *callback, std::string &&data)
94{
95#if defined(UNIX)
96 if (_http_ca_file.empty() && _http_ca_path.empty()) {
97 callback->OnFailure();
98 return;
99 }
100#endif /* UNIX */
101
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();
105}
106
108{
109 std::lock_guard<std::mutex> lock(_http_callback_mutex);
110
111 {
112 std::lock_guard<std::mutex> lock_new(_new_http_callback_mutex);
113 if (!_new_http_callbacks.empty()) {
114 /* We delay adding new callbacks, as HandleQueue() below might add a new callback. */
116 _new_http_callbacks.clear();
117 }
118 }
119
120 for (auto &callback : _http_callbacks) {
121 callback->HandleQueue();
122 }
123}
124
131void CurlSetOption(CURL *curl, auto option, auto value)
132{
133 CURLcode res = curl_easy_setopt(curl, option, value);
134 if (res != CURLE_OK) {
135 Debug(net, 0, "Could not execute curl_easy_setopt for {} [{}]", option, res);
136 }
137}
138
141{
142 CURL *curl = curl_easy_init();
143 assert(curl != nullptr);
144
145 for (;;) {
146 std::unique_lock<std::mutex> lock(_http_mutex);
147
148 /* Wait for a new request. */
149 while (_http_requests.empty() && !_http_thread_exit) {
150 _http_cv.wait(lock);
151 }
152 if (_http_thread_exit) break;
153
154 std::unique_ptr<NetworkHTTPRequest> request = std::move(_http_requests.front());
155 _http_requests.pop();
156
157 /* Release the lock, as we will take a while to process the request. */
158 lock.unlock();
159
160 /* Reset to default settings. */
161 curl_easy_reset(curl);
162 curl_slist *headers = nullptr;
163
164 if (_debug_net_level >= 5) {
165 CurlSetOption(curl, CURLOPT_VERBOSE, 1L);
166 }
167
168 /* Setup some default options. */
169 std::string user_agent = fmt::format("OpenTTD/{}", GetNetworkRevisionString());
170 CurlSetOption(curl, CURLOPT_USERAGENT, user_agent.c_str());
171 CurlSetOption(curl, CURLOPT_FOLLOWLOCATION, 1L);
172 CurlSetOption(curl, CURLOPT_MAXREDIRS, 5L);
173
174 /* Ensure we validate the certificate and hostname of the server. */
175#if defined(UNIX)
176 CurlSetOption(curl, CURLOPT_CAINFO, _http_ca_file.empty() ? nullptr : _http_ca_file.c_str());
177 CurlSetOption(curl, CURLOPT_CAPATH, _http_ca_path.empty() ? nullptr : _http_ca_path.c_str());
178#endif /* UNIX */
179 CurlSetOption(curl, CURLOPT_SSL_VERIFYHOST, 2);
180 CurlSetOption(curl, CURLOPT_SSL_VERIFYPEER, true);
181
182 /* Give the connection about 10 seconds to complete. */
183 CurlSetOption(curl, CURLOPT_CONNECTTIMEOUT, 10L);
184
185 /* Set a buffer of 100KiB, as the default of 16KiB seems a bit small. */
186 CurlSetOption(curl, CURLOPT_BUFFERSIZE, 100L * 1024L);
187
188 /* Fail our call if we don't receive a 2XX return value. */
189 CurlSetOption(curl, CURLOPT_FAILONERROR, 1L);
190
191 /* Prepare POST body and URI. */
192 if (!request->data.empty()) {
193 /* When the payload starts with a '{', it is a JSON payload. */
194 if (request->data.starts_with("{")) {
195 headers = curl_slist_append(headers, "Content-Type: application/json");
196 } else {
197 headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");
198 }
199
200 CurlSetOption(curl, CURLOPT_POST, 1L);
201 CurlSetOption(curl, CURLOPT_POSTFIELDS, request->data.c_str());
202 CurlSetOption(curl, CURLOPT_HTTPHEADER, headers);
203 }
204 CurlSetOption(curl, CURLOPT_URL, request->uri.c_str());
205
206 /* Setup our (C-style) callback function which we pipe back into the callback. */
207 CurlSetOption(curl, CURLOPT_WRITEFUNCTION, +[](char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t {
208 Debug(net, 6, "HTTP callback: {} bytes", size * nmemb);
209 HTTPThreadSafeCallback *callback = static_cast<HTTPThreadSafeCallback *>(userdata);
210
211 /* Copy the buffer out of CURL. OnReceiveData() will free it when done. */
212 std::unique_ptr<char[]> buffer = std::make_unique<char[]>(size * nmemb);
213 std::copy_n(ptr, size * nmemb, buffer.get());
214 callback->OnReceiveData(std::move(buffer), size * nmemb);
215
216 return size * nmemb;
217 });
218 CurlSetOption(curl, CURLOPT_WRITEDATA, &request->callback);
219
220 /* Create a callback from which we can cancel. Sadly, there is no other
221 * thread-safe way to do this. If the connection went idle, it can take
222 * up to a second before this callback is called. There is little we can
223 * do about this. */
224 CurlSetOption(curl, CURLOPT_NOPROGRESS, 0L);
225 CurlSetOption(curl, CURLOPT_XFERINFOFUNCTION, +[](void *userdata, curl_off_t /*dltotal*/, curl_off_t /*dlnow*/, curl_off_t /*ultotal*/, curl_off_t /*ulnow*/) -> int {
226 const HTTPThreadSafeCallback *callback = static_cast<HTTPThreadSafeCallback *>(userdata);
227 return (callback->cancelled || _http_thread_exit) ? 1 : 0;
228 });
229 CurlSetOption(curl, CURLOPT_XFERINFODATA, &request->callback);
230
231 /* Perform the request. */
232 CURLcode res = curl_easy_perform(curl);
233
234 curl_slist_free_all(headers);
235
236 if (res == CURLE_OK) {
237 Debug(net, 1, "HTTP request succeeded");
238 request->callback.OnReceiveData(nullptr, 0);
239 } else {
240 long status_code = 0;
241 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code);
242
243 /* No need to be verbose about rate limiting. */
244 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));
245 request->callback.OnFailure();
246 }
247
248 /* Wait till the callback tells us all data is dequeued, or _http_thread_exit has been set. */
249 request->callback.WaitTillEmptyOrCondition([]() -> bool {
250 return _http_thread_exit;
251 });
252 }
253
254 curl_easy_cleanup(curl);
255}
256
258{
259 curl_global_init(CURL_GLOBAL_DEFAULT);
260
261#if defined(UNIX)
262 /* Depending on the Linux distro, certificates can either be in
263 * a bundle or a folder, in a wide range of different locations.
264 * Try to find what location is used by this OS. */
265 for (auto &ca_file : _certificate_files) {
266 if (FileExists(ca_file)) {
267 _http_ca_file = ca_file;
268 break;
269 }
270 }
271 if (_http_ca_file.empty()) {
272 for (auto &ca_path : _certificate_directories) {
273 if (FileExists(ca_path)) {
274 _http_ca_path = ca_path;
275 break;
276 }
277 }
278 }
279 Debug(net, 3, "Using certificate file: {}", _http_ca_file.empty() ? "none" : _http_ca_file);
280 Debug(net, 3, "Using certificate path: {}", _http_ca_path.empty() ? "none" : _http_ca_path);
281
282 /* Tell the user why HTTPS will not be working. */
283 if (_http_ca_file.empty() && _http_ca_path.empty()) {
284 Debug(net, 0, "No certificate files or directories found, HTTPS will not work!");
285 }
286#endif /* UNIX */
287
288 _http_thread_exit = false;
289 StartNewThread(&_http_thread, "ottd:http", &HttpThread);
290}
291
293{
294 _http_thread_exit = true;
295
296 /* Ensure the callbacks are handled. This is mostly needed as we send
297 * a survey just before close, and that might be pending here. */
299
300 {
301 std::lock_guard<std::mutex> lock(_http_mutex);
302 _http_cv.notify_one();
303 }
304
305 if (_http_thread.joinable()) {
306 _http_thread.join();
307 }
308
309 curl_global_cleanup();
310}
Converts a HTTPCallback to a Thread-Safe variant.
Definition http_shared.h:20
void OnReceiveData(std::unique_ptr< char[]> data, size_t length)
Similar to HTTPCallback::OnReceiveData, but thread-safe.
Definition http_shared.h:53
std::atomic< bool > cancelled
Whether this callback has been cancelled, or not.
const std::string data
Data to send, if any.
Definition http_curl.cpp:79
~NetworkHTTPRequest()
Remove ourselves from the callback list.
Definition http_curl.cpp:71
NetworkHTTPRequest(std::string_view uri, HTTPCallback *callback, std::string &&data)
Create a new HTTP request.
Definition http_curl.cpp:61
HTTPThreadSafeCallback callback
Callback to send data back on.
Definition http_curl.cpp:78
const std::string uri
URI to connect to.
Definition http_curl.cpp:77
static void Connect(std::string_view uri, HTTPCallback *callback, std::string &&data="")
Connect to the given URI.
Definition http_curl.cpp:93
static void HTTPReceive()
Do the receiving for all HTTP connections.
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
bool FileExists(std::string_view filename)
Test whether the given filename exists.
Definition fileio.cpp:132
Functions for standard in/out file operations.
Basic functions to send and receive HTTP packets.
constexpr int HTTP_429_TOO_MANY_REQUESTS
HTTP error code for when the client is doing too many requests.
Definition http.h:15
static std::mutex _http_mutex
Mutex to prevent concurrent access _http_requests.
Definition http_curl.cpp:85
static std::queue< std::unique_ptr< NetworkHTTPRequest > > _http_requests
HTTP requests that are currently running.
Definition http_curl.cpp:84
void NetworkHTTPInitialize()
Initialize the HTTP socket handler.
static std::vector< HTTPThreadSafeCallback * > _new_http_callbacks
Callbacks for the request that should be started.
Definition http_curl.cpp:47
static std::thread _http_thread
The thread running the HTTP requests.
Definition http_curl.cpp:82
static std::condition_variable _http_cv
Conditional variable to wake up the HTTP request thread.
Definition http_curl.cpp:86
static std::atomic< bool > _http_thread_exit
Whether to ask the HTTP request thread to stop.
Definition http_curl.cpp:83
static std::mutex _http_callback_mutex
Mutex to prevent concurrent access to _http_callbacks.
Definition http_curl.cpp:48
void CurlSetOption(CURL *curl, auto option, auto value)
Set some specific option and emit debug information upon failure.
static std::mutex _new_http_callback_mutex
Mutex to prevent concurrent access to _new_http_callbacks.
Definition http_curl.cpp:49
void HttpThread()
Thread entry point for the HTTP request thread.
void NetworkHTTPUninitialize()
Uninitialize the HTTP socket handler.
static std::vector< HTTPThreadSafeCallback * > _http_callbacks
Callback for the current requests.
Definition http_curl.cpp:46
Shared functions for implementations of HTTP requests.
static std::vector< HTTPThreadSafeCallback * > _new_http_callbacks
Callbacks for the request that should be started.
static std::mutex _http_callback_mutex
Mutex to prevent concurrent access to _http_callbacks.
static std::mutex _new_http_callback_mutex
Mutex to prevent concurrent access to _new_http_callbacks.
static std::vector< HTTPThreadSafeCallback * > _http_callbacks
Callback for the current requests.
std::string_view GetNetworkRevisionString()
Get the network version string used by this build.
Variables and function used internally.
Declaration of OTTD revision dependent variables.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
Callback for when the HTTP handler has something to tell us.
Definition http.h:18
virtual void OnFailure()=0
An error has occurred and the connection has been closed.
Base of all threads.
bool StartNewThread(std::thread *thr, std::string_view name, TFn &&_Fx, TArgs &&... _Ax)
Start a new thread.
Definition thread.h:47
std::mutex lock
synchronization for playback status fields
Definition win32_m.cpp:35