mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-30 09:35:40 +00:00
207 lines
5.8 KiB
C++
207 lines
5.8 KiB
C++
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
|
|
|
#include "http_downloader_curl.h"
|
|
|
|
#include "common/assert.h"
|
|
#include "common/log.h"
|
|
#include "common/string_util.h"
|
|
#include "common/timer.h"
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
|
|
Log_SetChannel(HTTPDownloader);
|
|
|
|
HTTPDownloaderCurl::HTTPDownloaderCurl() : HTTPDownloader()
|
|
{
|
|
}
|
|
|
|
HTTPDownloaderCurl::~HTTPDownloaderCurl()
|
|
{
|
|
if (m_multi_handle)
|
|
curl_multi_cleanup(m_multi_handle);
|
|
}
|
|
|
|
std::unique_ptr<HTTPDownloader> HTTPDownloader::Create(std::string user_agent)
|
|
{
|
|
std::unique_ptr<HTTPDownloaderCurl> instance(std::make_unique<HTTPDownloaderCurl>());
|
|
if (!instance->Initialize(std::move(user_agent)))
|
|
return {};
|
|
|
|
return instance;
|
|
}
|
|
|
|
static bool s_curl_initialized = false;
|
|
static std::once_flag s_curl_initialized_once_flag;
|
|
|
|
bool HTTPDownloaderCurl::Initialize(std::string user_agent)
|
|
{
|
|
if (!s_curl_initialized)
|
|
{
|
|
std::call_once(s_curl_initialized_once_flag, []() {
|
|
s_curl_initialized = curl_global_init(CURL_GLOBAL_ALL) == CURLE_OK;
|
|
if (s_curl_initialized)
|
|
{
|
|
std::atexit([]() {
|
|
curl_global_cleanup();
|
|
s_curl_initialized = false;
|
|
});
|
|
}
|
|
});
|
|
if (!s_curl_initialized)
|
|
{
|
|
Log_ErrorPrint("curl_global_init() failed");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
m_multi_handle = curl_multi_init();
|
|
if (!m_multi_handle)
|
|
{
|
|
Log_ErrorPrint("curl_multi_init() failed");
|
|
return false;
|
|
}
|
|
|
|
m_user_agent = std::move(user_agent);
|
|
return true;
|
|
}
|
|
|
|
size_t HTTPDownloaderCurl::WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
|
|
{
|
|
Request* req = static_cast<Request*>(userdata);
|
|
const size_t current_size = req->data.size();
|
|
const size_t transfer_size = size * nmemb;
|
|
const size_t new_size = current_size + transfer_size;
|
|
req->data.resize(new_size);
|
|
req->start_time = Common::Timer::GetCurrentValue();
|
|
std::memcpy(&req->data[current_size], ptr, transfer_size);
|
|
|
|
if (req->content_length == 0)
|
|
{
|
|
curl_off_t length;
|
|
if (curl_easy_getinfo(req->handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &length) == CURLE_OK)
|
|
req->content_length = static_cast<u32>(length);
|
|
}
|
|
|
|
return nmemb;
|
|
}
|
|
|
|
HTTPDownloader::Request* HTTPDownloaderCurl::InternalCreateRequest()
|
|
{
|
|
Request* req = new Request();
|
|
req->handle = curl_easy_init();
|
|
if (!req->handle)
|
|
{
|
|
delete req;
|
|
return nullptr;
|
|
}
|
|
|
|
return req;
|
|
}
|
|
|
|
void HTTPDownloaderCurl::InternalPollRequests()
|
|
{
|
|
// Apparently OpenSSL can fire SIGPIPE...
|
|
sigset_t old_block_mask = {};
|
|
sigset_t new_block_mask = {};
|
|
sigemptyset(&old_block_mask);
|
|
sigemptyset(&new_block_mask);
|
|
sigaddset(&new_block_mask, SIGPIPE);
|
|
if (pthread_sigmask(SIG_BLOCK, &new_block_mask, &old_block_mask) != 0)
|
|
Log_WarningPrint("Failed to block SIGPIPE");
|
|
|
|
int running_handles;
|
|
const CURLMcode err = curl_multi_perform(m_multi_handle, &running_handles);
|
|
if (err != CURLM_OK)
|
|
Log_ErrorFmt("curl_multi_perform() returned {}", static_cast<int>(err));
|
|
|
|
for (;;)
|
|
{
|
|
int msgq;
|
|
struct CURLMsg* msg = curl_multi_info_read(m_multi_handle, &msgq);
|
|
if (!msg)
|
|
break;
|
|
|
|
if (msg->msg != CURLMSG_DONE)
|
|
{
|
|
Log_WarningFmt("Unexpected multi message {}", static_cast<int>(msg->msg));
|
|
continue;
|
|
}
|
|
|
|
Request* req;
|
|
if (curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &req) != CURLE_OK)
|
|
{
|
|
Log_ErrorPrint("curl_easy_getinfo() failed");
|
|
continue;
|
|
}
|
|
|
|
if (msg->data.result == CURLE_OK)
|
|
{
|
|
long response_code = 0;
|
|
curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code);
|
|
req->status_code = static_cast<s32>(response_code);
|
|
|
|
char* content_type = nullptr;
|
|
if (curl_easy_getinfo(req->handle, CURLINFO_CONTENT_TYPE, &content_type) == CURLE_OK && content_type)
|
|
req->content_type = content_type;
|
|
|
|
Log_DevFmt("Request for '{}' returned status code {} and {} bytes", req->url, req->status_code, req->data.size());
|
|
}
|
|
else
|
|
{
|
|
Log_ErrorFmt("Request for '{}' returned error {}", req->url, static_cast<int>(msg->data.result));
|
|
}
|
|
|
|
req->state.store(Request::State::Complete, std::memory_order_release);
|
|
}
|
|
|
|
if (pthread_sigmask(SIG_UNBLOCK, &new_block_mask, &old_block_mask) != 0)
|
|
Log_WarningPrint("Failed to unblock SIGPIPE");
|
|
}
|
|
|
|
bool HTTPDownloaderCurl::StartRequest(HTTPDownloader::Request* request)
|
|
{
|
|
Request* req = static_cast<Request*>(request);
|
|
curl_easy_setopt(req->handle, CURLOPT_URL, request->url.c_str());
|
|
curl_easy_setopt(req->handle, CURLOPT_USERAGENT, m_user_agent.c_str());
|
|
curl_easy_setopt(req->handle, CURLOPT_WRITEFUNCTION, &HTTPDownloaderCurl::WriteCallback);
|
|
curl_easy_setopt(req->handle, CURLOPT_WRITEDATA, req);
|
|
curl_easy_setopt(req->handle, CURLOPT_NOSIGNAL, 1L);
|
|
curl_easy_setopt(req->handle, CURLOPT_PRIVATE, req);
|
|
curl_easy_setopt(req->handle, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
|
if (request->type == Request::Type::Post)
|
|
{
|
|
curl_easy_setopt(req->handle, CURLOPT_POST, 1L);
|
|
curl_easy_setopt(req->handle, CURLOPT_POSTFIELDS, request->post_data.c_str());
|
|
}
|
|
|
|
Log_DevPrintf("Started HTTP request for '%s'", req->url.c_str());
|
|
req->state.store(Request::State::Started, std::memory_order_release);
|
|
req->start_time = Common::Timer::GetCurrentValue();
|
|
|
|
const CURLMcode err = curl_multi_add_handle(m_multi_handle, req->handle);
|
|
if (err != CURLM_OK)
|
|
{
|
|
Log_ErrorFmt("curl_multi_add_handle() returned {}", static_cast<int>(err));
|
|
req->callback(HTTP_STATUS_ERROR, std::string(), req->data);
|
|
curl_easy_cleanup(req->handle);
|
|
delete req;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void HTTPDownloaderCurl::CloseRequest(HTTPDownloader::Request* request)
|
|
{
|
|
Request* req = static_cast<Request*>(request);
|
|
DebugAssert(req->handle);
|
|
curl_multi_remove_handle(m_multi_handle, req->handle);
|
|
curl_easy_cleanup(req->handle);
|
|
delete req;
|
|
}
|