diff --git a/src/Components/WebAssembly/Build/src/Tasks/BlazorGetFileHash.cs b/src/Components/WebAssembly/Build/src/Tasks/BlazorGetFileHash.cs new file mode 100644 index 0000000000..f10f53a748 --- /dev/null +++ b/src/Components/WebAssembly/Build/src/Tasks/BlazorGetFileHash.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.Build.Tasks +{ + /// + /// Computes the checksum for a single file. + /// + public sealed class BlazorGetFileHash : Task + { + internal const string _defaultFileHashAlgorithm = "SHA256"; + internal const string _hashEncodingBase64 = "base64"; + + /// + /// The files to be hashed. + /// + [Required] + public ITaskItem[] Files { get; set; } + + /// + /// The algorithm. Allowed values: SHA256, SHA384, SHA512. Default = SHA256. + /// + public string Algorithm { get; set; } = _defaultFileHashAlgorithm; + + /// + /// The metadata name where the hash is stored in each item. Defaults to "FileHash". + /// + public string MetadataName { get; set; } = "FileHash"; + + /// + /// The encoding to use for generated hashs. Defaults to "hex". Allowed values = "base64". + /// + public string HashEncoding { get; set; } = _hashEncodingBase64; + + /// + /// The hash of the file. This is only set if there was one item group passed in. + /// + [Output] + public string Hash { get; set; } + + /// + /// The input files with additional metadata set to include the file hash. + /// + [Output] + public ITaskItem[] Items { get; set; } + + public override bool Execute() + { + if (!SupportsAlgorithm(Algorithm)) + { + Log.LogError("Unrecognized HashAlgorithm {0}", Algorithm); + return false; + } + + System.Threading.Tasks.Parallel.ForEach(Files, file => + { + if (!File.Exists(file.ItemSpec)) + { + Log.LogError("File not found '{0}", file.ItemSpec); + } + }); + + if (Log.HasLoggedErrors) + { + return false; + } + + System.Threading.Tasks.Parallel.ForEach(Files, file => + { + var hash = ComputeHash(Algorithm, file.ItemSpec); + file.SetMetadata("FileHashAlgorithm", Algorithm); + file.SetMetadata(MetadataName, EncodeHash(hash)); + }); + + Items = Files; + + if (Files.Length == 1) + { + Hash = Files[0].GetMetadata(MetadataName); + } + + return !Log.HasLoggedErrors; + } + + internal static string EncodeHash(byte[] hash) + { + return Convert.ToBase64String(hash); + } + + internal static bool SupportsAlgorithm(string algorithmName) + => _supportedAlgorithms.Contains(algorithmName); + + internal static byte[] ComputeHash(string algorithmName, string filePath) + { + using (var stream = File.OpenRead(filePath)) + using (var algorithm = CreateAlgorithm(algorithmName)) + { + return algorithm.ComputeHash(stream); + } + } + + private static readonly HashSet _supportedAlgorithms + = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "SHA256", + "SHA384", + "SHA512", + }; + + private static HashAlgorithm CreateAlgorithm(string algorithmName) + { + if (string.Equals(algorithmName, "SHA256", StringComparison.OrdinalIgnoreCase)) + { + return SHA256.Create(); + } + else if (string.Equals(algorithmName, "SHA384", StringComparison.OrdinalIgnoreCase)) + { + return SHA384.Create(); + } + else if (string.Equals(algorithmName, "SHA512", StringComparison.OrdinalIgnoreCase)) + { + return SHA512.Create(); + } + + throw new ArgumentOutOfRangeException(); + } + } +} \ No newline at end of file diff --git a/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.targets b/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.targets index 8c16457068..1b7614bbae 100644 --- a/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.targets +++ b/src/Components/WebAssembly/Build/src/targets/Blazor.MonoRuntime.targets @@ -1,4 +1,6 @@ + + true false @@ -153,9 +155,9 @@ <_ExistingBlazorOutputWithTargetPath Include="@(_BlazorOutputWithTargetPath)" Condition="Exists('%(FullPath)')" /> - + - + <_BlazorOutputWithIntegrity Include="@(_BlazorOutputWithHash)">