duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

http_downloader_curl.cpp (5932B)


      1 // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "http_downloader_curl.h"
      5 
      6 #include "common/assert.h"
      7 #include "common/log.h"
      8 #include "common/string_util.h"
      9 #include "common/timer.h"
     10 
     11 #include <algorithm>
     12 #include <functional>
     13 #include <pthread.h>
     14 #include <signal.h>
     15 
     16 Log_SetChannel(HTTPDownloader);
     17 
     18 HTTPDownloaderCurl::HTTPDownloaderCurl() : HTTPDownloader()
     19 {
     20 }
     21 
     22 HTTPDownloaderCurl::~HTTPDownloaderCurl()
     23 {
     24   if (m_multi_handle)
     25     curl_multi_cleanup(m_multi_handle);
     26 }
     27 
     28 std::unique_ptr<HTTPDownloader> HTTPDownloader::Create(std::string user_agent)
     29 {
     30   std::unique_ptr<HTTPDownloaderCurl> instance(std::make_unique<HTTPDownloaderCurl>());
     31   if (!instance->Initialize(std::move(user_agent)))
     32     return {};
     33 
     34   return instance;
     35 }
     36 
     37 static bool s_curl_initialized = false;
     38 static std::once_flag s_curl_initialized_once_flag;
     39 
     40 bool HTTPDownloaderCurl::Initialize(std::string user_agent)
     41 {
     42   if (!s_curl_initialized)
     43   {
     44     std::call_once(s_curl_initialized_once_flag, []() {
     45       s_curl_initialized = curl_global_init(CURL_GLOBAL_ALL) == CURLE_OK;
     46       if (s_curl_initialized)
     47       {
     48         std::atexit([]() {
     49           curl_global_cleanup();
     50           s_curl_initialized = false;
     51         });
     52       }
     53     });
     54     if (!s_curl_initialized)
     55     {
     56       ERROR_LOG("curl_global_init() failed");
     57       return false;
     58     }
     59   }
     60 
     61   m_multi_handle = curl_multi_init();
     62   if (!m_multi_handle)
     63   {
     64     ERROR_LOG("curl_multi_init() failed");
     65     return false;
     66   }
     67 
     68   m_user_agent = std::move(user_agent);
     69   return true;
     70 }
     71 
     72 size_t HTTPDownloaderCurl::WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
     73 {
     74   Request* req = static_cast<Request*>(userdata);
     75   const size_t current_size = req->data.size();
     76   const size_t transfer_size = size * nmemb;
     77   const size_t new_size = current_size + transfer_size;
     78   req->data.resize(new_size);
     79   req->start_time = Common::Timer::GetCurrentValue();
     80   std::memcpy(&req->data[current_size], ptr, transfer_size);
     81 
     82   if (req->content_length == 0)
     83   {
     84     curl_off_t length;
     85     if (curl_easy_getinfo(req->handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &length) == CURLE_OK)
     86       req->content_length = static_cast<u32>(length);
     87   }
     88 
     89   return nmemb;
     90 }
     91 
     92 HTTPDownloader::Request* HTTPDownloaderCurl::InternalCreateRequest()
     93 {
     94   Request* req = new Request();
     95   req->handle = curl_easy_init();
     96   if (!req->handle)
     97   {
     98     delete req;
     99     return nullptr;
    100   }
    101 
    102   return req;
    103 }
    104 
    105 void HTTPDownloaderCurl::InternalPollRequests()
    106 {
    107   // Apparently OpenSSL can fire SIGPIPE...
    108   sigset_t old_block_mask = {};
    109   sigset_t new_block_mask = {};
    110   sigemptyset(&old_block_mask);
    111   sigemptyset(&new_block_mask);
    112   sigaddset(&new_block_mask, SIGPIPE);
    113   if (pthread_sigmask(SIG_BLOCK, &new_block_mask, &old_block_mask) != 0)
    114     WARNING_LOG("Failed to block SIGPIPE");
    115 
    116   int running_handles;
    117   const CURLMcode err = curl_multi_perform(m_multi_handle, &running_handles);
    118   if (err != CURLM_OK)
    119     ERROR_LOG("curl_multi_perform() returned {}", static_cast<int>(err));
    120 
    121   for (;;)
    122   {
    123     int msgq;
    124     struct CURLMsg* msg = curl_multi_info_read(m_multi_handle, &msgq);
    125     if (!msg)
    126       break;
    127 
    128     if (msg->msg != CURLMSG_DONE)
    129     {
    130       WARNING_LOG("Unexpected multi message {}", static_cast<int>(msg->msg));
    131       continue;
    132     }
    133 
    134     Request* req;
    135     if (curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &req) != CURLE_OK)
    136     {
    137       ERROR_LOG("curl_easy_getinfo() failed");
    138       continue;
    139     }
    140 
    141     if (msg->data.result == CURLE_OK)
    142     {
    143       long response_code = 0;
    144       curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &response_code);
    145       req->status_code = static_cast<s32>(response_code);
    146 
    147       char* content_type = nullptr;
    148       if (curl_easy_getinfo(req->handle, CURLINFO_CONTENT_TYPE, &content_type) == CURLE_OK && content_type)
    149         req->content_type = content_type;
    150 
    151       DEV_LOG("Request for '{}' returned status code {} and {} bytes", req->url, req->status_code, req->data.size());
    152     }
    153     else
    154     {
    155       ERROR_LOG("Request for '{}' returned error {}", req->url, static_cast<int>(msg->data.result));
    156     }
    157 
    158     req->state.store(Request::State::Complete, std::memory_order_release);
    159   }
    160 
    161   if (pthread_sigmask(SIG_UNBLOCK, &new_block_mask, &old_block_mask) != 0)
    162     WARNING_LOG("Failed to unblock SIGPIPE");
    163 }
    164 
    165 bool HTTPDownloaderCurl::StartRequest(HTTPDownloader::Request* request)
    166 {
    167   Request* req = static_cast<Request*>(request);
    168   curl_easy_setopt(req->handle, CURLOPT_URL, request->url.c_str());
    169   curl_easy_setopt(req->handle, CURLOPT_USERAGENT, m_user_agent.c_str());
    170   curl_easy_setopt(req->handle, CURLOPT_WRITEFUNCTION, &HTTPDownloaderCurl::WriteCallback);
    171   curl_easy_setopt(req->handle, CURLOPT_WRITEDATA, req);
    172   curl_easy_setopt(req->handle, CURLOPT_NOSIGNAL, 1L);
    173   curl_easy_setopt(req->handle, CURLOPT_PRIVATE, req);
    174   curl_easy_setopt(req->handle, CURLOPT_FOLLOWLOCATION, 1L);
    175 
    176   if (request->type == Request::Type::Post)
    177   {
    178     curl_easy_setopt(req->handle, CURLOPT_POST, 1L);
    179     curl_easy_setopt(req->handle, CURLOPT_POSTFIELDS, request->post_data.c_str());
    180   }
    181 
    182   DEV_LOG("Started HTTP request for '{}'", req->url);
    183   req->state.store(Request::State::Started, std::memory_order_release);
    184   req->start_time = Common::Timer::GetCurrentValue();
    185 
    186   const CURLMcode err = curl_multi_add_handle(m_multi_handle, req->handle);
    187   if (err != CURLM_OK)
    188   {
    189     ERROR_LOG("curl_multi_add_handle() returned {}", static_cast<int>(err));
    190     req->callback(HTTP_STATUS_ERROR, std::string(), req->data);
    191     curl_easy_cleanup(req->handle);
    192     delete req;
    193     return false;
    194   }
    195 
    196   return true;
    197 }
    198 
    199 void HTTPDownloaderCurl::CloseRequest(HTTPDownloader::Request* request)
    200 {
    201   Request* req = static_cast<Request*>(request);
    202   DebugAssert(req->handle);
    203   curl_multi_remove_handle(m_multi_handle, req->handle);
    204   curl_easy_cleanup(req->handle);
    205   delete req;
    206 }