-
Notifications
You must be signed in to change notification settings - Fork 90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Nuget lockfiles #497
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ namespace Microsoft.ComponentDetection.Detectors.NuGet; | |
using System.IO; | ||
using System.Linq; | ||
using System.Reactive.Linq; | ||
using System.Text.Json; | ||
using System.Text.RegularExpressions; | ||
using System.Threading.Tasks; | ||
using System.Xml; | ||
|
@@ -20,6 +21,7 @@ public class NuGetComponentDetector : FileComponentDetector | |
private static readonly IEnumerable<string> LowConfidencePackages = new[] { "Newtonsoft.Json" }; | ||
|
||
public const string NugetConfigFileName = "nuget.config"; | ||
public const string NugetLockfileName = "packages.lock.json"; | ||
|
||
private readonly IList<string> repositoryPathKeyNames = new List<string> { "repositorypath", "globalpackagesfolder" }; | ||
|
||
|
@@ -37,7 +39,15 @@ public NuGetComponentDetector( | |
|
||
public override IEnumerable<string> Categories => new[] { Enum.GetName(typeof(DetectorClass), DetectorClass.NuGet) }; | ||
|
||
public override IList<string> SearchPatterns { get; } = new List<string> { "*.nupkg", "*.nuspec", NugetConfigFileName, "paket.lock" }; | ||
public override IList<string> SearchPatterns { get; } | ||
= new List<string> | ||
{ | ||
"*.nupkg", | ||
"*.nuspec", | ||
NugetConfigFileName, | ||
NugetLockfileName, | ||
"paket.lock", | ||
}; | ||
|
||
public override IEnumerable<ComponentType> SupportedComponentTypes { get; } = new[] { ComponentType.NuGet }; | ||
|
||
|
@@ -105,6 +115,12 @@ private async Task ProcessFileAsync(ProcessRequest processRequest) | |
else if ("paket.lock".Equals(stream.Pattern, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
this.ParsePaketLock(processRequest); | ||
return; | ||
} | ||
else if (NugetLockfileName.Equals(stream.Pattern, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
await this.ParseNugetLockfileAsync(processRequest); | ||
return; | ||
} | ||
else | ||
{ | ||
|
@@ -174,6 +190,41 @@ private void ParsePaketLock(ProcessRequest processRequest) | |
} | ||
} | ||
|
||
private async Task ParseNugetLockfileAsync(ProcessRequest processRequest) | ||
{ | ||
var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder; | ||
var stream = processRequest.ComponentStream; | ||
|
||
NuGetLockfileShape lockfile; | ||
try | ||
{ | ||
lockfile = await JsonSerializer.DeserializeAsync<NuGetLockfileShape>(stream.Stream).ConfigureAwait(false); | ||
} | ||
catch (Exception e) | ||
{ | ||
this.Logger.LogError(e, "Error loading NuGet lockfile from {Location}", stream.Location); | ||
singleFileComponentRecorder.RegisterPackageParseFailure(stream.Location); | ||
return; | ||
} | ||
|
||
if (lockfile.Version != 1) | ||
{ | ||
// only version 1 is supported | ||
this.Logger.LogError("Unsupported NuGet lockfile version {Version}", lockfile.Version); | ||
singleFileComponentRecorder.RegisterPackageParseFailure(stream.Location); | ||
return; | ||
} | ||
|
||
foreach (var framework in lockfile.Dependencies.Values) | ||
{ | ||
foreach (var (name, value) in framework) | ||
{ | ||
var component = new NuGetComponent(name, value.Resolved); | ||
singleFileComponentRecorder.RegisterUsage(new DetectedComponent(component)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if the team would want it included in this PR or not, but packages.lock.json supports reconstructing the whole component graph (see |
||
} | ||
} | ||
} | ||
|
||
private IList<DirectoryInfo> GetRepositoryPathsFromNugetConfig(IComponentStream componentStream) | ||
{ | ||
var potentialPaths = new List<string>(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
namespace Microsoft.ComponentDetection.Detectors.NuGet; | ||
|
||
using System.Collections.Generic; | ||
using System.Text.Json.Serialization; | ||
|
||
internal record NuGetLockfileShape | ||
{ | ||
[JsonPropertyName("version")] | ||
public int Version { get; set; } | ||
|
||
[JsonPropertyName("dependencies")] | ||
public Dictionary<string, Dictionary<string, PackageShape>> Dependencies { get; set; } = new(); | ||
|
||
public record PackageShape | ||
{ | ||
[JsonPropertyName("type")] | ||
public string Type { get; set; } | ||
|
||
[JsonPropertyName("resolved")] | ||
public string Resolved { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
namespace Microsoft.ComponentDetection.Detectors.Tests.Utilities; | ||
|
||
using System; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
||
internal class TestLogger<T> : ILogger<T>, IDisposable | ||
melotic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
private readonly TestContext context; | ||
|
||
public TestLogger(TestContext context) | ||
=> this.context = context; | ||
|
||
public IDisposable BeginScope<TState>(TState state) | ||
where TState : notnull | ||
=> this; | ||
|
||
public void Dispose() | ||
{ | ||
} | ||
|
||
public bool IsEnabled(LogLevel logLevel) => true; | ||
|
||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) | ||
=> this.context.WriteLine($"{logLevel} ({eventId}): {formatter(state, exception)}"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed what seems like a small bug here; it would always continue to the "nuget bytes" parsing and throw a null reference exception which is then caught.