diff --git a/src/openrct2-ui/windows/ServerList.cpp b/src/openrct2-ui/windows/ServerList.cpp index 5de608377b..cce30d6619 100644 --- a/src/openrct2-ui/windows/ServerList.cpp +++ b/src/openrct2-ui/windows/ServerList.cpp @@ -1,4 +1,4 @@ -#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include #include @@ -33,6 +33,10 @@ #include #include +#ifndef DISABLE_HTTP +using namespace OpenRCT2::Network; +#endif + #define WWIDTH_MIN 500 #define WHEIGHT_MIN 300 #define WWIDTH_MAX 1200 @@ -134,9 +138,9 @@ static void dispose_server_entry_list(); static server_entry & add_server_entry(const std::string &address); static void sort_servers(); static void join_server(std::string address); -static void fetch_servers(); #ifndef DISABLE_HTTP -static void fetch_servers_callback(http_response_t* response); +static void fetch_servers(); +static void fetch_servers_callback(Http::Response & response); #endif static bool is_version_valid(const std::string &version); @@ -179,7 +183,9 @@ rct_window * window_server_list_open() server_list_load_server_entries(); window->no_list_items = (uint16)_serverEntries.size(); +#ifndef DISABLE_HTTP fetch_servers(); +#endif return window; } @@ -219,7 +225,9 @@ static void window_server_list_mouseup(rct_window *w, rct_widgetindex widgetInde break; } case WIDX_FETCH_SERVERS: +#ifndef DISABLE_HTTP fetch_servers(); +#endif break; case WIDX_ADD_SERVER: window_text_input_open(w, widgetIndex, STR_ADD_SERVER, STR_ENTER_HOSTNAME_OR_IP_ADDRESS, STR_NONE, 0, 128); @@ -611,9 +619,9 @@ static void join_server(std::string address) } } +#ifndef DISABLE_HTTP static void fetch_servers() { -#ifndef DISABLE_HTTP const char *masterServerUrl = OPENRCT2_MASTER_SERVER_URL; if (!str_is_null_or_empty(gConfigNetwork.master_server_url)) { masterServerUrl = gConfigNetwork.master_server_url; @@ -633,17 +641,14 @@ static void fetch_servers() sort_servers(); } - http_request_t request = {}; + Http::Request request; request.url = masterServerUrl; - request.method = HTTP_METHOD_GET; - request.body = nullptr; - request.type = HTTP_DATA_JSON; + request.method = Http::Method::GET; + request.header["Accept"] = "application/json"; status_text = STR_SERVER_LIST_CONNECTING; - http_request_async(&request, fetch_servers_callback); -#endif + Http::DoAsync(request, fetch_servers_callback); } -#ifndef DISABLE_HTTP static uint32 get_total_player_count() { return std::accumulate( @@ -656,18 +661,19 @@ static uint32 get_total_player_count() }); } -static void fetch_servers_callback(http_response_t* response) +static void fetch_servers_callback(Http::Response & response) { - if (response == nullptr) { + if (response.status != Http::Status::OK) + { status_text = STR_SERVER_LIST_NO_CONNECTION; window_invalidate_by_class(WC_SERVER_LIST); log_warning("Unable to connect to master server"); return; } - json_t *jsonStatus = json_object_get(response->root, "status"); + json_t * root = Json::FromString(response.body); + json_t * jsonStatus = json_object_get(root, "status"); if (!json_is_number(jsonStatus)) { - http_request_dispose(response); status_text = STR_SERVER_LIST_INVALID_RESPONSE_JSON_NUMBER; window_invalidate_by_class(WC_SERVER_LIST); log_warning("Invalid response from master server"); @@ -676,16 +682,14 @@ static void fetch_servers_callback(http_response_t* response) sint32 status = (sint32)json_integer_value(jsonStatus); if (status != 200) { - http_request_dispose(response); status_text = STR_SERVER_LIST_MASTER_SERVER_FAILED; window_invalidate_by_class(WC_SERVER_LIST); log_warning("Master server failed to return servers"); return; } - json_t *jsonServers = json_object_get(response->root, "servers"); + json_t * jsonServers = json_object_get(root, "servers"); if (!json_is_array(jsonServers)) { - http_request_dispose(response); status_text = STR_SERVER_LIST_INVALID_RESPONSE_JSON_ARRAY; window_invalidate_by_class(WC_SERVER_LIST); log_warning("Invalid response from master server"); @@ -728,7 +732,6 @@ static void fetch_servers_callback(http_response_t* response) newserver.maxplayers = (uint8)json_integer_value(maxPlayers); } } - http_request_dispose(response); sort_servers(); _numPlayersOnline = get_total_player_count(); diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index 6bdd7eb6d0..010a1b8e2a 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -1,4 +1,4 @@ -#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * @@ -56,12 +56,13 @@ #include "world/Park.h" #include "Version.h" -#include "audio/audio.h" -#include "config/Config.h" -#include "drawing/LightFX.h" #include "Editor.h" #include "Game.h" #include "Input.h" +#include "Intro.h" +#include "audio/audio.h" +#include "config/Config.h" +#include "drawing/LightFX.h" #include "interface/Chat.h" #include "interface/InteractiveConsole.h" #include "interface/Viewport.h" @@ -69,7 +70,7 @@ #include "localisation/Date.h" #include "localisation/LocalisationService.h" #include "network/DiscordService.h" -#include "network/http.h" +#include "network/Http.h" #include "network/network.h" #include "network/twitch.h" #include "platform/platform.h" @@ -102,6 +103,9 @@ namespace OpenRCT2 std::unique_ptr _discordService; #endif StdInOutConsole _stdInOutConsole; +#ifndef DISABLE_HTTP + Network::Http::Http _http; +#endif // Game states std::unique_ptr _titleScreen; @@ -143,7 +147,6 @@ namespace OpenRCT2 ~Context() override { window_close_all(); - http_dispose(); object_manager_unload_all_objects(); gfx_object_check_all_images_freed(); gfx_unload_g2(); @@ -420,7 +423,6 @@ namespace OpenRCT2 audio_init_ride_sounds_and_info(); } - http_init(); network_set_env(_env); chat_init(); CopyOriginalUserFilesOver(); @@ -718,7 +720,7 @@ namespace OpenRCT2 #ifndef DISABLE_HTTP // Download park and open it using its temporary filename void * data; - size_t dataSize = http_download_park(gOpenRCT2StartupActionPath, &data); + size_t dataSize = Network::Http::DownloadPark(gOpenRCT2StartupActionPath, &data); if (dataSize == 0) { title_load(); diff --git a/src/openrct2/Context.h b/src/openrct2/Context.h index 78408221f9..0b13541273 100644 --- a/src/openrct2/Context.h +++ b/src/openrct2/Context.h @@ -1,4 +1,4 @@ -#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * diff --git a/src/openrct2/core/Json.cpp b/src/openrct2/core/Json.cpp index 0ddf1730cb..f27e000193 100644 --- a/src/openrct2/core/Json.cpp +++ b/src/openrct2/core/Json.cpp @@ -1,4 +1,4 @@ -#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * @@ -58,4 +58,16 @@ namespace Json size_t jsonOutputSize = String::SizeOf(jsonOutput); fs.Write(jsonOutput, jsonOutputSize); } + + json_t * FromString(const std::string & raw) + { + json_t * root; + json_error_t error; + root = json_loads(raw.c_str(), 0, &error); + if (root == nullptr) + { + throw JsonException(&error); + } + return root; + } } // namespace Json diff --git a/src/openrct2/core/Json.hpp b/src/openrct2/core/Json.hpp index ce305a8724..edacc6104a 100644 --- a/src/openrct2/core/Json.hpp +++ b/src/openrct2/core/Json.hpp @@ -1,4 +1,4 @@ -#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * @@ -29,6 +29,8 @@ namespace Json json_t * ReadFromFile(const utf8 * path, size_t maxSize = MAX_JSON_SIZE); void WriteToFile(const utf8 * path, const json_t * json, size_t flags = 0); + + json_t * FromString(const std::string & raw); } class JsonException final : public std::runtime_error diff --git a/src/openrct2/network/Http.cpp b/src/openrct2/network/Http.cpp index b58c14f071..78ac80ba10 100644 --- a/src/openrct2/network/Http.cpp +++ b/src/openrct2/network/Http.cpp @@ -1,4 +1,4 @@ -#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * @@ -14,322 +14,217 @@ *****************************************************************************/ #pragma endregion -#include "http.h" - -#ifdef DISABLE_HTTP - -void http_init() { } -void http_dispose() { } - -#else - +#include "Http.h" #include +#include +#include #include -#include "../core/Console.hpp" -#include "../core/Math.hpp" +#ifndef DISABLE_HTTP + #include "../Version.h" +#include "../core/Console.hpp" #ifdef _WIN32 - // cURL includes windows.h, but we don't need all of it. - #define WIN32_LEAN_AND_MEAN +// cURL includes windows.h, but we don't need all of it. +#define WIN32_LEAN_AND_MEAN #endif #include -#define MIME_TYPE_APPLICATION_JSON "application/json" #define OPENRCT2_USER_AGENT "OpenRCT2/" OPENRCT2_VERSION -struct HttpRequest2 +namespace OpenRCT2::Network::Http { - void * Tag = nullptr; - std::string Method; - std::string Url; - HTTP_DATA_TYPE Type; - bool ForceIPv4 = false; - size_t Size = 0; - union - { - char * Buffer = nullptr; - json_t * Json; - } Body; - HttpRequest2() { } - - HttpRequest2(const HttpRequest2 &request) - { - Tag = request.Tag; - Method = request.Method; - Url = request.Url; - Type = request.Type; - ForceIPv4 = request.ForceIPv4; - Size = request.Size; - if (request.Type == HTTP_DATA_JSON) - { - Body.Json = json_deep_copy(request.Body.Json); - } - else - { - Body.Buffer = new char[request.Size]; - memcpy(Body.Buffer, request.Body.Buffer, request.Size); - } - } - - explicit HttpRequest2(const http_request_t * request) - { - Tag = request->tag; - Method = std::string(request->method); - Url = std::string(request->url); - Type = request->type; - ForceIPv4 = request->forceIPv4; - Size = request->size; - if (request->type == HTTP_DATA_JSON) - { - Body.Json = json_deep_copy(request->root); - } - else - { - Body.Buffer = new char[request->size]; - memcpy(Body.Buffer, request->body, request->size); - } - } - - ~HttpRequest2() - { - if (Type == HTTP_DATA_JSON) - { - json_decref(Body.Json); - } - else - { - delete Body.Buffer; - } - } -}; - -struct read_buffer { - char *ptr; - size_t length; - size_t position; -}; - -struct write_buffer { - char *ptr; - size_t length; - size_t capacity; -}; - -void http_init() +Http::Http() { curl_global_init(CURL_GLOBAL_DEFAULT); } -void http_dispose() +Http::~Http() { curl_global_cleanup(); } -static size_t http_request_write_func(void *ptr, size_t size, size_t nmemb, void *userdata) +static size_t writeData(const char * src, size_t size, size_t nmemb, void * userdata) { - write_buffer *writeBuffer = (write_buffer*)userdata; + size_t realsize = size * nmemb; + Response * res = static_cast(userdata); + res->body += std::string(src, src + realsize); - size_t newBytesLength = size * nmemb; - if (newBytesLength > 0) { - size_t newCapacity = writeBuffer->capacity; - size_t newLength = writeBuffer->length + newBytesLength; - while (newLength > newCapacity) { - newCapacity = Math::Max(4096, newCapacity * 2); - } - if (newCapacity != writeBuffer->capacity) { - writeBuffer->ptr = (char*)realloc(writeBuffer->ptr, newCapacity); - writeBuffer->capacity = newCapacity; - } - - memcpy(writeBuffer->ptr + writeBuffer->length, ptr, newBytesLength); - writeBuffer->length = newLength; - } - return newBytesLength; + return realsize; } -static http_response_t *http_request(const HttpRequest2 &request) +static size_t header_callback(const char * src, size_t size, size_t nitems, void * userdata) { - CURL *curl; - CURLcode curlResult; - http_response_t *response; - read_buffer readBuffer = {}; - write_buffer writeBuffer; + size_t realsize = nitems * size; + Response * res = static_cast(userdata); - curl = curl_easy_init(); - if (curl == nullptr) - return nullptr; - - if (request.Type == HTTP_DATA_JSON && request.Body.Json != nullptr) { - readBuffer.ptr = json_dumps(request.Body.Json, JSON_COMPACT); - readBuffer.length = strlen(readBuffer.ptr); - readBuffer.position = 0; - } else if (request.Type == HTTP_DATA_RAW && request.Body.Buffer != nullptr) { - readBuffer.ptr = request.Body.Buffer; - readBuffer.length = request.Size; - readBuffer.position = 0; + auto line = std::string(src, src + realsize); + auto pos = line.find(':'); + if (pos != std::string::npos) + { + std::string key = line.substr(0, pos); + // substract 4 chars for ": " and "\r\n" + std::string value = line.substr(pos + 2, line.size() - pos - 4); + res->header[key] = value; } - writeBuffer.ptr = nullptr; - writeBuffer.length = 0; - writeBuffer.capacity = 0; + return realsize; +} - curl_slist *headers = nullptr; +struct WriteThis +{ + const char * readptr; + size_t sizeleft; +}; - if (request.Type == HTTP_DATA_JSON) { - headers = curl_slist_append(headers, "Accept: " MIME_TYPE_APPLICATION_JSON); +static size_t read_callback(void * dst, size_t size, size_t nmemb, void * userp) +{ + WriteThis * wt = static_cast(userp); + size_t buffer_size = size * nmemb; - if (request.Body.Json != nullptr) { - headers = curl_slist_append(headers, "Content-Type: " MIME_TYPE_APPLICATION_JSON); - } + if (wt->sizeleft) + { + size_t copy_this_much = wt->sizeleft; + if (copy_this_much > buffer_size) + copy_this_much = buffer_size; + memcpy(dst, wt->readptr, copy_this_much); + + wt->readptr += copy_this_much; + wt->sizeleft -= copy_this_much; + return copy_this_much; } - if (readBuffer.ptr != nullptr) { - char contentLengthHeaderValue[64]; - snprintf(contentLengthHeaderValue, sizeof(contentLengthHeaderValue), "Content-Length: %zu", readBuffer.length); - headers = curl_slist_append(headers, contentLengthHeaderValue); + return 0; +} - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, readBuffer.ptr); +Response Do(const Request & req) +{ + CURL * curl = curl_easy_init(); + std::shared_ptr _(nullptr, [curl](...) { curl_easy_cleanup(curl); }); + + if (!curl) + std::runtime_error("Failed to initialize curl"); + + Response res; + + if (req.method == Method::POST || req.method == Method::PUT) + { + WriteThis wt; + wt.readptr = req.body.c_str(); + wt.sizeleft = req.body.size(); + + curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); + curl_easy_setopt(curl, CURLOPT_READDATA, &wt); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)wt.sizeleft); } - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, request.Method.c_str()); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + if (req.forceIPv4) + curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + + if (req.method == Method::POST) + curl_easy_setopt(curl, CURLOPT_POST, 1L); + + if (req.method == Method::PUT) + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + + curl_easy_setopt(curl, CURLOPT_URL, req.url.c_str()); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&res); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)&res); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true); curl_easy_setopt(curl, CURLOPT_USERAGENT, OPENRCT2_USER_AGENT); - curl_easy_setopt(curl, CURLOPT_URL, request.Url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &writeBuffer); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_request_write_func); - if (request.ForceIPv4) + + curl_slist * chunk = nullptr; + std::shared_ptr __(nullptr, [chunk](...) { curl_slist_free_all(chunk); }); + for (auto header : req.header) { - // Force resolving to IPv4 to fix issues where advertising over IPv6 does not work - curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + std::string hs = header.first + ": " + header.second; + chunk = curl_slist_append(chunk, hs.c_str()); } - - curlResult = curl_easy_perform(curl); - - if (request.Type == HTTP_DATA_JSON && request.Body.Json != nullptr) { - free(readBuffer.ptr); - } - - if (curlResult != CURLE_OK) { - log_error("HTTP request failed: %s.", curl_easy_strerror(curlResult)); - if (writeBuffer.ptr != nullptr) - free(writeBuffer.ptr); - - return nullptr; - } - - long httpStatusCode; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpStatusCode); - - char* contentType; - curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &contentType); - - // Null terminate the response buffer - writeBuffer.length++; - writeBuffer.ptr = (char*)realloc(writeBuffer.ptr, writeBuffer.length); - writeBuffer.capacity = writeBuffer.length; - writeBuffer.ptr[writeBuffer.length - 1] = 0; - - response = nullptr; - - // Parse as JSON if response is JSON - if (contentType != nullptr && strstr(contentType, "json") != nullptr) { - json_t *root; - json_error_t error; - root = json_loads(writeBuffer.ptr, 0, &error); - if (root != nullptr) { - response = (http_response_t*) malloc(sizeof(http_response_t)); - response->tag = request.Tag; - response->status_code = (sint32) httpStatusCode; - response->root = root; - response->type = HTTP_DATA_JSON; - response->size = writeBuffer.length; + if (req.header.size() != 0) + { + if (chunk == nullptr) + { + throw std::runtime_error("Failed to set headers"); } - free(writeBuffer.ptr); - } else { - response = (http_response_t*) malloc(sizeof(http_response_t)); - response->tag = request.Tag; - response->status_code = (sint32) httpStatusCode; - response->body = writeBuffer.ptr; - response->type = HTTP_DATA_RAW; - response->size = writeBuffer.length; + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk); } - curl_easy_cleanup(curl); + CURLcode curl_code = curl_easy_perform(curl); + if (curl_code != CURLE_OK) + { + throw std::runtime_error("Failed to perform request"); + } - return response; + // gets freed by curl_easy_cleanup + char * content_type; + long code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type); + res.status = static_cast(code); + res.content_type = std::string(content_type); + + return res; } -void http_request_async(const http_request_t * request, void (*callback)(http_response_t*)) +void DoAsync(const Request & req, std::function fn) { - auto request2 = HttpRequest2(request); - auto thread = std::thread([](const HttpRequest2 &req, void(*callback2)(http_response_t*)) -> void - { - http_response_t * response = http_request(req); - callback2(response); - }, std::move(request2), callback); + auto thread = std::thread([=]() { + Response res; + try + { + res = Do(req); + } + catch (std::exception & e) + { + res.error = e.what(); + return; + } + fn(res); + }); thread.detach(); } -void http_request_dispose(http_response_t *response) -{ - if (response->type == HTTP_DATA_JSON && response->root != nullptr) - json_decref(response->root); - else if (response->type == HTTP_DATA_RAW && response->body != nullptr) - free(response->body); - - free(response); -} - -const char *http_get_extension_from_url(const char *url, const char *fallback) -{ - const char *extension = strrchr(url, '.'); - - // Assume a save file by default if no valid extension can be determined - if (extension == nullptr || strchr(extension, '/') != nullptr) { - return fallback; - } else { - return extension; - } -} - -size_t http_download_park(const char * url, void * * outData) +size_t DownloadPark(const char * url, void ** outData) { // Download park to buffer in memory - HttpRequest2 request; - request.Url = url; - request.Method = "GET"; - request.Type = HTTP_DATA_NONE; - - http_response_t *response = http_request(request); - - if (response == nullptr || response->status_code != 200) { - Console::Error::WriteLine("Failed to download '%s'", request.Url.c_str()); - if (response != nullptr) { - http_request_dispose(response); - } + Request request; + request.url = url; + request.method = Method::GET; + Response res; + try + { + res = Do(request); + if (res.status != Status::OK) + std::runtime_error("bad http status"); + } + catch (std::exception & e) + { + Console::Error::WriteLine("Failed to download '%s', cause %s", request.url.c_str(), e.what()); *outData = nullptr; return 0; } - size_t dataSize = response->size - 1; - void * data = malloc(dataSize); - if (data == nullptr) { - dataSize = 0; + size_t dataSize = res.body.size() - 1; + void * data = malloc(dataSize); + if (data == nullptr) + { Console::Error::WriteLine("Failed to allocate memory for downloaded park."); - } else { - memcpy(data, response->body, dataSize); + return 0; } - http_request_dispose(response); - + memcpy(data, res.body.c_str(), dataSize); *outData = data; + return dataSize; } +} // namespace OpenRCT2::Network + #endif diff --git a/src/openrct2/network/Http.h b/src/openrct2/network/Http.h new file mode 100644 index 0000000000..06ba37d605 --- /dev/null +++ b/src/openrct2/network/Http.h @@ -0,0 +1,80 @@ +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 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, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#ifndef DISABLE_HTTP + +#include +#include +#include + +#include "../common.h" + +namespace OpenRCT2::Network::Http +{ + +enum class Status +{ + OK = 200 +}; + +enum class Method +{ + GET, + POST, + PUT +}; + +struct Response +{ + Status status; + std::string content_type; + std::string body = ""; + std::map header = {}; + std::string error = ""; +}; + +struct Request +{ + std::string url; + std::map header = {}; + Method method = Method::GET; + std::string body = ""; + bool forceIPv4 = false; +}; + +struct Http +{ + Http(); + ~Http(); +}; + +Response Do(const Request & req); +void DoAsync(const Request & req, std::function fn); + +/** + * Download a park via HTTP/S from the given URL into a memory buffer. This is + * a blocking operation. + * @param url The URL to download the park from. + * @param outData The data returned. + * @returns The size of the data or 0 if the download failed. + */ +size_t DownloadPark(const char * url, void ** outData); + +} /* namespace http */ + +#endif // DISABLE_HTTP diff --git a/src/openrct2/network/NetworkServerAdvertiser.cpp b/src/openrct2/network/NetworkServerAdvertiser.cpp index 2af7d5d6ef..40189a690a 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.cpp +++ b/src/openrct2/network/NetworkServerAdvertiser.cpp @@ -1,4 +1,4 @@ -#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * @@ -31,7 +31,9 @@ #include "../util/Util.h" #include "../world/Map.h" #include "../world/Park.h" -#include "http.h" +#include "Http.h" + +using namespace OpenRCT2::Network; #ifndef DISABLE_HTTP @@ -103,30 +105,26 @@ private: _lastAdvertiseTime = platform_get_ticks(); // Send the registration request - http_request_t request = {}; - request.tag = this; + Http::Request request; request.url = GetMasterServerUrl(); - request.method = HTTP_METHOD_POST; + request.method = Http::Method::POST; request.forceIPv4 = forceIPv4; json_t *body = json_object(); json_object_set_new(body, "key", json_string(_key.c_str())); json_object_set_new(body, "port", json_integer(_port)); - request.root = body; - request.type = HTTP_DATA_JSON; + request.body = json_dumps(body, JSON_COMPACT); + request.header["Content-Type"] = "application/json"; - http_request_async(&request, [](http_response_t * response) -> void - { - if (response == nullptr) + Http::DoAsync(request, [&](Http::Response response) -> void { + if (response.status != Http::Status::OK) { Console::WriteLine("Unable to connect to master server"); + return; } - else - { - auto advertiser = static_cast(response->tag); - advertiser->OnRegistrationResponse(response->root); - http_request_dispose(response); - } + + json_t * root = Json::FromString(response.body); + this->OnRegistrationResponse(root); }); json_decref(body); @@ -134,31 +132,27 @@ private: void SendHeartbeat() { - http_request_t request = {}; - request.tag = this; + Http::Request request; request.url = GetMasterServerUrl(); - request.method = HTTP_METHOD_PUT; + request.method = Http::Method::POST; - json_t * jsonBody = GetHeartbeatJson(); - request.root = jsonBody; - request.type = HTTP_DATA_JSON; + json_t * body = GetHeartbeatJson(); + request.body = json_dumps(body, JSON_COMPACT); + request.header["Content-Type"] = "application/json"; _lastHeartbeatTime = platform_get_ticks(); - http_request_async(&request, [](http_response_t *response) -> void - { - if (response == nullptr) + Http::DoAsync(request, [&](Http::Response response) -> void { + if (response.status != Http::Status::OK) { - log_warning("Unable to connect to master server"); - } - else - { - auto advertiser = static_cast(response->tag); - advertiser->OnHeartbeatResponse(response->root); - http_request_dispose(response); + Console::WriteLine("Unable to connect to master server"); + return; } + + json_t * root = Json::FromString(response.body); + this->OnHeartbeatResponse(root); }); - json_decref(jsonBody); + json_decref(body); } void OnRegistrationResponse(json_t * jsonRoot) diff --git a/src/openrct2/network/NetworkServerAdvertiser.h b/src/openrct2/network/NetworkServerAdvertiser.h index 376bbf3eb3..06e6abc158 100644 --- a/src/openrct2/network/NetworkServerAdvertiser.h +++ b/src/openrct2/network/NetworkServerAdvertiser.h @@ -1,4 +1,4 @@ -#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * diff --git a/src/openrct2/network/Twitch.cpp b/src/openrct2/network/Twitch.cpp index c53cb26f8a..a823286527 100644 --- a/src/openrct2/network/Twitch.cpp +++ b/src/openrct2/network/Twitch.cpp @@ -1,4 +1,4 @@ -#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. * @@ -26,13 +26,17 @@ #error HTTP must be enabled to use the TWITCH functionality. #endif +#include +#include #include #include "../Context.h" + #include "../core/Math.hpp" #include "../core/String.hpp" #include "../OpenRCT2.h" #include "../config/Config.h" +#include "../core/Json.hpp" #include "../drawing/Drawing.h" #include "../Game.h" #include "../interface/InteractiveConsole.h" @@ -42,10 +46,12 @@ #include "../platform/platform.h" #include "../util/Util.h" #include "../world/Sprite.h" -#include "http.h" +#include "Http.h" #include "twitch.h" using namespace OpenRCT2; +using namespace OpenRCT2::Network; + bool gTwitchEnable = false; @@ -107,7 +113,7 @@ namespace Twitch static bool _twitchIdle = true; static uint32 _twitchLastPulseTick = 0; static sint32 _twitchLastPulseOperation = 1; - static http_response_t * _twitchJsonResponse; + static Http::Response _twitchJsonResponse; static void Join(); static void Leave(); @@ -203,37 +209,33 @@ namespace Twitch _twitchState = TWITCH_STATE_JOINING; _twitchIdle = false; - http_request_t request = {}; + Http::Request request; request.url = url; - request.method = HTTP_METHOD_GET; - request.body = nullptr; - request.type = HTTP_DATA_JSON; - http_request_async(&request, [](http_response_t *jsonResponse) -> void - { - auto context = GetContext(); - if (jsonResponse == nullptr) + request.method = Http::Method::GET; + + Http::DoAsync(request, [](Http::Response res) { + std::shared_ptr _(nullptr, [&](...) { _twitchIdle = true; }); + + if (res.status != Http::Status::OK) { _twitchState = TWITCH_STATE_LEFT; - context->WriteLine("Unable to connect to twitch channel."); + GetContext()->WriteLine("Unable to connect to twitch channel."); + return; + } + + auto root = Json::FromString(res.body); + json_t * jsonStatus = json_object_get(root, "status"); + if (json_is_number(jsonStatus) && json_integer_value(jsonStatus) == TWITCH_STATUS_OK) + { + _twitchState = TWITCH_STATE_JOINED; } else { - json_t * jsonStatus = json_object_get(jsonResponse->root, "status"); - if (json_is_number(jsonStatus) && json_integer_value(jsonStatus) == TWITCH_STATUS_OK) - { - _twitchState = TWITCH_STATE_JOINED; - } - else - { - _twitchState = TWITCH_STATE_LEFT; - } - - http_request_dispose(jsonResponse); - - _twitchLastPulseTick = 0; - context->WriteLine("Connected to twitch channel."); + _twitchState = TWITCH_STATE_LEFT; } - _twitchIdle = true; + + _twitchLastPulseTick = 0; + GetContext()->WriteLine("Connected to twitch channel."); }); } @@ -242,13 +244,8 @@ namespace Twitch */ static void Leave() { - if (_twitchJsonResponse != nullptr) - { - http_request_dispose(_twitchJsonResponse); - _twitchJsonResponse = nullptr; - } - GetContext()->WriteLine("Left twitch channel."); + _twitchJsonResponse = {}; _twitchState = TWITCH_STATE_LEFT; _twitchLastPulseTick = 0; gTwitchEnable = false; @@ -288,23 +285,17 @@ namespace Twitch _twitchState = TWITCH_STATE_WAITING; _twitchIdle = false; - http_request_t request = {}; - request.url = url; - request.method = HTTP_METHOD_GET; - request.body = nullptr; - request.type = HTTP_DATA_JSON; - http_request_async(&request, [](http_response_t * jsonResponse) -> void - { - if (jsonResponse == nullptr) + Http::DoAsync({ url }, [](Http::Response res) { + std::shared_ptr _(nullptr, [&](...) { _twitchIdle = true; }); + + if (res.status != Http::Status::OK) { _twitchState = TWITCH_STATE_JOINED; + return; } - else - { - _twitchJsonResponse = jsonResponse; - _twitchState = TWITCH_STATE_GET_FOLLOWERS; - } - _twitchIdle = true; + + _twitchJsonResponse = res; + _twitchState = TWITCH_STATE_GET_FOLLOWERS; }); } @@ -326,37 +317,31 @@ namespace Twitch _twitchState = TWITCH_STATE_WAITING; _twitchIdle = false; - http_request_t request = {}; - request.url = url; - request.method = HTTP_METHOD_GET; - request.body = nullptr; - request.type = HTTP_DATA_JSON; - http_request_async(&request, [](http_response_t * jsonResponse) -> void - { - if (jsonResponse == nullptr) + Http::DoAsync({ url }, [](Http::Response res) { + std::shared_ptr _(nullptr, [&](...) { _twitchIdle = true; }); + + if (res.status != Http::Status::OK) { _twitchState = TWITCH_STATE_JOINED; + return; } - else - { - _twitchJsonResponse = jsonResponse; - _twitchState = TWITCH_STATE_GET_MESSAGES; - } - _twitchIdle = true; + + _twitchJsonResponse = res; + _twitchState = TWITCH_STATE_GET_MESSAGES; }); } static void ParseFollowers() { - http_response_t *jsonResponse = _twitchJsonResponse; - if (json_is_array(jsonResponse->root)) + json_t * root = Json::FromString(_twitchJsonResponse.body); + if (json_is_array(root)) { std::vector members; - size_t audienceCount = json_array_size(jsonResponse->root); + size_t audienceCount = json_array_size(root); for (size_t i = 0; i < audienceCount; i++) { - json_t * jsonAudienceMember = json_array_get(jsonResponse->root, i); + json_t * jsonAudienceMember = json_array_get(root, i); auto member = AudienceMember::FromJson(jsonAudienceMember); if (!String::IsNullOrEmpty(member.Name)) { @@ -371,8 +356,7 @@ namespace Twitch ManageGuestNames(members); } - http_request_dispose(_twitchJsonResponse); - _twitchJsonResponse = nullptr; + _twitchJsonResponse = {}; _twitchState = TWITCH_STATE_JOINED; gfx_invalidate_screen(); @@ -380,12 +364,12 @@ namespace Twitch static void ParseMessages() { - http_response_t * jsonResponse = _twitchJsonResponse; - if (json_is_array(jsonResponse->root)) + json_t * root = Json::FromString(_twitchJsonResponse.body); + if (json_is_array(root)) { - size_t messageCount = json_array_size(jsonResponse->root); + size_t messageCount = json_array_size(root); for (size_t i = 0; i < messageCount; i++) { - json_t * jsonMessage = json_array_get(jsonResponse->root, i); + json_t * jsonMessage = json_array_get(root, i); if (!json_is_object(jsonMessage)) { continue; @@ -397,8 +381,7 @@ namespace Twitch } } - http_request_dispose(_twitchJsonResponse); - _twitchJsonResponse = nullptr; + _twitchJsonResponse = {}; _twitchState = TWITCH_STATE_JOINED; } @@ -569,7 +552,7 @@ namespace Twitch safe_strcpy(buffer + 1, message, sizeof(buffer) - 1); utf8_remove_formatting(buffer, false); - + // TODO Create a new news item type for twitch which has twitch icon news_item_add_to_queue_raw(NEWS_ITEM_BLANK, buffer, 0); } diff --git a/src/openrct2/network/http.h b/src/openrct2/network/http.h deleted file mode 100644 index bf003e2fd4..0000000000 --- a/src/openrct2/network/http.h +++ /dev/null @@ -1,79 +0,0 @@ -#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers -/***************************************************************************** - * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. - * - * OpenRCT2 is the work of many authors, a full list can be found in contributors.md - * For more information, visit https://github.com/OpenRCT2/OpenRCT2 - * - * OpenRCT2 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, either version 3 of the License, or - * (at your option) any later version. - * - * A full copy of the GNU General Public License can be found in licence.txt - *****************************************************************************/ -#pragma endregion - -#ifndef _HTTP_H_ -#define _HTTP_H_ - -#ifndef DISABLE_HTTP -#include -#include -#include "../common.h" - -enum HTTP_DATA_TYPE { - HTTP_DATA_NONE, - HTTP_DATA_RAW, - HTTP_DATA_JSON -}; - -struct http_request_t { - void *tag; - std::string method; - std::string url; - HTTP_DATA_TYPE type = HTTP_DATA_NONE; - bool forceIPv4; - size_t size; - union { - const json_t *root; - char* body; - }; -}; - -struct http_response_t { - void *tag; - sint32 status_code; - HTTP_DATA_TYPE type; - size_t size; - union { - json_t *root; - char* body; - }; -}; - -#define HTTP_METHOD_GET "GET" -#define HTTP_METHOD_POST "POST" -#define HTTP_METHOD_PUT "PUT" -#define HTTP_METHOD_DELETE "DELETE" - -void http_request_async(const http_request_t *request, void (*callback)(http_response_t*)); -void http_request_dispose(http_response_t *response); - -const char *http_get_extension_from_url(const char *url, const char *fallback); - -/** - * Download a park via HTTP/S from the given URL into a memory buffer. This is - * a blocking operation. - * @param url The URL to download the park from. - * @param outData The data returned. - * @returns The size of the data or 0 if the download failed. - */ -size_t http_download_park(const char * url, void * * outData); -#endif // DISABLE_HTTP - -// These callbacks are defined anyway, but are dummy if HTTP is disabled -void http_init(); -void http_dispose(); - -#endif diff --git a/src/openrct2/network/twitch.h b/src/openrct2/network/twitch.h index 87dc80464e..03478482ba 100644 --- a/src/openrct2/network/twitch.h +++ b/src/openrct2/network/twitch.h @@ -1,4 +1,4 @@ -#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers +#pragma region Copyright (c) 2014-2018 OpenRCT2 Developers /***************************************************************************** * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. *