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_winhttp.cpp (11099B)


      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_winhttp.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 
     13 Log_SetChannel(HTTPDownloader);
     14 
     15 HTTPDownloaderWinHttp::HTTPDownloaderWinHttp() : HTTPDownloader()
     16 {
     17 }
     18 
     19 HTTPDownloaderWinHttp::~HTTPDownloaderWinHttp()
     20 {
     21   if (m_hSession)
     22   {
     23     WinHttpSetStatusCallback(m_hSession, nullptr, WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, NULL);
     24     WinHttpCloseHandle(m_hSession);
     25   }
     26 }
     27 
     28 std::unique_ptr<HTTPDownloader> HTTPDownloader::Create(std::string user_agent)
     29 {
     30   std::unique_ptr<HTTPDownloaderWinHttp> instance(std::make_unique<HTTPDownloaderWinHttp>());
     31   if (!instance->Initialize(std::move(user_agent)))
     32     return {};
     33 
     34   return instance;
     35 }
     36 
     37 bool HTTPDownloaderWinHttp::Initialize(std::string user_agent)
     38 {
     39   static constexpr DWORD dwAccessType = WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY;
     40 
     41   m_hSession = WinHttpOpen(StringUtil::UTF8StringToWideString(user_agent).c_str(), dwAccessType, nullptr, nullptr,
     42                            WINHTTP_FLAG_ASYNC);
     43   if (m_hSession == NULL)
     44   {
     45     ERROR_LOG("WinHttpOpen() failed: {}", GetLastError());
     46     return false;
     47   }
     48 
     49   const DWORD notification_flags = WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | WINHTTP_CALLBACK_FLAG_REQUEST_ERROR |
     50                                    WINHTTP_CALLBACK_FLAG_HANDLES | WINHTTP_CALLBACK_FLAG_SECURE_FAILURE;
     51   if (WinHttpSetStatusCallback(m_hSession, HTTPStatusCallback, notification_flags, NULL) ==
     52       WINHTTP_INVALID_STATUS_CALLBACK)
     53   {
     54     ERROR_LOG("WinHttpSetStatusCallback() failed: {}", GetLastError());
     55     return false;
     56   }
     57 
     58   return true;
     59 }
     60 
     61 void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWORD_PTR dwContext, DWORD dwInternetStatus,
     62                                                         LPVOID lpvStatusInformation, DWORD dwStatusInformationLength)
     63 {
     64   Request* req = reinterpret_cast<Request*>(dwContext);
     65   switch (dwInternetStatus)
     66   {
     67     case WINHTTP_CALLBACK_STATUS_HANDLE_CREATED:
     68       return;
     69 
     70     case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
     71     {
     72       if (!req)
     73         return;
     74 
     75       DebugAssert(hRequest == req->hRequest);
     76 
     77       HTTPDownloaderWinHttp* parent = static_cast<HTTPDownloaderWinHttp*>(req->parent);
     78       std::unique_lock<std::mutex> lock(parent->m_pending_http_request_lock);
     79       Assert(std::none_of(parent->m_pending_http_requests.begin(), parent->m_pending_http_requests.end(),
     80                           [req](HTTPDownloader::Request* it) { return it == req; }));
     81 
     82       // we can clean up the connection as well
     83       DebugAssert(req->hConnection != NULL);
     84       WinHttpCloseHandle(req->hConnection);
     85       delete req;
     86       return;
     87     }
     88 
     89     case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
     90     {
     91       const WINHTTP_ASYNC_RESULT* res = reinterpret_cast<const WINHTTP_ASYNC_RESULT*>(lpvStatusInformation);
     92       ERROR_LOG("WinHttp async function {} returned error {}", res->dwResult, res->dwError);
     93       req->status_code = HTTP_STATUS_ERROR;
     94       req->state.store(Request::State::Complete);
     95       return;
     96     }
     97     case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
     98     {
     99       DEV_LOG("SendRequest complete");
    100       if (!WinHttpReceiveResponse(hRequest, nullptr))
    101       {
    102         ERROR_LOG("WinHttpReceiveResponse() failed: {}", GetLastError());
    103         req->status_code = HTTP_STATUS_ERROR;
    104         req->state.store(Request::State::Complete);
    105       }
    106 
    107       return;
    108     }
    109     case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
    110     {
    111       DEV_LOG("Headers available");
    112 
    113       DWORD buffer_size = sizeof(req->status_code);
    114       if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
    115                                WINHTTP_HEADER_NAME_BY_INDEX, &req->status_code, &buffer_size, WINHTTP_NO_HEADER_INDEX))
    116       {
    117         ERROR_LOG("WinHttpQueryHeaders() for status code failed: {}", GetLastError());
    118         req->status_code = HTTP_STATUS_ERROR;
    119         req->state.store(Request::State::Complete);
    120         return;
    121       }
    122 
    123       buffer_size = sizeof(req->content_length);
    124       if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER,
    125                                WINHTTP_HEADER_NAME_BY_INDEX, &req->content_length, &buffer_size,
    126                                WINHTTP_NO_HEADER_INDEX))
    127       {
    128         if (GetLastError() != ERROR_WINHTTP_HEADER_NOT_FOUND)
    129           WARNING_LOG("WinHttpQueryHeaders() for content length failed: {}", GetLastError());
    130 
    131         req->content_length = 0;
    132       }
    133 
    134       DWORD content_type_length = 0;
    135       if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_TYPE, WINHTTP_HEADER_NAME_BY_INDEX,
    136                                WINHTTP_NO_OUTPUT_BUFFER, &content_type_length, WINHTTP_NO_HEADER_INDEX) &&
    137           GetLastError() == ERROR_INSUFFICIENT_BUFFER && content_type_length >= sizeof(content_type_length))
    138       {
    139         std::wstring content_type_wstring;
    140         content_type_wstring.resize((content_type_length / sizeof(wchar_t)) - 1);
    141         if (WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_TYPE, WINHTTP_HEADER_NAME_BY_INDEX,
    142                                 content_type_wstring.data(), &content_type_length, WINHTTP_NO_HEADER_INDEX))
    143         {
    144           req->content_type = StringUtil::WideStringToUTF8String(content_type_wstring);
    145         }
    146       }
    147 
    148       DEV_LOG("Status code {}, content-length is {}", req->status_code, req->content_length);
    149       req->data.reserve(req->content_length);
    150       req->state = Request::State::Receiving;
    151 
    152       // start reading
    153       if (!WinHttpQueryDataAvailable(hRequest, nullptr) && GetLastError() != ERROR_IO_PENDING)
    154       {
    155         ERROR_LOG("WinHttpQueryDataAvailable() failed: {}", GetLastError());
    156         req->status_code = HTTP_STATUS_ERROR;
    157         req->state.store(Request::State::Complete);
    158       }
    159 
    160       return;
    161     }
    162     case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
    163     {
    164       DWORD bytes_available;
    165       std::memcpy(&bytes_available, lpvStatusInformation, sizeof(bytes_available));
    166       if (bytes_available == 0)
    167       {
    168         // end of request
    169         DEV_LOG("End of request '{}', {} bytes received", req->url, req->data.size());
    170         req->state.store(Request::State::Complete);
    171         return;
    172       }
    173 
    174       // start the transfer
    175       DEV_LOG("{} bytes available", bytes_available);
    176       req->io_position = static_cast<u32>(req->data.size());
    177       req->data.resize(req->io_position + bytes_available);
    178       if (!WinHttpReadData(hRequest, req->data.data() + req->io_position, bytes_available, nullptr) &&
    179           GetLastError() != ERROR_IO_PENDING)
    180       {
    181         ERROR_LOG("WinHttpReadData() failed: {}", GetLastError());
    182         req->status_code = HTTP_STATUS_ERROR;
    183         req->state.store(Request::State::Complete);
    184       }
    185 
    186       return;
    187     }
    188     case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
    189     {
    190       DEV_LOG("Read of {} complete", dwStatusInformationLength);
    191 
    192       const u32 new_size = req->io_position + dwStatusInformationLength;
    193       Assert(new_size <= req->data.size());
    194       req->data.resize(new_size);
    195       req->start_time = Common::Timer::GetCurrentValue();
    196 
    197       if (!WinHttpQueryDataAvailable(hRequest, nullptr) && GetLastError() != ERROR_IO_PENDING)
    198       {
    199         ERROR_LOG("WinHttpQueryDataAvailable() failed: {}", GetLastError());
    200         req->status_code = HTTP_STATUS_ERROR;
    201         req->state.store(Request::State::Complete);
    202       }
    203 
    204       return;
    205     }
    206     default:
    207       // unhandled, ignore
    208       return;
    209   }
    210 }
    211 
    212 HTTPDownloader::Request* HTTPDownloaderWinHttp::InternalCreateRequest()
    213 {
    214   Request* req = new Request();
    215   return req;
    216 }
    217 
    218 void HTTPDownloaderWinHttp::InternalPollRequests()
    219 {
    220   // noop - it uses windows's worker threads
    221 }
    222 
    223 bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request)
    224 {
    225   Request* req = static_cast<Request*>(request);
    226 
    227   std::wstring host_name;
    228   host_name.resize(req->url.size());
    229   req->object_name.resize(req->url.size());
    230 
    231   URL_COMPONENTSW uc = {};
    232   uc.dwStructSize = sizeof(uc);
    233   uc.lpszHostName = host_name.data();
    234   uc.dwHostNameLength = static_cast<DWORD>(host_name.size());
    235   uc.lpszUrlPath = req->object_name.data();
    236   uc.dwUrlPathLength = static_cast<DWORD>(req->object_name.size());
    237 
    238   const std::wstring url_wide(StringUtil::UTF8StringToWideString(req->url));
    239   if (!WinHttpCrackUrl(url_wide.c_str(), static_cast<DWORD>(url_wide.size()), 0, &uc))
    240   {
    241     ERROR_LOG("WinHttpCrackUrl() failed: {}", GetLastError());
    242     req->callback(HTTP_STATUS_ERROR, std::string(), req->data);
    243     delete req;
    244     return false;
    245   }
    246 
    247   host_name.resize(uc.dwHostNameLength);
    248   req->object_name.resize(uc.dwUrlPathLength);
    249 
    250   req->hConnection = WinHttpConnect(m_hSession, host_name.c_str(), uc.nPort, 0);
    251   if (!req->hConnection)
    252   {
    253     ERROR_LOG("Failed to start HTTP request for '{}': {}", req->url, GetLastError());
    254     req->callback(HTTP_STATUS_ERROR, std::string(), req->data);
    255     delete req;
    256     return false;
    257   }
    258 
    259   const DWORD request_flags = uc.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0;
    260   req->hRequest =
    261     WinHttpOpenRequest(req->hConnection, (req->type == HTTPDownloader::Request::Type::Post) ? L"POST" : L"GET",
    262                        req->object_name.c_str(), NULL, NULL, NULL, request_flags);
    263   if (!req->hRequest)
    264   {
    265     ERROR_LOG("WinHttpOpenRequest() failed: {}", GetLastError());
    266     WinHttpCloseHandle(req->hConnection);
    267     return false;
    268   }
    269 
    270   BOOL result;
    271   if (req->type == HTTPDownloader::Request::Type::Post)
    272   {
    273     const std::wstring_view additional_headers(L"Content-Type: application/x-www-form-urlencoded\r\n");
    274     result = WinHttpSendRequest(req->hRequest, additional_headers.data(), static_cast<DWORD>(additional_headers.size()),
    275                                 req->post_data.data(), static_cast<DWORD>(req->post_data.size()),
    276                                 static_cast<DWORD>(req->post_data.size()), reinterpret_cast<DWORD_PTR>(req));
    277   }
    278   else
    279   {
    280     result = WinHttpSendRequest(req->hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0,
    281                                 reinterpret_cast<DWORD_PTR>(req));
    282   }
    283 
    284   if (!result && GetLastError() != ERROR_IO_PENDING)
    285   {
    286     ERROR_LOG("WinHttpSendRequest() failed: {}", GetLastError());
    287     req->status_code = HTTP_STATUS_ERROR;
    288     req->state.store(Request::State::Complete);
    289   }
    290 
    291   DEV_LOG("Started HTTP request for '{}'", req->url);
    292   req->state = Request::State::Started;
    293   req->start_time = Common::Timer::GetCurrentValue();
    294   return true;
    295 }
    296 
    297 void HTTPDownloaderWinHttp::CloseRequest(HTTPDownloader::Request* request)
    298 {
    299   Request* req = static_cast<Request*>(request);
    300 
    301   if (req->hRequest != NULL)
    302   {
    303     // req will be freed by the callback.
    304     // the callback can fire immediately here if there's nothing running async, so don't touch req afterwards
    305     WinHttpCloseHandle(req->hRequest);
    306     return;
    307   }
    308 
    309   if (req->hConnection != NULL)
    310     WinHttpCloseHandle(req->hConnection);
    311 
    312   delete req;
    313 }