diff --git a/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj b/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj index c7a311e87358ff..11eac88f6dca6b 100644 --- a/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj +++ b/src/mono/sample/wasm/browser-advanced/Wasm.Advanced.Sample.csproj @@ -3,6 +3,7 @@ true true + true -s USE_CLOSURE_COMPILER=1 -s LEGACY_GL_EMULATION=1 -lGL -lSDL -lidbfs.js diff --git a/src/mono/sample/wasm/browser-advanced/index.html b/src/mono/sample/wasm/browser-advanced/index.html index b5c8649a6d7e45..b4cefe75f40724 100644 --- a/src/mono/sample/wasm/browser-advanced/index.html +++ b/src/mono/sample/wasm/browser-advanced/index.html @@ -12,7 +12,7 @@ - + diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 38544d0d94894c..30b43d61c84124 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -74,7 +74,7 @@ export function get_preferred_icu_asset(): string | null { return OTHERS; } -export function shouldLoadIcuAsset(asset : AssetEntryInternal, preferredIcuAsset: string | null) : boolean{ +export function shouldLoadIcuAsset(asset: AssetEntryInternal, preferredIcuAsset: string | null): boolean { return !(asset.behavior == "icu" && asset.name != preferredIcuAsset); } @@ -293,6 +293,14 @@ async function start_asset_download_sources(asset: AssetEntryInternal): Promise< return response; } catch (err) { + if (!response) { + response = { + ok: false, + url: attemptUrl, + status: 0, + statusText: "" + err, + } as any; + } continue; //next source } } diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index bcfce611388fee..d6e84c710f1c41 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -132,6 +132,10 @@ type MonoConfig = { * initial number of workers to add to the emscripten pthread pool */ pthreadPoolSize?: number; + /** + * hash of assets + */ + assetsHash?: string; }; interface ResourceRequest { name: string; diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index 1de2fc6998912b..5896e9736f9242 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -119,6 +119,10 @@ export type MonoConfig = { * initial number of workers to add to the emscripten pthread pool */ pthreadPoolSize?: number, + /** + * hash of assets + */ + assetsHash?: string, }; export type MonoConfigInternal = MonoConfig & { diff --git a/src/tasks/AndroidAppBuilder/AndroidAppBuilder.csproj b/src/tasks/AndroidAppBuilder/AndroidAppBuilder.csproj index 3dd38caa3c8809..69c1877a575cfc 100644 --- a/src/tasks/AndroidAppBuilder/AndroidAppBuilder.csproj +++ b/src/tasks/AndroidAppBuilder/AndroidAppBuilder.csproj @@ -5,7 +5,7 @@ true false enable - $(NoWarn),CA1050 + $(NoWarn),CA1050,CA1850 diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.csproj b/src/tasks/AotCompilerTask/MonoAOTCompiler.csproj index 28e5cc265c80aa..e76730b5aeca07 100644 --- a/src/tasks/AotCompilerTask/MonoAOTCompiler.csproj +++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.csproj @@ -5,7 +5,7 @@ true false enable - $(NoWarn),CA1050 + $(NoWarn),CA1050,CA1850 $(NoWarn),CS8604,CS8602 diff --git a/src/tasks/AppleAppBuilder/AppleAppBuilder.csproj b/src/tasks/AppleAppBuilder/AppleAppBuilder.csproj index 75c898c560c0e3..61f3cc89e3c61b 100644 --- a/src/tasks/AppleAppBuilder/AppleAppBuilder.csproj +++ b/src/tasks/AppleAppBuilder/AppleAppBuilder.csproj @@ -5,7 +5,7 @@ true false enable - $(NoWarn),CA1050 + $(NoWarn),CA1050,CA1850 diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index 91e5b77c1e198e..17b915f330bae5 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -3,9 +3,12 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; +using System.Reflection.PortableExecutable; +using System.Reflection.Metadata; using System.Security.Cryptography; using System.Text; using Microsoft.Build.Framework; @@ -237,6 +240,32 @@ public static string ComputeHash(string filepath) return Convert.ToBase64String(hash); } + public static string ComputeIntegrity(string filepath) + { + using var stream = File.OpenRead(filepath); + using HashAlgorithm hashAlgorithm = SHA256.Create(); + + byte[] hash = hashAlgorithm.ComputeHash(stream); + return "sha256-" + Convert.ToBase64String(hash); + } + + public static string ComputeIntegrity(byte[] bytes) + { + using HashAlgorithm hashAlgorithm = SHA256.Create(); + + byte[] hash = hashAlgorithm.ComputeHash(bytes); + return "sha256-" + Convert.ToBase64String(hash); + } + + public static string ComputeTextIntegrity(string str) + { + using HashAlgorithm hashAlgorithm = SHA256.Create(); + + var bytes = Encoding.UTF8.GetBytes(str); + byte[] hash = hashAlgorithm.ComputeHash(bytes); + return "sha256-" + Convert.ToBase64String(hash); + } + #if NETCOREAPP public static void DirectoryCopy(string sourceDir, string destDir, Func? predicate=null) { @@ -258,4 +287,44 @@ public static void DirectoryCopy(string sourceDir, string destDir, Func$(TargetFrameworkForNETCoreTasks);$(TargetFrameworkForNETFrameworkTasks) false enable - $(NoWarn),CA1050 + $(NoWarn),CA1050,CA1850 diff --git a/src/tasks/TestExclusionListTasks/TestExclusionListTasks.csproj b/src/tasks/TestExclusionListTasks/TestExclusionListTasks.csproj index d17dc95c41af62..a717c45f42c5ae 100644 --- a/src/tasks/TestExclusionListTasks/TestExclusionListTasks.csproj +++ b/src/tasks/TestExclusionListTasks/TestExclusionListTasks.csproj @@ -5,7 +5,7 @@ true false enable - $(NoWarn),CA1050 + $(NoWarn),CA1050,CA1850 diff --git a/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs b/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs index 081803b4b3c994..74855a41daf85d 100644 --- a/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs +++ b/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs @@ -6,7 +6,6 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using System.Reflection.PortableExecutable; using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -128,7 +127,7 @@ private List FilterOutUnmanagedBinaries(string[] assemblies) try { - if (!IsManagedAssembly(asmPath)) + if (!Utils.IsManagedAssembly(asmPath)) { Log.LogMessage(MessageImportance.Low, $"Skipping unmanaged {asmPath}."); continue; @@ -145,15 +144,4 @@ private List FilterOutUnmanagedBinaries(string[] assemblies) return managedAssemblies; } - - private static bool IsManagedAssembly(string filePath) - { - if (!File.Exists(filePath)) - return false; - - using FileStream fileStream = File.OpenRead(filePath); - using PEReader reader = new(fileStream, PEStreamOptions.Default); - return reader.HasMetadata; - } - } diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index 95a065dbdf177f..b7441396754fe1 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -51,46 +52,49 @@ private sealed class WasmAppConfig public List RemoteSources { get; set; } = new List(); [JsonExtensionData] public Dictionary Extra { get; set; } = new(); + [JsonPropertyName("assetsHash")] + public string AssetsHash { get; set; } = "none"; } private class AssetEntry { - protected AssetEntry (string name, string behavior) + protected AssetEntry (string name, string hash, string behavior) { Name = name; Behavior = behavior; + Hash = hash; } [JsonPropertyName("behavior")] public string Behavior { get; init; } [JsonPropertyName("name")] public string Name { get; init; } - // TODO [JsonPropertyName("hash")] - // TODO public string? Hash { get; set; } + [JsonPropertyName("hash")] + public string? Hash { get; set; } } private sealed class WasmEntry : AssetEntry { - public WasmEntry(string name) : base(name, "dotnetwasm") { } + public WasmEntry(string name, string hash) : base(name, hash, "dotnetwasm") { } } private sealed class ThreadsWorkerEntry : AssetEntry { - public ThreadsWorkerEntry(string name) : base(name, "js-module-threads") { } + public ThreadsWorkerEntry(string name, string hash) : base(name, hash, "js-module-threads") { } } private sealed class AssemblyEntry : AssetEntry { - public AssemblyEntry(string name) : base(name, "assembly") {} + public AssemblyEntry(string name, string hash) : base(name, hash, "assembly") {} } private sealed class PdbEntry : AssetEntry { - public PdbEntry(string name) : base(name, "pdb") {} + public PdbEntry(string name, string hash) : base(name, hash, "pdb") {} } private sealed class SatelliteAssemblyEntry : AssetEntry { - public SatelliteAssemblyEntry(string name, string culture) : base(name, "resource") + public SatelliteAssemblyEntry(string name, string hash, string culture) : base(name, hash, "resource") { CultureName = culture; } @@ -101,14 +105,14 @@ public SatelliteAssemblyEntry(string name, string culture) : base(name, "resourc private sealed class VfsEntry : AssetEntry { - public VfsEntry(string name) : base(name, "vfs") {} + public VfsEntry(string name, string hash) : base(name, hash, "vfs") {} [JsonPropertyName("virtualPath")] public string? VirtualPath { get; set; } } private sealed class IcuData : AssetEntry { - public IcuData(string name) : base(name, "icu") {} + public IcuData(string name, string hash) : base(name, hash, "icu") {} [JsonPropertyName("loadRemote")] public bool LoadRemote { get; set; } } @@ -183,12 +187,20 @@ protected override bool ExecuteInternal() foreach (ITaskItem item in NativeAssets) { - string dest = Path.Combine(AppDir!, Path.GetFileName(item.ItemSpec)); + var name = Path.GetFileName(item.ItemSpec); + var dest = Path.Combine(AppDir!, name); if (!FileCopyChecked(item.ItemSpec, dest, "NativeAssets")) return false; + if (name == "dotnet.wasm") + { + config.Assets.Add(new WasmEntry (name, Utils.ComputeIntegrity(item.ItemSpec)) ); + } + else if (IncludeThreadsWorker && name == "dotnet.worker.js") + { + config.Assets.Add(new ThreadsWorkerEntry (name, Utils.ComputeIntegrity(item.ItemSpec))); + } } - string packageJsonPath = Path.Combine(AppDir, "package.json"); if (!File.Exists(packageJsonPath)) { @@ -199,14 +211,29 @@ protected override bool ExecuteInternal() foreach (var assembly in _assemblies) { string assemblyPath = assembly; - if (UseWebcil) - assemblyPath = Path.ChangeExtension(assemblyPath, ".webcil"); - config.Assets.Add(new AssemblyEntry(Path.GetFileName(assemblyPath))); - if (DebugLevel != 0) { - var pdb = assembly; - pdb = Path.ChangeExtension(pdb, ".pdb"); - if (File.Exists(pdb)) - config.Assets.Add(new PdbEntry(Path.GetFileName(pdb))); + var bytes = File.ReadAllBytes(assemblyPath); + // for the is IL IsAssembly check we need to read the bytes from the original DLL + if (!Utils.IsManagedAssembly(bytes)) + { + Log.LogMessage(MessageImportance.Low, "Skipping non-assembly file: " + assemblyPath); + } + else + { + if (UseWebcil) + { + assemblyPath = Path.Combine(asmRootPath, Path.ChangeExtension(Path.GetFileName(assembly), ".webcil")); + // For the hash, read the bytes from the webcil file, not the dll file. + bytes = File.ReadAllBytes(assemblyPath); + } + + config.Assets.Add(new AssemblyEntry(Path.GetFileName(assemblyPath), Utils.ComputeIntegrity(bytes))); + if (DebugLevel != 0) + { + var pdb = assembly; + pdb = Path.ChangeExtension(pdb, ".pdb"); + if (File.Exists(pdb)) + config.Assets.Add(new PdbEntry(Path.GetFileName(pdb), Utils.ComputeIntegrity(pdb))); + } } } @@ -228,12 +255,13 @@ protected override bool ExecuteInternal() else Log.LogMessage(MessageImportance.Low, $"Skipped generating {finalWebcil} as the contents are unchanged."); _fileWrites.Add(finalWebcil); - config.Assets.Add(new SatelliteAssemblyEntry(Path.GetFileName(finalWebcil), args.culture)); + config.Assets.Add(new SatelliteAssemblyEntry(Path.GetFileName(finalWebcil), Utils.ComputeIntegrity(finalWebcil), args.culture)); } else { - FileCopyChecked(args.fullPath, Path.Combine(directory, name), "SatelliteAssemblies"); - config.Assets.Add(new SatelliteAssemblyEntry(name, args.culture)); + var satellitePath = Path.Combine(directory, name); + FileCopyChecked(args.fullPath, satellitePath, "SatelliteAssemblies"); + config.Assets.Add(new SatelliteAssemblyEntry(name, Utils.ComputeIntegrity(satellitePath), args.culture)); } }); @@ -271,10 +299,10 @@ protected override bool ExecuteInternal() targetPathTable[targetPath] = item.ItemSpec; var generatedFileName = $"{i++}_{Path.GetFileName(item.ItemSpec)}"; + var vfsPath = Path.Combine(supportFilesDir, generatedFileName); + FileCopyChecked(item.ItemSpec, vfsPath, "FilesToIncludeInFileSystem"); - FileCopyChecked(item.ItemSpec, Path.Combine(supportFilesDir, generatedFileName), "FilesToIncludeInFileSystem"); - - var asset = new VfsEntry ($"supportFiles/{generatedFileName}") { + var asset = new VfsEntry ($"supportFiles/{generatedFileName}", Utils.ComputeIntegrity(vfsPath)) { VirtualPath = targetPath }; config.Assets.Add(asset); @@ -291,15 +319,11 @@ protected override bool ExecuteInternal() Log.LogError($"Expected the file defined as ICU resource: {idfn} to exist but it does not."); return false; } - config.Assets.Add(new IcuData(Path.GetFileName(idfn)) { LoadRemote = loadRemote }); + config.Assets.Add(new IcuData(Path.GetFileName(idfn), Utils.ComputeIntegrity(idfn)) { LoadRemote = loadRemote }); } } - config.Assets.Add(new WasmEntry ("dotnet.wasm") ); - if (IncludeThreadsWorker) - config.Assets.Add(new ThreadsWorkerEntry ("dotnet.worker.js") ); - if (RemoteSources?.Length > 0) { foreach (var source in RemoteSources) @@ -328,6 +352,13 @@ protected override bool ExecuteInternal() string tmpMonoConfigPath = Path.GetTempFileName(); using (var sw = File.CreateText(tmpMonoConfigPath)) { + var sb = new StringBuilder(); + foreach(AssetEntry asset in config.Assets) + { + sb.Append(asset.Hash); + } + config.AssetsHash = Utils.ComputeTextIntegrity(sb.ToString()); + var json = JsonSerializer.Serialize (config, new JsonSerializerOptions { WriteIndented = true }); sw.Write(json); } diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj index 3fd2f17c30a253..320586d8693944 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.csproj @@ -5,6 +5,7 @@ $(NoWarn),CA1050 $(NoWarn),CS8604,CS8602 + $(NoWarn),CA1850 false true true diff --git a/src/tasks/WorkloadBuildTasks/WorkloadBuildTasks.csproj b/src/tasks/WorkloadBuildTasks/WorkloadBuildTasks.csproj index 0153d9a31ae4d1..fbb13d8d9c4ecf 100644 --- a/src/tasks/WorkloadBuildTasks/WorkloadBuildTasks.csproj +++ b/src/tasks/WorkloadBuildTasks/WorkloadBuildTasks.csproj @@ -2,7 +2,7 @@ $(TargetFrameworkForNETCoreTasks) enable - $(NoWarn),CA1050 + $(NoWarn),CA1050,CA1850