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 }