Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace Microsoft.ComponentDetection.Contracts.Internal
{
public class NpmAuthor
{
public NpmAuthor(string name, string email = null)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Email = string.IsNullOrEmpty(email) ? null : email;
}

public string Name { get; set; }

public string Email { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using PackageUrl;
using Microsoft.ComponentDetection.Contracts.Internal;
using PackageUrl;

namespace Microsoft.ComponentDetection.Contracts.TypedComponent
{
Expand All @@ -9,11 +10,12 @@ private NpmComponent()
/* Reserved for deserialization */
}

public NpmComponent(string name, string version, string hash = null)
public NpmComponent(string name, string version, string hash = null, NpmAuthor author = null)
{
Name = ValidateRequiredInput(name, nameof(Name), nameof(ComponentType.Npm));
Version = ValidateRequiredInput(version, nameof(Version), nameof(ComponentType.Npm));
Hash = hash; // Not required; only found in package-lock.json, not package.json
Author = author;
}

public string Name { get; set; }
Expand All @@ -22,6 +24,8 @@ public NpmComponent(string name, string version, string hash = null)

public string Hash { get; set; }

public NpmAuthor Author { get; set; }

public override ComponentType Type => ComponentType.Npm;

public override string Id => $"{Name} {Version} - {Type}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ private NuGetComponent()
/* Reserved for deserialization */
}

public NuGetComponent(string name, string version)
public NuGetComponent(string name, string version, string[] authors = null)
{
Name = ValidateRequiredInput(name, nameof(Name), nameof(ComponentType.NuGet));
Version = ValidateRequiredInput(version, nameof(Version), nameof(ComponentType.NuGet));
Authors = authors;
}

public string Name { get; set; }

public string Version { get; set; }

public string[] Authors { get; set; }

public override ComponentType Type => ComponentType.NuGet;

public override string Id => $"{Name} {Version} - {Type}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Newtonsoft.Json.Linq;
using NuGet.Versioning;
using System.Text.RegularExpressions;

namespace Microsoft.ComponentDetection.Detectors.Npm
{
Expand Down Expand Up @@ -81,16 +82,68 @@ protected virtual bool ProcessIndividualPackageJTokens(string filePath, ISingleF
{
var name = packageJToken["name"].ToString();
var version = packageJToken["version"].ToString();

var authorToken = packageJToken["author"];

if (!SemanticVersion.TryParse(version, out _))
{
Logger.LogWarning($"Unable to parse version \"{version}\" for package \"{name}\" found at path \"{filePath}\". This may indicate an invalid npm package component and it will not be registered.");
return false;
}

var detectedComponent = new DetectedComponent(new NpmComponent(name, version));
singleFileComponentRecorder.RegisterUsage(detectedComponent);
NpmComponent npmComponent = new NpmComponent(name, version, author: GetAuthor(authorToken, name, filePath));

singleFileComponentRecorder.RegisterUsage(new DetectedComponent(npmComponent));
return true;
}

private NpmAuthor GetAuthor(JToken authorToken, string packageName, string filePath)
{
var authorString = authorToken?.ToString();
if (string.IsNullOrEmpty(authorString))
{
return null;
}

string authorName;
string authorEmail;
string authorSingleStringPattern = @"^(?<name>([^<(]+?)?)[ \t]*(?:<(?<email>([^>(]+?))>)?[ \t]*(?:\(([^)]+?)\)|$)";
Match authorMatch = new Regex(authorSingleStringPattern).Match(authorString);

/*
* for parsing author in Json Format
* for e.g.
* "author": {
* "name": "John Doe",
* "email": "johndoe@outlook.com",
* "name": "https://jd.com",
*/
if (authorToken.HasValues)
{
authorName = authorToken["name"]?.ToString();
authorEmail = authorToken["email"]?.ToString();

/*
* for parsing author in single string format.
* for e.g.
* "author": "John Doe <johdoe@outlook.com> https://jd.com"
*/
} else if (authorMatch.Success)
{
Comment on lines +130 to +131
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else if (authorMatch.Success)
{
}
else if (authorMatch.Success)
{

authorName = authorMatch.Groups["name"].ToString().Trim();
authorEmail = authorMatch.Groups["email"].ToString().Trim();
} else
{
Comment on lines +134 to +135
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else
{
}
else
{

Logger.LogWarning("Unable to parse author:[{authorString}] for package:[{packageName}] found at path:[{filePath}]. This may indicate an invalid npm package author, and author will not be registered.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs $ to make sure the strings are interpolated.

Suggested change
Logger.LogWarning("Unable to parse author:[{authorString}] for package:[{packageName}] found at path:[{filePath}]. This may indicate an invalid npm package author, and author will not be registered.");
Logger.LogWarning($"Unable to parse author:[{authorString}] for package:[{packageName}] found at path:[{filePath}]. This may indicate an invalid npm package author, and author will not be registered.");

return null;
}

if (string.IsNullOrEmpty(authorName))
{
Logger.LogWarning("Unable to parse author:[{authorString}] for package:[{packageName}] found at path:[{filePath}]. This may indicate an invalid npm package author, and author will not be registered.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Suggested change
Logger.LogWarning("Unable to parse author:[{authorString}] for package:[{packageName}] found at path:[{filePath}]. This may indicate an invalid npm package author, and author will not be registered.");
Logger.LogWarning($"Unable to parse author:[{authorString}] for package:[{packageName}] found at path:[{filePath}]. This may indicate an invalid npm package author, and author will not be registered.");

return null;
}

return new NpmAuthor(authorName, authorEmail);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.Internal;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using MoreLinq;
using NuGet.Versioning;

namespace Microsoft.ComponentDetection.Detectors.NuGet
Expand Down Expand Up @@ -106,14 +107,16 @@ private async Task ProcessFile(ProcessRequest processRequest)
string name = metadataNode["id"].InnerText;
string version = metadataNode["version"].InnerText;

string[] authors = metadataNode["authors"]?.InnerText.Split(",").Select(author => author.Trim()).ToArray();

if (!NuGetVersion.TryParse(version, out NuGetVersion parsedVer))
{
Logger.LogInfo($"Version '{version}' from {stream.Location} could not be parsed as a NuGet version");

return;
}

TypedComponent component = new NuGetComponent(name, version);
NuGetComponent component = new NuGetComponent(name, version, authors);
singleFileComponentRecorder.RegisterUsage(new DetectedComponent(component));
}
catch (Exception e)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using FluentAssertions;
using Microsoft.ComponentDetection.Contracts.Internal;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
Expand Down Expand Up @@ -28,25 +29,32 @@ public void TypedComponent_Serialization_Other()
[TestMethod]
public void TypedComponent_Serialization_NuGet()
{
TypedComponent.TypedComponent tc = new NuGetComponent("SomeNuGetComponent", "1.2.3");
string testComponentName = "SomeNuGetComponent";
string testVersion = "1.2.3";
string[] testAuthors = { "John Doe", "Jane Doe" };
TypedComponent.TypedComponent tc = new NuGetComponent(testComponentName, testVersion, testAuthors);
var result = JsonConvert.SerializeObject(tc);
var deserializedTC = JsonConvert.DeserializeObject<TypedComponent.TypedComponent>(result);
deserializedTC.Should().BeOfType(typeof(NuGetComponent));
var nugetComponent = (NuGetComponent)deserializedTC;
nugetComponent.Name.Should().Be("SomeNuGetComponent");
nugetComponent.Version.Should().Be("1.2.3");
nugetComponent.Name.Should().Be(testComponentName);
nugetComponent.Version.Should().Be(testVersion);
nugetComponent.Authors.Should().BeEquivalentTo(testAuthors);
}

[TestMethod]
public void TypedComponent_Serialization_Npm()
{
TypedComponent.TypedComponent tc = new NpmComponent("SomeNpmComponent", "1.2.3");
var result = JsonConvert.SerializeObject(tc);
NpmAuthor npmAuthor = new Internal.NpmAuthor("someAuthorName", "someAuthorEmail");
NpmComponent npmCompObj = new NpmComponent("SomeNpmComponent", "1.2.3");
npmCompObj.Author = npmAuthor;
var result = JsonConvert.SerializeObject(npmCompObj);
var deserializedTC = JsonConvert.DeserializeObject<TypedComponent.TypedComponent>(result);
deserializedTC.Should().BeOfType(typeof(NpmComponent));
var npmComponent = (NpmComponent)deserializedTC;
npmComponent.Name.Should().Be("SomeNpmComponent");
npmComponent.Version.Should().Be("1.2.3");
npmComponent.Author.Should().BeEquivalentTo(npmAuthor);
}

[TestMethod]
Expand Down
Loading