From de177e3a80cfa8cfa869d8164e19dfb3b82e28b7 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Mon, 16 Nov 2020 22:56:18 -0800 Subject: [PATCH] Pass file path with runtime version to hash generator (#27835) Description This pull request addresses an issue reported by users in #27752 in which the integrity checks that occur in the browser for assemblies loaded by a Blazor WebAssembly application incorrectly fail after a user upgrades their application from one version to another. This occurs because our MSBuild targets don't correctly update the compressed assemblies when a user upgrades, which results in the non-compressed assemblies and integrity hash pointing to the new version but the compressed assembly pointing to the old version which causes an integrity check failure. Technical Description The GzipCompression task iterates through a list of provided FilesToCompress and determines whether or not a file needs to be updated by checking to see if the input file is older than the compressed file that already exists in the intermediate output path. aspnetcore/src/Components/WebAssembly/Sdk/src/GZipCompress.cs Lines 45 to 50 in 45540f7 if (File.Exists(outputRelativePath) && File.GetLastWriteTimeUtc(inputPath) < File.GetLastWriteTimeUtc(outputRelativePath)) { // Incrementalism. If input source doesn't exist or it exists and is not newer than the expected output, do nothing. Log.LogMessage(MessageImportance.Low, $"Skipping '{inputPath}' because '{outputRelativePath}' is newer than '{inputPath}'."); return; } The outputRelativePath used in the comparison above is a hashed value generated from the the RelativePath which is set to wwwroot/_framework/Microsoft.CSharp.dll for example. If a user changes from version 5.0-rc2 to 5.0 of a package, then the RelativePath will be the same whereas the FullPath will be ~/.nuget/packages/microsoft.netcore.app.runtime.browser-wasm/5.0.0/runtimes/browser-wasm/lib/net5.0/Microsoft.CSharp.dll compared to /Users/captainsafia/.nuget/packages/microsoft.netcore.app.runtime.browser-wasm/5.0.0-rc.2.20475.5/runtimes/browser-wasm/lib/net5.0/Microsoft.CSharp.dll. By passing the FullPath we are able to account for the package version in the generated output which will cause a unique hash to be generated for different package versions and the File.Exists check in the conditional to fail and result in the new gzipped outputs being generated as expected. Customer Impact This bug was reported by multiple customers after the release of .NET 5. The bug makes the upgrade experience between .NET versions a lot rougher since users run into unexpected exceptions in their apps at runtime. Viable workarounds for this include running dotnet clean before building the project after an upgrade. Regression? This is not a regression, but the issue is more serious since users are upgrading from Blazor WASM 3.2 to Blazor WASM 5 or from a 5.0 RC to the RTM. Risk The risk associated with this change is relatively slim, because: Manually validation was completed The behavior implemented in the changeset mimicks what we already do in the Brotli compression The impact area is only limited to Blazor WASM apps running in development --- src/Components/WebAssembly/Sdk/src/GZipCompress.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Components/WebAssembly/Sdk/src/GZipCompress.cs b/src/Components/WebAssembly/Sdk/src/GZipCompress.cs index 4b8a0c542a..14ae1ae1c8 100644 --- a/src/Components/WebAssembly/Sdk/src/GZipCompress.cs +++ b/src/Components/WebAssembly/Sdk/src/GZipCompress.cs @@ -32,26 +32,26 @@ namespace Microsoft.NET.Sdk.BlazorWebAssembly System.Threading.Tasks.Parallel.For(0, FilesToCompress.Length, i => { var file = FilesToCompress[i]; - var inputPath = file.ItemSpec; + var inputFullPath = file.GetMetadata("FullPath"); var relativePath = file.GetMetadata("RelativePath"); var outputRelativePath = Path.Combine( OutputDirectory, - BrotliCompress.CalculateTargetPath(relativePath, ".gz")); + BrotliCompress.CalculateTargetPath(inputFullPath, ".gz")); var outputItem = new TaskItem(outputRelativePath); outputItem.SetMetadata("RelativePath", relativePath + ".gz"); CompressedFiles[i] = outputItem; - if (File.Exists(outputRelativePath) && File.GetLastWriteTimeUtc(inputPath) < File.GetLastWriteTimeUtc(outputRelativePath)) + if (File.Exists(outputRelativePath) && File.GetLastWriteTimeUtc(inputFullPath) < File.GetLastWriteTimeUtc(outputRelativePath)) { // Incrementalism. If input source doesn't exist or it exists and is not newer than the expected output, do nothing. - Log.LogMessage(MessageImportance.Low, $"Skipping '{inputPath}' because '{outputRelativePath}' is newer than '{inputPath}'."); + Log.LogMessage(MessageImportance.Low, $"Skipping '{file.ItemSpec}' because '{outputRelativePath}' is newer than '{file.ItemSpec}'."); return; } try { - using var sourceStream = File.OpenRead(inputPath); + using var sourceStream = File.OpenRead(file.ItemSpec); using var fileStream = File.Create(outputRelativePath); using var stream = new GZipStream(fileStream, CompressionLevel.Optimal);