mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-16 11:33:03 +01:00
Add new config option to allow any address to be advertised. This then doesn't rely on the master server retrieving the server IP address via the HTTP request which can often be IPv6 by default.
241 lines
8.3 KiB
C++
241 lines
8.3 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2020 OpenRCT2 developers
|
|
*
|
|
* For a complete list of all authors, please refer to contributors.md
|
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
|
*
|
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
|
*****************************************************************************/
|
|
|
|
#if !defined(DISABLE_HTTP) && defined(_WIN32)
|
|
|
|
# include "Http.h"
|
|
|
|
# include "../Version.h"
|
|
# include "String.hpp"
|
|
|
|
# include <cstdio>
|
|
# include <stdexcept>
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# include <windows.h>
|
|
# include <winhttp.h>
|
|
|
|
namespace Http
|
|
{
|
|
static constexpr char OPENRCT2_USER_AGENT[] = "OpenRCT2/" OPENRCT2_VERSION;
|
|
|
|
static void ThrowWin32Exception(const char* methodName)
|
|
{
|
|
auto errorCode = GetLastError();
|
|
auto msg = String::StdFormat("%s failed, error: %d.", methodName, errorCode);
|
|
throw std::runtime_error(msg);
|
|
}
|
|
|
|
static const wchar_t* GetVerb(Method method)
|
|
{
|
|
switch (method)
|
|
{
|
|
case Method::GET:
|
|
return L"GET";
|
|
case Method::POST:
|
|
return L"POST";
|
|
case Method::PUT:
|
|
return L"PUT";
|
|
default:
|
|
throw std::runtime_error("Unsupported verb.");
|
|
}
|
|
}
|
|
|
|
static int32_t ReadStatusCode(HINTERNET hRequest)
|
|
{
|
|
wchar_t headerBuffer[32]{};
|
|
auto headerBufferLen = (DWORD)std::size(headerBuffer);
|
|
if (!WinHttpQueryHeaders(
|
|
hRequest, WINHTTP_QUERY_STATUS_CODE, L"StatusCode", headerBuffer, &headerBufferLen, WINHTTP_NO_HEADER_INDEX))
|
|
{
|
|
ThrowWin32Exception("WinHttpQueryHeaders");
|
|
}
|
|
auto statusCode = std::stoi(headerBuffer);
|
|
return statusCode < 0 || statusCode > 999 ? 0 : statusCode;
|
|
}
|
|
|
|
static std::map<std::string, std::string> ReadHeaders(HINTERNET hRequest)
|
|
{
|
|
DWORD dwSize{};
|
|
WinHttpQueryHeaders(
|
|
hRequest, WINHTTP_QUERY_RAW_HEADERS, WINHTTP_HEADER_NAME_BY_INDEX, NULL, &dwSize, WINHTTP_NO_HEADER_INDEX);
|
|
|
|
std::wstring buffer;
|
|
buffer.resize(dwSize);
|
|
if (!WinHttpQueryHeaders(
|
|
hRequest, WINHTTP_QUERY_RAW_HEADERS, WINHTTP_HEADER_NAME_BY_INDEX, buffer.data(), &dwSize,
|
|
WINHTTP_NO_HEADER_INDEX))
|
|
ThrowWin32Exception("WinHttpQueryHeaders");
|
|
|
|
std::map<std::string, std::string> headers;
|
|
std::wstring wKey, wValue;
|
|
|
|
constexpr int32_t STATE_EXPECT_KEY = 0;
|
|
constexpr int32_t STATE_EXPECT_WHITESPACE_VALUE = 1;
|
|
constexpr int32_t STATE_EXPECT_VALUE = 2;
|
|
int32_t state = STATE_EXPECT_KEY;
|
|
int32_t index = 0;
|
|
for (auto c : buffer)
|
|
{
|
|
if (c == '\0')
|
|
{
|
|
// Ignore first header as that is the HTTP version which
|
|
// we don't really count as a header.
|
|
if (index != 0 && wKey.size() != 0)
|
|
{
|
|
auto key = String::ToUtf8(wKey);
|
|
auto value = String::ToUtf8(wValue);
|
|
headers[key] = value;
|
|
}
|
|
wKey.clear();
|
|
wValue.clear();
|
|
index++;
|
|
state = STATE_EXPECT_KEY;
|
|
continue;
|
|
}
|
|
if (state == STATE_EXPECT_KEY && c == ':')
|
|
{
|
|
state = STATE_EXPECT_WHITESPACE_VALUE;
|
|
}
|
|
else if (state == 1 && c == ' ')
|
|
{
|
|
state = STATE_EXPECT_VALUE;
|
|
}
|
|
else if (state == STATE_EXPECT_KEY)
|
|
{
|
|
wKey.push_back(c);
|
|
}
|
|
else
|
|
{
|
|
state = STATE_EXPECT_VALUE;
|
|
wValue.push_back(c);
|
|
}
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
static std::string ReadBody(HINTERNET hRequest)
|
|
{
|
|
std::string body;
|
|
DWORD dwRealSize{};
|
|
DWORD dwDownloaded{};
|
|
do
|
|
{
|
|
// Check for available data.
|
|
DWORD dwSizeToRead{};
|
|
if (!WinHttpQueryDataAvailable(hRequest, &dwSizeToRead))
|
|
ThrowWin32Exception("WinHttpQueryDataAvailable");
|
|
|
|
// Apparently some servers may report no bytes left to
|
|
// download incorrectly, so still attempt to read...
|
|
if (dwSizeToRead == 0)
|
|
dwSizeToRead = 4096;
|
|
|
|
body.resize(dwRealSize + dwSizeToRead);
|
|
auto dst = (LPVOID)&body[dwRealSize];
|
|
|
|
dwDownloaded = 0;
|
|
if (!WinHttpReadData(hRequest, dst, dwSizeToRead, &dwDownloaded))
|
|
ThrowWin32Exception("WinHttpReadData");
|
|
|
|
dwRealSize += dwDownloaded;
|
|
} while (dwDownloaded > 0);
|
|
body.resize(dwRealSize);
|
|
body.shrink_to_fit();
|
|
return body;
|
|
}
|
|
|
|
Response Do(const Request& req)
|
|
{
|
|
HINTERNET hSession{}, hConnect{}, hRequest{};
|
|
try
|
|
{
|
|
URL_COMPONENTS url{};
|
|
url.dwStructSize = sizeof(url);
|
|
url.dwSchemeLength = (DWORD)-1;
|
|
url.dwHostNameLength = (DWORD)-1;
|
|
url.dwUrlPathLength = (DWORD)-1;
|
|
url.dwExtraInfoLength = (DWORD)-1;
|
|
|
|
auto wUrl = String::ToWideChar(req.url);
|
|
if (!WinHttpCrackUrl(wUrl.c_str(), 0, 0, &url))
|
|
throw std::invalid_argument("Unable to parse URI.");
|
|
|
|
auto userAgent = String::ToWideChar(OPENRCT2_USER_AGENT);
|
|
hSession = WinHttpOpen(
|
|
userAgent.c_str(), WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
|
if (hSession == nullptr)
|
|
ThrowWin32Exception("WinHttpOpen");
|
|
|
|
auto wHostName = std::wstring(url.lpszHostName, url.dwHostNameLength);
|
|
hConnect = WinHttpConnect(hSession, wHostName.c_str(), url.nPort, 0);
|
|
if (hConnect == nullptr)
|
|
ThrowWin32Exception("WinHttpConnect");
|
|
|
|
auto dwFlags = 0;
|
|
if (lstrcmpiW(std::wstring(url.lpszScheme, url.dwSchemeLength).c_str(), L"https") == 0)
|
|
{
|
|
dwFlags = WINHTTP_FLAG_SECURE;
|
|
}
|
|
|
|
auto wVerb = GetVerb(req.method);
|
|
auto wQuery = std::wstring(url.lpszUrlPath, url.dwUrlPathLength);
|
|
hRequest = WinHttpOpenRequest(
|
|
hConnect, wVerb, wQuery.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, dwFlags);
|
|
if (hRequest == nullptr)
|
|
ThrowWin32Exception("WinHttpOpenRequest");
|
|
|
|
for (auto header : req.header)
|
|
{
|
|
auto fullHeader = String::ToWideChar(header.first) + L": " + String::ToWideChar(header.second);
|
|
if (!WinHttpAddRequestHeaders(hRequest, fullHeader.c_str(), (ULONG)-1L, WINHTTP_ADDREQ_FLAG_ADD))
|
|
ThrowWin32Exception("WinHttpAddRequestHeaders");
|
|
}
|
|
|
|
auto reqBody = (LPVOID)req.body.data();
|
|
auto reqBodyLen = (DWORD)req.body.size();
|
|
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, reqBody, reqBodyLen, reqBodyLen, 0))
|
|
ThrowWin32Exception("WinHttpSendRequest");
|
|
|
|
if (!WinHttpReceiveResponse(hRequest, NULL))
|
|
ThrowWin32Exception("WinHttpReceiveResponse");
|
|
|
|
auto statusCode = ReadStatusCode(hRequest);
|
|
auto headers = ReadHeaders(hRequest);
|
|
auto body = ReadBody(hRequest);
|
|
|
|
Response response;
|
|
response.body = std::move(body);
|
|
response.status = (Status)statusCode;
|
|
auto it = headers.find("Content-Type");
|
|
if (it != headers.end())
|
|
{
|
|
response.content_type = it->second;
|
|
}
|
|
response.header = std::move(headers);
|
|
|
|
WinHttpCloseHandle(hSession);
|
|
WinHttpCloseHandle(hConnect);
|
|
WinHttpCloseHandle(hRequest);
|
|
return response;
|
|
}
|
|
catch ([[maybe_unused]] const std::exception& e)
|
|
{
|
|
# ifdef DEBUG
|
|
std::fprintf(stderr, "HTTP request failed: %s\n", e.what());
|
|
# endif
|
|
WinHttpCloseHandle(hSession);
|
|
WinHttpCloseHandle(hConnect);
|
|
WinHttpCloseHandle(hRequest);
|
|
throw;
|
|
}
|
|
}
|
|
} // namespace Http
|
|
|
|
#endif
|