diff --git a/model/src/main/kotlin/utils/PurlUtils.kt b/model/src/main/kotlin/utils/PurlUtils.kt index 1e0531c1b6fc5..a80df3940d659 100644 --- a/model/src/main/kotlin/utils/PurlUtils.kt +++ b/model/src/main/kotlin/utils/PurlUtils.kt @@ -92,28 +92,33 @@ internal fun createPurl( ): String = buildString { append("pkg:") - append(type) + append(type.lowercase()) + append('/') if (namespace.isNotEmpty()) { + append(namespace.trim('/').split('/').joinToString("/") { it.percentEncode() }) append('/') - append(namespace.percentEncode()) + append(name.trim('/').percentEncode()) + } else { + append(name.percentEncode()) } - append('/') - append(name.percentEncode()) - - append('@') - append(version.percentEncode()) + if (version.isNotEmpty()) { + append('@') + append(version.percentEncode()) + } - qualifiers.onEachIndexed { index, entry -> + qualifiers.filterValues { it.isNotEmpty() }.toSortedMap().onEachIndexed { index, entry -> if (index == 0) append("?") else append("&") - append(entry.key.percentEncode()) + append(entry.key.lowercase()) append("=") append(entry.value.percentEncode()) } if (subpath.isNotEmpty()) { - val value = subpath.split('/').joinToString("/", prefix = "#") { it.percentEncode() } + val value = subpath.trim('/').split('/') + .filter { it.isNotEmpty() && it != "." && it != ".." } + .joinToString("/", prefix = "#") { it.percentEncode() } append(value) } } diff --git a/model/src/test/kotlin/utils/PurlExtensionsTest.kt b/model/src/test/kotlin/utils/PurlExtensionsTest.kt index d496066b38f63..a8334f16ec2bb 100644 --- a/model/src/test/kotlin/utils/PurlExtensionsTest.kt +++ b/model/src/test/kotlin/utils/PurlExtensionsTest.kt @@ -68,10 +68,10 @@ class PurlExtensionsTest : WordSpec({ purl shouldBe "pkg:generic/name@version" } - "percent-encode namespaces with segments" { - val purl = Identifier("generic", "name/space", "name", "version").toPurl() + "percent-encode namespace segments" { + val purl = Identifier("generic", "name space/with spaces", "name", "version").toPurl() - purl shouldBe "pkg:generic/name%2Fspace/name@version" + purl shouldBe "pkg:generic/name%20space/with%20spaces/name@version" } "percent-encode the name" { @@ -95,7 +95,7 @@ class PurlExtensionsTest : WordSpec({ mapOf("argName" to "argValue") ) - purl shouldBe "pkg:type/namespace/name@version?argName=argValue" + purl shouldBe "pkg:type/namespace/name@version?argname=argValue" } "allow multiple qualifiers" { @@ -107,7 +107,7 @@ class PurlExtensionsTest : WordSpec({ mapOf("argName1" to "argValue1", "argName2" to "argValue2") ) - purl shouldBe "pkg:type/namespace/name@version?argName1=argValue1&argName2=argValue2" + purl shouldBe "pkg:type/namespace/name@version?argname1=argValue1&argname2=argValue2" } "allow subpath" { @@ -140,8 +140,8 @@ class PurlExtensionsTest : WordSpec({ val purl = id.toPurl(extras.qualifiers, extras.subpath) purl shouldBe "pkg:maven/com.example/sources@1.2.3?" + - "download_url=https%3A%2F%2Fexample.com%2Fsources.zip&" + - "checksum=md5%3Addce269a1e3d054cae349621c198dd52" + "checksum=md5%3Addce269a1e3d054cae349621c198dd52&" + + "download_url=https%3A%2F%2Fexample.com%2Fsources.zip" purl.toProvenance() shouldBe provenance } @@ -161,10 +161,10 @@ class PurlExtensionsTest : WordSpec({ val purl = id.toPurl(extras.qualifiers, extras.subpath) purl shouldBe "pkg:maven/com.example/sources@1.2.3?" + - "vcs_type=Git&" + - "vcs_url=https%3A%2F%2Fgithub.com%2Fapache%2Fcommons-text.git&" + + "resolved_revision=7643b12421100d29fd2b78053e77bcb04a251b2e&" + "vcs_revision=7643b12421100d29fd2b78053e77bcb04a251b2e&" + - "resolved_revision=7643b12421100d29fd2b78053e77bcb04a251b2e" + + "vcs_type=Git&" + + "vcs_url=https%3A%2F%2Fgithub.com%2Fapache%2Fcommons-text.git" + "#subpath" purl.toProvenance() shouldBe provenance } diff --git a/plugins/package-managers/swiftpm/src/funTest/assets/projects/synthetic/expected-output-only-lockfile-v1.yml b/plugins/package-managers/swiftpm/src/funTest/assets/projects/synthetic/expected-output-only-lockfile-v1.yml index 320581788ab07..9ebd3e148325a 100644 --- a/plugins/package-managers/swiftpm/src/funTest/assets/projects/synthetic/expected-output-only-lockfile-v1.yml +++ b/plugins/package-managers/swiftpm/src/funTest/assets/projects/synthetic/expected-output-only-lockfile-v1.yml @@ -51,7 +51,7 @@ packages: revision: "eb51f949cdd0c9d88abba9ce79d37eb7ea1231d0" path: "" - id: "Swift::github.com/apple/swift-crypto:" - purl: "pkg:swift/github.com%2Fapple%2Fswift-crypto@" + purl: "pkg:swift/github.com%2Fapple%2Fswift-crypto" declared_licenses: [] declared_licenses_processed: {} description: ""