aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Hall <r.sean.hall@gmail.com>2022-02-18 17:56:31 -0600
committerSean Hall <r.sean.hall@gmail.com>2022-02-19 11:53:06 -0700
commit28c0a27ddf03dcf07a11c291699428a32f381fbc (patch)
tree443b0cf0413d3bff3d70bde53d702372cea5caf6
parent7750404222a7c5bb6543dd246c2cce0f7c097d8d (diff)
downloadwix-28c0a27ddf03dcf07a11c291699428a32f381fbc.tar.gz
wix-28c0a27ddf03dcf07a11c291699428a32f381fbc.tar.bz2
wix-28c0a27ddf03dcf07a11c291699428a32f381fbc.zip
Handle missing content length with range request and empty files.
Add test for server without range request support.
-rw-r--r--src/libs/dutil/WixToolset.DUtil/dlutil.cpp46
-rw-r--r--src/test/burn/TestData/CacheTests/BundleC/BundleC.wxs2
-rw-r--r--src/test/burn/WixToolsetTest.BurnE2E/CacheTests.cs81
-rw-r--r--src/test/burn/WixToolsetTest.BurnE2E/IWebServer.cs1
-rw-r--r--src/test/burn/WixToolsetTest.BurnE2E/WebServer/CoreOwinWebServer.cs44
5 files changed, 161 insertions, 13 deletions
diff --git a/src/libs/dutil/WixToolset.DUtil/dlutil.cpp b/src/libs/dutil/WixToolset.DUtil/dlutil.cpp
index f6b793b2..9c704ee8 100644
--- a/src/libs/dutil/WixToolset.DUtil/dlutil.cpp
+++ b/src/libs/dutil/WixToolset.DUtil/dlutil.cpp
@@ -295,7 +295,10 @@ static HRESULT DownloadResource(
295 HANDLE hPayloadFile = INVALID_HANDLE_VALUE; 295 HANDLE hPayloadFile = INVALID_HANDLE_VALUE;
296 DWORD cbMaxData = 64 * 1024; // 64 KB 296 DWORD cbMaxData = 64 * 1024; // 64 KB
297 BYTE* pbData = NULL; 297 BYTE* pbData = NULL;
298 BOOL fRangeRequestsAccepted = TRUE; 298 BOOL fUseRangeRequest = TRUE;
299 BOOL fRangeRequestsAccepted = FALSE;
300 BOOL fRequestedRangeRequest = FALSE;
301 BOOL fInvalidRangeRequestResponse = FALSE;
299 LPWSTR sczRangeRequestHeader = NULL; 302 LPWSTR sczRangeRequestHeader = NULL;
300 HINTERNET hConnect = NULL; 303 HINTERNET hConnect = NULL;
301 HINTERNET hUrl = NULL; 304 HINTERNET hUrl = NULL;
@@ -315,10 +318,19 @@ static HRESULT DownloadResource(
315 // are not supported we'll have to start over and accept the fact that we only get one shot 318 // are not supported we'll have to start over and accept the fact that we only get one shot
316 // downloading the file however big it is. Hopefully, not more than 2 GB since wininet doesn't 319 // downloading the file however big it is. Hopefully, not more than 2 GB since wininet doesn't
317 // like files that big. 320 // like files that big.
318 while (fRangeRequestsAccepted && (0 == dw64ResourceLength || dw64ResumeOffset < dw64ResourceLength)) 321 for (;;)
319 { 322 {
320 hr = AllocateRangeRequestHeader(dw64ResumeOffset, 0 == dw64ResourceLength ? dw64AuthoredResourceLength : dw64ResourceLength, &sczRangeRequestHeader); 323 fInvalidRangeRequestResponse = FALSE;
321 DlExitOnFailure(hr, "Failed to allocate range request header."); 324
325 if (fUseRangeRequest)
326 {
327 hr = AllocateRangeRequestHeader(dw64ResumeOffset, 0 == dw64ResourceLength ? dw64AuthoredResourceLength : dw64ResourceLength, &sczRangeRequestHeader);
328 DlExitOnFailure(hr, "Failed to allocate range request header.");
329 }
330 else
331 {
332 ReleaseNullStr(sczRangeRequestHeader);
333 }
322 334
323 ReleaseNullInternet(hConnect); 335 ReleaseNullInternet(hConnect);
324 ReleaseNullInternet(hUrl); 336 ReleaseNullInternet(hUrl);
@@ -326,6 +338,13 @@ static HRESULT DownloadResource(
326 hr = MakeRequest(hSession, psczUrl, L"GET", sczRangeRequestHeader, wzUser, wzPassword, pAuthenticate, &hConnect, &hUrl, &fRangeRequestsAccepted); 338 hr = MakeRequest(hSession, psczUrl, L"GET", sczRangeRequestHeader, wzUser, wzPassword, pAuthenticate, &hConnect, &hUrl, &fRangeRequestsAccepted);
327 DlExitOnFailure(hr, "Failed to request URL for download: %ls", *psczUrl); 339 DlExitOnFailure(hr, "Failed to request URL for download: %ls", *psczUrl);
328 340
341 fRequestedRangeRequest = sczRangeRequestHeader && *sczRangeRequestHeader;
342
343 if (fRequestedRangeRequest && !fRangeRequestsAccepted)
344 {
345 LogStringLine(REPORT_VERBOSE, "Range request not supported for URL: %ls", *psczUrl);
346 }
347
329 // If we didn't get the size of the resource from the initial "HEAD" request 348 // If we didn't get the size of the resource from the initial "HEAD" request
330 // then let's try to get the size from this "GET" request. 349 // then let's try to get the size from this "GET" request.
331 if (0 == dw64ResourceLength) 350 if (0 == dw64ResourceLength)
@@ -337,23 +356,36 @@ static HRESULT DownloadResource(
337 } 356 }
338 else // server didn't tell us the resource length. 357 else // server didn't tell us the resource length.
339 { 358 {
359 LogStringLine(REPORT_VERBOSE, "Content-Length not returned for URL: %ls", *psczUrl);
360
340 // Fallback to the authored size of the resource. However, since we 361 // Fallback to the authored size of the resource. However, since we
341 // don't really know the size on the server, don't try to use 362 // don't really know the size on the server, don't try to use
342 // range requests either. 363 // range requests either.
343 dw64ResourceLength = dw64AuthoredResourceLength; 364 dw64ResourceLength = dw64AuthoredResourceLength;
365 fInvalidRangeRequestResponse = fRequestedRangeRequest;
344 fRangeRequestsAccepted = FALSE; 366 fRangeRequestsAccepted = FALSE;
345 } 367 }
346 } 368 }
347 369
348 // If we just tried to do a range request and found out that it isn't supported, start over. 370 // If we just tried to do a range request and found out that it isn't supported, ignore the offset.
349 if (!fRangeRequestsAccepted) 371 if (fRequestedRangeRequest && !fRangeRequestsAccepted)
350 { 372 {
351 // TODO: log a message that the server did not accept range requests.
352 dw64ResumeOffset = 0; 373 dw64ResumeOffset = 0;
374 fUseRangeRequest = FALSE;
375 }
376
377 if (fInvalidRangeRequestResponse)
378 {
379 continue;
353 } 380 }
354 381
355 hr = WriteToFile(hUrl, hPayloadFile, &dw64ResumeOffset, hResumeFile, dw64ResourceLength, pbData, cbMaxData, pCache); 382 hr = WriteToFile(hUrl, hPayloadFile, &dw64ResumeOffset, hResumeFile, dw64ResourceLength, pbData, cbMaxData, pCache);
356 DlExitOnFailure(hr, "Failed while reading from internet and writing to: %ls", wzDestinationPath); 383 DlExitOnFailure(hr, "Failed while reading from internet and writing to: %ls", wzDestinationPath);
384
385 if (!fUseRangeRequest || dw64ResumeOffset >= dw64ResourceLength)
386 {
387 break;
388 }
357 } 389 }
358 390
359LExit: 391LExit:
diff --git a/src/test/burn/TestData/CacheTests/BundleC/BundleC.wxs b/src/test/burn/TestData/CacheTests/BundleC/BundleC.wxs
index ca21cc6e..865f7578 100644
--- a/src/test/burn/TestData/CacheTests/BundleC/BundleC.wxs
+++ b/src/test/burn/TestData/CacheTests/BundleC/BundleC.wxs
@@ -5,7 +5,7 @@
5 <Fragment> 5 <Fragment>
6 <PackageGroup Id="BundlePackages"> 6 <PackageGroup Id="BundlePackages">
7 <MsiPackage Id="PackageA" SourceFile="$(var.PackageA.TargetPath)"> 7 <MsiPackage Id="PackageA" SourceFile="$(var.PackageA.TargetPath)">
8 <Payload SourceFile="fivegb.file" Compressed="no" /> 8 <Payload SourceFile="fivegb.file" Compressed="no" DownloadUrl="$(var.WebServerBaseUrl)BundleC/{2}" />
9 </MsiPackage> 9 </MsiPackage>
10 </PackageGroup> 10 </PackageGroup>
11 </Fragment> 11 </Fragment>
diff --git a/src/test/burn/WixToolsetTest.BurnE2E/CacheTests.cs b/src/test/burn/WixToolsetTest.BurnE2E/CacheTests.cs
index 4a3ffa2e..ee61baff 100644
--- a/src/test/burn/WixToolsetTest.BurnE2E/CacheTests.cs
+++ b/src/test/burn/WixToolsetTest.BurnE2E/CacheTests.cs
@@ -15,8 +15,7 @@ namespace WixToolsetTest.BurnE2E
15 { 15 {
16 public CacheTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } 16 public CacheTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { }
17 17
18 [Fact] 18 private bool Is5GBFileAvailable()
19 public void CanCache5GBFile()
20 { 19 {
21 // Recreate the 5GB payload to avoid having to copy it to the VM to run the tests. 20 // Recreate the 5GB payload to avoid having to copy it to the VM to run the tests.
22 const long FiveGB = 5_368_709_120; 21 const long FiveGB = 5_368_709_120;
@@ -28,8 +27,8 @@ namespace WixToolsetTest.BurnE2E
28 var drive = new DriveInfo(targetFilePath.Substring(0, 1)); 27 var drive = new DriveInfo(targetFilePath.Substring(0, 1));
29 if (drive.AvailableFreeSpace < FiveGB + OneGB) 28 if (drive.AvailableFreeSpace < FiveGB + OneGB)
30 { 29 {
31 Console.WriteLine("Skipping CanCache5GBFile() test because there is not enough disk space available to run the test."); 30 Console.WriteLine($"Skipping {this.TestContext.TestName} because there is not enough disk space available to run the test.");
32 return; 31 return false;
33 } 32 }
34 33
35 if (!File.Exists(targetFilePath)) 34 if (!File.Exists(targetFilePath))
@@ -42,6 +41,17 @@ namespace WixToolsetTest.BurnE2E
42 testTool.Run(true); 41 testTool.Run(true);
43 } 42 }
44 43
44 return true;
45 }
46
47 [Fact]
48 public void CanCache5GBFile()
49 {
50 if (!this.Is5GBFileAvailable())
51 {
52 return;
53 }
54
45 var packageA = this.CreatePackageInstaller("PackageA"); 55 var packageA = this.CreatePackageInstaller("PackageA");
46 var bundleC = this.CreateBundleInstaller("BundleC"); 56 var bundleC = this.CreateBundleInstaller("BundleC");
47 57
@@ -53,6 +63,69 @@ namespace WixToolsetTest.BurnE2E
53 packageA.VerifyInstalled(true); 63 packageA.VerifyInstalled(true);
54 } 64 }
55 65
66 private string Cache5GBFileFromDownload(bool disableRangeRequests)
67 {
68 if (!this.Is5GBFileAvailable())
69 {
70 return null;
71 }
72
73 var packageA = this.CreatePackageInstaller("PackageA");
74 var bundleC = this.CreateBundleInstaller("BundleC");
75 var webServer = this.CreateWebServer();
76
77 webServer.AddFiles(new Dictionary<string, string>
78 {
79 { "/BundleC/fivegb.file", Path.Combine(this.TestContext.TestDataFolder, "fivegb.file") },
80 { "/BundleC/PackageA.msi", Path.Combine(this.TestContext.TestDataFolder, "PackageA.msi") },
81 });
82 webServer.DisableRangeRequests = disableRangeRequests;
83 webServer.Start();
84
85 using var dfs = new DisposableFileSystem();
86 var separateDirectory = dfs.GetFolder(true);
87
88 // Manually copy bundle to separate directory and then run from there so the non-compressed payloads have to be resolved.
89 var bundleCFileInfo = new FileInfo(bundleC.Bundle);
90 var bundleCCopiedPath = Path.Combine(separateDirectory, bundleCFileInfo.Name);
91 bundleCFileInfo.CopyTo(bundleCCopiedPath);
92
93 packageA.VerifyInstalled(false);
94
95 var installLogPath = bundleC.Install(bundleCCopiedPath);
96 bundleC.VerifyRegisteredAndInPackageCache();
97
98 packageA.VerifyInstalled(true);
99
100 return installLogPath;
101 }
102
103 [Fact]
104 public void CanCache5GBFileFromDownloadWithRangeRequestSupport()
105 {
106 var logPath = this.Cache5GBFileFromDownload(false);
107 if (logPath == null)
108 {
109 return;
110 }
111
112 Assert.False(LogVerifier.MessageInLogFile(logPath, "Range request not supported for URL: http://localhost:9999/e2e/BundleC/fivegb.file"));
113 Assert.True(LogVerifier.MessageInLogFile(logPath, "Content-Length not returned for URL: http://localhost:9999/e2e/BundleC/fivegb.file"));
114 }
115
116 [Fact]
117 public void CanCache5GBFileFromDownloadWithoutRangeRequestSupport()
118 {
119 var logPath = this.Cache5GBFileFromDownload(true);
120 if (logPath == null)
121 {
122 return;
123 }
124
125 Assert.True(LogVerifier.MessageInLogFile(logPath, "Range request not supported for URL: http://localhost:9999/e2e/BundleC/fivegb.file"));
126 Assert.True(LogVerifier.MessageInLogFile(logPath, "Content-Length not returned for URL: http://localhost:9999/e2e/BundleC/fivegb.file"));
127 }
128
56 [Fact] 129 [Fact]
57 public void CanDownloadPayloadsFromMissingAttachedContainer() 130 public void CanDownloadPayloadsFromMissingAttachedContainer()
58 { 131 {
diff --git a/src/test/burn/WixToolsetTest.BurnE2E/IWebServer.cs b/src/test/burn/WixToolsetTest.BurnE2E/IWebServer.cs
index 3846ae94..a4d46d48 100644
--- a/src/test/burn/WixToolsetTest.BurnE2E/IWebServer.cs
+++ b/src/test/burn/WixToolsetTest.BurnE2E/IWebServer.cs
@@ -8,6 +8,7 @@ namespace WixToolsetTest.BurnE2E
8 public interface IWebServer : IDisposable 8 public interface IWebServer : IDisposable
9 { 9 {
10 bool DisableHeadResponses { get; set; } 10 bool DisableHeadResponses { get; set; }
11 bool DisableRangeRequests { get; set; }
11 12
12 /// <summary> 13 /// <summary>
13 /// Registers a collection of relative URLs (the key) with its absolute path to the file (the value). 14 /// Registers a collection of relative URLs (the key) with its absolute path to the file (the value).
diff --git a/src/test/burn/WixToolsetTest.BurnE2E/WebServer/CoreOwinWebServer.cs b/src/test/burn/WixToolsetTest.BurnE2E/WebServer/CoreOwinWebServer.cs
index 025e01ff..8066cea5 100644
--- a/src/test/burn/WixToolsetTest.BurnE2E/WebServer/CoreOwinWebServer.cs
+++ b/src/test/burn/WixToolsetTest.BurnE2E/WebServer/CoreOwinWebServer.cs
@@ -5,8 +5,10 @@ namespace WixToolsetTest.BurnE2E
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.IO; 7 using System.IO;
8 using System.Threading.Tasks;
8 using Microsoft.AspNetCore.Builder; 9 using Microsoft.AspNetCore.Builder;
9 using Microsoft.AspNetCore.Hosting; 10 using Microsoft.AspNetCore.Hosting;
11 using Microsoft.AspNetCore.Http;
10 using Microsoft.AspNetCore.StaticFiles; 12 using Microsoft.AspNetCore.StaticFiles;
11 using Microsoft.Extensions.FileProviders; 13 using Microsoft.Extensions.FileProviders;
12 using Microsoft.Extensions.FileProviders.Physical; 14 using Microsoft.Extensions.FileProviders.Physical;
@@ -15,11 +17,14 @@ namespace WixToolsetTest.BurnE2E
15 17
16 public class CoreOwinWebServer : IWebServer, IFileProvider 18 public class CoreOwinWebServer : IWebServer, IFileProvider
17 { 19 {
20 const string StaticFileBasePath = "/e2e";
21
18 private Dictionary<string, string> PhysicalPathsByRelativeUrl { get; } = new Dictionary<string, string>(); 22 private Dictionary<string, string> PhysicalPathsByRelativeUrl { get; } = new Dictionary<string, string>();
19 23
20 private IHost WebHost { get; set; } 24 private IHost WebHost { get; set; }
21 25
22 public bool DisableHeadResponses { get; set; } 26 public bool DisableHeadResponses { get; set; }
27 public bool DisableRangeRequests { get; set; }
23 28
24 public void AddFiles(Dictionary<string, string> physicalPathsByRelativeUrl) 29 public void AddFiles(Dictionary<string, string> physicalPathsByRelativeUrl)
25 { 30 {
@@ -38,10 +43,11 @@ namespace WixToolsetTest.BurnE2E
38 webBuilder.UseUrls("http://localhost:9999"); 43 webBuilder.UseUrls("http://localhost:9999");
39 webBuilder.Configure(appBuilder => 44 webBuilder.Configure(appBuilder =>
40 { 45 {
46 appBuilder.Use(this.CustomStaticFileMiddleware);
41 appBuilder.UseStaticFiles(new StaticFileOptions 47 appBuilder.UseStaticFiles(new StaticFileOptions
42 { 48 {
43 FileProvider = this, 49 FileProvider = this,
44 RequestPath = "/e2e", 50 RequestPath = StaticFileBasePath,
45 ServeUnknownFileTypes = true, 51 ServeUnknownFileTypes = true,
46 OnPrepareResponse = this.OnPrepareStaticFileResponse, 52 OnPrepareResponse = this.OnPrepareStaticFileResponse,
47 }); 53 });
@@ -51,6 +57,42 @@ namespace WixToolsetTest.BurnE2E
51 this.WebHost.Start(); 57 this.WebHost.Start();
52 } 58 }
53 59
60 private async Task CustomStaticFileMiddleware(HttpContext context, Func<Task> next)
61 {
62 if (!this.DisableRangeRequests || (!HttpMethods.IsGet(context.Request.Method) && !HttpMethods.IsHead(context.Request.Method)))
63 {
64 await next();
65 return;
66 }
67
68 // Only send Content-Length header.
69 // Don't support range requests.
70 // https://github.com/dotnet/aspnetcore/blob/60abfafe32a4692f9dc4a172665524f163b10012/src/Middleware/StaticFiles/src/StaticFileMiddleware.cs
71 if (!context.Request.Path.StartsWithSegments(StaticFileBasePath, out var subpath))
72 {
73 context.Response.StatusCode = 404;
74 return;
75 }
76
77 var fileInfo = this.GetFileInfo(subpath);
78 if (!fileInfo.Exists)
79 {
80 context.Response.StatusCode = 404;
81 return;
82 }
83
84 var responseHeaders = context.Response.GetTypedHeaders();
85 var fileLength = fileInfo.Length;
86 responseHeaders.ContentLength = fileLength;
87
88 this.OnPrepareStaticFileResponse(new StaticFileResponseContext(context, fileInfo));
89
90 if (HttpMethods.IsGet(context.Request.Method))
91 {
92 await context.Response.SendFileAsync(fileInfo, 0, fileLength);
93 }
94 }
95
54 private void OnPrepareStaticFileResponse(StaticFileResponseContext obj) 96 private void OnPrepareStaticFileResponse(StaticFileResponseContext obj)
55 { 97 {
56 if (this.DisableHeadResponses && obj.Context.Request.Method == "HEAD") 98 if (this.DisableHeadResponses && obj.Context.Request.Method == "HEAD")