Merge branch 'master' into jgrpp
# Conflicts: # .github/workflows/release-linux.yml # .github/workflows/release-macos.yml # src/industry_cmd.cpp # src/industry_cmd.h # src/network/core/http_curl.cpp # src/network/core/tcp_http.cpp # src/network/core/tcp_http.h # src/network/network_content.h # src/script/api/script_goal.cpp # src/script/api/script_industry.cpp # src/script/api/script_league.cpp # src/script/api/script_story_page.cpp # src/script/api/script_town.cpp # src/train.h # src/train_cmd.cpppull/491/head
commit
ae7c86c49d
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* 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.
|
||||
* 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.
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file http.h Basic functions to send and receive HTTP packets.
|
||||
*/
|
||||
|
||||
#ifndef NETWORK_CORE_HTTP_H
|
||||
#define NETWORK_CORE_HTTP_H
|
||||
|
||||
#include "tcp.h"
|
||||
|
||||
/** Callback for when the HTTP handler has something to tell us. */
|
||||
struct HTTPCallback {
|
||||
/**
|
||||
* An error has occurred and the connection has been closed.
|
||||
* @note HTTP socket handler is closed/freed.
|
||||
*/
|
||||
virtual void OnFailure() = 0;
|
||||
|
||||
/**
|
||||
* We're receiving data.
|
||||
* @param data the received data, nullptr when all data has been received.
|
||||
* @param length the amount of received data, 0 when all data has been received.
|
||||
* @note When nullptr is sent the HTTP socket handler is closed/freed.
|
||||
*/
|
||||
virtual void OnReceiveData(const char *data, size_t length) = 0;
|
||||
|
||||
/**
|
||||
* Check if there is a request to cancel the transfer.
|
||||
*
|
||||
* @return true iff the connection is cancelled.
|
||||
* @note Cancellations are never instant, and can take a bit of time to be processed.
|
||||
* The object needs to remain valid until the OnFailure() callback is called.
|
||||
*/
|
||||
virtual bool IsCancelled() const = 0;
|
||||
|
||||
/** Silentium */
|
||||
virtual ~HTTPCallback() {}
|
||||
};
|
||||
|
||||
/** Base socket handler for HTTP traffic. */
|
||||
class NetworkHTTPSocketHandler {
|
||||
public:
|
||||
/**
|
||||
* Connect to the given URI.
|
||||
*
|
||||
* @param uri the URI to connect to (https://.../..).
|
||||
* @param callback the callback to send data back on.
|
||||
* @param data the data we want to send. When non-empty, this will be a POST request, otherwise a GET request.
|
||||
*/
|
||||
static void Connect(const std::string &uri, HTTPCallback *callback, const std::string data = "");
|
||||
|
||||
/**
|
||||
* Do the receiving for all HTTP connections.
|
||||
*/
|
||||
static void HTTPReceive();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the HTTP socket handler.
|
||||
*/
|
||||
void NetworkHTTPInitialize();
|
||||
|
||||
/**
|
||||
* Uninitialize the HTTP socket handler.
|
||||
*/
|
||||
void NetworkHTTPUninitialize();
|
||||
|
||||
#endif /* NETWORK_CORE_HTTP_H */
|
@ -0,0 +1,239 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* 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.
|
||||
* 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.
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file http_curl.cpp CURL-based implementation for HTTP requests.
|
||||
*/
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../debug_fmt.h"
|
||||
#include "../../fileio_func.h"
|
||||
#include "../../rev.h"
|
||||
#include "../../thread.h"
|
||||
#include "../network_internal.h"
|
||||
|
||||
#include "http.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <curl/curl.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
#if defined(UNIX)
|
||||
/** List of certificate bundles, depending on OS. Taken from: https://go.dev/src/crypto/x509/root_linux.go. */
|
||||
static auto _certificate_files = {
|
||||
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
|
||||
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
|
||||
"/etc/ssl/ca-bundle.pem", // OpenSUSE
|
||||
"/etc/pki/tls/cacert.pem", // OpenELEC
|
||||
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7
|
||||
"/etc/ssl/cert.pem", // Alpine Linux
|
||||
};
|
||||
/** List of certificate directories, depending on OS. Taken from: https://go.dev/src/crypto/x509/root_linux.go. */
|
||||
static auto _certificate_directories = {
|
||||
"/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139
|
||||
"/etc/pki/tls/certs", // Fedora/RHEL
|
||||
"/system/etc/security/cacerts", // Android
|
||||
};
|
||||
#endif /* UNIX */
|
||||
|
||||
/** Single HTTP request. */
|
||||
class NetworkHTTPRequest {
|
||||
public:
|
||||
/**
|
||||
* Create a new HTTP request.
|
||||
*
|
||||
* @param uri the URI to connect to (https://.../..).
|
||||
* @param callback the callback to send data back on.
|
||||
* @param data the data we want to send. When non-empty, this will be a POST request, otherwise a GET request.
|
||||
*/
|
||||
NetworkHTTPRequest(const std::string &uri, HTTPCallback *callback, const std::string &data) :
|
||||
uri(uri),
|
||||
callback(callback),
|
||||
data(data)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string uri; ///< URI to connect to.
|
||||
HTTPCallback *callback; ///< Callback to send data back on.
|
||||
const std::string data; ///< Data to send, if any.
|
||||
};
|
||||
|
||||
static std::thread _http_thread;
|
||||
static std::atomic<bool> _http_thread_exit = false;
|
||||
static std::queue<std::unique_ptr<NetworkHTTPRequest>> _http_requests;
|
||||
static std::mutex _http_mutex;
|
||||
static std::condition_variable _http_cv;
|
||||
#if defined(UNIX)
|
||||
static std::string _http_ca_file = "";
|
||||
static std::string _http_ca_path = "";
|
||||
#endif /* UNIX */
|
||||
|
||||
/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const std::string data)
|
||||
{
|
||||
#if defined(UNIX)
|
||||
if (_http_ca_file.empty() && _http_ca_path.empty()) {
|
||||
callback->OnFailure();
|
||||
return;
|
||||
}
|
||||
#endif /* UNIX */
|
||||
|
||||
std::lock_guard<std::mutex> lock(_http_mutex);
|
||||
_http_requests.push(std::make_unique<NetworkHTTPRequest>(uri, callback, data));
|
||||
_http_cv.notify_one();
|
||||
}
|
||||
|
||||
/* static */ void NetworkHTTPSocketHandler::HTTPReceive()
|
||||
{
|
||||
}
|
||||
|
||||
void HttpThread()
|
||||
{
|
||||
CURL *curl = curl_easy_init();
|
||||
assert(curl != nullptr);
|
||||
|
||||
for (;;) {
|
||||
std::unique_lock<std::mutex> lock(_http_mutex);
|
||||
|
||||
/* Wait for a new request. */
|
||||
while (_http_requests.empty() && !_http_thread_exit) {
|
||||
_http_cv.wait(lock);
|
||||
}
|
||||
if (_http_thread_exit) break;
|
||||
|
||||
std::unique_ptr<NetworkHTTPRequest> request = std::move(_http_requests.front());
|
||||
_http_requests.pop();
|
||||
|
||||
/* Release the lock, as we will take a while to process the request. */
|
||||
lock.unlock();
|
||||
|
||||
/* Reset to default settings. */
|
||||
curl_easy_reset(curl);
|
||||
|
||||
if (_debug_net_level >= 5) {
|
||||
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
}
|
||||
|
||||
/* Setup some default options. */
|
||||
std::string user_agent = fmt::format("OpenTTD/{}", GetNetworkRevisionString());
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L);
|
||||
|
||||
/* Ensure we validate the certificate and hostname of the server. */
|
||||
#if defined(UNIX)
|
||||
curl_easy_setopt(curl, CURLOPT_CAINFO, _http_ca_file.empty() ? nullptr : _http_ca_file.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_CAPATH, _http_ca_path.empty() ? nullptr : _http_ca_path.c_str());
|
||||
#endif /* UNIX */
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2);
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
|
||||
|
||||
/* Give the connection about 10 seconds to complete. */
|
||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
||||
|
||||
/* Set a buffer of 100KiB, as the default of 16KiB seems a bit small. */
|
||||
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 100L * 1024L);
|
||||
|
||||
/* Fail our call if we don't receive a 2XX return value. */
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
|
||||
|
||||
/* Prepare POST body and URI. */
|
||||
if (!request->data.empty()) {
|
||||
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request->data.c_str());
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_URL, request->uri.c_str());
|
||||
|
||||
/* Setup our (C-style) callback function which we pipe back into the callback. */
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +[](char *ptr, size_t size, size_t nmemb, void *userdata) -> size_t {
|
||||
Debug(net, 4, "HTTP callback: {} bytes", size * nmemb);
|
||||
HTTPCallback *callback = static_cast<HTTPCallback *>(userdata);
|
||||
callback->OnReceiveData(ptr, size * nmemb);
|
||||
return size * nmemb;
|
||||
});
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, request->callback);
|
||||
|
||||
/* Create a callback from which we can cancel. Sadly, there is no other
|
||||
* thread-safe way to do this. If the connection went idle, it can take
|
||||
* up to a second before this callback is called. There is little we can
|
||||
* do about this. */
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, +[](void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) -> int {
|
||||
const HTTPCallback *callback = static_cast<HTTPCallback *>(userdata);
|
||||
return (callback->IsCancelled() || _http_thread_exit) ? 1 : 0;
|
||||
});
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, request->callback);
|
||||
|
||||
/* Perform the request. */
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
if (res == CURLE_OK) {
|
||||
Debug(net, 1, "HTTP request succeeded");
|
||||
request->callback->OnReceiveData(nullptr, 0);
|
||||
} else {
|
||||
Debug(net, (request->callback->IsCancelled() || _http_thread_exit) ? 1 : 0, "HTTP request failed: {}", curl_easy_strerror(res));
|
||||
request->callback->OnFailure();
|
||||
}
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
|
||||
void NetworkHTTPInitialize()
|
||||
{
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
|
||||
#if defined(UNIX)
|
||||
/* Depending on the Linux distro, certificates can either be in
|
||||
* a bundle or a folder, in a wide range of different locations.
|
||||
* Try to find what location is used by this OS. */
|
||||
for (auto &ca_file : _certificate_files) {
|
||||
if (FileExists(ca_file)) {
|
||||
_http_ca_file = ca_file;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (_http_ca_file.empty()) {
|
||||
for (auto &ca_path : _certificate_directories) {
|
||||
if (FileExists(ca_path)) {
|
||||
_http_ca_path = ca_path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Debug(net, 3, "Using certificate file: {}", _http_ca_file.empty() ? "none" : _http_ca_file);
|
||||
Debug(net, 3, "Using certificate path: {}", _http_ca_path.empty() ? "none" : _http_ca_path);
|
||||
|
||||
/* Tell the user why HTTPS will not be working. */
|
||||
if (_http_ca_file.empty() && _http_ca_path.empty()) {
|
||||
Debug(net, 0, "No certificate files or directories found, HTTPS will not work!");
|
||||
}
|
||||
#endif /* UNIX */
|
||||
|
||||
_http_thread_exit = false;
|
||||
StartNewThread(&_http_thread, "ottd:http", &HttpThread);
|
||||
}
|
||||
|
||||
void NetworkHTTPUninitialize()
|
||||
{
|
||||
curl_global_cleanup();
|
||||
|
||||
_http_thread_exit = true;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_http_mutex);
|
||||
_http_cv.notify_one();
|
||||
}
|
||||
|
||||
if (_http_thread.joinable()) {
|
||||
_http_thread.join();
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* 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.
|
||||
* 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.
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file http_emscripten.cpp Emscripten-based implementation for HTTP requests.
|
||||
*/
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../debug.h"
|
||||
#include "../../rev.h"
|
||||
#include "../network_internal.h"
|
||||
|
||||
#include "http.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const std::string data)
|
||||
{
|
||||
/* No valid HTTP backend was compiled in, so we fail all HTTP requests. */
|
||||
callback->OnFailure();
|
||||
}
|
||||
|
||||
/* static */ void NetworkHTTPSocketHandler::HTTPReceive()
|
||||
{
|
||||
}
|
||||
|
||||
void NetworkHTTPInitialize()
|
||||
{
|
||||
}
|
||||
|
||||
void NetworkHTTPUninitialize()
|
||||
{
|
||||
}
|
@ -0,0 +1,325 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* 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.
|
||||
* 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.
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file http_winhttp.cpp WinHTTP-based implementation for HTTP requests.
|
||||
*/
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../debug_fmt.h"
|
||||
#include "../../rev.h"
|
||||
#include "../network_internal.h"
|
||||
|
||||
#include "http.h"
|
||||
|
||||
#include <winhttp.h>
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
static HINTERNET _winhttp_session = nullptr;
|
||||
|
||||
/** Single HTTP request. */
|
||||
class NetworkHTTPRequest {
|
||||
private:
|
||||
const std::wstring uri; ///< URI to connect to.
|
||||
HTTPCallback *callback; ///< Callback to send data back on.
|
||||
const std::string data; ///< Data to send, if any.
|
||||
|
||||
HINTERNET connection = nullptr; ///< Current connection object.
|
||||
HINTERNET request = nullptr; ///< Current request object.
|
||||
std::atomic<bool> finished = false; ///< Whether we are finished with the request.
|
||||
int depth = 0; ///< Current redirect depth we are in.
|
||||
|
||||
public:
|
||||
NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *callback, const std::string &data);
|
||||
|
||||
~NetworkHTTPRequest();
|
||||
|
||||
void Connect();
|
||||
bool Receive();
|
||||
void WinHttpCallback(DWORD code, void *info, DWORD length);
|
||||
};
|
||||
|
||||
static std::vector<NetworkHTTPRequest *> _http_requests;
|
||||
static std::vector<NetworkHTTPRequest *> _new_http_requests;
|
||||
|
||||
/**
|
||||
* Create a new HTTP request.
|
||||
*
|
||||
* @param uri the URI to connect to (https://.../..).
|
||||
* @param callback the callback to send data back on.
|
||||
* @param data the data we want to send. When non-empty, this will be a POST request, otherwise a GET request.
|
||||
*/
|
||||
NetworkHTTPRequest::NetworkHTTPRequest(const std::wstring &uri, HTTPCallback *callback, const std::string &data) :
|
||||
uri(uri),
|
||||
callback(callback),
|
||||
data(data)
|
||||
{
|
||||
}
|
||||
|
||||
static std::string GetLastErrorAsString()
|
||||
{
|
||||
char buffer[512];
|
||||
DWORD error_code = GetLastError();
|
||||
|
||||
if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandleA("winhttp.dll"), error_code,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, sizeof(buffer), NULL) == 0) {
|
||||
return fmt::format("unknown error {}", error_code);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback from the WinHTTP library, called when-ever something changes about the HTTP request status.
|
||||
*
|
||||
* The callback needs to call some WinHttp functions for certain states, so WinHttp continues
|
||||
* to read the request. This also allows us to abort when things go wrong, by simply not calling
|
||||
* those functions.
|
||||
* Comments with "Next step:" mark where WinHttp needs a call to continue.
|
||||
*
|
||||
* @param code The code of the event.
|
||||
* @param info The information about the event.
|
||||
* @param length The length of the information.
|
||||
*/
|
||||
void NetworkHTTPRequest::WinHttpCallback(DWORD code, void *info, DWORD length)
|
||||
{
|
||||
if (this->finished) return;
|
||||
|
||||
switch (code) {
|
||||
case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME:
|
||||
case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED:
|
||||
case WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER:
|
||||
case WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER:
|
||||
case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
|
||||
case WINHTTP_CALLBACK_STATUS_REQUEST_SENT:
|
||||
case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE:
|
||||
case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED:
|
||||
case WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION:
|
||||
case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED:
|
||||
case WINHTTP_CALLBACK_STATUS_HANDLE_CREATED:
|
||||
case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
|
||||
/* We don't care about these events, and explicitly ignore them. */
|
||||
break;
|
||||
|
||||
case WINHTTP_CALLBACK_STATUS_REDIRECT:
|
||||
/* Make sure we are not in a redirect loop. */
|
||||
if (this->depth++ > 5) {
|
||||
Debug(net, 0, "HTTP request failed: too many redirects");
|
||||
this->finished = true;
|
||||
this->callback->OnFailure();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
|
||||
/* Next step: read response. */
|
||||
WinHttpReceiveResponse(this->request, nullptr);
|
||||
break;
|
||||
|
||||
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
|
||||
{
|
||||
/* Retrieve the status code. */
|
||||
DWORD status_code = 0;
|
||||
DWORD status_code_size = sizeof(status_code);
|
||||
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);
|
||||
Debug(net, 3, "HTTP request status code: {}", status_code);
|
||||
|
||||
/* If there is any error, we simply abort the request. */
|
||||
if (status_code >= 400) {
|
||||
Debug(net, 0, "HTTP request failed: status-code {}", status_code);
|
||||
this->finished = true;
|
||||
this->callback->OnFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Next step: query for any data. */
|
||||
WinHttpQueryDataAvailable(this->request, nullptr);
|
||||
} break;
|
||||
|
||||
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
|
||||
{
|
||||
/* Retrieve the amount of data available to process. */
|
||||
DWORD size = *(DWORD *)info;
|
||||
|
||||
/* Next step: read the data in a temporary allocated buffer.
|
||||
* The buffer will be free'd in the next step. */
|
||||
char *buffer = size == 0 ? nullptr : MallocT<char>(size);
|
||||
WinHttpReadData(this->request, buffer, size, 0);
|
||||
} break;
|
||||
|
||||
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
|
||||
Debug(net, 4, "HTTP callback: {} bytes", length);
|
||||
|
||||
this->callback->OnReceiveData(static_cast<char *>(info), length);
|
||||
/* Free the temporary buffer that was allocated in the previous step. */
|
||||
free(info);
|
||||
|
||||
if (length == 0) {
|
||||
/* Next step: no more data available: request is finished. */
|
||||
this->finished = true;
|
||||
Debug(net, 1, "HTTP request succeeded");
|
||||
} else {
|
||||
/* Next step: query for more data. */
|
||||
WinHttpQueryDataAvailable(this->request, nullptr);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
|
||||
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
|
||||
Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString());
|
||||
this->finished = true;
|
||||
this->callback->OnFailure();
|
||||
break;
|
||||
|
||||
default:
|
||||
Debug(net, 0, "HTTP request failed: unexepected callback code 0x{:x}", code);
|
||||
this->finished = true;
|
||||
this->callback->OnFailure();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void CALLBACK StaticWinHttpCallback(HINTERNET handle, DWORD_PTR context, DWORD code, void *info, DWORD length)
|
||||
{
|
||||
if (context == 0) return;
|
||||
|
||||
NetworkHTTPRequest *request = (NetworkHTTPRequest *)context;
|
||||
request->WinHttpCallback(code, info, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the HTTP request handling.
|
||||
*
|
||||
* This is done in an async manner, so we can do other things while waiting for
|
||||
* the HTTP request to finish. The actual receiving of the data is done in
|
||||
* Receive().
|
||||
*/
|
||||
void NetworkHTTPRequest::Connect()
|
||||
{
|
||||
Debug(net, 1, "HTTP request to {}", std::string(uri.begin(), uri.end()));
|
||||
|
||||
URL_COMPONENTS url_components = {};
|
||||
wchar_t scheme[32];
|
||||
wchar_t hostname[128];
|
||||
wchar_t url_path[4096];
|
||||
|
||||
/* Convert the URL to its components. */
|
||||
url_components.dwStructSize = sizeof(url_components);
|
||||
url_components.lpszScheme = scheme;
|
||||
url_components.dwSchemeLength = lengthof(scheme);
|
||||
url_components.lpszHostName = hostname;
|
||||
url_components.dwHostNameLength = lengthof(hostname);
|
||||
url_components.lpszUrlPath = url_path;
|
||||
url_components.dwUrlPathLength = lengthof(url_path);
|
||||
WinHttpCrackUrl(this->uri.c_str(), 0, 0, &url_components);
|
||||
|
||||
/* Create the HTTP connection. */
|
||||
this->connection = WinHttpConnect(_winhttp_session, url_components.lpszHostName, url_components.nPort, 0);
|
||||
if (this->connection == nullptr) {
|
||||
Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString());
|
||||
this->finished = true;
|
||||
this->callback->OnFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
if (this->request == nullptr) {
|
||||
WinHttpCloseHandle(this->connection);
|
||||
|
||||
Debug(net, 0, "HTTP request failed: {}", GetLastErrorAsString());
|
||||
this->finished = true;
|
||||
this->callback->OnFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send the request (possibly with a payload). */
|
||||
if (data.empty()) {
|
||||
WinHttpSendRequest(this->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, reinterpret_cast<DWORD_PTR>(this));
|
||||
} else {
|
||||
WinHttpSendRequest(this->request, L"Content-Type: application/x-www-form-urlencoded\r\n", -1, const_cast<char *>(data.c_str()), static_cast<DWORD>(data.size()), static_cast<DWORD>(data.size()), reinterpret_cast<DWORD_PTR>(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll and process the HTTP request/response.
|
||||
*
|
||||
* @return True iff the request is done; no call to Receive() should be done after it returns true.
|
||||
*/
|
||||
bool NetworkHTTPRequest::Receive()
|
||||
{
|
||||
if (this->callback->IsCancelled()) {
|
||||
Debug(net, 1, "HTTP request failed: cancelled by user");
|
||||
this->finished = true;
|
||||
this->callback->OnFailure();
|
||||
return true;
|
||||
}
|
||||
|
||||
return this->finished;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor of the HTTP request.
|
||||
*
|
||||
* Makes sure all handlers are closed, and all memory is free'd.
|
||||
*/
|
||||
NetworkHTTPRequest::~NetworkHTTPRequest()
|
||||
{
|
||||
if (this->request) {
|
||||
WinHttpCloseHandle(this->request);
|
||||
WinHttpCloseHandle(this->connection);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void NetworkHTTPSocketHandler::Connect(const std::string &uri, HTTPCallback *callback, const std::string data)
|
||||
{
|
||||
auto request = new NetworkHTTPRequest(std::wstring(uri.begin(), uri.end()), callback, data);
|
||||
request->Connect();
|
||||
_new_http_requests.push_back(request);
|
||||
}
|
||||
|
||||
/* static */ void NetworkHTTPSocketHandler::HTTPReceive()
|
||||
{
|
||||
if (!_new_http_requests.empty()) {
|
||||
/* We delay adding new requests, as Receive() below can cause a callback which adds a new requests. */
|
||||
_http_requests.insert(_http_requests.end(), _new_http_requests.begin(), _new_http_requests.end());
|
||||
_new_http_requests.clear();
|
||||
}
|
||||
|
||||
if (_http_requests.empty()) return;
|
||||
|
||||
for (auto it = _http_requests.begin(); it != _http_requests.end(); /* nothing */) {
|
||||
NetworkHTTPRequest *cur = *it;
|
||||
|
||||
if (cur->Receive()) {
|
||||
it = _http_requests.erase(it);
|
||||
delete cur;
|
||||
continue;
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkHTTPInitialize()
|
||||
{
|
||||
/* We create a single session, from which we build up every other request. */
|
||||
std::string user_agent = fmt::format("OpenTTD/{}", GetNetworkRevisionString());
|
||||
_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);
|
||||
|
||||
/* Set the callback function for all requests. The "context" maps it back into the actual request instance. */
|
||||
WinHttpSetStatusCallback(_winhttp_session, StaticWinHttpCallback, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0);
|
||||
|
||||
/* 10 seconds timeout for requests. */
|
||||
WinHttpSetTimeouts(_winhttp_session, 10000, 10000, 10000, 10000);
|
||||
}
|
||||
|
||||
void NetworkHTTPUninitialize()
|
||||
{
|
||||
WinHttpCloseHandle(_winhttp_session);
|
||||
}
|
@ -1,325 +0,0 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* 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.
|
||||
* 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.
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file tcp_http.cpp Basic functions to receive and send HTTP TCP packets.
|
||||
*/
|
||||
|
||||
#include "../../stdafx.h"
|
||||
#include "../../debug.h"
|
||||
#include "../../rev.h"
|
||||
#include "../network_internal.h"
|
||||
#include "game_info.h"
|
||||
|
||||
#include "tcp_http.h"
|
||||
|
||||
#include "../../safeguards.h"
|
||||
|
||||
/** List of open HTTP connections. */
|
||||
static std::vector<NetworkHTTPSocketHandler *> _http_connections;
|
||||
|
||||
/**
|
||||
* Start the querying
|
||||
* @param s the socket of this connection
|
||||
* @param callback the callback for HTTP retrieval
|
||||
* @param host the hostname of the server to connect to
|
||||
* @param url the url at the server
|
||||
* @param data the data to send
|
||||
* @param depth the depth (redirect recursion) of the queries
|
||||
*/
|
||||
NetworkHTTPSocketHandler::NetworkHTTPSocketHandler(SOCKET s,
|
||||
HTTPCallback *callback, const char *host, const char *url,
|
||||
const char *data, int depth) :
|
||||
NetworkSocketHandler(),
|
||||
recv_pos(0),
|
||||
recv_length(0),
|
||||
callback(callback),
|
||||
data(data),
|
||||
redirect_depth(depth),
|
||||
sock(s)
|
||||
{
|
||||
size_t bufferSize = strlen(url) + strlen(host) + strlen(_openttd_revision) + (data == nullptr ? 0 : strlen(data)) + 128;
|
||||
char *buffer = AllocaM(char, bufferSize);
|
||||
|
||||
DEBUG(net, 5, "[tcp/http] Requesting %s%s", host, url);
|
||||
if (data != nullptr) {
|
||||
seprintf(buffer, buffer + bufferSize - 1, "POST %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: OpenTTD/%s\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s\r\n", url, host, _openttd_revision, (int)strlen(data), data);
|
||||
} else {
|
||||
seprintf(buffer, buffer + bufferSize - 1, "GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: OpenTTD/%s\r\n\r\n", url, host, _openttd_revision);
|
||||
}
|
||||
|
||||
ssize_t size = strlen(buffer);
|
||||
ssize_t res = send(this->sock, (const char*)buffer, size, 0);
|
||||
if (res != size) {
|
||||
/* Sending all data failed. Socket can't handle this little bit
|
||||
* of information? Just fall back to the old system! */
|
||||
this->callback->OnFailure();
|
||||
delete this;
|
||||
return;
|
||||
}
|
||||
|
||||
_http_connections.push_back(this);
|
||||
}
|
||||
|
||||
/** Free whatever needs to be freed. */
|
||||
NetworkHTTPSocketHandler::~NetworkHTTPSocketHandler()
|
||||
{
|
||||
this->CloseSocket();
|
||||
|
||||
free(this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the actual socket of the connection.
|
||||
*/
|
||||
void NetworkHTTPSocketHandler::CloseSocket()
|
||||
{
|
||||
if (this->sock != INVALID_SOCKET) closesocket(this->sock);
|
||||
this->sock = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to simplify the error handling.
|
||||
* @param msg the error message to show.
|
||||
*/
|
||||
#define return_error(msg) { DEBUG(net, 1, msg); return -1; }
|
||||
|
||||
static const char * const NEWLINE = "\r\n"; ///< End of line marker
|
||||
static const char * const END_OF_HEADER = "\r\n\r\n"; ///< End of header marker
|
||||
static const char * const HTTP_1_0 = "HTTP/1.0 "; ///< Preamble for HTTP 1.0 servers
|
||||
static const char * const HTTP_1_1 = "HTTP/1.1 "; ///< Preamble for HTTP 1.1 servers
|
||||
static const char * const CONTENT_LENGTH = "Content-Length: "; ///< Header for the length of the content
|
||||
static const char * const LOCATION = "Location: "; ///< Header for location
|
||||
|
||||
/**
|
||||
* Handle the header of a HTTP reply.
|
||||
* @return amount of data to continue downloading.
|
||||
* > 0: we need to download N bytes.
|
||||
* = 0: we're being redirected.
|
||||
* < 0: an error occurred. Downloading failed.
|
||||
* @note if an error occurred the header might not be in its
|
||||
* original state. No effort is undertaken to bring
|
||||
* the header in its original state.
|
||||
*/
|
||||
int NetworkHTTPSocketHandler::HandleHeader()
|
||||
{
|
||||
assert(strlen(HTTP_1_0) == strlen(HTTP_1_1));
|
||||
assert(strstr(this->recv_buffer, END_OF_HEADER) != nullptr);
|
||||
|
||||
/* We expect a HTTP/1.[01] reply */
|
||||
if (strncmp(this->recv_buffer, HTTP_1_0, strlen(HTTP_1_0)) != 0 &&
|
||||
strncmp(this->recv_buffer, HTTP_1_1, strlen(HTTP_1_1)) != 0) {
|
||||
return_error("[tcp/http] Received invalid HTTP reply");
|
||||
}
|
||||
|
||||
char *status = this->recv_buffer + strlen(HTTP_1_0);
|
||||
if (strncmp(status, "200", 3) == 0) {
|
||||
/* We are going to receive a document. */
|
||||
|
||||
/* Get the length of the document to receive */
|
||||
char *length = strcasestr(this->recv_buffer, CONTENT_LENGTH);
|
||||
if (length == nullptr) return_error("[tcp/http] Missing 'content-length' header");
|
||||
|
||||
/* Skip the header */
|
||||
length += strlen(CONTENT_LENGTH);
|
||||
|
||||
/* Search the end of the line. This is safe because the header will
|
||||
* always end with two newlines. */
|
||||
char *end_of_line = strstr(length, NEWLINE);
|
||||
|
||||
/* Read the length */
|
||||
*end_of_line = '\0';
|
||||
int len = atoi(length);
|
||||
/* Restore the header. */
|
||||
*end_of_line = '\r';
|
||||
|
||||
/* Make sure we're going to download at least something;
|
||||
* zero sized files are, for OpenTTD's purposes, always
|
||||
* wrong. You can't have gzips of 0 bytes! */
|
||||
if (len == 0) return_error("[tcp/http] Refusing to download 0 bytes");
|
||||
|
||||
DEBUG(net, 7, "[tcp/http] Downloading %i bytes", len);
|
||||
return len;
|
||||
}
|
||||
|
||||
if (strncmp(status, "301", 3) != 0 &&
|
||||
strncmp(status, "302", 3) != 0 &&
|
||||
strncmp(status, "303", 3) != 0 &&
|
||||
strncmp(status, "307", 3) != 0) {
|
||||
/* We are not going to be redirected :(. */
|
||||
|
||||
/* Search the end of the line. This is safe because the header will
|
||||
* always end with two newlines. */
|
||||
*strstr(status, NEWLINE) = '\0';
|
||||
DEBUG(net, 1, "[tcp/http] Unhandled status reply %s", status);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (this->redirect_depth == 5) return_error("[tcp/http] Too many redirects, looping redirects?");
|
||||
|
||||
/* Redirect to other URL */
|
||||
char *uri = strcasestr(this->recv_buffer, LOCATION);
|
||||
if (uri == nullptr) return_error("[tcp/http] Missing 'location' header for redirect");
|
||||
|
||||
uri += strlen(LOCATION);
|
||||
|
||||
/* Search the end of the line. This is safe because the header will
|
||||
* always end with two newlines. */
|
||||
char *end_of_line = strstr(uri, NEWLINE);
|
||||
*end_of_line = '\0';
|
||||
|
||||
DEBUG(net, 7, "[tcp/http] Redirecting to %s", uri);
|
||||
|
||||
int ret = NetworkHTTPSocketHandler::Connect(uri, this->callback, this->data, this->redirect_depth + 1);
|
||||
if (ret != 0) return ret;
|
||||
|
||||
/* We've relinquished control of data now. */
|
||||
this->data = nullptr;
|
||||
|
||||
/* Restore the header. */
|
||||
*end_of_line = '\r';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the given URI.
|
||||
* @param uri the URI to connect to.
|
||||
* @param callback the callback to send data back on.
|
||||
* @param data the data we want to send (as POST).
|
||||
* @param depth the recursion/redirect depth.
|
||||
*/
|
||||
/* static */ int NetworkHTTPSocketHandler::Connect(char *uri, HTTPCallback *callback, const char *data, int depth)
|
||||
{
|
||||
char *hname = strstr(uri, "://");
|
||||
if (hname == nullptr) return_error("[tcp/http] Invalid location");
|
||||
|
||||
hname += 3;
|
||||
|
||||
char *url = strchr(hname, '/');
|
||||
if (url == nullptr) return_error("[tcp/http] Invalid location");
|
||||
|
||||
*url = '\0';
|
||||
|
||||
std::string hostname = std::string(hname);
|
||||
|
||||
/* Restore the URL. */
|
||||
*url = '/';
|
||||
new NetworkHTTPContentConnecter(hostname, callback, url, data, depth);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#undef return_error
|
||||
|
||||
/**
|
||||
* Handle receiving of HTTP data.
|
||||
* @return state of the receival of HTTP data.
|
||||
* > 0: we need more cycles for downloading
|
||||
* = 0: we are done downloading
|
||||
* < 0: we have hit an error
|
||||
*/
|
||||
int NetworkHTTPSocketHandler::Receive()
|
||||
{
|
||||
for (;;) {
|
||||
ssize_t res = recv(this->sock, (char *)this->recv_buffer + this->recv_pos, lengthof(this->recv_buffer) - this->recv_pos, 0);
|
||||
if (res == -1) {
|
||||
NetworkError err = NetworkError::GetLast();
|
||||
if (!err.WouldBlock()) {
|
||||
/* Something went wrong... */
|
||||
if (!err.IsConnectionReset()) DEBUG(net, 0, "Recv failed: %s", err.AsString());
|
||||
return -1;
|
||||
}
|
||||
/* Connection would block, so stop for now */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* No more data... did we get everything we wanted? */
|
||||
if (res == 0) {
|
||||
if (this->recv_length != 0) return -1;
|
||||
|
||||
this->callback->OnReceiveData(nullptr, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Wait till we read the end-of-header identifier */
|
||||
if (this->recv_length == 0) {
|
||||
ssize_t read = this->recv_pos + res;
|
||||
ssize_t end = std::min<ssize_t>(read, lengthof(this->recv_buffer) - 1);
|
||||
|
||||
/* Do a 'safe' search for the end of the header. */
|
||||
char prev = this->recv_buffer[end];
|
||||
this->recv_buffer[end] = '\0';
|
||||
char *end_of_header = strstr(this->recv_buffer, END_OF_HEADER);
|
||||
this->recv_buffer[end] = prev;
|
||||
|
||||
if (end_of_header == nullptr) {
|
||||
if (read == lengthof(this->recv_buffer)) {
|
||||
DEBUG(net, 1, "[tcp/http] Header too big");
|
||||
return -1;
|
||||
}
|
||||
this->recv_pos = read;
|
||||
} else {
|
||||
int ret = this->HandleHeader();
|
||||
if (ret <= 0) return ret;
|
||||
|
||||
this->recv_length = ret;
|
||||
|
||||
end_of_header += strlen(END_OF_HEADER);
|
||||
int len = std::min(read - (end_of_header - this->recv_buffer), res);
|
||||
if (len != 0) {
|
||||
this->callback->OnReceiveData(end_of_header, len);
|
||||
this->recv_length -= len;
|
||||
}
|
||||
|
||||
this->recv_pos = 0;
|
||||
}
|
||||
} else {
|
||||
res = std::min<ssize_t>(this->recv_length, res);
|
||||
/* Receive whatever we're expecting. */
|
||||
this->callback->OnReceiveData(this->recv_buffer, res);
|
||||
this->recv_length -= res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the receiving for all HTTP connections.
|
||||
*/
|
||||
/* static */ void NetworkHTTPSocketHandler::HTTPReceive()
|
||||
{
|
||||
/* No connections, just bail out. */
|
||||
if (_http_connections.size() == 0) return;
|
||||
|
||||
fd_set read_fd;
|
||||
struct timeval tv;
|
||||
|
||||
FD_ZERO(&read_fd);
|
||||
for (NetworkHTTPSocketHandler *handler : _http_connections) {
|
||||
FD_SET(handler->sock, &read_fd);
|
||||
}
|
||||
|
||||
tv.tv_sec = tv.tv_usec = 0; // don't block at all.
|
||||
int n = select(FD_SETSIZE, &read_fd, nullptr, nullptr, &tv);
|
||||
if (n == -1) return;
|
||||
|
||||
for (auto iter = _http_connections.begin(); iter < _http_connections.end(); /* nothing */) {
|
||||
NetworkHTTPSocketHandler *cur = *iter;
|
||||
|
||||
if (FD_ISSET(cur->sock, &read_fd)) {
|
||||
int ret = cur->Receive();
|
||||
/* First send the failure. */
|
||||
if (ret < 0) cur->callback->OnFailure();
|
||||
if (ret <= 0) {
|
||||
/* Then... the connection can be closed */
|
||||
cur->CloseSocket();
|
||||
iter = _http_connections.erase(iter);
|
||||
delete cur;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
/*
|
||||
* This file is part of OpenTTD.
|
||||
* 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.
|
||||
* 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.
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file tcp_http.h Basic functions to receive and send HTTP TCP packets.
|
||||
*/
|
||||
|
||||
#ifndef NETWORK_CORE_TCP_HTTP_H
|
||||
#define NETWORK_CORE_TCP_HTTP_H
|
||||
|
||||
#include "tcp.h"
|
||||
|
||||
/** Callback for when the HTTP handler has something to tell us. */
|
||||
struct HTTPCallback {
|
||||
/**
|
||||
* An error has occurred and the connection has been closed.
|
||||
* @note HTTP socket handler is closed/freed.
|
||||
*/
|
||||
virtual void OnFailure() = 0;
|
||||
|
||||
/**
|
||||
* We're receiving data.
|
||||
* @param data the received data, nullptr when all data has been received.
|
||||
* @param length the amount of received data, 0 when all data has been received.
|
||||
* @note When nullptr is sent the HTTP socket handler is closed/freed.
|
||||
*/
|
||||
virtual void OnReceiveData(const char *data, size_t length) = 0;
|
||||
|
||||
/** Silentium */
|
||||
virtual ~HTTPCallback() {}
|
||||
};
|
||||
|
||||
/** Base socket handler for HTTP traffic. */
|
||||
class NetworkHTTPSocketHandler : public NetworkSocketHandler {
|
||||
private:
|
||||
char recv_buffer[4096]; ///< Partially received message.
|
||||
int recv_pos; ///< Current position in buffer.
|
||||
int recv_length; ///< Length of the data still retrieving.
|
||||
HTTPCallback *callback; ///< The callback to call for the incoming data.
|
||||
const char *data; ///< The (POST) data we might want to forward (to a redirect).
|
||||
int redirect_depth; ///< The depth of the redirection.
|
||||
|
||||
int HandleHeader();
|
||||
int Receive();
|
||||
public:
|
||||
SOCKET sock; ///< The socket currently connected to
|
||||
|
||||
/**
|
||||
* Whether this socket is currently bound to a socket.
|
||||
* @return true when the socket is bound, false otherwise
|
||||
*/
|
||||
bool IsConnected() const
|
||||
{
|
||||
return this->sock != INVALID_SOCKET;
|
||||
}
|
||||
|
||||
void CloseSocket();
|
||||
|
||||
NetworkHTTPSocketHandler(SOCKET sock, HTTPCallback *callback,
|
||||
const char *host, const char *url, const char *data, int depth);
|
||||
|
||||
~NetworkHTTPSocketHandler();
|
||||
|
||||
static int Connect(char *uri, HTTPCallback *callback,
|
||||
const char *data = nullptr, int depth = 0);
|
||||
|
||||
static void HTTPReceive();
|
||||
};
|
||||
|
||||
/** Connect with a HTTP server and do ONE query. */
|
||||
class NetworkHTTPContentConnecter : TCPConnecter {
|
||||
std::string hostname; ///< Hostname we are connecting to.
|
||||
HTTPCallback *callback; ///< Callback to tell that we received some data (or won't).
|
||||
const char *url; ///< The URL we want to get at the server.
|
||||
const char *data; ///< The data to send
|
||||
int depth; ///< How far we have recursed
|
||||
|
||||
public:
|
||||
/**
|
||||
* Start the connecting.
|
||||
* @param hostname The hostname to connect to.
|
||||
* @param callback The callback for HTTP retrieval.
|
||||
* @param url The url at the server.
|
||||
* @param data The data to send.
|
||||
* @param depth The depth (redirect recursion) of the queries.
|
||||
*/
|
||||
NetworkHTTPContentConnecter(const std::string &hostname, HTTPCallback *callback, const char *url, const char *data = nullptr, int depth = 0) :
|
||||
TCPConnecter(hostname, 80),
|
||||
hostname(hostname),
|
||||
callback(callback),
|
||||
url(stredup(url)),
|
||||
data(data),
|
||||
depth(depth)
|
||||
{
|
||||
}
|
||||
|
||||
/** Free all our allocated data. */
|
||||
~NetworkHTTPContentConnecter()
|
||||
{
|
||||
free(this->url);
|
||||
}
|
||||
|
||||
void OnFailure() override
|
||||
{
|
||||
this->callback->OnFailure();
|
||||
free(this->data);
|
||||
}
|
||||
|
||||
void OnConnect(SOCKET s) override
|
||||
{
|
||||
new NetworkHTTPSocketHandler(s, this->callback, this->hostname.c_str(), this->url, this->data, this->depth);
|
||||
/* We've relinquished control of data now. */
|
||||
this->data = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* NETWORK_CORE_TCP_HTTP_H */
|
Loading…
Reference in New Issue