2020-09-21 17:17:34 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2020-05-26 16:34:33 +00:00
|
|
|
//
|
2020-09-21 17:17:34 +00:00
|
|
|
// EmulationStation Desktop Edition
|
2020-06-21 12:25:28 +00:00
|
|
|
// HttpReq.cpp
|
2020-05-26 16:34:33 +00:00
|
|
|
//
|
2020-06-21 12:25:28 +00:00
|
|
|
// HTTP request functions.
|
|
|
|
// Used by Scraper, GamesDBJSONScraper, GamesDBJSONScraperResources and
|
|
|
|
// ScreenScraper to download game information and media files.
|
2020-05-26 16:34:33 +00:00
|
|
|
//
|
|
|
|
|
2013-09-15 17:56:47 +00:00
|
|
|
#include "HttpReq.h"
|
2017-11-01 22:21:10 +00:00
|
|
|
|
2021-07-07 18:31:46 +00:00
|
|
|
#include "Log.h"
|
2022-06-29 15:28:39 +00:00
|
|
|
#include "Settings.h"
|
2020-07-03 18:23:51 +00:00
|
|
|
#include "resources/ResourceManager.h"
|
2018-01-09 22:55:09 +00:00
|
|
|
#include "utils/FileSystemUtil.h"
|
2020-07-03 18:23:51 +00:00
|
|
|
|
2018-01-09 22:55:09 +00:00
|
|
|
#include <assert.h>
|
2013-09-15 17:56:47 +00:00
|
|
|
|
2020-10-18 09:01:56 +00:00
|
|
|
CURLM* HttpReq::s_multi_handle;
|
2013-10-10 18:11:01 +00:00
|
|
|
std::map<CURL*, HttpReq*> HttpReq::s_requests;
|
2013-09-15 17:56:47 +00:00
|
|
|
|
2021-07-07 18:31:46 +00:00
|
|
|
std::string HttpReq::urlEncode(const std::string& s)
|
2013-09-24 07:02:14 +00:00
|
|
|
{
|
2022-01-19 17:01:54 +00:00
|
|
|
const std::string unreserved {
|
|
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"};
|
2013-09-24 07:02:14 +00:00
|
|
|
|
2022-01-19 17:01:54 +00:00
|
|
|
std::string escaped {""};
|
2021-11-17 16:48:49 +00:00
|
|
|
for (size_t i = 0; i < s.length(); ++i) {
|
2020-05-26 16:34:33 +00:00
|
|
|
if (unreserved.find_first_of(s[i]) != std::string::npos) {
|
2013-09-24 07:02:14 +00:00
|
|
|
escaped.push_back(s[i]);
|
|
|
|
}
|
2020-05-26 16:34:33 +00:00
|
|
|
else {
|
2013-09-24 07:02:14 +00:00
|
|
|
escaped.append("%");
|
|
|
|
char buf[3];
|
2020-12-29 10:06:01 +00:00
|
|
|
snprintf(buf, 3, "%.2X", static_cast<unsigned char>(s[i]));
|
2013-09-24 07:02:14 +00:00
|
|
|
escaped.append(buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return escaped;
|
|
|
|
}
|
|
|
|
|
2013-10-10 00:50:42 +00:00
|
|
|
bool HttpReq::isUrl(const std::string& str)
|
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
// The worst guess.
|
|
|
|
return (!str.empty() && !Utils::FileSystem::exists(str) &&
|
2021-07-07 18:31:46 +00:00
|
|
|
(str.find("http://") != std::string::npos ||
|
|
|
|
str.find("https://") != std::string::npos || str.find("www.") != std::string::npos));
|
2013-10-10 00:50:42 +00:00
|
|
|
}
|
|
|
|
|
2021-07-07 18:31:46 +00:00
|
|
|
HttpReq::HttpReq(const std::string& url)
|
|
|
|
: mStatus(REQ_IN_PROGRESS)
|
|
|
|
, mHandle(nullptr)
|
2013-09-20 23:55:05 +00:00
|
|
|
{
|
2020-10-18 09:01:56 +00:00
|
|
|
// The multi-handle is cleaned up via a call from GuiScraperSearch after the scraping
|
2022-06-29 15:28:39 +00:00
|
|
|
// has been completed for a game, meaning the handle is valid for all curl requests
|
2020-10-18 09:01:56 +00:00
|
|
|
// performed for the current game.
|
|
|
|
if (!s_multi_handle)
|
|
|
|
s_multi_handle = curl_multi_init();
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
mHandle = curl_easy_init();
|
|
|
|
|
2021-11-25 16:34:34 +00:00
|
|
|
#if defined(USE_BUNDLED_CERTIFICATES)
|
2022-06-29 15:28:39 +00:00
|
|
|
// Use the bundled curl TLS/SSL certificates (which actually come from the Mozilla project).
|
2021-11-25 17:56:30 +00:00
|
|
|
// This is enabled by default on Windows. Although there is a possibility to use the OS
|
|
|
|
// provided Schannel certificates I haven't been able to get this to work, and it also seems
|
|
|
|
// to be problematic on older Windows versions.
|
|
|
|
// The bundled certificates are also required on Linux when building an AppImage package as
|
|
|
|
// distributions such as Debian, Ubuntu, Linux Mint and Manjaro place the TLS certificates in
|
2022-06-29 15:28:39 +00:00
|
|
|
// a different directory than for example Fedora and openSUSE. This makes curl unusable on
|
2021-11-25 17:56:30 +00:00
|
|
|
// these latter operating systems unless the bundled file is used.
|
2021-07-07 18:31:46 +00:00
|
|
|
curl_easy_setopt(mHandle, CURLOPT_CAINFO,
|
|
|
|
ResourceManager::getInstance()
|
2022-01-04 20:38:46 +00:00
|
|
|
.getResourcePath(":/certificates/curl-ca-bundle.crt")
|
2021-07-07 18:31:46 +00:00
|
|
|
.c_str());
|
|
|
|
#endif
|
2020-07-03 18:23:51 +00:00
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
if (mHandle == nullptr) {
|
2020-06-21 12:25:28 +00:00
|
|
|
mStatus = REQ_IO_ERROR;
|
|
|
|
onError("curl_easy_init failed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the url.
|
2022-09-07 17:59:27 +00:00
|
|
|
CURLcode err {curl_easy_setopt(mHandle, CURLOPT_URL, url.c_str())};
|
2020-06-21 12:25:28 +00:00
|
|
|
if (err != CURLE_OK) {
|
|
|
|
mStatus = REQ_IO_ERROR;
|
|
|
|
onError(curl_easy_strerror(err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-06-29 15:28:39 +00:00
|
|
|
long connectionTimeout {
|
|
|
|
static_cast<long>(Settings::getInstance()->getInt("ScraperConnectionTimeout"))};
|
|
|
|
|
|
|
|
if (connectionTimeout < 0 || connectionTimeout > 300)
|
|
|
|
connectionTimeout =
|
|
|
|
static_cast<long>(Settings::getInstance()->getDefaultInt("ScraperConnectionTimeout"));
|
|
|
|
|
|
|
|
// Set connection timeout (default is 300 seconds).
|
|
|
|
err = curl_easy_setopt(mHandle, CURLOPT_CONNECTTIMEOUT, connectionTimeout);
|
|
|
|
if (err != CURLE_OK) {
|
|
|
|
mStatus = REQ_IO_ERROR;
|
|
|
|
onError(curl_easy_strerror(err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
long transferTimeout {
|
|
|
|
static_cast<long>(Settings::getInstance()->getInt("ScraperTransferTimeout"))};
|
|
|
|
|
|
|
|
if (transferTimeout < 0 || transferTimeout > 300)
|
|
|
|
transferTimeout =
|
|
|
|
static_cast<long>(Settings::getInstance()->getDefaultInt("ScraperTransferTimeout"));
|
|
|
|
|
|
|
|
// Set transfer timeout (default is 0/infinity).
|
|
|
|
err = curl_easy_setopt(mHandle, CURLOPT_TIMEOUT, transferTimeout);
|
|
|
|
if (err != CURLE_OK) {
|
|
|
|
mStatus = REQ_IO_ERROR;
|
|
|
|
onError(curl_easy_strerror(err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
// Set curl to handle redirects.
|
|
|
|
err = curl_easy_setopt(mHandle, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
|
if (err != CURLE_OK) {
|
|
|
|
mStatus = REQ_IO_ERROR;
|
|
|
|
onError(curl_easy_strerror(err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set curl max redirects.
|
|
|
|
err = curl_easy_setopt(mHandle, CURLOPT_MAXREDIRS, 2L);
|
|
|
|
if (err != CURLE_OK) {
|
|
|
|
mStatus = REQ_IO_ERROR;
|
|
|
|
onError(curl_easy_strerror(err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set curl restrict redirect protocols.
|
|
|
|
err = curl_easy_setopt(mHandle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
|
|
|
if (err != CURLE_OK) {
|
|
|
|
mStatus = REQ_IO_ERROR;
|
|
|
|
onError(curl_easy_strerror(err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tell curl how to write the data.
|
|
|
|
err = curl_easy_setopt(mHandle, CURLOPT_WRITEFUNCTION, &HttpReq::write_content);
|
|
|
|
if (err != CURLE_OK) {
|
|
|
|
mStatus = REQ_IO_ERROR;
|
|
|
|
onError(curl_easy_strerror(err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Give curl a pointer to this HttpReq so we know where to write the
|
|
|
|
// data *to* in our write function.
|
|
|
|
err = curl_easy_setopt(mHandle, CURLOPT_WRITEDATA, this);
|
|
|
|
if (err != CURLE_OK) {
|
|
|
|
mStatus = REQ_IO_ERROR;
|
|
|
|
onError(curl_easy_strerror(err));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the handle to our multi.
|
|
|
|
CURLMcode merr = curl_multi_add_handle(s_multi_handle, mHandle);
|
|
|
|
if (merr != CURLM_OK) {
|
|
|
|
mStatus = REQ_IO_ERROR;
|
|
|
|
onError(curl_multi_strerror(merr));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
s_requests[mHandle] = this;
|
2013-09-15 17:56:47 +00:00
|
|
|
}
|
|
|
|
|
2013-10-10 18:11:01 +00:00
|
|
|
HttpReq::~HttpReq()
|
2013-09-15 17:56:47 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mHandle) {
|
|
|
|
s_requests.erase(mHandle);
|
2013-09-15 17:56:47 +00:00
|
|
|
|
2022-09-07 17:59:27 +00:00
|
|
|
CURLMcode merr {curl_multi_remove_handle(s_multi_handle, mHandle)};
|
2013-09-15 17:56:47 +00:00
|
|
|
|
2020-06-25 17:52:38 +00:00
|
|
|
if (merr != CURLM_OK) {
|
2021-07-07 18:31:46 +00:00
|
|
|
LOG(LogError) << "Error removing curl_easy handle from curl_multi: "
|
|
|
|
<< curl_multi_strerror(merr);
|
2020-06-25 17:52:38 +00:00
|
|
|
}
|
2013-09-15 17:56:47 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
curl_easy_cleanup(mHandle);
|
|
|
|
}
|
2013-09-15 17:56:47 +00:00
|
|
|
}
|
|
|
|
|
2013-10-10 18:11:01 +00:00
|
|
|
HttpReq::Status HttpReq::status()
|
2013-09-15 17:56:47 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
if (mStatus == REQ_IN_PROGRESS) {
|
|
|
|
int handle_count;
|
2022-09-07 17:59:27 +00:00
|
|
|
CURLMcode merr {curl_multi_perform(s_multi_handle, &handle_count)};
|
2020-06-21 12:25:28 +00:00
|
|
|
if (merr != CURLM_OK && merr != CURLM_CALL_MULTI_PERFORM) {
|
|
|
|
mStatus = REQ_IO_ERROR;
|
|
|
|
onError(curl_multi_strerror(merr));
|
|
|
|
return mStatus;
|
|
|
|
}
|
|
|
|
|
|
|
|
int msgs_left;
|
|
|
|
CURLMsg* msg;
|
|
|
|
while ((msg = curl_multi_info_read(s_multi_handle, &msgs_left)) != nullptr) {
|
|
|
|
if (msg->msg == CURLMSG_DONE) {
|
|
|
|
HttpReq* req = s_requests[msg->easy_handle];
|
|
|
|
|
2020-06-28 16:39:18 +00:00
|
|
|
if (req == nullptr) {
|
2020-06-21 12:25:28 +00:00
|
|
|
LOG(LogError) << "Cannot find easy handle!";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (msg->data.result == CURLE_OK) {
|
|
|
|
req->mStatus = REQ_SUCCESS;
|
|
|
|
}
|
2021-05-24 16:51:16 +00:00
|
|
|
else if (msg->data.result == CURLE_PEER_FAILED_VERIFICATION) {
|
|
|
|
req->mStatus = REQ_FAILED_VERIFICATION;
|
|
|
|
req->onError(curl_easy_strerror(msg->data.result));
|
|
|
|
}
|
2020-06-21 12:25:28 +00:00
|
|
|
else {
|
|
|
|
req->mStatus = REQ_IO_ERROR;
|
|
|
|
req->onError(curl_easy_strerror(msg->data.result));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return mStatus;
|
2013-09-15 17:56:47 +00:00
|
|
|
}
|
|
|
|
|
2014-03-19 00:55:37 +00:00
|
|
|
std::string HttpReq::getContent() const
|
2013-09-15 17:56:47 +00:00
|
|
|
{
|
2020-06-21 12:25:28 +00:00
|
|
|
assert(mStatus == REQ_SUCCESS);
|
|
|
|
return mContent.str();
|
2013-09-15 17:56:47 +00:00
|
|
|
}
|
|
|
|
|
2020-05-26 16:34:33 +00:00
|
|
|
// Used as a curl callback.
|
|
|
|
// size = size of an element, nmemb = number of elements.
|
|
|
|
// Return value is number of elements successfully read.
|
2013-10-10 18:11:01 +00:00
|
|
|
size_t HttpReq::write_content(void* buff, size_t size, size_t nmemb, void* req_ptr)
|
|
|
|
{
|
2022-09-07 17:59:27 +00:00
|
|
|
std::stringstream& ss {static_cast<HttpReq*>(req_ptr)->mContent};
|
2020-10-18 09:01:56 +00:00
|
|
|
ss.write(static_cast<char*>(buff), size * nmemb);
|
2013-10-10 18:11:01 +00:00
|
|
|
|
2020-06-21 12:25:28 +00:00
|
|
|
return nmemb;
|
2013-10-10 18:11:01 +00:00
|
|
|
}
|