diff --git a/src/Uno.Wasm.Bootstrap/StaticWebAssetsResolverTask.cs b/src/Uno.Wasm.Bootstrap/StaticWebAssetsResolverTask.cs index 99d2c65a..724ac49e 100644 --- a/src/Uno.Wasm.Bootstrap/StaticWebAssetsResolverTask.cs +++ b/src/Uno.Wasm.Bootstrap/StaticWebAssetsResolverTask.cs @@ -1,4 +1,5 @@ -// ****************************************************************** + +// ****************************************************************** // Copyright � 2015-2022 Uno Platform inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +23,8 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Numerics; +using System.Security.Cryptography; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Uno.Wasm.Bootstrap.Extensions; @@ -70,20 +73,7 @@ public override bool Execute() Log.LogMessage(MessageImportance.Low, $"Found {filteredAssets.Length} filtered assets"); StaticWebAsset = - filteredAssets.Select(asset => - new TaskItem(asset, new Dictionary - { - ["SourceType"] = "Discovered", - ["SourceId"] = AssemblyName, - ["ContentRoot"] = fixedDistPath, - ["RelativePath"] = asset.Replace(fixedDistPath, ""), - ["BasePath"] = WebAppBasePath is { Length: > 0 } ? WebAppBasePath : "/", - ["AssetKind"] = "All", - ["AssetMode"] = "All", - ["AssetRole"] = "Primary", - ["OriginalItemSpec"] = asset, - } - )) + filteredAssets.Select(asset => new TaskItem(asset, GetMetadata(asset, fixedDistPath))) .ToArray(); } else @@ -93,4 +83,58 @@ public override bool Execute() return true; } + + private Dictionary GetMetadata(string asset, string fixedDistPath) + { + var dict = new Dictionary + { + ["SourceType"] = "Discovered", + ["SourceId"] = AssemblyName, + ["ContentRoot"] = fixedDistPath, + ["RelativePath"] = asset.Replace(fixedDistPath, ""), + ["BasePath"] = WebAppBasePath is { Length: > 0 } ? WebAppBasePath : "/", + ["AssetKind"] = "All", + ["AssetMode"] = "All", + ["AssetRole"] = "Primary", + ["OriginalItemSpec"] = asset, + }; + + var (fingerprint, integrity) = ComputeFingerprintAndIntegrity(asset, asset); + dict.Add("Fingerprint", fingerprint); + dict.Add("Integrity", integrity); + return dict; + } + + // https://github.com/dotnet/sdk/blob/cc17704acfbee4b2ef49a82aa6f65aaa9cafffef/src/StaticWebAssetsSdk/Tasks/Data/StaticWebAsset.cs#L233-L248 + private static (string fingerprint, string integrity) ComputeFingerprintAndIntegrity(string identity, string originalItemSpec) + { + using var file = File.Exists(identity) ? + File.OpenRead(identity) : + (File.Exists(originalItemSpec) ? + File.OpenRead(originalItemSpec) : + throw new InvalidOperationException($"No file exists for the asset at either location '{identity}' or '{originalItemSpec}'.")); + +#if NET6_0_OR_GREATER + var hash = SHA256.HashData(file); +#else + using var sha = SHA256.Create(); + var hash = sha.ComputeHash(file); +#endif + return (ToBase36(hash), Convert.ToBase64String(hash)); + } + + private static string ToBase36(byte[] hash) + { + const string chars = "0123456789abcdefghijklmnopqrstuvwxyz"; + + var result = new char[10]; + var dividend = BigInteger.Abs(new BigInteger(hash.AsSpan().Slice(0, 9).ToArray())); + for (var i = 0; i < 10; i++) + { + dividend = BigInteger.DivRem(dividend, 36, out var remainder); + result[i] = chars[(int)remainder]; + } + + return new string(result); + } }