|
// 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();
}
}
}
|