OpenTTD Source  20240919-master-gdf0233f4c2
http_winhttp.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 <http://www.gnu.org/licenses/>.
6  */
7 
12 #include "../../stdafx.h"
13 #include "../../debug.h"
14 #include "../../rev.h"
15 #include "../network_internal.h"
16 
17 #include "http.h"
18 #include "http_shared.h"
19 
20 #include <mutex>
21 #include <winhttp.h>
22 
23 #include "../../safeguards.h"
24 
25 static HINTERNET _winhttp_session = nullptr;
26 
28 class NetworkHTTPRequest {
29 private:
30  const std::wstring uri;
32  const std::string data;
33 
34  HINTERNET connection = nullptr;
35  HINTERNET request = nullptr;
36  std::atomic<bool> finished = false;
37  int depth = 0;
38 
39 public:
40  NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *callback, const std::string &data);
41 
43 
44  void Connect();
45  bool Receive();
46  void WinHttpCallback(DWORD code, void *info, DWORD length);
47 };
48 
49 static std::vector<NetworkHTTPRequest *> _http_requests;
50 static std::vector<NetworkHTTPRequest *> _new_http_requests;
51 static std::mutex _new_http_requests_mutex;
52 
53 static std::vector<HTTPThreadSafeCallback *> _http_callbacks;
54 static std::vector<HTTPThreadSafeCallback *> _new_http_callbacks;
55 static std::mutex _http_callback_mutex;
56 static std::mutex _new_http_callback_mutex;
57 
65 NetworkHTTPRequest::NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *callback, const std::string &data) :
66  uri(uri),
67  callback(callback),
68  data(data)
69 {
70  std::lock_guard<std::mutex> lock(_new_http_callback_mutex);
71  _new_http_callbacks.push_back(&this->callback);
72 }
73 
74 static std::string GetLastErrorAsString()
75 {
76  wchar_t buffer[512];
77 
78  DWORD error_code = GetLastError();
79 
80  if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandle(L"winhttp.dll"), error_code,
81  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, static_cast<DWORD>(std::size(buffer)), nullptr) == 0) {
82  return fmt::format("unknown error {}", error_code);
83  }
84 
85  return FS2OTTD(buffer);
86 }
87 
100 void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length)
101 {
102  if (this->finished) return;
103 
104  switch (code) {
105  case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME:
106  case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED:
107  case WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER:
108  case WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER:
109  case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
110  case WINHTTP_CALLBACK_STATUS_REQUEST_SENT:
111  case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE:
112  case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED:
113  case WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION:
114  case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED:
115  case WINHTTP_CALLBACK_STATUS_HANDLE_CREATED:
116  case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
117  /* We don't care about these events, and explicitly ignore them. */
118  break;
119 
120  case WINHTTP_CALLBACK_STATUS_REDIRECT:
121  /* Make sure we are not in a redirect loop. */
122  if (this->depth++ > 5) {
123  Debug(net, 0, "HTTP request failed: too many redirects");
124  this->callback.OnFailure();
125  this->finished = true;
126  return;
127  }
128  break;
129 
130  case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
131  /* Next step: read response. */
132  WinHttpReceiveResponse(this->request, nullptr);
133  break;
134 
135  case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
136  {
137  /* Retrieve the status code. */
138  DWORD status_code = 0;
139  DWORD status_code_size = sizeof(status_code);
140  WinHttpQueryHeaders(this->request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &status_code, &status_code_size, WINHTTP_NO_HEADER_INDEX);
141  Debug(net, 3, "HTTP request status code: {}", status_code);
142 
143  /* If there is any error, we simply abort the request. */
144  if (status_code >= 400) {
145  /* No need to be verbose about rate limiting. */
146  Debug(net, status_code == HTTP_429_TOO_MANY_REQUESTS ? 1 : 0, "HTTP request failed: status-code {}", status_code);
147  this->callback.OnFailure();
148  this->finished = true;
149  return;
150  }
151 
152  /* Next step: query for any data. */
153  WinHttpQueryDataAvailable(this->request, nullptr);
154  } break;
155 
156  case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
157  {
158  /* Retrieve the amount of data available to process. */
159  DWORD size = *(DWORD *)info;
160 
161  /* Next step: read the data in a temporary allocated buffer.
162  * The buffer will be free'd by OnReceiveData() in the next step. */
163  char *buffer = size == 0 ? nullptr : new char[size];
164  WinHttpReadData(this->request, buffer, size, 0);
165  } break;
166 
167  case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
168  Debug(net, 6, "HTTP callback: {} bytes", length);
169 
170  this->callback.OnReceiveData(std::unique_ptr<char[]>(static_cast<char *>(info)), length);
171 
172  if (length == 0) {
173  /* Next step: no more data available: request is finished. */
174  this->finished = true;
175  Debug(net, 1, "HTTP request succeeded");
176  } else {
177  /* Next step: query for more data. */
178  WinHttpQueryDataAvailable(this->request, nullptr);
179  }
180 
181  break;
182 
183  case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
184  case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
185  Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString());
186  this->callback.OnFailure();
187  this->finished = true;
188  break;
189 
190  default:
191  Debug(net, 0, "HTTP request failed: unexepected callback code 0x{:x}", code);
192  this->callback.OnFailure();
193  this->finished = true;
194  return;
195  }
196 }
197 
198 static void CALLBACK StaticWinHttpCallback(HINTERNET, DWORD_PTR context, DWORD code, void *info, DWORD length)
199 {
200  if (context == 0) return;
201 
202  NetworkHTTPRequest *request = (NetworkHTTPRequest *)context;
203  request->WinHttpCallback(code, info, length);
204 }
205 
214 {
215  Debug(net, 1, "HTTP request to {}", std::string(uri.begin(), uri.end()));
216 
217  URL_COMPONENTS url_components = {};
218  wchar_t scheme[32];
219  wchar_t hostname[128];
220  wchar_t url_path[4096];
221 
222  /* Convert the URL to its components. */
223  url_components.dwStructSize = sizeof(url_components);
224  url_components.lpszScheme = scheme;
225  url_components.dwSchemeLength = static_cast<DWORD>(std::size(scheme));
226  url_components.lpszHostName = hostname;
227  url_components.dwHostNameLength = static_cast<DWORD>(std::size(hostname));
228  url_components.lpszUrlPath = url_path;
229  url_components.dwUrlPathLength = static_cast<DWORD>(std::size(url_path));
230  WinHttpCrackUrl(this->uri.c_str(), 0, 0, &url_components);
231 
232  /* Create the HTTP connection. */
233  this->connection = WinHttpConnect(_winhttp_session, url_components.lpszHostName, url_components.nPort, 0);
234  if (this->connection == nullptr) {
235  Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString());
236  this->callback.OnFailure();
237  this->finished = true;
238  return;
239  }
240 
241  this->request = WinHttpOpenRequest(connection, data.empty() ? L"GET" : L"POST", url_components.lpszUrlPath, nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, url_components.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0);
242  if (this->request == nullptr) {
243  WinHttpCloseHandle(this->connection);
244 
245  Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString());
246  this->callback.OnFailure();
247  this->finished = true;
248  return;
249  }
250 
251  /* Send the request (possibly with a payload). */
252  if (data.empty()) {
253  WinHttpSendRequest(this->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, reinterpret_cast<DWORD_PTR>(this));
254  } else {
255  /* When the payload starts with a '{', it is a JSON payload. */
256  LPCWSTR content_type = data.starts_with("{") ? L"Content-Type: application/json\r\n" : L"Content-Type: application/x-www-form-urlencoded\r\n";
257  WinHttpSendRequest(this->request, content_type, -1, const_cast<char *>(data.c_str()), static_cast<DWORD>(data.size()), static_cast<DWORD>(data.size()), reinterpret_cast<DWORD_PTR>(this));
258  }
259 }
260 
267 {
268  if (this->callback.cancelled && !this->finished) {
269  Debug(net, 1, "HTTP request failed: cancelled by user");
270  this->callback.OnFailure();
271  this->finished = true;
272  /* Fall-through, as we are waiting for IsQueueEmpty() to happen. */
273  }
274 
275  return this->finished && this->callback.IsQueueEmpty();
276 }
277 
284 {
285  if (this->request) {
286  WinHttpCloseHandle(this->request);
287  WinHttpCloseHandle(this->connection);
288  }
289 
290  std::lock_guard<std::mutex> lock(_http_callback_mutex);
291  _http_callbacks.erase(std::remove(_http_callbacks.begin(), _http_callbacks.end(), &this->callback), _http_callbacks.end());
292 }
293 
294 /* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const std::string data)
295 {
296  auto request = new NetworkHTTPRequest(std::wstring(uri.begin(), uri.end()), callback, data);
297  request->Connect();
298 
299  std::lock_guard<std::mutex> lock(_new_http_requests_mutex);
300  _new_http_requests.push_back(request);
301 }
302 
303 /* static */ void NetworkHTTPSocketHandler::HTTPReceive()
304 {
305  /* Process all callbacks. */
306  {
307  std::lock_guard<std::mutex> lock(_http_callback_mutex);
308 
309  {
310  std::lock_guard<std::mutex> lock(_new_http_callback_mutex);
311  if (!_new_http_callbacks.empty()) {
312  /* We delay adding new callbacks, as HandleQueue() below might add a new callback. */
313  _http_callbacks.insert(_http_callbacks.end(), _new_http_callbacks.begin(), _new_http_callbacks.end());
314  _new_http_callbacks.clear();
315  }
316  }
317 
318  for (auto &callback : _http_callbacks) {
319  callback->HandleQueue();
320  }
321  }
322 
323  /* Process all requests. */
324  {
325  std::lock_guard<std::mutex> lock(_new_http_requests_mutex);
326  if (!_new_http_requests.empty()) {
327  /* We delay adding new requests, as Receive() below can cause a callback which adds a new requests. */
328  _http_requests.insert(_http_requests.end(), _new_http_requests.begin(), _new_http_requests.end());
329  _new_http_requests.clear();
330  }
331  }
332 
333  if (_http_requests.empty()) return;
334 
335  for (auto it = _http_requests.begin(); it != _http_requests.end(); /* nothing */) {
336  NetworkHTTPRequest *cur = *it;
337 
338  if (cur->Receive()) {
339  it = _http_requests.erase(it);
340  delete cur;
341  continue;
342  }
343 
344  ++it;
345  }
346 }
347 
349 {
350  /* We create a single session, from which we build up every other request. */
351  std::string user_agent = fmt::format("OpenTTD/{}", GetNetworkRevisionString());
352  _winhttp_session = WinHttpOpen(std::wstring(user_agent.begin(), user_agent.end()).c_str(), WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC);
353 
354  /* Set the callback function for all requests. The "context" maps it back into the actual request instance. */
355  WinHttpSetStatusCallback(_winhttp_session, StaticWinHttpCallback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0);
356 
357  /* 10 seconds timeout for requests. */
358  WinHttpSetTimeouts(_winhttp_session, 10000, 10000, 10000, 10000);
359 }
360 
362 {
363  WinHttpCloseHandle(_winhttp_session);
364 }
HTTPCallback
Callback for when the HTTP handler has something to tell us.
Definition: http.h:20
lock
std::mutex lock
synchronization for playback status fields
Definition: win32_m.cpp:35
NetworkHTTPRequest::WinHttpCallback
void WinHttpCallback(DWORD code, void *info, DWORD length)
Callback from the WinHTTP library, called when-ever something changes about the HTTP request status.
Definition: http_winhttp.cpp:100
NetworkHTTPRequest::uri
const std::wstring uri
URI to connect to.
Definition: http_winhttp.cpp:30
NetworkHTTPSocketHandler::Connect
static void Connect(const std::string &uri, HTTPCallback *callback, const std::string data="")
Connect to the given URI.
Definition: http_curl.cpp:93
NetworkHTTPRequest::finished
std::atomic< bool > finished
Whether we are finished with the request.
Definition: http_winhttp.cpp:36
Debug
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
NetworkHTTPUninitialize
void NetworkHTTPUninitialize()
Uninitialize the HTTP socket handler.
Definition: http_winhttp.cpp:361
HTTPThreadSafeCallback::OnReceiveData
void OnReceiveData(std::unique_ptr< char[]> data, size_t length)
Similar to HTTPCallback::OnReceiveData, but thread-safe.
Definition: http_shared.h:48
NetworkHTTPRequest::uri
const std::string uri
URI to connect to.
Definition: http_curl.cpp:78
FS2OTTD
std::string FS2OTTD(const std::wstring &name)
Convert to OpenTTD's encoding from a wide string.
Definition: win32.cpp:337
NetworkHTTPRequest::NetworkHTTPRequest
NetworkHTTPRequest(const std::string &uri, HTTPCallback *callback, const std::string &data)
Create a new HTTP request.
Definition: http_curl.cpp:63
NetworkHTTPRequest::callback
HTTPThreadSafeCallback callback
Callback to send data back on.
Definition: http_curl.cpp:79
NetworkHTTPRequest::~NetworkHTTPRequest
~NetworkHTTPRequest()
Destructor of the HTTP request.
Definition: http_curl.cpp:72
NetworkHTTPRequest::Receive
bool Receive()
Poll and process the HTTP request/response.
Definition: http_winhttp.cpp:266
NetworkHTTPRequest::request
HINTERNET request
Current request object.
Definition: http_winhttp.cpp:35
HTTPThreadSafeCallback::OnFailure
void OnFailure()
Similar to HTTPCallback::OnFailure, but thread-safe.
Definition: http_shared.h:39
NetworkHTTPSocketHandler::HTTPReceive
static void HTTPReceive()
Do the receiving for all HTTP connections.
Definition: http_curl.cpp:107
NetworkHTTPRequest
Single HTTP request.
Definition: http_curl.cpp:54
NetworkHTTPRequest::connection
HINTERNET connection
Current connection object.
Definition: http_winhttp.cpp:34
http_shared.h
NetworkHTTPRequest::data
const std::string data
Data to send, if any.
Definition: http_curl.cpp:80
HTTPThreadSafeCallback::IsQueueEmpty
bool IsQueueEmpty()
Check if the queue is empty.
Definition: http_shared.h:94
NetworkHTTPInitialize
void NetworkHTTPInitialize()
Initialize the HTTP socket handler.
Definition: http_winhttp.cpp:348
NetworkHTTPRequest::depth
int depth
Current redirect depth we are in.
Definition: http_winhttp.cpp:37
http.h
HTTPThreadSafeCallback
Converts a HTTPCallback to a Thread-Safe variant.
Definition: http_shared.h:22
NetworkHTTPRequest::Connect
void Connect()
Start the HTTP request handling.
Definition: http_winhttp.cpp:213