File: RestoreCommand\VulnerabilityInformationProvider.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Commands\NuGet.Commands.csproj (NuGet.Commands)
// 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.Threading;
using System.Threading.Tasks;
using NuGet.Common;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
using NuGet.Protocol.Model;

namespace NuGet.Commands
{
    internal sealed class VulnerabilityInformationProvider : IVulnerabilityInformationProvider, IDisposable
    {
        private readonly SemaphoreSlim _semaphore = new(initialCount: 1, maxCount: 1);
        private readonly SourceRepository _source;
        private readonly ILogger _logger;

        private GetVulnerabilityInfoResult? _cachedResult;

        public VulnerabilityInformationProvider(SourceRepository source, ILogger logger, bool isAuditSource)
        {
            _source = source;
            _logger = logger;
            IsAuditSource = isAuditSource;
        }

        public async Task<GetVulnerabilityInfoResult?> GetVulnerabilityInformationAsync(CancellationToken cancellationToken)
        {
            if (_cachedResult is not null)
            {
                return _cachedResult;
            }

            await _semaphore.WaitAsync(cancellationToken);

            try
            {
                if (_cachedResult is not null)
                {
                    return _cachedResult;
                }

                _cachedResult = await GetVulnerabilityInfoAsync(cancellationToken);


                return _cachedResult;
            }
            finally
            {
                _semaphore.Release();
            }
        }

        public bool IsAuditSource { get; }

        public string SourceName => _source.PackageSource.Name;

        private async Task<GetVulnerabilityInfoResult?> GetVulnerabilityInfoAsync(CancellationToken cancellationToken)
        {
            try
            {
                IVulnerabilityInfoResource vulnerabilityInfoResource =
                    await _source.GetResourceAsync<IVulnerabilityInfoResource>(cancellationToken);
                if (vulnerabilityInfoResource is null)
                {
                    return null;
                }

                // Don't re-use a SourceCacheContext from whatever triggered this, because installing packages
                // (and a few other scenarios, look at everything that calls SourceCacheContext.MaxAge's setter)
                // will ignore the http cache, because it wants to allow newly published packages to be installable.
                // However, in VS, when a new project template installs packages, we don't want to force a re-download
                // of the vulnerability data. Firstly, from a customer point of view, it's not necessary. The reason
                // we bust the cache for package install doesn't apply to vulnerability data. Secondly, it triggers
                // regressions in VS's performance tests.
                using SourceCacheContext cacheContext = new();
                GetVulnerabilityInfoResult result = await vulnerabilityInfoResource.GetVulnerabilityInfoAsync(cacheContext, _logger, cancellationToken);
                return result;
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception e)
            {
                GetVulnerabilityInfoResult result = new(knownVulnerabilities: null, exceptions: new(e));
                return result;
            }
        }

        public void Dispose()
        {
            _semaphore.Dispose();
        }
    }
}