diff --git a/Source/Core/Common/Analytics.cpp b/Source/Core/Common/Analytics.cpp index 2761e86943..57670fd1e7 100644 --- a/Source/Core/Common/Analytics.cpp +++ b/Source/Core/Common/Analytics.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include "Common/Analytics.h" @@ -60,13 +59,6 @@ void AppendType(std::string* out, TypeId type) { out->push_back(static_cast(type)); } - -// Dummy write function for curl. -size_t DummyCurlWriteFunction(char* ptr, size_t size, size_t nmemb, void* userdata) -{ - return size * nmemb; -} - } // namespace AnalyticsReportBuilder::AnalyticsReportBuilder() @@ -187,44 +179,16 @@ void StdoutAnalyticsBackend::Send(std::string report) HexDump(reinterpret_cast(report.data()), report.size()).c_str()); } -HttpAnalyticsBackend::HttpAnalyticsBackend(const std::string& endpoint) +HttpAnalyticsBackend::HttpAnalyticsBackend(const std::string& endpoint) : m_endpoint(endpoint) { - CURL* curl = curl_easy_init(); - if (curl) - { - // libcurl may not have been built with async DNS support, so we disable - // signal handlers to avoid a possible and likely crash if a resolve times out. - curl_easy_setopt(curl, CURLOPT_NOSIGNAL, true); - curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); - curl_easy_setopt(curl, CURLOPT_POST, true); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &DummyCurlWriteFunction); - curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 3000); - -#ifdef _WIN32 - // ALPN support is enabled by default but requires Windows >= 8.1. - curl_easy_setopt(curl, CURLOPT_SSL_ENABLE_ALPN, false); -#endif - - m_curl = curl; - } } -HttpAnalyticsBackend::~HttpAnalyticsBackend() -{ - if (m_curl) - { - curl_easy_cleanup(m_curl); - } -} +HttpAnalyticsBackend::~HttpAnalyticsBackend() = default; void HttpAnalyticsBackend::Send(std::string report) { - if (!m_curl) - return; - - curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, report.c_str()); - curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, report.size()); - curl_easy_perform(m_curl); + if (m_http.IsValid()) + m_http.Post(m_endpoint, report); } } // namespace Common diff --git a/Source/Core/Common/Analytics.h b/Source/Core/Common/Analytics.h index 0dd167c087..76fb8727c1 100644 --- a/Source/Core/Common/Analytics.h +++ b/Source/Core/Common/Analytics.h @@ -11,12 +11,11 @@ #include #include -#include - #include "Common/CommonTypes.h" #include "Common/Event.h" #include "Common/FifoQueue.h" #include "Common/Flag.h" +#include "Common/HttpRequest.h" // Utilities for analytics reporting in Dolphin. This reporting is designed to // provide anonymous data about how well Dolphin performs in the wild. It also @@ -179,7 +178,8 @@ public: void Send(std::string report) override; protected: - CURL* m_curl = nullptr; + std::string m_endpoint; + HttpRequest m_http; }; } // namespace Common diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index a42f4d24f4..921074cb9f 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -11,6 +11,7 @@ set(SRCS FileUtil.cpp GekkoDisassembler.cpp Hash.cpp + HttpRequest.cpp IniFile.cpp JitRegister.cpp MathUtil.cpp diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj index 6e8ff6ec64..5bb8ccf8cf 100644 --- a/Source/Core/Common/Common.vcxproj +++ b/Source/Core/Common/Common.vcxproj @@ -121,6 +121,7 @@ + @@ -173,6 +174,7 @@ + diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters index 107355431a..dd404fe35d 100644 --- a/Source/Core/Common/Common.vcxproj.filters +++ b/Source/Core/Common/Common.vcxproj.filters @@ -48,6 +48,7 @@ + @@ -263,6 +264,7 @@ + diff --git a/Source/Core/Common/HttpRequest.cpp b/Source/Core/Common/HttpRequest.cpp new file mode 100644 index 0000000000..b87ce4fb98 --- /dev/null +++ b/Source/Core/Common/HttpRequest.cpp @@ -0,0 +1,140 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "Common/HttpRequest.h" + +#include +#include + +#include "Common/Logging/Log.h" +#include "Common/ScopeGuard.h" +#include "Common/StringUtil.h" + +namespace Common +{ +class HttpRequest::Impl final +{ +public: + enum class Method + { + GET, + POST, + }; + + Impl(int timeout_ms); + + bool IsValid() const; + Response Fetch(const std::string& url, Method method, const Headers& headers, const u8* payload, + size_t size); + +private: + std::unique_ptr m_curl{curl_easy_init(), curl_easy_cleanup}; +}; + +HttpRequest::HttpRequest(int timeout_ms) : m_impl(std::make_unique(timeout_ms)) +{ +} + +HttpRequest::~HttpRequest() = default; + +bool HttpRequest::IsValid() const +{ + return m_impl->IsValid(); +} + +HttpRequest::Response HttpRequest::Get(const std::string& url, const Headers& headers) +{ + return m_impl->Fetch(url, Impl::Method::GET, headers, nullptr, 0); +} + +HttpRequest::Response HttpRequest::Post(const std::string& url, const std::vector& payload, + const Headers& headers) +{ + return m_impl->Fetch(url, Impl::Method::POST, headers, payload.data(), payload.size()); +} + +HttpRequest::Response HttpRequest::Post(const std::string& url, const std::string& payload, + const Headers& headers) +{ + return m_impl->Fetch(url, Impl::Method::POST, headers, + reinterpret_cast(payload.data()), payload.size()); +} + +HttpRequest::Impl::Impl(int timeout_ms) +{ + if (!m_curl) + return; + + // libcurl may not have been built with async DNS support, so we disable + // signal handlers to avoid a possible and likely crash if a resolve times out. + curl_easy_setopt(m_curl.get(), CURLOPT_NOSIGNAL, true); + curl_easy_setopt(m_curl.get(), CURLOPT_TIMEOUT_MS, timeout_ms); +#ifdef _WIN32 + // ALPN support is enabled by default but requires Windows >= 8.1. + curl_easy_setopt(m_curl.get(), CURLOPT_SSL_ENABLE_ALPN, false); +#endif +} + +bool HttpRequest::Impl::IsValid() const +{ + return m_curl != nullptr; +} + +static size_t CurlCallback(char* data, size_t size, size_t nmemb, void* userdata) +{ + auto* buffer = static_cast*>(userdata); + const size_t actual_size = size * nmemb; + buffer->insert(buffer->end(), data, data + actual_size); + return actual_size; +} + +HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method method, + const Headers& headers, const u8* payload, + size_t size) +{ + curl_easy_setopt(m_curl.get(), CURLOPT_POST, method == Method::POST); + curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str()); + if (method == Method::POST) + { + curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDS, payload); + curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE, size); + } + + curl_slist* list = nullptr; + Common::ScopeGuard list_guard{[&list] { curl_slist_free_all(list); }}; + for (const std::pair>& header : headers) + { + if (!header.second) + list = curl_slist_append(list, (header.first + ":").c_str()); + else if (header.second->empty()) + list = curl_slist_append(list, (header.first + ";").c_str()); + else + list = curl_slist_append(list, (header.first + ": " + *header.second).c_str()); + } + curl_easy_setopt(m_curl.get(), CURLOPT_HTTPHEADER, list); + + std::vector buffer; + curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, CurlCallback); + curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, &buffer); + + const char* type = method == Method::POST ? "POST" : "GET"; + const CURLcode res = curl_easy_perform(m_curl.get()); + if (res != CURLE_OK) + { + ERROR_LOG(COMMON, "Failed to %s %s: %s", type, url.c_str(), curl_easy_strerror(res)); + return {}; + } + + long response_code = 0; + curl_easy_getinfo(m_curl.get(), CURLINFO_RESPONSE_CODE, &response_code); + if (response_code != 200) + { + ERROR_LOG(COMMON, "Failed to %s %s: server replied with code %li and body\n\x1b[0m%s", type, + url.c_str(), response_code, buffer.data()); + return {}; + } + + return buffer; +} +} // namespace Common diff --git a/Source/Core/Common/HttpRequest.h b/Source/Core/Common/HttpRequest.h new file mode 100644 index 0000000000..4a1d7730d0 --- /dev/null +++ b/Source/Core/Common/HttpRequest.h @@ -0,0 +1,35 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include + +#include "Common/CommonTypes.h" + +namespace Common +{ +class HttpRequest final +{ +public: + HttpRequest(int timeout_ms = 3000); + ~HttpRequest(); + bool IsValid() const; + + using Response = std::optional>; + using Headers = std::map>; + Response Get(const std::string& url, const Headers& headers = {}); + Response Post(const std::string& url, const std::vector& payload, + const Headers& headers = {}); + Response Post(const std::string& url, const std::string& payload, const Headers& headers = {}); + +private: + class Impl; + std::unique_ptr m_impl; +}; +} // namespace Common diff --git a/Source/Core/Core/GeckoCodeConfig.cpp b/Source/Core/Core/GeckoCodeConfig.cpp index eac79d099f..df62768acd 100644 --- a/Source/Core/Core/GeckoCodeConfig.cpp +++ b/Source/Core/Core/GeckoCodeConfig.cpp @@ -5,28 +5,17 @@ #include "Core/GeckoCodeConfig.h" #include -#include #include #include #include -#include - +#include "Common/HttpRequest.h" #include "Common/IniFile.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" namespace Gecko { -static size_t DownloadCodesWriteCallback(void* contents, size_t size, size_t nmemb, - std::string* body) -{ - size_t realsize = size * nmemb; - body->insert(body->end(), reinterpret_cast(contents), - reinterpret_cast(contents) + realsize); - return realsize; -} - std::vector DownloadCodes(std::string gameid, bool* succeeded) { switch (gameid[0]) @@ -41,37 +30,18 @@ std::vector DownloadCodes(std::string gameid, bool* succeeded) break; } - std::unique_ptr curl{curl_easy_init(), curl_easy_cleanup}; - std::string endpoint{"http://geckocodes.org/txt.php?txt=" + gameid}; - curl_easy_setopt(curl.get(), CURLOPT_URL, endpoint.c_str()); - curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, 5); - std::string response_body; - curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, DownloadCodesWriteCallback); - curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_body); - - *succeeded = true; - CURLcode res = curl_easy_perform(curl.get()); - if (res != CURLE_OK) - { - ERROR_LOG(COMMON, "DownloadCodes: Curl error: %s", curl_easy_strerror(res)); - *succeeded = false; + Common::HttpRequest http; + const Common::HttpRequest::Response response = http.Get(endpoint); + *succeeded = response.has_value(); + if (!response) return {}; - } - long response_code{0}; - curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &response_code); - if (response_code != 200) - { - WARN_LOG(COMMON, "DownloadCodes: Curl response code: %li", response_code); - *succeeded = false; - return {}; - } // temp vector containing parsed codes std::vector gcodes; // parse the codes - std::istringstream ss(response_body); + std::istringstream ss(reinterpret_cast(response->data())); std::string line;