winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

HttpClientWrapper.cpp (7803B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 
      4 #include "pch.h"
      5 #include "Public/AppInstallerStrings.h"
      6 #include "HttpClientWrapper.h"
      7 #include "Public/AppInstallerRuntime.h"
      8 #include "Public/AppInstallerDownloader.h"
      9 
     10 using namespace winrt::Windows::Foundation;
     11 using namespace winrt::Windows::Security::Cryptography;
     12 using namespace winrt::Windows::Storage;
     13 using namespace winrt::Windows::Storage::Streams;
     14 using namespace winrt::Windows::Web::Http;
     15 using namespace winrt::Windows::Web::Http::Headers;
     16 using namespace winrt::Windows::Web::Http::Filters;
     17 
     18 // Note: this class is used by the HttpRandomAccessStream which is passed to the AppxPackaging COM API
     19 // All exceptions thrown across dll boundaries should be WinRT exception not custom exceptions.
     20 // The HRESULTs will be mapped to UI error code by the appropriate component
     21 namespace AppInstaller::Utility::HttpStream
     22 {
     23     std::future<std::shared_ptr<HttpClientWrapper>> HttpClientWrapper::CreateAsync(const Uri& uri)
     24     {
     25         // TODO: Use proxy info. HttpClient does not support using a custom proxy, only using the system-wide one.
     26         std::shared_ptr<HttpClientWrapper> instance = std::make_shared<HttpClientWrapper>();
     27 
     28         // Use an HTTP filter to disable the default caching behavior and use the Most Recent caching behavior instead
     29         // so we don't use a stale cached resource. Note: this wrapper object is used in the custom HTTP stream implementation
     30         // so this affects the parsing of HTTP-based packages/bundles.
     31         HttpBaseProtocolFilter filter;
     32         filter.CacheControl().ReadBehavior(HttpCacheReadBehavior::MostRecent);
     33         instance->m_httpClient = HttpClient(filter);
     34         instance->m_requestUri = uri;
     35 
     36         instance->m_httpClient.DefaultRequestHeaders().Connection().Clear();
     37         instance->m_httpClient.DefaultRequestHeaders().Append(L"Connection", L"Keep-Alive");
     38         instance->m_httpClient.DefaultRequestHeaders().UserAgent().ParseAdd(Utility::ConvertToUTF16(Runtime::GetDefaultUserAgent().get()));
     39 
     40         co_await instance->PopulateInfoAsync();
     41 
     42         co_return instance;
     43     }
     44 
     45     // this function will issue a HEAD request to determine the size of the file and the redirect URI
     46     std::future<void> HttpClientWrapper::PopulateInfoAsync()
     47     {
     48         HttpRequestMessage request(HttpMethod::Head(), m_requestUri);
     49 
     50         HttpResponseMessage response = co_await m_httpClient.SendRequestAsync(request, HttpCompletionOption::ResponseHeadersRead);
     51 
     52         switch (response.StatusCode())
     53         {
     54         case HttpStatusCode::Ok:
     55             // All good
     56             break;
     57         case HttpStatusCode::TooManyRequests:
     58         case HttpStatusCode::ServiceUnavailable:
     59         {
     60             THROW_EXCEPTION(ServiceUnavailableException(GetRetryAfter(response)));
     61         }
     62         default:
     63             THROW_HR(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, response.StatusCode()));
     64         }
     65 
     66         // Get the length from the response
     67         if (response.Content().Headers().HasKey(L"Content-Length"))
     68         {
     69             std::wstring contentLength(response.Content().Headers().Lookup(L"Content-Length"));
     70             m_sizeInBytes = std::stoll(contentLength);
     71         }
     72         else
     73         {
     74             m_sizeInBytes = 0;
     75         }
     76 
     77         // Get the extension from the redirect URI
     78         m_redirectUri = response.RequestMessage().RequestUri();
     79 
     80         m_contentType = response.Content().Headers().HasKey(L"Content-Type") ?
     81             response.Content().Headers().Lookup(L"Content-Type")
     82             : L"";
     83 
     84         // If the size wasn't resolved try with a GET 0-0 request
     85         if (m_sizeInBytes == 0)
     86         {
     87             co_await SendHttpRequestAsync(0, 1);
     88         }
     89     }
     90 
     91 #ifdef WINGET_DISABLE_FOR_FUZZING
     92 #pragma warning( push )
     93 #pragma warning( disable : 4714) // HRESULT_FROM_WIN32 marked as forceinline not inlined
     94 #endif
     95 
     96     std::future<IBuffer> HttpClientWrapper::SendHttpRequestAsync(
     97         _In_ ULONG64 startPosition,
     98         _In_ UINT32 requestedSizeInBytes)
     99     {
    100         unsigned long long endPosition = 0;
    101 
    102         winrt::check_hresult(ULong64Add(startPosition, requestedSizeInBytes, &endPosition));
    103 
    104         // Subtracting one should be safe, as the consumer of the stream should not request
    105         // an empty range, so this number can't go negative.
    106         endPosition -= 1;
    107 
    108         std::wstring rangeHeaderValue = L"bytes=" + std::to_wstring(startPosition) + L"-" + std::to_wstring(endPosition);
    109 
    110         HttpRequestMessage request(HttpMethod::Get(), m_requestUri);
    111         request.Headers().Append(L"Range", rangeHeaderValue);
    112 
    113         if (!Utility::IsEmptyOrWhitespace(m_etagHeader))
    114         {
    115             request.Headers().Append(L"If-Match", m_etagHeader);
    116         }
    117 
    118         if (!Utility::IsEmptyOrWhitespace(m_lastModifiedHeader))
    119         {
    120             request.Headers().Append(L"If-Unmodified-Since", m_lastModifiedHeader);
    121         }
    122 
    123         HttpResponseMessage response = co_await m_httpClient.SendRequestAsync(request, HttpCompletionOption::ResponseHeadersRead);
    124         HttpContentHeaderCollection contentHeaders = response.Content().Headers();
    125 
    126         switch (response.StatusCode())
    127         {
    128         case HttpStatusCode::Ok:
    129         case HttpStatusCode::PartialContent:
    130             // All good
    131             break;
    132         case HttpStatusCode::TooManyRequests:
    133         case HttpStatusCode::ServiceUnavailable:
    134         {
    135             THROW_EXCEPTION(ServiceUnavailableException(GetRetryAfter(response)));
    136         }
    137         default:
    138             THROW_HR(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, response.StatusCode()));
    139         }
    140 
    141         if (response.StatusCode() != HttpStatusCode::PartialContent && startPosition != 0)
    142         {
    143             // throw HRESULT used for range-request error
    144             THROW_HR(HRESULT_FROM_WIN32(ERROR_NO_RANGES_PROCESSED));
    145         }
    146 
    147         if (response.Headers().HasKey(L"Accept-Ranges") &&
    148             Utility::ToLower(std::wstring(response.Headers().Lookup(L"Accept-Ranges"))) == L"none")
    149         {
    150             // throw HRESULT used for range-request error
    151             THROW_HR(HRESULT_FROM_WIN32(ERROR_NO_RANGES_PROCESSED));
    152         }
    153 
    154         if (Utility::IsEmptyOrWhitespace(m_etagHeader) && response.Headers().HasKey(L"ETag"))
    155         {
    156             m_etagHeader = response.Headers().Lookup(L"ETag");
    157         }
    158 
    159         if (Utility::IsEmptyOrWhitespace(m_lastModifiedHeader) && contentHeaders.HasKey(L"Last-Modified"))
    160         {
    161             m_lastModifiedHeader = contentHeaders.Lookup(L"Last-Modified");
    162         }
    163 
    164         // If we don't know the size, parse it from the Content-Range field.
    165         if (m_sizeInBytes == 0 && contentHeaders.HasKey(L"Content-Range"))
    166         {
    167             // format: a-b/x where x is either a number or *
    168             std::wstring contentRange(contentHeaders.Lookup(L"Content-Range"));
    169             std::wstring length = contentRange.substr(contentRange.find(L"/") + 1);
    170             m_sizeInBytes = (length == L"*") ? 0 : std::stoll(length);
    171         }
    172 
    173         co_return co_await response.Content().ReadAsBufferAsync();
    174     }
    175 
    176 #ifdef WINGET_DISABLE_FOR_FUZZING
    177 #pragma warning( pop ) 
    178 #endif
    179 
    180     std::future<IBuffer> HttpClientWrapper::DownloadRangeAsync(
    181         const ULONG64 startPosition,
    182         const UINT32 requestedSizeInBytes,
    183         const InputStreamOptions& options)
    184     {
    185         std::vector<byte> byteArray(requestedSizeInBytes);
    186         IBuffer buffer = CryptographicBuffer::CreateFromByteArray(byteArray);
    187 
    188         co_return co_await SendHttpRequestAsync(startPosition, requestedSizeInBytes);
    189     }
    190 }