diff --git a/Directory.Packages.props b/Directory.Packages.props index dd8034204..f0565ac4f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -27,7 +27,7 @@ - + diff --git a/docs/creating-a-new-detector.md b/docs/creating-a-new-detector.md index eafe08413..0dfdd6d77 100644 --- a/docs/creating-a-new-detector.md +++ b/docs/creating-a-new-detector.md @@ -104,7 +104,7 @@ public class YourEcosystemComponent : TypedComponent public string Version { get; set; } public override ComponentType Type => ComponentType.YourType; - public override PackageURL PackageUrl => new PackageURL("your-type", null, this.Name, this.Version, null, null); + public override PackageUrl PackageUrl => new PackageUrl("your-type", null, this.Name, this.Version, null, null); protected override string ComputeId() => $"{this.Name} {this.Version} - {this.Type}"; } ``` diff --git a/docs/schema/manifest.schema.json b/docs/schema/manifest.schema.json index 1680dd0cc..0db3bdc7d 100644 --- a/docs/schema/manifest.schema.json +++ b/docs/schema/manifest.schema.json @@ -547,4 +547,4 @@ "resultCode", "sourceDirectory" ] -} \ No newline at end of file +} diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/CargoComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/CargoComponent.cs index 324596bc7..d981fe239 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/CargoComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/CargoComponent.cs @@ -44,7 +44,7 @@ public CargoComponent(string name, string version, string author = null, string public override ComponentType Type => ComponentType.Cargo; [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl => new PackageURL("cargo", string.Empty, this.Name, this.Version, null, string.Empty); + public override PackageUrl PackageUrl => new PackageUrl("cargo", string.Empty, this.Name, this.Version, null, string.Empty); protected override string ComputeBaseId() => $"{this.Name} {this.Version} - {this.Type}"; } diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ConanComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ConanComponent.cs index b1101d6fc..4a449e03a 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ConanComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/ConanComponent.cs @@ -38,7 +38,7 @@ public ConanComponent(string name, string version, string previous, string packa public override ComponentType Type => ComponentType.Conan; [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl => new PackageURL("conan", string.Empty, this.Name, this.Version, null, string.Empty); + public override PackageUrl PackageUrl => new PackageUrl("conan", string.Empty, this.Name, this.Version, null, string.Empty); protected override string ComputeBaseId() => $"{this.Name} {this.Version} - {this.Type}"; } diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/CppSdkComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/CppSdkComponent.cs index 70afd99de..af4b2ab0e 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/CppSdkComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/CppSdkComponent.cs @@ -39,7 +39,7 @@ public CppSdkComponent(string name, string version) public override ComponentType Type => ComponentType.CppSdk; [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl + public override PackageUrl PackageUrl { get { @@ -47,7 +47,7 @@ public override PackageURL PackageUrl { { "type", "cppsdk" }, }; - return new PackageURL("generic", null, this.Name, this.Version, qualifiers, null); + return new PackageUrl("generic", null, this.Name, this.Version, qualifiers, null); } } diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/GoComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/GoComponent.cs index 57161336a..3a096dd98 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/GoComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/GoComponent.cs @@ -37,14 +37,34 @@ public GoComponent() // Commit should be used in place of version when available // https://github.com/package-url/purl-spec/blame/180c46d266c45aa2bd81a2038af3f78e87bb4a25/README.rst#L610 + // The golang purl spec requires a namespace: https://github.com/package-url/purl-spec/blob/master/types/golang-definition.json [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl => new PackageURL("golang", null, this.Name, string.IsNullOrWhiteSpace(this.Hash) ? this.Version : this.Hash, null, null); + public override PackageUrl PackageUrl + { + get + { + var version = string.IsNullOrWhiteSpace(this.Hash) ? this.Version : this.Hash; + var (ns, name) = this.GetNamespaceAndName(); + return new PackageUrl("golang", ns, name, version, null, null); + } + } [JsonIgnore] public override ComponentType Type => ComponentType.Go; protected override string ComputeBaseId() => $"{this.Name} {this.Version} - {this.Type}"; + private (string Namespace, string Name) GetNamespaceAndName() + { + var lastSlash = this.Name.LastIndexOf('/'); + if (lastSlash > 0) + { + return (this.Name.Substring(0, lastSlash), this.Name.Substring(lastSlash + 1)); + } + + return (null, this.Name); + } + public override bool Equals(object obj) { return obj is GoComponent otherComponent && this.Equals(otherComponent); diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/LinuxComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/LinuxComponent.cs index c1ede70a6..f95501ec2 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/LinuxComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/LinuxComponent.cs @@ -46,7 +46,7 @@ public LinuxComponent(string distribution, string release, string name, string v public override ComponentType Type => ComponentType.Linux; [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl + public override PackageUrl PackageUrl { get { @@ -63,7 +63,7 @@ public override PackageURL PackageUrl if (packageType != null) { - return new PackageURL(packageType, this.Distribution, this.Name, this.Version, null, null); + return new PackageUrl(packageType, this.Distribution, this.Name, this.Version, null, null); } return null; diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/MavenComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/MavenComponent.cs index 034a156ea..dd4d50d76 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/MavenComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/MavenComponent.cs @@ -31,7 +31,7 @@ public MavenComponent() public override ComponentType Type => ComponentType.Maven; [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl => new PackageURL("maven", this.GroupId, this.ArtifactId, this.Version, null, null); + public override PackageUrl PackageUrl => new PackageUrl("maven", this.GroupId, this.ArtifactId, this.Version, null, null); protected override string ComputeBaseId() => $"{this.GroupId} {this.ArtifactId} {this.Version} - {this.Type}"; } diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NpmComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NpmComponent.cs index 33599a5fb..675877002 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NpmComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NpmComponent.cs @@ -36,7 +36,7 @@ public NpmComponent(string name, string version, string hash = null, NpmAuthor a public override ComponentType Type => ComponentType.Npm; [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl => new PackageURL("npm", null, this.Name, this.Version, null, null); + public override PackageUrl PackageUrl => new PackageUrl("npm", null, this.Name, this.Version, null, null); protected override string ComputeBaseId() => $"{this.Name} {this.Version} - {this.Type}"; } diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NugetComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NugetComponent.cs index d2c5e461c..7761bb2a3 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NugetComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/NugetComponent.cs @@ -31,7 +31,7 @@ public NuGetComponent(string name, string version, string[] authors = null) public override ComponentType Type => ComponentType.NuGet; [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl => new PackageURL("nuget", null, this.Name, this.Version, null, null); + public override PackageUrl PackageUrl => new PackageUrl("nuget", null, this.Name, this.Version, null, null); protected override string ComputeBaseId() => $"{this.Name} {this.Version} - {this.Type}"; } diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/PipComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/PipComponent.cs index 36bb04a4c..fa5bd6d44 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/PipComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/PipComponent.cs @@ -40,7 +40,7 @@ public PipComponent(string name, string version, string author = null, string li public override ComponentType Type => ComponentType.Pip; [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl => new PackageURL("pypi", null, this.Name, this.Version, null, null); + public override PackageUrl PackageUrl => new PackageUrl("pypi", null, this.Name, this.Version, null, null); [SuppressMessage("Usage", "CA1308:Normalize String to Uppercase", Justification = "Casing cannot be overwritten.")] protected override string ComputeBaseId() => $"{this.Name} {this.Version} - {this.Type}".ToLowerInvariant(); diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/PodComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/PodComponent.cs index 9d7442cf9..dc62095a7 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/PodComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/PodComponent.cs @@ -32,7 +32,7 @@ public PodComponent(string name, string version, string specRepo = "") public override ComponentType Type => ComponentType.Pod; [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl + public override PackageUrl PackageUrl { get { @@ -42,7 +42,7 @@ public override PackageURL PackageUrl qualifiers.Add("repository_url", this.SpecRepo); } - return new PackageURL("cocoapods", null, this.Name, this.Version, qualifiers, null); + return new PackageUrl("cocoapods", null, this.Name, this.Version, qualifiers, null); } } diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/RubyGemsComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/RubyGemsComponent.cs index 11469eb67..cee2b76dc 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/RubyGemsComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/RubyGemsComponent.cs @@ -31,7 +31,7 @@ public RubyGemsComponent(string name, string version, string source = "") public override ComponentType Type => ComponentType.RubyGems; [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl => new PackageURL("gem", null, this.Name, this.Version, null, null); + public override PackageUrl PackageUrl => new PackageUrl("gem", null, this.Name, this.Version, null, null); protected override string ComputeBaseId() => $"{this.Name} {this.Version} - {this.Type}"; } diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/SwiftComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/SwiftComponent.cs index 1140c31d6..830e86d88 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/SwiftComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/SwiftComponent.cs @@ -40,12 +40,12 @@ public SwiftComponent(string name, string version, string packageUrl, string has [JsonIgnore] public override ComponentType Type => ComponentType.Swift; - // Example PackageURL -> pkg:swift/github.com/apple/swift-asn1 + // Example PackageUrl -> pkg:swift/github.com/apple/swift-asn1 // type: swift // namespace: github.com/apple // name: swift-asn1 [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl => new PackageURL( + public override PackageUrl PackageUrl => new PackageUrl( type: "swift", @namespace: this.GetNamespaceFromPackageUrl(), name: this.Name, diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/TypedComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/TypedComponent.cs index 22be7eb61..95a62f541 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/TypedComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/TypedComponent.cs @@ -61,7 +61,7 @@ internal TypedComponent() public string BaseId => this.baseId ??= this.ComputeBaseId(); [SystemTextJson.JsonPropertyName("packageUrl")] - public virtual PackageURL PackageUrl { get; } + public virtual PackageUrl PackageUrl { get; } /// Gets or sets SPDX license expression(s) declared by the package author. [JsonProperty("licenses", NullValueHandling = NullValueHandling.Ignore)] diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/VcpkgComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/VcpkgComponent.cs index bb3668234..ae70a95c9 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/VcpkgComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/VcpkgComponent.cs @@ -49,21 +49,21 @@ public VcpkgComponent(string spdxid, string name, string version, string triplet public override ComponentType Type => ComponentType.Vcpkg; [JsonPropertyName("packageUrl")] - public override PackageURL PackageUrl + public override PackageUrl PackageUrl { get { if (this.PortVersion > 0) { - return new PackageURL($"pkg:vcpkg/{this.Name}@{this.Version}?port_version={this.PortVersion}"); + return new PackageUrl($"pkg:vcpkg/{this.Name}@{this.Version}?port_version={this.PortVersion}"); } else if (this.Version != null) { - return new PackageURL($"pkg:vcpkg/{this.Name}@{this.Version}"); + return new PackageUrl($"pkg:vcpkg/{this.Name}@{this.Version}"); } else { - return new PackageURL($"pkg:vcpkg/{this.Name}"); + return new PackageUrl($"pkg:vcpkg/{this.Name}"); } } } diff --git a/test/Microsoft.ComponentDetection.Contracts.Tests/CppSdkComponentTests.cs b/test/Microsoft.ComponentDetection.Contracts.Tests/CppSdkComponentTests.cs index 40ba034bf..bd9877270 100644 --- a/test/Microsoft.ComponentDetection.Contracts.Tests/CppSdkComponentTests.cs +++ b/test/Microsoft.ComponentDetection.Contracts.Tests/CppSdkComponentTests.cs @@ -77,9 +77,7 @@ public void PackageUrl_ShouldReturnCorrectFormat() var packageUrl = component.PackageUrl; packageUrl.Type.Should().Be("generic"); -#pragma warning disable CA1308 // PackageURL normalizes to lowercase - packageUrl.Name.Should().Be(name.ToLowerInvariant()); -#pragma warning restore CA1308 + packageUrl.Name.Should().Be(name); packageUrl.Version.Should().Be(version); packageUrl.Namespace.Should().BeNull(); packageUrl.Qualifiers.Should().ContainKey("type"); diff --git a/test/Microsoft.ComponentDetection.Contracts.Tests/PurlGenerationTests.cs b/test/Microsoft.ComponentDetection.Contracts.Tests/PurlGenerationTests.cs index 305400e47..18b9f99c4 100644 --- a/test/Microsoft.ComponentDetection.Contracts.Tests/PurlGenerationTests.cs +++ b/test/Microsoft.ComponentDetection.Contracts.Tests/PurlGenerationTests.cs @@ -24,7 +24,7 @@ public void GoPackageShouldPreferHashOverVersion() { // Commit should be used in place of version when available // https://github.com/package-url/purl-spec/blame/180c46d266c45aa2bd81a2038af3f78e87bb4a25/README.rst#L610 - var goComponent = new GoComponent("test", "1.2.3", "deadbeef"); + var goComponent = new GoComponent("github.com/example/test", "1.2.3", "deadbeef"); goComponent.PackageUrl.Version.Should().Be("deadbeef"); } @@ -97,9 +97,9 @@ public void CocoaPodNameShouldSupportPurl() var packageThree = new PodComponent("googleUtilities", "7.5.2"); packageOne.PackageUrl.Type.Should().Be("cocoapods"); - packageOne.PackageUrl.ToString().Should().Be("pkg:cocoapods/afnetworking@4.0.1"); - packageTwo.PackageUrl.ToString().Should().Be("pkg:cocoapods/mapsindoors@3.24.0"); - packageThree.PackageUrl.ToString().Should().Be("pkg:cocoapods/googleutilities@7.5.2"); + packageOne.PackageUrl.ToString().Should().Be("pkg:cocoapods/AFNetworking@4.0.1"); + packageTwo.PackageUrl.ToString().Should().Be("pkg:cocoapods/MapsIndoors@3.24.0"); + packageThree.PackageUrl.ToString().Should().Be("pkg:cocoapods/googleUtilities@7.5.2"); } [TestMethod] @@ -108,6 +108,6 @@ public void CocoaPodNameShouldPurlWithCustomQualifier() // https://github.com/package-url/purl-spec/blob/b8ddd39a6d533b8895f3b741f2e62e2695d82aa4/PURL-TYPES.rst#cocoapods var packageOne = new PodComponent("AFNetworking", "4.0.1", "https://custom_repo.example.com/path/to/repo/specs.git"); - packageOne.PackageUrl.ToString().Should().Be("pkg:cocoapods/afnetworking@4.0.1?repository_url=https://custom_repo.example.com/path/to/repo/specs.git"); + packageOne.PackageUrl.ToString().Should().Be("pkg:cocoapods/AFNetworking@4.0.1?repository_url=https:%2F%2Fcustom_repo.example.com%2Fpath%2Fto%2Frepo%2Fspecs.git"); } } diff --git a/test/Microsoft.ComponentDetection.Contracts.Tests/TypedComponentSerializationTests.cs b/test/Microsoft.ComponentDetection.Contracts.Tests/TypedComponentSerializationTests.cs index 366551ebb..57342f7e7 100644 --- a/test/Microsoft.ComponentDetection.Contracts.Tests/TypedComponentSerializationTests.cs +++ b/test/Microsoft.ComponentDetection.Contracts.Tests/TypedComponentSerializationTests.cs @@ -162,12 +162,12 @@ public void TypedComponent_Serialization_Pip() [TestMethod] public void TypedComponent_Serialization_Go() { - TypedComponent tc = new GoComponent("SomeGoPackage", "1.2.3", "SomeHash"); + TypedComponent tc = new GoComponent("github.com/example/SomeGoPackage", "1.2.3", "SomeHash"); var result = JsonSerializer.Serialize(tc); var deserializedTC = JsonSerializer.Deserialize(result); deserializedTC.Should().BeOfType(typeof(GoComponent)); var goComponent = (GoComponent)deserializedTC; - goComponent.Name.Should().Be("SomeGoPackage"); + goComponent.Name.Should().Be("github.com/example/SomeGoPackage"); goComponent.Version.Should().Be("1.2.3"); goComponent.Hash.Should().Be("SomeHash"); } @@ -305,7 +305,7 @@ public void TypedComponent_Serialization_AllComponentTypes_TypePropertyNotDuplic new NuGetComponent("test", "1.0.0"), new MavenComponent("group", "artifact", "1.0.0"), new PipComponent("test", "1.0.0"), - new GoComponent("test", "1.0.0"), + new GoComponent("github.com/example/test", "1.0.0"), new CargoComponent("test", "1.0.0"), new RubyGemsComponent("test", "1.0.0"), new GitComponent(new Uri("https://github.com/test/test"), "abc123"), diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/SwiftComponentTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/SwiftComponentTests.cs index 78165e6e9..a015bebaa 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/SwiftComponentTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/SwiftComponentTests.cs @@ -102,7 +102,7 @@ public void PackageURL_ShouldReturnCorrectPackageURL_GithubHostname() var component = new SwiftComponent(name, version, packageUrl, hash); - var expectedPackageURL = new PackageURL( + var expectedPackageURL = new PackageUrl( type: "swift", @namespace: "github.com/Alamofire", name: name, @@ -124,7 +124,7 @@ public void PackageURL_ShouldReturnCorrectPackageURL_GithubHostname_Alternate() var component = new SwiftComponent(name, version, packageUrl, hash); - var expectedPackageURL = new PackageURL( + var expectedPackageURL = new PackageUrl( type: "swift", @namespace: "github.com/Alamofire", name: name, @@ -149,7 +149,7 @@ public void PackageURL_ShouldReturnCorrectPackageURL_OtherHostname() var component = new SwiftComponent(name, version, packageUrl, hash); - var expectedPackageURL = new PackageURL( + var expectedPackageURL = new PackageUrl( type: "swift", @namespace: "otherhostname.com", name: name,