aboutsummaryrefslogtreecommitdiff
path: root/src/libs/dutil/WixToolset.DUtil/dlutil.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/dutil/WixToolset.DUtil/dlutil.cpp')
-rw-r--r--src/libs/dutil/WixToolset.DUtil/dlutil.cpp802
1 files changed, 802 insertions, 0 deletions
diff --git a/src/libs/dutil/WixToolset.DUtil/dlutil.cpp b/src/libs/dutil/WixToolset.DUtil/dlutil.cpp
new file mode 100644
index 00000000..70155e6f
--- /dev/null
+++ b/src/libs/dutil/WixToolset.DUtil/dlutil.cpp
@@ -0,0 +1,802 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5#include <inetutil.h>
6#include <uriutil.h>
7
8
9// Exit macros
10#define DlExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_DLUTIL, x, s, __VA_ARGS__)
11#define DlExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_DLUTIL, x, s, __VA_ARGS__)
12#define DlExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_DLUTIL, x, s, __VA_ARGS__)
13#define DlExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_DLUTIL, x, s, __VA_ARGS__)
14#define DlExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_DLUTIL, x, s, __VA_ARGS__)
15#define DlExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_DLUTIL, x, s, __VA_ARGS__)
16#define DlExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_DLUTIL, p, x, e, s, __VA_ARGS__)
17#define DlExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_DLUTIL, p, x, s, __VA_ARGS__)
18#define DlExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_DLUTIL, p, x, e, s, __VA_ARGS__)
19#define DlExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_DLUTIL, p, x, s, __VA_ARGS__)
20#define DlExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_DLUTIL, e, x, s, __VA_ARGS__)
21#define DlExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_DLUTIL, g, x, s, __VA_ARGS__)
22
23static const DWORD64 DOWNLOAD_ENGINE_TWO_GIGABYTES = DWORD64(2) * 1024 * 1024 * 1024;
24static LPCWSTR DOWNLOAD_ENGINE_ACCEPT_TYPES[] = { L"*/*", NULL };
25
26// internal function declarations
27
28static HRESULT InitializeResume(
29 __in LPCWSTR wzDestinationPath,
30 __out LPWSTR* psczResumePath,
31 __out HANDLE* phResumeFile,
32 __out DWORD64* pdw64ResumeOffset
33 );
34static HRESULT GetResourceMetadata(
35 __in HINTERNET hSession,
36 __inout_z LPWSTR* psczUrl,
37 __in_z_opt LPCWSTR wzUser,
38 __in_z_opt LPCWSTR wzPassword,
39 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
40 __out DWORD64* pdw64ResourceSize,
41 __out FILETIME* pftResourceCreated
42 );
43static HRESULT DownloadResource(
44 __in HINTERNET hSession,
45 __inout_z LPWSTR* psczUrl,
46 __in_z_opt LPCWSTR wzUser,
47 __in_z_opt LPCWSTR wzPassword,
48 __in_z LPCWSTR wzDestinationPath,
49 __in DWORD64 dw64AuthoredResourceLength,
50 __in DWORD64 dw64ResourceLength,
51 __in DWORD64 dw64ResumeOffset,
52 __in HANDLE hResumeFile,
53 __in_opt DOWNLOAD_CACHE_CALLBACK* pCache,
54 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate
55 );
56static HRESULT AllocateRangeRequestHeader(
57 __in DWORD64 dw64ResumeOffset,
58 __in DWORD64 dw64ResourceLength,
59 __deref_inout_z LPWSTR* psczHeader
60 );
61static HRESULT WriteToFile(
62 __in HINTERNET hUrl,
63 __in HANDLE hPayloadFile,
64 __inout DWORD64* pdw64ResumeOffset,
65 __in HANDLE hResumeFile,
66 __in DWORD64 dw64ResourceLength,
67 __in LPBYTE pbData,
68 __in DWORD cbData,
69 __in_opt DOWNLOAD_CACHE_CALLBACK* pCallback
70 );
71static HRESULT UpdateResumeOffset(
72 __inout DWORD64* pdw64ResumeOffset,
73 __in HANDLE hResumeFile,
74 __in DWORD cbData
75 );
76static HRESULT MakeRequest(
77 __in HINTERNET hSession,
78 __inout_z LPWSTR* psczSourceUrl,
79 __in_z_opt LPCWSTR wzMethod,
80 __in_z_opt LPCWSTR wzHeaders,
81 __in_z_opt LPCWSTR wzUser,
82 __in_z_opt LPCWSTR wzPassword,
83 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
84 __out HINTERNET* phConnect,
85 __out HINTERNET* phUrl,
86 __out BOOL* pfRangeRequestsAccepted
87 );
88static HRESULT OpenRequest(
89 __in HINTERNET hConnect,
90 __in_z_opt LPCWSTR wzMethod,
91 __in INTERNET_SCHEME scheme,
92 __in_z LPCWSTR wzResource,
93 __in_z_opt LPCWSTR wzQueryString,
94 __in_z_opt LPCWSTR wzHeader,
95 __out HINTERNET* phUrl
96 );
97static HRESULT SendRequest(
98 __in HINTERNET hUrl,
99 __inout_z LPWSTR* psczUrl,
100 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
101 __out BOOL* pfRetry,
102 __out BOOL* pfRangesAccepted
103 );
104static HRESULT AuthenticationRequired(
105 __in HINTERNET hUrl,
106 __in long lHttpCode,
107 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
108 __out BOOL* pfRetrySend,
109 __out BOOL* pfRetry
110 );
111static HRESULT DownloadGetResumePath(
112 __in_z LPCWSTR wzPayloadWorkingPath,
113 __deref_out_z LPWSTR* psczResumePath
114 );
115static HRESULT DownloadSendProgressCallback(
116 __in DOWNLOAD_CACHE_CALLBACK* pCallback,
117 __in DWORD64 dw64Progress,
118 __in DWORD64 dw64Total,
119 __in HANDLE hDestinationFile
120 );
121// function definitions
122
123extern "C" HRESULT DAPI DownloadUrl(
124 __in DOWNLOAD_SOURCE* pDownloadSource,
125 __in DWORD64 dw64AuthoredDownloadSize,
126 __in LPCWSTR wzDestinationPath,
127 __in_opt DOWNLOAD_CACHE_CALLBACK* pCache,
128 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate
129 )
130{
131 HRESULT hr = S_OK;
132 LPWSTR sczUrl = NULL;
133 HINTERNET hSession = NULL;
134 DWORD dwTimeout = 0;
135 LPWSTR sczResumePath = NULL;
136 HANDLE hResumeFile = INVALID_HANDLE_VALUE;
137 DWORD64 dw64ResumeOffset = 0;
138 DWORD64 dw64Size = 0;
139 FILETIME ftCreated = { };
140
141 // Copy the download source into a working variable to handle redirects then
142 // open the internet session.
143 hr = StrAllocString(&sczUrl, pDownloadSource->sczUrl, 0);
144 DlExitOnFailure(hr, "Failed to copy download source URL.");
145
146 hSession = ::InternetOpenW(L"Burn", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
147 DlExitOnNullWithLastError(hSession, hr, "Failed to open internet session");
148
149 // Make a best effort to set the download timeouts to 2 minutes or whatever policy says.
150 PolcReadNumber(POLICY_BURN_REGISTRY_PATH, L"DownloadTimeout", 2 * 60, &dwTimeout);
151 if (0 < dwTimeout)
152 {
153 dwTimeout *= 1000; // convert to milliseconds.
154 ::InternetSetOptionW(hSession, INTERNET_OPTION_CONNECT_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
155 ::InternetSetOptionW(hSession, INTERNET_OPTION_RECEIVE_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
156 ::InternetSetOptionW(hSession, INTERNET_OPTION_SEND_TIMEOUT, &dwTimeout, sizeof(dwTimeout));
157 }
158
159 // Get the resource size and creation time from the internet.
160 hr = GetResourceMetadata(hSession, &sczUrl, pDownloadSource->sczUser, pDownloadSource->sczPassword, pAuthenticate, &dw64Size, &ftCreated);
161 DlExitOnFailure(hr, "Failed to get size and time for URL: %ls", sczUrl);
162
163 // Ignore failure to initialize resume because we will fall back to full download then
164 // download.
165 InitializeResume(wzDestinationPath, &sczResumePath, &hResumeFile, &dw64ResumeOffset);
166
167 hr = DownloadResource(hSession, &sczUrl, pDownloadSource->sczUser, pDownloadSource->sczPassword, wzDestinationPath, dw64AuthoredDownloadSize, dw64Size, dw64ResumeOffset, hResumeFile, pCache, pAuthenticate);
168 DlExitOnFailure(hr, "Failed to download URL: %ls", sczUrl);
169
170 // Cleanup the resume file because we successfully downloaded the whole file.
171 if (sczResumePath && *sczResumePath)
172 {
173 ::DeleteFileW(sczResumePath);
174 }
175
176LExit:
177 ReleaseFileHandle(hResumeFile);
178 ReleaseStr(sczResumePath);
179 ReleaseInternet(hSession);
180 ReleaseStr(sczUrl);
181
182 return hr;
183}
184
185
186// internal helper functions
187
188static HRESULT InitializeResume(
189 __in LPCWSTR wzDestinationPath,
190 __out LPWSTR* psczResumePath,
191 __out HANDLE* phResumeFile,
192 __out DWORD64* pdw64ResumeOffset
193 )
194{
195 HRESULT hr = S_OK;
196 HANDLE hResumeFile = INVALID_HANDLE_VALUE;
197 DWORD cbTotalReadResumeData = 0;
198 DWORD cbReadData = 0;
199
200 *pdw64ResumeOffset = 0;
201
202 hr = DownloadGetResumePath(wzDestinationPath, psczResumePath);
203 DlExitOnFailure(hr, "Failed to calculate resume path from working path: %ls", wzDestinationPath);
204
205 hResumeFile = ::CreateFileW(*psczResumePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
206 if (INVALID_HANDLE_VALUE == hResumeFile)
207 {
208 DlExitWithLastError(hr, "Failed to create resume file: %ls", *psczResumePath);
209 }
210
211 do
212 {
213 if (!::ReadFile(hResumeFile, reinterpret_cast<BYTE*>(pdw64ResumeOffset) + cbTotalReadResumeData, sizeof(DWORD64) - cbTotalReadResumeData, &cbReadData, NULL))
214 {
215 DlExitWithLastError(hr, "Failed to read resume file: %ls", *psczResumePath);
216 }
217 cbTotalReadResumeData += cbReadData;
218 } while (cbReadData && sizeof(DWORD64) > cbTotalReadResumeData);
219
220 // Start over if we couldn't get a resume offset.
221 if (cbTotalReadResumeData != sizeof(DWORD64))
222 {
223 *pdw64ResumeOffset = 0;
224 }
225
226 *phResumeFile = hResumeFile;
227 hResumeFile = INVALID_HANDLE_VALUE;
228
229LExit:
230 ReleaseFileHandle(hResumeFile);
231 return hr;
232}
233
234static HRESULT GetResourceMetadata(
235 __in HINTERNET hSession,
236 __inout_z LPWSTR* psczUrl,
237 __in_z_opt LPCWSTR wzUser,
238 __in_z_opt LPCWSTR wzPassword,
239 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
240 __out DWORD64* pdw64ResourceSize,
241 __out FILETIME* pftResourceCreated
242 )
243{
244 HRESULT hr = S_OK;
245 BOOL fRangeRequestsAccepted = TRUE;
246 HINTERNET hConnect = NULL;
247 HINTERNET hUrl = NULL;
248 LONGLONG llLength = 0;
249
250 hr = MakeRequest(hSession, psczUrl, L"HEAD", NULL, wzUser, wzPassword, pAuthenticate, &hConnect, &hUrl, &fRangeRequestsAccepted);
251 DlExitOnFailure(hr, "Failed to connect to URL: %ls", *psczUrl);
252
253 hr = InternetGetSizeByHandle(hUrl, &llLength);
254 if (FAILED(hr))
255 {
256 llLength = 0;
257 hr = S_OK;
258 }
259
260 *pdw64ResourceSize = llLength;
261
262 // Get the last modified time from the server, we'll use that as our downloaded time here. If
263 // the server time isn't available then use the local system time.
264 hr = InternetGetCreateTimeByHandle(hUrl, pftResourceCreated);
265 if (FAILED(hr))
266 {
267 ::GetSystemTimeAsFileTime(pftResourceCreated);
268 hr = S_OK;
269 }
270
271LExit:
272 ReleaseInternet(hUrl);
273 ReleaseInternet(hConnect);
274 return hr;
275}
276
277static HRESULT DownloadResource(
278 __in HINTERNET hSession,
279 __inout_z LPWSTR* psczUrl,
280 __in_z_opt LPCWSTR wzUser,
281 __in_z_opt LPCWSTR wzPassword,
282 __in_z LPCWSTR wzDestinationPath,
283 __in DWORD64 dw64AuthoredResourceLength,
284 __in DWORD64 dw64ResourceLength,
285 __in DWORD64 dw64ResumeOffset,
286 __in HANDLE hResumeFile,
287 __in_opt DOWNLOAD_CACHE_CALLBACK* pCache,
288 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate
289 )
290{
291 HRESULT hr = S_OK;
292 HANDLE hPayloadFile = INVALID_HANDLE_VALUE;
293 DWORD cbMaxData = 64 * 1024; // 64 KB
294 BYTE* pbData = NULL;
295 BOOL fRangeRequestsAccepted = TRUE;
296 LPWSTR sczRangeRequestHeader = NULL;
297 HINTERNET hConnect = NULL;
298 HINTERNET hUrl = NULL;
299 LONGLONG llLength = 0;
300
301 hPayloadFile = ::CreateFileW(wzDestinationPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_DELETE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
302 if (INVALID_HANDLE_VALUE == hPayloadFile)
303 {
304 DlExitWithLastError(hr, "Failed to create download destination file: %ls", wzDestinationPath);
305 }
306
307 // Allocate a memory block on a page boundary in case we want to do optimal writing.
308 pbData = static_cast<BYTE*>(::VirtualAlloc(NULL, cbMaxData, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
309 DlExitOnNullWithLastError(pbData, hr, "Failed to allocate buffer to download files into.");
310
311 // Let's try downloading the file assuming that range requests are accepted. If range requests
312 // are not supported we'll have to start over and accept the fact that we only get one shot
313 // downloading the file however big it is. Hopefully, not more than 2 GB since wininet doesn't
314 // like files that big.
315 while (fRangeRequestsAccepted && (0 == dw64ResourceLength || dw64ResumeOffset < dw64ResourceLength))
316 {
317 hr = AllocateRangeRequestHeader(dw64ResumeOffset, 0 == dw64ResourceLength ? dw64AuthoredResourceLength : dw64ResourceLength, &sczRangeRequestHeader);
318 DlExitOnFailure(hr, "Failed to allocate range request header.");
319
320 ReleaseNullInternet(hConnect);
321 ReleaseNullInternet(hUrl);
322
323 hr = MakeRequest(hSession, psczUrl, L"GET", sczRangeRequestHeader, wzUser, wzPassword, pAuthenticate, &hConnect, &hUrl, &fRangeRequestsAccepted);
324 DlExitOnFailure(hr, "Failed to request URL for download: %ls", *psczUrl);
325
326 // If we didn't get the size of the resource from the initial "HEAD" request
327 // then let's try to get the size from this "GET" request.
328 if (0 == dw64ResourceLength)
329 {
330 hr = InternetGetSizeByHandle(hUrl, &llLength);
331 if (SUCCEEDED(hr))
332 {
333 dw64ResourceLength = llLength;
334 }
335 else // server didn't tell us the resource length.
336 {
337 // Fallback to the authored size of the resource. However, since we
338 // don't really know the size on the server, don't try to use
339 // range requests either.
340 dw64ResourceLength = dw64AuthoredResourceLength;
341 fRangeRequestsAccepted = FALSE;
342 }
343 }
344
345 // If we just tried to do a range request and found out that it isn't supported, start over.
346 if (!fRangeRequestsAccepted)
347 {
348 // TODO: log a message that the server did not accept range requests.
349 dw64ResumeOffset = 0;
350 }
351
352 hr = WriteToFile(hUrl, hPayloadFile, &dw64ResumeOffset, hResumeFile, dw64ResourceLength, pbData, cbMaxData, pCache);
353 DlExitOnFailure(hr, "Failed while reading from internet and writing to: %ls", wzDestinationPath);
354 }
355
356LExit:
357 ReleaseInternet(hUrl);
358 ReleaseInternet(hConnect);
359 ReleaseStr(sczRangeRequestHeader);
360 if (pbData)
361 {
362 ::VirtualFree(pbData, 0, MEM_RELEASE);
363 }
364 ReleaseFileHandle(hPayloadFile);
365
366 return hr;
367}
368
369static HRESULT AllocateRangeRequestHeader(
370 __in DWORD64 dw64ResumeOffset,
371 __in DWORD64 dw64ResourceLength,
372 __deref_inout_z LPWSTR* psczHeader
373 )
374{
375 HRESULT hr = S_OK;
376
377 // If the remaining length is less that 2GB we'll be able to ask for everything.
378 DWORD64 dw64RemainingLength = dw64ResourceLength - dw64ResumeOffset;
379 if (DOWNLOAD_ENGINE_TWO_GIGABYTES > dw64RemainingLength)
380 {
381 // If we have a resume offset, let's download everything from there. Otherwise, we'll
382 // just get everything with no headers in the way.
383 if (0 < dw64ResumeOffset)
384 {
385 hr = StrAllocFormatted(psczHeader, L"Range: bytes=%I64u-", dw64ResumeOffset);
386 DlExitOnFailure(hr, "Failed to add range read header.");
387 }
388 else
389 {
390 ReleaseNullStr(*psczHeader);
391 }
392 }
393 else // we'll have to download in chunks.
394 {
395 hr = StrAllocFormatted(psczHeader, L"Range: bytes=%I64u-%I64u", dw64ResumeOffset, dw64ResumeOffset + dw64RemainingLength - 1);
396 DlExitOnFailure(hr, "Failed to add range read header.");
397 }
398
399LExit:
400 return hr;
401}
402
403static HRESULT WriteToFile(
404 __in HINTERNET hUrl,
405 __in HANDLE hPayloadFile,
406 __inout DWORD64* pdw64ResumeOffset,
407 __in HANDLE hResumeFile,
408 __in DWORD64 dw64ResourceLength,
409 __in LPBYTE pbData,
410 __in DWORD cbData,
411 __in_opt DOWNLOAD_CACHE_CALLBACK* pCallback
412 )
413{
414 HRESULT hr = S_OK;
415 DWORD cbReadData = 0;
416
417 hr = FileSetPointer(hPayloadFile, *pdw64ResumeOffset, NULL, FILE_BEGIN);
418 DlExitOnFailure(hr, "Failed to seek to start point in file.");
419
420 do
421 {
422 // Read bits from the internet.
423 if (!::InternetReadFile(hUrl, static_cast<void*>(pbData), cbData, &cbReadData))
424 {
425 DlExitWithLastError(hr, "Failed while reading from internet.");
426 }
427
428 // Write bits to disk (if there are any).
429 if (cbReadData)
430 {
431 DWORD cbTotalWritten = 0;
432 DWORD cbWritten = 0;
433 do
434 {
435 if (!::WriteFile(hPayloadFile, pbData + cbTotalWritten, cbReadData - cbTotalWritten, &cbWritten, NULL))
436 {
437 DlExitWithLastError(hr, "Failed to write data from internet.");
438 }
439
440 cbTotalWritten += cbWritten;
441 } while (cbWritten && cbTotalWritten < cbReadData);
442
443 // Ignore failure from updating resume file as this doesn't mean the download cannot succeed.
444 UpdateResumeOffset(pdw64ResumeOffset, hResumeFile, cbTotalWritten);
445
446 if (pCallback && pCallback->pfnProgress)
447 {
448 hr = DownloadSendProgressCallback(pCallback, *pdw64ResumeOffset, dw64ResourceLength, hPayloadFile);
449 DlExitOnFailure(hr, "UX aborted on cache progress.");
450 }
451 }
452 } while (cbReadData);
453
454LExit:
455 return hr;
456}
457
458static HRESULT UpdateResumeOffset(
459 __inout DWORD64* pdw64ResumeOffset,
460 __in HANDLE hResumeFile,
461 __in DWORD cbData
462 )
463{
464 HRESULT hr = S_OK;
465
466 *pdw64ResumeOffset += cbData;
467
468 if (INVALID_HANDLE_VALUE != hResumeFile)
469 {
470 DWORD cbTotalWrittenResumeData = 0;
471 DWORD cbWrittenResumeData = 0;
472
473 hr = FileSetPointer(hResumeFile, 0, NULL, FILE_BEGIN);
474 DlExitOnFailure(hr, "Failed to seek to start point in file.");
475
476 do
477 {
478 // Ignore failure to write to the resume file as that should not prevent the download from happening.
479 if (!::WriteFile(hResumeFile, pdw64ResumeOffset + cbTotalWrittenResumeData, sizeof(DWORD64) - cbTotalWrittenResumeData, &cbWrittenResumeData, NULL))
480 {
481 DlExitOnFailure(hr, "Failed to seek to write to file.");
482 }
483
484 cbTotalWrittenResumeData += cbWrittenResumeData;
485 } while (cbWrittenResumeData && sizeof(DWORD64) > cbTotalWrittenResumeData);
486 }
487
488LExit:
489 return hr;
490}
491
492static HRESULT MakeRequest(
493 __in HINTERNET hSession,
494 __inout_z LPWSTR* psczSourceUrl,
495 __in_z_opt LPCWSTR wzMethod,
496 __in_z_opt LPCWSTR wzHeaders,
497 __in_z_opt LPCWSTR wzUser,
498 __in_z_opt LPCWSTR wzPassword,
499 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
500 __out HINTERNET* phConnect,
501 __out HINTERNET* phUrl,
502 __out BOOL* pfRangeRequestsAccepted
503 )
504{
505 HRESULT hr = S_OK;
506 HINTERNET hConnect = NULL;
507 HINTERNET hUrl = NULL;
508 URI_INFO uri = { };
509
510 // Try to open the URL.
511 BOOL fRetry;
512 do
513 {
514 fRetry = FALSE;
515
516 // If the URL was opened close it, so we can reopen it again.
517 ReleaseInternet(hUrl);
518 ReleaseInternet(hConnect);
519
520 // Open the url.
521 hr = UriCrackEx(*psczSourceUrl, &uri);
522 DlExitOnFailure(hr, "Failed to break URL into server and resource parts.");
523
524 hConnect = ::InternetConnectW(hSession, uri.sczHostName, uri.port, (wzUser && *wzUser) ? wzUser : uri.sczUser, (wzPassword && *wzPassword) ? wzPassword : uri.sczPassword, INTERNET_SCHEME_FTP == uri.scheme ? INTERNET_SERVICE_FTP : INTERNET_SERVICE_HTTP, 0, 0);
525 DlExitOnNullWithLastError(hConnect, hr, "Failed to connect to URL: %ls", *psczSourceUrl);
526
527 // Best effort set the proxy username and password, if they were provided.
528 if ((wzUser && *wzUser) && (wzPassword && *wzPassword))
529 {
530 if (::InternetSetOptionW(hConnect, INTERNET_OPTION_PROXY_USERNAME, (LPVOID)wzUser, lstrlenW(wzUser)))
531 {
532 ::InternetSetOptionW(hConnect, INTERNET_OPTION_PROXY_PASSWORD, (LPVOID)wzPassword, lstrlenW(wzPassword));
533 }
534 }
535
536 hr = OpenRequest(hConnect, wzMethod, uri.scheme, uri.sczPath, uri.sczQueryString, wzHeaders, &hUrl);
537 DlExitOnFailure(hr, "Failed to open internet URL: %ls", *psczSourceUrl);
538
539 hr = SendRequest(hUrl, psczSourceUrl, pAuthenticate, &fRetry, pfRangeRequestsAccepted);
540 DlExitOnFailure(hr, "Failed to send request to URL: %ls", *psczSourceUrl);
541 } while (fRetry);
542
543 // Okay, we're all ready to start downloading. Update the connection information.
544 *phConnect = hConnect;
545 hConnect = NULL;
546 *phUrl = hUrl;
547 hUrl = NULL;
548
549LExit:
550 UriInfoUninitialize(&uri);
551 ReleaseInternet(hUrl);
552 ReleaseInternet(hConnect);
553
554 return hr;
555}
556
557static HRESULT OpenRequest(
558 __in HINTERNET hConnect,
559 __in_z_opt LPCWSTR wzMethod,
560 __in INTERNET_SCHEME scheme,
561 __in_z LPCWSTR wzResource,
562 __in_z_opt LPCWSTR wzQueryString,
563 __in_z_opt LPCWSTR wzHeader,
564 __out HINTERNET* phUrl
565 )
566{
567 HRESULT hr = S_OK;
568 DWORD dwRequestFlags = INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD;
569 LPWSTR sczResource = NULL;
570 HINTERNET hUrl = NULL;
571
572 if (INTERNET_SCHEME_HTTPS == scheme)
573 {
574 dwRequestFlags |= INTERNET_FLAG_SECURE;
575 }
576 else if (INTERNET_SCHEME_HTTP == scheme)
577 {
578 dwRequestFlags |= INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS;
579 }
580
581 // Allocate the resource name.
582 hr = StrAllocString(&sczResource, wzResource, 0);
583 DlExitOnFailure(hr, "Failed to allocate string for resource URI.");
584
585 if (wzQueryString && *wzQueryString)
586 {
587 hr = StrAllocConcat(&sczResource, wzQueryString, 0);
588 DlExitOnFailure(hr, "Failed to append query strong to resource from URI.");
589 }
590
591 // Open the request and add the header if provided.
592 hUrl = ::HttpOpenRequestW(hConnect, wzMethod, sczResource, NULL, NULL, DOWNLOAD_ENGINE_ACCEPT_TYPES, dwRequestFlags, NULL);
593 DlExitOnNullWithLastError(hUrl, hr, "Failed to open internet request.");
594
595 if (wzHeader && *wzHeader)
596 {
597 if (!::HttpAddRequestHeadersW(hUrl, wzHeader, static_cast<DWORD>(-1), HTTP_ADDREQ_FLAG_COALESCE))
598 {
599 DlExitWithLastError(hr, "Failed to add header to HTTP request.");
600 }
601 }
602
603 *phUrl = hUrl;
604 hUrl = NULL;
605
606LExit:
607 ReleaseInternet(hUrl);
608 ReleaseStr(sczResource);
609 return hr;
610}
611
612static HRESULT SendRequest(
613 __in HINTERNET hUrl,
614 __inout_z LPWSTR* psczUrl,
615 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
616 __out BOOL* pfRetry,
617 __out BOOL* pfRangesAccepted
618 )
619{
620 HRESULT hr = S_OK;
621 BOOL fRetrySend = FALSE;
622 LONG lCode = 0;
623
624 do
625 {
626 fRetrySend = FALSE;
627
628 if (!::HttpSendRequestW(hUrl, NULL, 0, NULL, 0))
629 {
630 hr = HRESULT_FROM_WIN32(::GetLastError()); // remember the error that occurred and log it.
631 LogErrorString(hr, "Failed to send request to URL: %ls, trying to process HTTP status code anyway.", *psczUrl);
632
633 // Try to get the HTTP status code and, if good, handle via the switch statement below but if it
634 // fails return the error code from the send request above as the result of the function.
635 HRESULT hrQueryStatusCode = InternetQueryInfoNumber(hUrl, HTTP_QUERY_STATUS_CODE, &lCode);
636 DlExitOnFailure(hrQueryStatusCode, "Failed to get HTTP status code for failed request to URL: %ls", *psczUrl);
637 }
638 else // get the http status code.
639 {
640 hr = InternetQueryInfoNumber(hUrl, HTTP_QUERY_STATUS_CODE, &lCode);
641 DlExitOnFailure(hr, "Failed to get HTTP status code for request to URL: %ls", *psczUrl);
642 }
643
644 switch (lCode)
645 {
646 case 200: // OK but range requests don't work.
647 *pfRangesAccepted = FALSE;
648 hr = S_OK;
649 break;
650
651 case 206: // Partial content means that range requests work!
652 *pfRangesAccepted = TRUE;
653 hr = S_OK;
654 break;
655
656 // redirection cases
657 case 301: __fallthrough; // file moved
658 case 302: __fallthrough; // temporary
659 case 303: // redirect method
660 hr = InternetQueryInfoString(hUrl, HTTP_QUERY_CONTENT_LOCATION, psczUrl);
661 DlExitOnFailure(hr, "Failed to get redirect url: %ls", *psczUrl);
662
663 *pfRetry = TRUE;
664 break;
665
666 // error cases
667 case 400: // bad request
668 hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
669 break;
670
671 case 401: __fallthrough; // unauthorized
672 case 407: __fallthrough; // proxy unauthorized
673 hr = AuthenticationRequired(hUrl, lCode, pAuthenticate, &fRetrySend, pfRetry);
674 break;
675
676 case 403: // forbidden
677 hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
678 break;
679
680 case 404: // file not found
681 case 410: // gone
682 hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
683 break;
684
685 case 405: // method not allowed
686 hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
687 break;
688
689 case 408: __fallthrough; // request timedout
690 case 504: // gateway timeout
691 hr = HRESULT_FROM_WIN32(WAIT_TIMEOUT);
692 break;
693
694 case 414: // request URI too long
695 hr = CO_E_PATHTOOLONG;
696 break;
697
698 case 502: __fallthrough; // server (through a gateway) was not found
699 case 503: // server unavailable
700 hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
701 break;
702
703 case 418: // I'm a teapot.
704 default:
705 // If the request failed and the HTTP status code was invalid (but wininet gave us a number anyway)
706 // do not overwrite the error code from the failed request. Otherwise, the error was unexpected.
707 if (SUCCEEDED(hr))
708 {
709 hr = E_UNEXPECTED;
710 }
711
712 LogErrorString(hr, "Unknown HTTP status code %d, returned from URL: %ls", lCode, *psczUrl);
713 break;
714 }
715 } while (fRetrySend);
716
717LExit:
718 return hr;
719}
720
721static HRESULT AuthenticationRequired(
722 __in HINTERNET hUrl,
723 __in long lHttpCode,
724 __in_opt DOWNLOAD_AUTHENTICATION_CALLBACK* pAuthenticate,
725 __out BOOL* pfRetrySend,
726 __out BOOL* pfRetry
727 )
728{
729 Assert(401 == lHttpCode || 407 == lHttpCode);
730
731 HRESULT hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
732 *pfRetrySend = FALSE;
733 *pfRetry = FALSE;
734
735 if (pAuthenticate && pAuthenticate->pfnAuthenticate)
736 {
737 hr = (*pAuthenticate->pfnAuthenticate)(pAuthenticate->pv, hUrl, lHttpCode, pfRetrySend, pfRetry);
738 }
739
740 return hr;
741}
742
743
744static HRESULT DownloadGetResumePath(
745 __in_z LPCWSTR wzPayloadWorkingPath,
746 __deref_out_z LPWSTR* psczResumePath
747 )
748{
749 HRESULT hr = S_OK;
750
751 hr = StrAllocFormatted(psczResumePath, L"%ls.R", wzPayloadWorkingPath);
752 DlExitOnFailure(hr, "Failed to create resume path.");
753
754LExit:
755 return hr;
756}
757
758static HRESULT DownloadSendProgressCallback(
759 __in DOWNLOAD_CACHE_CALLBACK* pCallback,
760 __in DWORD64 dw64Progress,
761 __in DWORD64 dw64Total,
762 __in HANDLE hDestinationFile
763 )
764{
765 static LARGE_INTEGER LARGE_INTEGER_ZERO = { };
766
767 HRESULT hr = S_OK;
768 DWORD dwResult = PROGRESS_CONTINUE;
769 LARGE_INTEGER liTotalSize = { };
770 LARGE_INTEGER liTotalTransferred = { };
771
772 if (pCallback->pfnProgress)
773 {
774 liTotalSize.QuadPart = dw64Total;
775 liTotalTransferred.QuadPart = dw64Progress;
776
777 dwResult = (*pCallback->pfnProgress)(liTotalSize, liTotalTransferred, LARGE_INTEGER_ZERO, LARGE_INTEGER_ZERO, 1, CALLBACK_CHUNK_FINISHED, INVALID_HANDLE_VALUE, hDestinationFile, pCallback->pv);
778 switch (dwResult)
779 {
780 case PROGRESS_CONTINUE:
781 hr = S_OK;
782 break;
783
784 case PROGRESS_CANCEL: __fallthrough; // TODO: should cancel and stop be treated differently?
785 case PROGRESS_STOP:
786 hr = HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT);
787 DlExitOnRootFailure(hr, "UX aborted on download progress.");
788
789 case PROGRESS_QUIET: // Not actually an error, just an indication to the caller to stop requesting progress.
790 pCallback->pfnProgress = NULL;
791 hr = S_OK;
792 break;
793
794 default:
795 hr = E_UNEXPECTED;
796 DlExitOnRootFailure(hr, "Invalid return code from progress routine.");
797 }
798 }
799
800LExit:
801 return hr;
802}