diff --git a/README.md b/README.md index a72890916..5b129da11 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ ComponentDetection is a package scanning tool intended to be used at build time. | Go | ✔ | ❌ | | Maven | ✔ | ✔ | | NPM (including Yarn, Pnpm) | ✔ | ✔ | -| NuGet | ✔ | ✔ | +| NuGet (including Paket) | ✔ | ✔ | | Pip (Python) | ✔ | ✔ | | Poetry (Python, lockfiles only) | ✔ | ❌ | | Ruby | ✔ | ✔ | diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetComponentDetector.cs index 6e3cf8bea..10cd87105 100644 --- a/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetComponentDetector.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reactive.Linq; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; @@ -23,7 +24,7 @@ public class NuGetComponentDetector : FileComponentDetector public override IEnumerable Categories => new[] { Enum.GetName(typeof(DetectorClass), DetectorClass.NuGet) }; - public override IList SearchPatterns { get; } = new List { "*.nupkg", "*.nuspec", NugetConfigFileName }; + public override IList SearchPatterns { get; } = new List { "*.nupkg", "*.nuspec", NugetConfigFileName, "paket.lock" }; public override IEnumerable SupportedComponentTypes { get; } = new[] { ComponentType.NuGet }; @@ -92,6 +93,10 @@ private async Task ProcessFile(ProcessRequest processRequest) { nuspecBytes = await NuGetNuspecUtilities.GetNuspecBytesFromNuspecStream(stream.Stream, stream.Stream.Length); } + else if ("paket.lock".Equals(stream.Pattern, StringComparison.OrdinalIgnoreCase)) + { + ParsePaketLock(processRequest); + } else { return; @@ -130,6 +135,27 @@ private async Task ProcessFile(ProcessRequest processRequest) } } + private void ParsePaketLock(ProcessRequest processRequest) + { + var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder; + var stream = processRequest.ComponentStream; + + using StreamReader reader = new StreamReader(stream.Stream); + + string line; + while ((line = reader.ReadLine()) != null) + { + var matches = Regex.Matches(line, @"\s*([a-zA-Z0-9-.]*) \([<>=]*[ ]*([0-9a-zA-Z-.]*)\)", RegexOptions.Singleline); + foreach (Match match in matches) + { + string name = match.Groups[1].Value; + string version = match.Groups[2].Value; + NuGetComponent component = new NuGetComponent(name, version); + singleFileComponentRecorder.RegisterUsage(new DetectedComponent(component)); + } + } + } + private IList GetRepositoryPathsFromNugetConfig(IComponentStream componentStream) { var potentialPaths = new List(); diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetComponentDetectorTests.cs index 51629e084..15b9b4aab 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetComponentDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetComponentDetectorTests.cs @@ -113,6 +113,50 @@ public async Task TestNugetDetector_ReturnsValidMixedComponent() Assert.AreEqual(2, componentRecorder.GetDetectedComponents().Count()); } + [TestMethod] + public async Task TestNugetDetector_ReturnsValidPaketComponent() + { + var paketLock = @" +NUGET + remote: https://nuget.org/api/v2 + Castle.Core (3.3.0) + Castle.Core-log4net (3.3.0) + Castle.Core (>= 3.3.0) + log4net (1.2.10) + Castle.LoggingFacility (3.3.0) + Castle.Core (>= 3.3.0) + Castle.Windsor (>= 3.3.0) + Castle.Windsor (3.3.0) + Castle.Core (>= 3.3.0) + Castle.Windsor-log4net (3.3.0) + Castle.Core-log4net (>= 3.3.0) + Castle.LoggingFacility (>= 3.3.0) + Rx-Core (2.2.5) + Rx-Interfaces (>= 2.2.5) + Rx-Interfaces (2.2.5) + Rx-Linq (2.2.5) + Rx-Interfaces (>= 2.2.5) + Rx-Core (>= 2.2.5) + Rx-Main (2.2.5) + Rx-Interfaces (>= 2.2.5) + Rx-Core (>= 2.2.5) + Rx-Linq (>= 2.2.5) + Rx-PlatformServices (>= 2.2.5) + Rx-PlatformServices (2.2.5) + Rx-Interfaces (>= 2.2.5) + Rx-Core (>= 2.2.5) + log4net (1.2.10) + "; + + var (scanResult, componentRecorder) = await detectorTestUtility + .WithFile("paket.lock", paketLock) + .ExecuteDetector(); + + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); + // While there are 26 lines in the sample, several dependencies are identical, so there are only 11 matches. + Assert.AreEqual(11, componentRecorder.GetDetectedComponents().Count()); + } + [TestMethod] public async Task TestNugetDetector_HandlesMalformedComponentsInComponentList() {