10#include "../../stdafx.h"
11#include "../../debug.h"
12#include "../../fileio_func.h"
14#include "../../thread.h"
15#include "../network_internal.h"
21#include <condition_variable>
26#include "../../safeguards.h"
30static constexpr std::initializer_list<std::string_view> _certificate_files = {
31 "/etc/ssl/certs/ca-certificates.crt"sv,
32 "/etc/pki/tls/certs/ca-bundle.crt"sv,
33 "/etc/ssl/ca-bundle.pem"sv,
34 "/etc/pki/tls/cacert.pem"sv,
35 "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"sv,
36 "/etc/ssl/cert.pem"sv,
39static constexpr std::initializer_list<std::string_view> _certificate_directories = {
41 "/etc/pki/tls/certs"sv,
42 "/system/etc/security/cacerts"sv,
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;
66 std::lock_guard<std::mutex>
lock(_new_http_callback_mutex);
67 _new_http_callbacks.push_back(&this->callback);
72 std::lock_guard<std::mutex>
lock(_http_callback_mutex);
73 _http_callbacks.erase(std::remove(_http_callbacks.begin(), _http_callbacks.end(), &this->callback), _http_callbacks.end());
76 const std::string
uri;
81static std::thread _http_thread;
82static std::atomic<bool> _http_thread_exit =
false;
83static std::queue<std::unique_ptr<NetworkHTTPRequest>> _http_requests;
84static std::mutex _http_mutex;
85static std::condition_variable _http_cv;
87static std::string _http_ca_file =
"";
88static std::string _http_ca_path =
"";
94 if (_http_ca_file.empty() && _http_ca_path.empty()) {
100 std::lock_guard<std::mutex>
lock(_http_mutex);
101 _http_requests.push(std::make_unique<NetworkHTTPRequest>(uri, callback, std::move(data)));
102 _http_cv.notify_one();
107 std::lock_guard<std::mutex>
lock(_http_callback_mutex);
110 std::lock_guard<std::mutex> lock_new(_new_http_callback_mutex);
111 if (!_new_http_callbacks.empty()) {
113 _http_callbacks.insert(_http_callbacks.end(), _new_http_callbacks.begin(), _new_http_callbacks.end());
114 _new_http_callbacks.clear();
118 for (
auto &callback : _http_callbacks) {
119 callback->HandleQueue();
123void CurlSetOption(CURL *curl,
auto option,
auto value)
125 CURLcode res = curl_easy_setopt(curl, option, value);
126 if (res != CURLE_OK) {
127 Debug(net, 0,
"Could not execute curl_easy_setopt for {} [{}]", option, res);
133 CURL *curl = curl_easy_init();
134 assert(curl !=
nullptr);
137 std::unique_lock<std::mutex>
lock(_http_mutex);
140 while (_http_requests.empty() && !_http_thread_exit) {
143 if (_http_thread_exit)
break;
145 std::unique_ptr<NetworkHTTPRequest> request = std::move(_http_requests.front());
146 _http_requests.pop();
152 curl_easy_reset(curl);
153 curl_slist *headers =
nullptr;
155 if (_debug_net_level >= 5) {
156 CurlSetOption(curl, CURLOPT_VERBOSE, 1L);
161 CurlSetOption(curl, CURLOPT_USERAGENT, user_agent.c_str());
162 CurlSetOption(curl, CURLOPT_FOLLOWLOCATION, 1L);
163 CurlSetOption(curl, CURLOPT_MAXREDIRS, 5L);
167 CurlSetOption(curl, CURLOPT_CAINFO, _http_ca_file.empty() ?
nullptr : _http_ca_file.c_str());
168 CurlSetOption(curl, CURLOPT_CAPATH, _http_ca_path.empty() ?
nullptr : _http_ca_path.c_str());
170 CurlSetOption(curl, CURLOPT_SSL_VERIFYHOST, 2);
171 CurlSetOption(curl, CURLOPT_SSL_VERIFYPEER,
true);
174 CurlSetOption(curl, CURLOPT_CONNECTTIMEOUT, 10L);
177 CurlSetOption(curl, CURLOPT_BUFFERSIZE, 100L * 1024L);
180 CurlSetOption(curl, CURLOPT_FAILONERROR, 1L);
183 if (!request->data.empty()) {
185 if (request->data.starts_with(
"{")) {
186 headers = curl_slist_append(headers,
"Content-Type: application/json");
188 headers = curl_slist_append(headers,
"Content-Type: application/x-www-form-urlencoded");
191 CurlSetOption(curl, CURLOPT_POST, 1L);
192 CurlSetOption(curl, CURLOPT_POSTFIELDS, request->data.c_str());
193 CurlSetOption(curl, CURLOPT_HTTPHEADER, headers);
195 CurlSetOption(curl, CURLOPT_URL, request->uri.c_str());
198 CurlSetOption(curl, CURLOPT_WRITEFUNCTION, +[](
char *ptr,
size_t size,
size_t nmemb,
void *userdata) ->
size_t {
199 Debug(net, 6,
"HTTP callback: {} bytes", size * nmemb);
203 std::unique_ptr<char[]> buffer = std::make_unique<char[]>(size * nmemb);
204 std::copy_n(ptr, size * nmemb, buffer.get());
209 CurlSetOption(curl, CURLOPT_WRITEDATA, &request->callback);
215 CurlSetOption(curl, CURLOPT_NOPROGRESS, 0L);
216 CurlSetOption(curl, CURLOPT_XFERINFOFUNCTION, +[](
void *userdata, curl_off_t , curl_off_t , curl_off_t , curl_off_t ) ->
int {
218 return (callback->cancelled || _http_thread_exit) ? 1 : 0;
220 CurlSetOption(curl, CURLOPT_XFERINFODATA, &request->callback);
223 CURLcode res = curl_easy_perform(curl);
225 curl_slist_free_all(headers);
227 if (res == CURLE_OK) {
228 Debug(net, 1,
"HTTP request succeeded");
229 request->callback.OnReceiveData(
nullptr, 0);
231 long status_code = 0;
232 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code);
235 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));
236 request->callback.OnFailure();
240 request->callback.WaitTillEmptyOrCondition([]() ->
bool {
241 return _http_thread_exit;
245 curl_easy_cleanup(curl);
250 curl_global_init(CURL_GLOBAL_DEFAULT);
256 for (
auto &ca_file : _certificate_files) {
258 _http_ca_file = ca_file;
262 if (_http_ca_file.empty()) {
263 for (
auto &ca_path : _certificate_directories) {
265 _http_ca_path = ca_path;
270 Debug(net, 3,
"Using certificate file: {}", _http_ca_file.empty() ?
"none" : _http_ca_file);
271 Debug(net, 3,
"Using certificate path: {}", _http_ca_path.empty() ?
"none" : _http_ca_path);
274 if (_http_ca_file.empty() && _http_ca_path.empty()) {
275 Debug(net, 0,
"No certificate files or directories found, HTTPS will not work!");
279 _http_thread_exit =
false;
285 _http_thread_exit =
true;
292 std::lock_guard<std::mutex>
lock(_http_mutex);
293 _http_cv.notify_one();
296 if (_http_thread.joinable()) {
300 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.
std::string_view GetNetworkRevisionString()
Get the network version string used by this build.
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