diff --git a/NuGet.config b/NuGet.config index 22a4df19c9e3..a43d19994451 100644 --- a/NuGet.config +++ b/NuGet.config @@ -1,3 +1,4 @@ + @@ -5,22 +6,23 @@ - + - - + + + @@ -45,14 +47,15 @@ - + - + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 7e592b6831fd..5d105252a69c 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,17 +1,17 @@ - + https://github.com/dotnet/templating - e3c582deeab9ac91e1a6567a16fc80eceb0bb050 + 582216c8db382a998f79e63ca8996641a8f9c29b - + https://github.com/dotnet/templating - e3c582deeab9ac91e1a6567a16fc80eceb0bb050 + 582216c8db382a998f79e63ca8996641a8f9c29b - + https://github.com/dotnet/templating - e3c582deeab9ac91e1a6567a16fc80eceb0bb050 + 582216c8db382a998f79e63ca8996641a8f9c29b @@ -55,61 +55,61 @@ https://github.com/dotnet/emsdk 35ebe6f3e6953f878ed92f637ad8d7f27202f7a6 - - https://github.com/dotnet/msbuild - 7806cbf7b0fd91ea6ab55c2e42d8ed973114e197 + + https://dev.azure.com/devdiv/DevDiv/_git/DotNet-msbuild-Trusted + 02bf66295b64ab368d12933041f7281aad186a2d - - https://github.com/dotnet/msbuild - 7806cbf7b0fd91ea6ab55c2e42d8ed973114e197 + + https://dev.azure.com/devdiv/DevDiv/_git/DotNet-msbuild-Trusted + 02bf66295b64ab368d12933041f7281aad186a2d - - https://github.com/dotnet/msbuild - 7806cbf7b0fd91ea6ab55c2e42d8ed973114e197 + + https://dev.azure.com/devdiv/DevDiv/_git/DotNet-msbuild-Trusted + 02bf66295b64ab368d12933041f7281aad186a2d - + https://github.com/dotnet/fsharp - fc5e9eda234e2b69aa479f4f83faddc31fdd4da7 + e11d7079bebc6f101c5313fe0d1de9e3d38a7c02 - + https://github.com/dotnet/fsharp - fc5e9eda234e2b69aa479f4f83faddc31fdd4da7 + e11d7079bebc6f101c5313fe0d1de9e3d38a7c02 - + https://github.com/dotnet/format - a8be5ff9c558f921f565c461dd7688905f84742a + 6eca172fd9205e0275d2d88bebe1e4f4fbb173d4 - + https://github.com/dotnet/roslyn - 38896ab4e7cee896fcde8a4e26914a777c794e3b + 3fb752d448006a3144a60ccf181d745e555422f9 - + https://github.com/dotnet/roslyn - 38896ab4e7cee896fcde8a4e26914a777c794e3b + 3fb752d448006a3144a60ccf181d745e555422f9 - + https://github.com/dotnet/roslyn - 38896ab4e7cee896fcde8a4e26914a777c794e3b + 3fb752d448006a3144a60ccf181d745e555422f9 - + https://github.com/dotnet/roslyn - 38896ab4e7cee896fcde8a4e26914a777c794e3b + 3fb752d448006a3144a60ccf181d745e555422f9 - + https://github.com/dotnet/roslyn - 38896ab4e7cee896fcde8a4e26914a777c794e3b + 3fb752d448006a3144a60ccf181d745e555422f9 - + https://github.com/dotnet/roslyn - 38896ab4e7cee896fcde8a4e26914a777c794e3b + 3fb752d448006a3144a60ccf181d745e555422f9 - + https://github.com/dotnet/roslyn - 38896ab4e7cee896fcde8a4e26914a777c794e3b + 3fb752d448006a3144a60ccf181d745e555422f9 https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore @@ -119,86 +119,86 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore bb9eccba9080e07bce32c0bc27c3564c753a7cfe - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted - 60584b9c2451c0e324655f3968bc4ed1a1a8c85c + c754078ce0c9bba2fc03aa6fd45c1fd760c25a62 - + https://github.com/microsoft/vstest - aa59400b11e1aeee2e8af48928dbd48748a8bef9 + 7855c9b221686104532ebf3380f2d45b3613b369 - + https://github.com/microsoft/vstest - aa59400b11e1aeee2e8af48928dbd48748a8bef9 + 7855c9b221686104532ebf3380f2d45b3613b369 - + https://github.com/microsoft/vstest - aa59400b11e1aeee2e8af48928dbd48748a8bef9 + 7855c9b221686104532ebf3380f2d45b3613b369 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -293,18 +293,18 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore bb9eccba9080e07bce32c0bc27c3564c753a7cfe - + https://github.com/dotnet/razor - e137c0322ddb9e3d4f4a5de385647baa9719f9a6 + 2f31e7f15e35ce060c1ba81174a87fdd0ae9f479 - + https://github.com/dotnet/razor - e137c0322ddb9e3d4f4a5de385647baa9719f9a6 + 2f31e7f15e35ce060c1ba81174a87fdd0ae9f479 - + https://github.com/dotnet/razor - e137c0322ddb9e3d4f4a5de385647baa9719f9a6 + 2f31e7f15e35ce060c1ba81174a87fdd0ae9f479 https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore @@ -327,17 +327,17 @@ 9a1c3e1b7f0c8763d4c96e593961a61a72679a7b - + https://github.com/dotnet/roslyn-analyzers - 0963af6bfc2a3d5083faa2b7c1c91a4eee29a6f4 + abef8ced132657943b7150f01a308e2199a17d5d - + https://github.com/dotnet/roslyn-analyzers - 0963af6bfc2a3d5083faa2b7c1c91a4eee29a6f4 + abef8ced132657943b7150f01a308e2199a17d5d - + https://github.com/dotnet/roslyn-analyzers - 0963af6bfc2a3d5083faa2b7c1c91a4eee29a6f4 + abef8ced132657943b7150f01a308e2199a17d5d @@ -354,10 +354,10 @@ f537cc21040df4a14d26a3045a0d4b6fdc1efc3b - - https://github.com/dotnet/source-build-assets - 2bb24ee4d15de5a4550abed0265ca9e0ef0beae3 - + + https://github.com/dotnet/source-build-reference-packages + 9d83c59ff890ff2020aa38ebcf3bb514e38e9ffe + https://github.com/dotnet/deployment-tools @@ -469,9 +469,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 81cabf2857a01351e5ab578947c7403a5b128ad1 - + https://dev.azure.com/dnceng/internal/_git/dotnet-winforms - e4ede9b8979b9d2b1b1d4383f30a791414f0625b + fdc20074cf1e48b8cf11fe6ac78f255b1fbfe611 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime diff --git a/eng/Versions.props b/eng/Versions.props index 6364e75c865a..c77d57cb6e9d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -11,12 +11,13 @@ - 8.0.130 + 8.0.424 + 8.0.400 true release - rtm + preview rtm servicing @@ -30,10 +31,11 @@ 2.0.1-servicing-26011-01 2.0.3 13.0.3 + 4.8.6 1.2.0-beta.435 - 7.0.0 + 8.0.0 4.0.0 - 7.0.0 + 8.0.1 8.0.0-beta.26278.3 7.0.0-preview.22423.2 8.0.0 @@ -57,6 +59,7 @@ 8.0.28 8.0.28-servicing.26264.13 8.0.0 + $(MicrosoftExtensionsDependencyModelPackageVersion) 8.0.1 8.0.3 8.0.1 @@ -72,7 +75,7 @@ 8.0.0 8.0.2 8.0.1 - 8.0.0 + 8.0.7 8.0.1 8.0.3 8.0.0 @@ -85,26 +88,26 @@ - 6.8.2-rc.3 - 6.8.2-rc.3 - 6.8.2-rc.3 - 6.8.2-rc.3 - 6.8.2-rc.3 - 6.8.2-rc.3 - 6.8.2-rc.3 - 6.8.2-rc.3 - 6.8.2-rc.3 - 6.8.2-rc.3 - 6.8.2-rc.3 - 6.8.2-rc.3 + 6.11.2-rc.1 + 6.11.2-rc.1 + 6.11.2-rc.1 + 6.11.2-rc.1 + 6.11.2-rc.1 + 6.11.2-rc.1 + 6.11.2-rc.1 + 6.11.2-rc.1 + 6.11.2-rc.1 + 6.11.2-rc.1 + 6.11.2-rc.1 + 6.11.2-rc.1 $(NuGetPackagingPackageVersion) $(NuGetProjectModelPackageVersion) - 17.8.0-release-23615-02 - 17.8.0-release-23615-02 - 17.8.0-release-23615-02 + 17.11.1-release-24455-02 + 17.11.1-release-24455-02 + 17.11.1-release-24455-02 @@ -114,58 +117,56 @@ - 8.1.657407 + 8.3.731007 - 8.0.0-preview.26274.2 - 3.11.0-beta1.26274.2 + 8.0.0-preview.23614.1 + 3.11.0-beta1.23614.1 - 17.8.49 + 17.11.48 $(MicrosoftBuildPackageVersion) - $(MicrosoftBuildPackageVersion) - $(MicrosoftBuildPackageVersion) - 17.8.49-servicing-25616-08 - $(MicrosoftBuildPackageVersion) - $(MicrosoftBuildPackageVersion) + This avoids the need to juggle references to packages that have been updated in newer MSBuild. --> + 17.8.43 + $(MicrosoftBuildMinimumVersion) + $(MicrosoftBuildMinimumVersion) + 17.11.48-servicing-25466-05 + $(MicrosoftBuildMinimumVersion) + $(MicrosoftBuildMinimumVersion) $(MicrosoftBuildTasksCorePackageVersion) + $(MicrosoftBuildMinimumVersion) - 8.0.130 + 8.0.423 $(MicrosoftTemplateEngineAbstractionsPackageVersion) $(MicrosoftTemplateEngineAbstractionsPackageVersion) $(MicrosoftTemplateEngineAbstractionsPackageVersion) $(MicrosoftTemplateEngineAbstractionsPackageVersion) - 8.0.130-servicing.26359.5 + 8.0.423-servicing.26302.9 $(MicrosoftTemplateEngineMocksPackageVersion) $(MicrosoftTemplateEngineAbstractionsPackageVersion) $(MicrosoftTemplateEngineMocksPackageVersion) - 12.8.102-beta.24081.2 + 12.8.403-beta.24526.2 - 4.8.0-7.25569.25 - 4.8.0-7.25569.25 - 4.8.0-7.25569.25 - 4.8.0-7.25569.25 - 4.8.0-7.25569.25 - 4.8.0-7.25569.25 - 4.8.0-7.25569.25 + 4.11.0-3.25569.22 + 4.11.0-3.25569.22 + 4.11.0-3.25569.22 + 4.11.0-3.25569.22 + 4.11.0-3.25569.22 + 4.11.0-3.25569.22 + 4.11.0-3.25569.22 $(MicrosoftNetCompilersToolsetPackageVersion) @@ -180,9 +181,9 @@ - 7.0.0-preview.26064.3 - 7.0.0-preview.26064.3 - 7.0.0-preview.26064.3 + 9.0.0-preview.26227.1 + 9.0.0-preview.26227.1 + 9.0.0-preview.26227.1 diff --git a/src/Containers/Microsoft.NET.Build.Containers/ContentStore.cs b/src/Containers/Microsoft.NET.Build.Containers/ContentStore.cs index 2fb16be3ec5d..488764482f27 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ContentStore.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ContentStore.cs @@ -8,30 +8,18 @@ namespace Microsoft.NET.Build.Containers; internal static class ContentStore { - public static string ArtifactRoot { get; set; } = Path.Combine(Path.GetTempPath(), "Containers"); + public static string ArtifactRoot { get; set; } = EnsureCacheDirectory(); public static string ContentRoot { get { string contentPath = Path.Join(ArtifactRoot, "Content"); - - Directory.CreateDirectory(contentPath); - + CreateCacheDirectory(contentPath); return contentPath; } } - public static string TempPath - { - get - { - string tempPath = Path.Join(ArtifactRoot, "Temp"); - - Directory.CreateDirectory(tempPath); - - return tempPath; - } - } + public static string TempPath { get; } = Directory.CreateTempSubdirectory().FullName; public static string PathForDescriptor(Descriptor descriptor) { @@ -62,8 +50,81 @@ public static string GetPathForHash(string contentHash) return Path.Combine(ContentRoot, contentHash); } - public static string GetTempFile() + public static string GetTempFile() => Path.Join(TempPath, Path.GetRandomFileName()); + + /// + /// Gets the path to the user cache directory for SDK container builds, creating it if it does + /// not already exist. + /// + private static string EnsureCacheDirectory() + { + string userCacheDir = GetUserCacheDirectoryPath(); + CreateCacheDirectory(userCacheDir); + + string dotnetCacheDir = Path.Join(userCacheDir, "dotnet"); + CreateCacheDirectory(dotnetCacheDir); + + string containersCacheDir = Path.Join(dotnetCacheDir, "Containers"); + CreateCacheDirectory(containersCacheDir); + + return containersCacheDir; + } + + /// + /// Creates a cache directory with user-scoped permissions based on the OS. + /// + /// + /// Permissions are only set for the leaf directory. The caller is expected to handle + /// permissions for intermediate directories. + /// + private static void CreateCacheDirectory(string path) + { + if (OperatingSystem.IsWindows()) + { + Directory.CreateDirectory(path); + } + else + { + // Per XDG_CACHE_HOME spec: if the cache directory doesn't exist, try to create it with + // permissions 0700. If it already exists, don't try to change the permissions. + // See https://specifications.freedesktop.org/basedir/latest/#referencing + Directory.CreateDirectory(path, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute); + } + } + + /// + /// Gets the user-scoped cache directory, accounting for OS differences. + /// + private static string GetUserCacheDirectoryPath() { - return Path.Join(TempPath, Path.GetRandomFileName()); + if (OperatingSystem.IsLinux()) + { + // See "XDG Base Directory Specification": + // https://specifications.freedesktop.org/basedir/latest/ + string? xdgCacheHome = Environment.GetEnvironmentVariable("XDG_CACHE_HOME"); + if (!string.IsNullOrEmpty(xdgCacheHome) && Path.IsPathFullyQualified(xdgCacheHome)) + { + return xdgCacheHome; + } + + // From the spec, if XDG_CACHE_HOME is not set then fall back to ~/.cache + string userHomeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + return Path.Join(userHomeDirectory, ".cache"); + } + + if (OperatingSystem.IsMacOS()) + { + // See "macOS Library Directory Details": + // https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html + string userHomeDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + return Path.Join(userHomeDirectory, "Library", "Caches"); + } + + // On other platforms, use the local application data folder. + string localAppData = Environment.GetFolderPath( + Environment.SpecialFolder.LocalApplicationData, + Environment.SpecialFolderOption.DoNotVerify); + + return localAppData; } } diff --git a/src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs b/src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs index e549defd4635..54458d7a471c 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/DigestUtils.cs @@ -27,4 +27,17 @@ internal static string GetSha(string str) return Convert.ToHexString(hash).ToLowerInvariant(); } + + /// + /// Returns the encoded (hash) portion of a digest string as raw bytes. + /// + /// + /// GetEncodedValue("sha256:e3b0c4...") returns the bytes for "e3b0c4...". + /// + internal static byte[] GetEncodedValue(string digest) + { + int separatorIndex = digest.IndexOf(':'); + string encoded = separatorIndex >= 0 ? digest[(separatorIndex + 1)..] : digest; + return Convert.FromHexString(encoded); + } } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Exceptions/InvalidDigestException.cs b/src/Containers/Microsoft.NET.Build.Containers/Exceptions/InvalidDigestException.cs new file mode 100644 index 000000000000..071f07682a99 --- /dev/null +++ b/src/Containers/Microsoft.NET.Build.Containers/Exceptions/InvalidDigestException.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.Cryptography; + +namespace Microsoft.NET.Build.Containers; + +internal sealed class InvalidDigestException : Exception +{ + public InvalidDigestException(string message) + : base(message) + { + } + + public static void ThrowIfMismatched(ReadOnlySpan expected, ReadOnlySpan actual) + { + if (!CryptographicOperations.FixedTimeEquals(expected, actual)) + { + string expectedHashString = Convert.ToHexString(expected).ToLowerInvariant(); + string actualHashString = Convert.ToHexString(actual).ToLowerInvariant(); + throw new InvalidDigestException($"Expected {expectedHashString}, got {actualHashString}."); + } + } +} diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs index b3df405b7457..5d07ca41410c 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs @@ -6,6 +6,7 @@ using NuGet.RuntimeModel; using System.Diagnostics; using System.Net.Http.Json; +using System.Security.Cryptography; using System.Text.Json.Nodes; namespace Microsoft.NET.Build.Containers; @@ -257,11 +258,31 @@ public async Task DownloadBlobAsync(string repository, Descriptor descri cancellationToken.ThrowIfCancellationRequested(); string localPath = ContentStore.PathForDescriptor(descriptor); - if (File.Exists(localPath)) + try { - // Assume file is up to date and just return it + var fileStream = File.OpenRead(localPath); + + var actualHash = SHA256.HashData(fileStream); + var expectedHash = DigestUtils.GetEncodedValue(descriptor.Digest); + InvalidDigestException.ThrowIfMismatched(expectedHash, actualHash); + return localPath; } + catch (DirectoryNotFoundException) + { + // Cache miss + } + catch (FileNotFoundException) + { + // Cache miss + } + catch (InvalidDigestException exception) + { + // Incorrect digest + _logger.LogTrace( + "Digest validation failed for cached blob {1} ({2}), redownloading from registry.", + localPath, exception.Message); + } // No local copy, so download one using Stream responseStream = await _registryAPI.Blob.GetStreamAsync(repository, descriptor.Digest, cancellationToken).ConfigureAwait(false); @@ -269,7 +290,9 @@ public async Task DownloadBlobAsync(string repository, Descriptor descri string tempTarballPath = ContentStore.GetTempFile(); using (FileStream fs = File.Create(tempTarballPath)) { - await responseStream.CopyToAsync(fs, cancellationToken).ConfigureAwait(false); + await responseStream + .CopyToAndVerifyAsync(fs, descriptor.Digest, cancellationToken) + .ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); @@ -319,7 +342,7 @@ internal async Task UploadBlobChunkedAsync(Stream con // manual because ACR throws an error with the .NET type {"Range":"bytes 0-84521/*","Reason":"the Content-Range header format is invalid"} // content.Headers.Add("Content-Range", $"0-{contents.Length - 1}"); Debug.Assert(content.Headers.TryAddWithoutValidation("Content-Range", $"{chunkStart}-{chunkStart + bytesRead - 1}")); - + NextChunkUploadInformation nextChunk = await _registryAPI.Blob.Upload.UploadChunkAsync(patchUri, content, cancellationToken).ConfigureAwait(false); patchUri = nextChunk.UploadUri; diff --git a/src/Containers/Microsoft.NET.Build.Containers/StreamExtensions.cs b/src/Containers/Microsoft.NET.Build.Containers/StreamExtensions.cs new file mode 100644 index 000000000000..f66e88080ea8 --- /dev/null +++ b/src/Containers/Microsoft.NET.Build.Containers/StreamExtensions.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.Cryptography; + +namespace Microsoft.NET.Build.Containers; + +internal static class StreamExtensions +{ + public static async Task CopyToAndVerifyAsync( + this Stream source, + Stream destination, + string digest, + CancellationToken cancellationToken) + { + byte[] expectedHash = DigestUtils.GetEncodedValue(digest).ToArray(); + + // Assumption: SHA256 is the only supported algorithm. + // See DigestUtils > s_registeredAlgorithms for more details. + using HashAlgorithm hashAlgorithm = SHA256.Create(); + + await using var cryptoStream = new CryptoStream( + stream: source, + transform: hashAlgorithm, + mode: CryptoStreamMode.Read, + // cryptoStream will be disposed at the end of the method, but setting leaveOpen=true + // tells CryptoStream not to dispose the source Stream. The caller is responsible for + // the lifetime of the source Stream. + leaveOpen: true + ); + + await cryptoStream.CopyToAsync(destination, cancellationToken); + + InvalidDigestException.ThrowIfMismatched(expectedHash, hashAlgorithm.Hash ?? []); + } +}