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