HttpRequest.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. // Copyright 2017 Dolphin Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include "Common/HttpRequest.h"
  4. #include <chrono>
  5. #include <cstddef>
  6. #include <mutex>
  7. #include <curl/curl.h>
  8. #include "Common/Logging/Log.h"
  9. #include "Common/ScopeGuard.h"
  10. #include "Common/StringUtil.h"
  11. namespace Common
  12. {
  13. class HttpRequest::Impl final
  14. {
  15. public:
  16. enum class Method
  17. {
  18. GET,
  19. POST,
  20. };
  21. explicit Impl(std::chrono::milliseconds timeout_ms, ProgressCallback callback);
  22. bool IsValid() const;
  23. std::string GetHeaderValue(std::string_view name) const;
  24. void SetCookies(const std::string& cookies);
  25. void UseIPv4();
  26. void FollowRedirects(long max);
  27. s32 GetLastResponseCode();
  28. Response Fetch(const std::string& url, Method method, const Headers& headers, const u8* payload,
  29. size_t size, AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only,
  30. std::span<Multiform> multiform = {});
  31. static int CurlProgressCallback(Impl* impl, curl_off_t dltotal, curl_off_t dlnow,
  32. curl_off_t ultotal, curl_off_t ulnow);
  33. std::string EscapeComponent(const std::string& string);
  34. private:
  35. static inline std::once_flag s_curl_was_initialized;
  36. ProgressCallback m_callback;
  37. Headers m_response_headers;
  38. std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> m_curl{nullptr, curl_easy_cleanup};
  39. std::string m_error_string;
  40. };
  41. HttpRequest::HttpRequest(std::chrono::milliseconds timeout_ms, ProgressCallback callback)
  42. : m_impl(std::make_unique<Impl>(timeout_ms, std::move(callback)))
  43. {
  44. }
  45. HttpRequest::~HttpRequest() = default;
  46. bool HttpRequest::IsValid() const
  47. {
  48. return m_impl->IsValid();
  49. }
  50. void HttpRequest::SetCookies(const std::string& cookies)
  51. {
  52. m_impl->SetCookies(cookies);
  53. }
  54. void HttpRequest::UseIPv4()
  55. {
  56. m_impl->UseIPv4();
  57. }
  58. void HttpRequest::FollowRedirects(long max)
  59. {
  60. m_impl->FollowRedirects(max);
  61. }
  62. std::string HttpRequest::EscapeComponent(const std::string& string)
  63. {
  64. return m_impl->EscapeComponent(string);
  65. }
  66. s32 HttpRequest::GetLastResponseCode() const
  67. {
  68. return m_impl->GetLastResponseCode();
  69. }
  70. std::string HttpRequest::GetHeaderValue(std::string_view name) const
  71. {
  72. return m_impl->GetHeaderValue(name);
  73. }
  74. HttpRequest::Response HttpRequest::Get(const std::string& url, const Headers& headers,
  75. AllowedReturnCodes codes)
  76. {
  77. return m_impl->Fetch(url, Impl::Method::GET, headers, nullptr, 0, codes);
  78. }
  79. HttpRequest::Response HttpRequest::Post(const std::string& url, const std::vector<u8>& payload,
  80. const Headers& headers, AllowedReturnCodes codes)
  81. {
  82. return m_impl->Fetch(url, Impl::Method::POST, headers, payload.data(), payload.size(), codes);
  83. }
  84. HttpRequest::Response HttpRequest::Post(const std::string& url, const std::string& payload,
  85. const Headers& headers, AllowedReturnCodes codes)
  86. {
  87. return m_impl->Fetch(url, Impl::Method::POST, headers,
  88. reinterpret_cast<const u8*>(payload.data()), payload.size(), codes);
  89. }
  90. int HttpRequest::Impl::CurlProgressCallback(Impl* impl, curl_off_t dltotal, curl_off_t dlnow,
  91. curl_off_t ultotal, curl_off_t ulnow)
  92. {
  93. // Abort if callback isn't true
  94. return !impl->m_callback(static_cast<s64>(dltotal), static_cast<s64>(dlnow),
  95. static_cast<s64>(ultotal), static_cast<s64>(ulnow));
  96. }
  97. HttpRequest::Impl::Impl(std::chrono::milliseconds timeout_ms, ProgressCallback callback)
  98. : m_callback(std::move(callback))
  99. {
  100. std::call_once(s_curl_was_initialized, [] { curl_global_init(CURL_GLOBAL_DEFAULT); });
  101. m_curl.reset(curl_easy_init());
  102. if (!m_curl)
  103. return;
  104. curl_easy_setopt(m_curl.get(), CURLOPT_NOPROGRESS, m_callback == nullptr);
  105. if (m_callback)
  106. {
  107. curl_easy_setopt(m_curl.get(), CURLOPT_PROGRESSDATA, this);
  108. curl_easy_setopt(m_curl.get(), CURLOPT_XFERINFOFUNCTION, CurlProgressCallback);
  109. }
  110. // Set up error buffer
  111. m_error_string.resize(CURL_ERROR_SIZE);
  112. curl_easy_setopt(m_curl.get(), CURLOPT_ERRORBUFFER, m_error_string.data());
  113. // libcurl may not have been built with async DNS support, so we disable
  114. // signal handlers to avoid a possible and likely crash if a resolve times out.
  115. curl_easy_setopt(m_curl.get(), CURLOPT_NOSIGNAL, true);
  116. curl_easy_setopt(m_curl.get(), CURLOPT_CONNECTTIMEOUT_MS, static_cast<long>(timeout_ms.count()));
  117. // Sadly CURLOPT_LOW_SPEED_TIME doesn't have a millisecond variant so we have to use seconds
  118. curl_easy_setopt(
  119. m_curl.get(), CURLOPT_LOW_SPEED_TIME,
  120. static_cast<long>(std::chrono::duration_cast<std::chrono::seconds>(timeout_ms).count()));
  121. curl_easy_setopt(m_curl.get(), CURLOPT_LOW_SPEED_LIMIT, 1);
  122. #ifdef _WIN32
  123. // ALPN support is enabled by default but requires Windows >= 8.1.
  124. curl_easy_setopt(m_curl.get(), CURLOPT_SSL_ENABLE_ALPN, false);
  125. #endif
  126. }
  127. bool HttpRequest::Impl::IsValid() const
  128. {
  129. return m_curl != nullptr;
  130. }
  131. s32 HttpRequest::Impl::GetLastResponseCode()
  132. {
  133. long response_code{};
  134. curl_easy_getinfo(m_curl.get(), CURLINFO_RESPONSE_CODE, &response_code);
  135. return static_cast<s32>(response_code);
  136. }
  137. void HttpRequest::Impl::SetCookies(const std::string& cookies)
  138. {
  139. curl_easy_setopt(m_curl.get(), CURLOPT_COOKIE, cookies.c_str());
  140. }
  141. void HttpRequest::Impl::UseIPv4()
  142. {
  143. curl_easy_setopt(m_curl.get(), CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
  144. }
  145. HttpRequest::Response HttpRequest::PostMultiform(const std::string& url,
  146. std::span<Multiform> multiform,
  147. const Headers& headers, AllowedReturnCodes codes)
  148. {
  149. return m_impl->Fetch(url, Impl::Method::POST, headers, nullptr, 0, codes, multiform);
  150. }
  151. void HttpRequest::Impl::FollowRedirects(long max)
  152. {
  153. curl_easy_setopt(m_curl.get(), CURLOPT_FOLLOWLOCATION, 1);
  154. curl_easy_setopt(m_curl.get(), CURLOPT_MAXREDIRS, max);
  155. }
  156. std::string HttpRequest::Impl::GetHeaderValue(std::string_view name) const
  157. {
  158. for (const auto& [key, value] : m_response_headers)
  159. {
  160. if (key == name)
  161. return value.value();
  162. }
  163. return {};
  164. }
  165. std::string HttpRequest::Impl::EscapeComponent(const std::string& string)
  166. {
  167. char* escaped = curl_easy_escape(m_curl.get(), string.c_str(), static_cast<int>(string.size()));
  168. std::string escaped_str(escaped);
  169. curl_free(escaped);
  170. return escaped_str;
  171. }
  172. static size_t CurlWriteCallback(char* data, size_t size, size_t nmemb, void* userdata)
  173. {
  174. auto* buffer = static_cast<std::vector<u8>*>(userdata);
  175. const size_t actual_size = size * nmemb;
  176. buffer->insert(buffer->end(), data, data + actual_size);
  177. return actual_size;
  178. }
  179. static size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata)
  180. {
  181. auto* headers = static_cast<HttpRequest::Headers*>(userdata);
  182. std::string_view full_buffer = std::string_view{buffer, nitems};
  183. const size_t colon_pos = full_buffer.find(':');
  184. if (colon_pos == std::string::npos)
  185. return nitems * size;
  186. const std::string_view key = full_buffer.substr(0, colon_pos);
  187. const std::string_view value = StripWhitespace(full_buffer.substr(colon_pos + 1));
  188. headers->emplace(std::string{key}, std::string{value});
  189. return nitems * size;
  190. }
  191. HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method method,
  192. const Headers& headers, const u8* payload,
  193. size_t size, AllowedReturnCodes codes,
  194. std::span<Multiform> multiform)
  195. {
  196. m_response_headers.clear();
  197. curl_easy_setopt(m_curl.get(), CURLOPT_POST, method == Method::POST);
  198. curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str());
  199. if (method == Method::POST && multiform.empty())
  200. {
  201. curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDS, payload);
  202. curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE, size);
  203. }
  204. curl_mime* form = nullptr;
  205. Common::ScopeGuard multiform_guard{[&form] { curl_mime_free(form); }};
  206. if (!multiform.empty())
  207. {
  208. form = curl_mime_init(m_curl.get());
  209. for (const auto& value : multiform)
  210. {
  211. curl_mimepart* part = curl_mime_addpart(form);
  212. curl_mime_name(part, value.name.c_str());
  213. curl_mime_data(part, value.data.c_str(), value.data.size());
  214. }
  215. curl_easy_setopt(m_curl.get(), CURLOPT_MIMEPOST, form);
  216. }
  217. curl_slist* list = nullptr;
  218. Common::ScopeGuard list_guard{[&list] { curl_slist_free_all(list); }};
  219. for (const auto& [name, value] : headers)
  220. {
  221. if (!value)
  222. list = curl_slist_append(list, (name + ':').c_str());
  223. else if (value->empty())
  224. list = curl_slist_append(list, (name + ';').c_str());
  225. else
  226. list = curl_slist_append(list, (name + ": " + *value).c_str());
  227. }
  228. curl_easy_setopt(m_curl.get(), CURLOPT_HTTPHEADER, list);
  229. curl_easy_setopt(m_curl.get(), CURLOPT_HEADERFUNCTION, header_callback);
  230. curl_easy_setopt(m_curl.get(), CURLOPT_HEADERDATA, static_cast<void*>(&m_response_headers));
  231. std::vector<u8> buffer;
  232. curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, CurlWriteCallback);
  233. curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, &buffer);
  234. const char* type = method == Method::POST ? "POST" : "GET";
  235. const CURLcode res = curl_easy_perform(m_curl.get());
  236. if (res != CURLE_OK)
  237. {
  238. ERROR_LOG_FMT(COMMON, "Failed to {} {}: {}", type, url, m_error_string);
  239. return {};
  240. }
  241. if (codes == AllowedReturnCodes::All)
  242. return buffer;
  243. long response_code = 0;
  244. curl_easy_getinfo(m_curl.get(), CURLINFO_RESPONSE_CODE, &response_code);
  245. if (response_code != 200)
  246. {
  247. if (buffer.empty())
  248. {
  249. ERROR_LOG_FMT(COMMON, "Failed to {} {}: server replied with code {}", type, url,
  250. response_code);
  251. }
  252. else
  253. {
  254. ERROR_LOG_FMT(COMMON, "Failed to {} {}: server replied with code {} and body\n\x1b[0m{:.{}}",
  255. type, url, response_code, reinterpret_cast<char*>(buffer.data()),
  256. static_cast<int>(buffer.size()));
  257. }
  258. return {};
  259. }
  260. return buffer;
  261. }
  262. } // namespace Common