| File: Providers\RepositorySignatureResourceProvider.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Protocol\NuGet.Protocol.csproj (NuGet.Protocol) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Globalization; using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using NuGet.Common; using NuGet.Protocol.Core.Types; using NuGet.Protocol.Utility; using NuGet.Shared; namespace NuGet.Protocol { public class RepositorySignatureResourceProvider : ResourceProvider { private readonly IEnvironmentVariableReader? _environmentVariableReader; public RepositorySignatureResourceProvider() : this(null) { } internal RepositorySignatureResourceProvider(IEnvironmentVariableReader? environmentVariableReader) : base(typeof(RepositorySignatureResource), nameof(RepositorySignatureResource), NuGetResourceProviderPositions.Last) { _environmentVariableReader = environmentVariableReader; } public override async Task<Tuple<bool, INuGetResource?>> TryCreate(SourceRepository source, CancellationToken token) { RepositorySignatureResource? resource = null; var serviceIndex = await source.GetResourceAsync<ServiceIndexResourceV3>(token); if (serviceIndex != null) { var serviceEntry = serviceIndex.GetServiceEntries(ServiceTypes.RepositorySignatures).FirstOrDefault(); if (serviceEntry != null) { resource = await GetRepositorySignatureResourceAsync(source, serviceEntry, NullLogger.Instance, token); } } return new Tuple<bool, INuGetResource?>(resource != null, resource); } private async Task<RepositorySignatureResource?> GetRepositorySignatureResourceAsync( SourceRepository source, ServiceIndexEntry serviceEntry, ILogger log, CancellationToken token) { var repositorySignaturesResourceUri = serviceEntry.Uri; if (repositorySignaturesResourceUri == null || !string.Equals(repositorySignaturesResourceUri.Scheme, "https", StringComparison.OrdinalIgnoreCase) && !string.Equals(repositorySignaturesResourceUri.Scheme, "http", StringComparison.OrdinalIgnoreCase)) { throw new FatalProtocolException(string.Format(CultureInfo.CurrentCulture, Strings.RepositorySignaturesResourceMustBeHttps, source.PackageSource.Source)); } var httpSourceResource = await source.GetResourceAsync<HttpSourceResource>(token) ?? throw new InvalidOperationException($"The source '{source.PackageSource.Source}' does not provide {nameof(HttpSourceResource)}."); var client = httpSourceResource.HttpSource; var cacheKey = GenerateCacheKey(serviceEntry); const int maxRetries = 3; for (var retry = 1; retry <= maxRetries; retry++) { using (var sourceCacheContext = new SourceCacheContext()) { var cacheContext = HttpSourceCacheContext.Create(sourceCacheContext, isFirstAttempt: retry == 1); try { if (NuGetFeatureFlags.UseSystemTextJsonDeserializationFeatureSwitch) { return await client.GetAsync( new HttpSourceCachedRequest( serviceEntry.Uri.AbsoluteUri, cacheKey, cacheContext) { EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(repositorySignaturesResourceUri.AbsoluteUri, stream, _environmentVariableReader), MaxTries = 1, IsRetry = retry > 1, IsLastAttempt = retry == maxRetries }, async httpSourceResult => { var model = await JsonSerializer.DeserializeAsync( httpSourceResult.Stream!, RepositorySignatureJsonContext.Default.RepositorySignatureModel, token) ?? throw new FatalProtocolException(string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadRepositorySignature, repositorySignaturesResourceUri.AbsoluteUri)); return new RepositorySignatureResource(model, source); }, log, token); } else { return await client.GetAsync( new HttpSourceCachedRequest( serviceEntry.Uri.AbsoluteUri, cacheKey, cacheContext) { EnsureValidContents = stream => HttpStreamValidation.ValidateJObject(repositorySignaturesResourceUri.AbsoluteUri, stream, _environmentVariableReader), MaxTries = 1, IsRetry = retry > 1, IsLastAttempt = retry == maxRetries }, async httpSourceResult => { if (NuGetFeatureFlags.IsSystemTextJsonDeserializationEnabledByEnvironment(_environmentVariableReader)) { var model = await JsonSerializer.DeserializeAsync( httpSourceResult.Stream!, RepositorySignatureJsonContext.Default.RepositorySignatureModel, token) ?? throw new FatalProtocolException(string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadRepositorySignature, repositorySignaturesResourceUri.AbsoluteUri)); return new RepositorySignatureResource(model, source); } else { var json = (await httpSourceResult.Stream!.AsJObjectAsync(token))!; #pragma warning disable IL2026, IL3050 // Legacy Newtonsoft.Json code path is unreachable when feature switch is true; ILC trims this branch in AOT return new RepositorySignatureResource(json, source); #pragma warning restore IL2026, IL3050 } }, log, token); } } catch (Exception ex) when (retry < maxRetries) { var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_RetryingRepositorySignature, repositorySignaturesResourceUri.AbsoluteUri) + Environment.NewLine + ExceptionUtilities.DisplayMessage(ex); log.LogMinimal(message); } catch (Exception ex) when (retry == maxRetries) { var message = string.Format(CultureInfo.CurrentCulture, Strings.Log_FailedToReadRepositorySignature, repositorySignaturesResourceUri.AbsoluteUri); throw new FatalProtocolException(message, ex); } } } return null; } private static string GenerateCacheKey(ServiceIndexEntry serviceEntry) { #if NETCOREAPP var index = serviceEntry.Type.IndexOf('/', StringComparison.Ordinal); #else var index = serviceEntry.Type.IndexOf('/'); #endif var version = serviceEntry.Type.Substring(index + 1).Trim(); return $"repository_signatures_{version}"; } } }