OpenTTD Source  20241121-master-g67a0fccfad
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 }
Converts a HTTPCallback to a Thread-Safe variant.
Definition: http_shared.h:22
void OnFailure()
Similar to HTTPCallback::OnFailure, but thread-safe.
Definition: http_shared.h:39
void OnReceiveData(std::unique_ptr< char[]> data, size_t length)
Similar to HTTPCallback::OnReceiveData, but thread-safe.
Definition: http_shared.h:48
bool IsQueueEmpty()
Check if the queue is empty.
Definition: http_shared.h:94
Single HTTP request.
Definition: http_curl.cpp:54
bool Receive()
Poll and process the HTTP request/response.
std::atomic< bool > finished
Whether we are finished with the request.
NetworkHTTPRequest(const std::string &uri, HTTPCallback *callback, const std::string &data)
Create a new HTTP request.
Definition: http_curl.cpp:63
HINTERNET connection
Current connection object.
const std::string data
Data to send, if any.
Definition: http_curl.cpp:80
~NetworkHTTPRequest()
Destructor of the HTTP request.
Definition: http_curl.cpp:72
const std::wstring uri
URI to connect to.
HINTERNET request
Current request object.
void Connect()
Start the HTTP request handling.
HTTPThreadSafeCallback callback
Callback to send data back on.
Definition: http_curl.cpp:79
void WinHttpCallback(DWORD code, void *info, DWORD length)
Callback from the WinHTTP library, called when-ever something changes about the HTTP request status.
const std::string uri
URI to connect to.
Definition: http_curl.cpp:78
int depth
Current redirect depth we are in.
static void Connect(const std::string &uri, HTTPCallback *callback, const std::string data="")
Connect to the given URI.
Definition: http_curl.cpp:93
static void HTTPReceive()
Do the receiving for all HTTP connections.
Definition: http_curl.cpp:107
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
Basic functions to send and receive HTTP packets.
Shared functions for implementations of HTTP requests.
void NetworkHTTPInitialize()
Initialize the HTTP socket handler.
void NetworkHTTPUninitialize()
Uninitialize the HTTP socket handler.
Callback for when the HTTP handler has something to tell us.
Definition: http.h:20
std::string FS2OTTD(const std::wstring &name)
Convert to OpenTTD's encoding from a wide string.
Definition: win32.cpp:337
std::mutex lock
synchronization for playback status fields
Definition: win32_m.cpp:35