Skip to content

Commit

Permalink
fix(model): Rework purl conversion according to the specs
Browse files Browse the repository at this point in the history
Implement the pseudo-algorithm described at [1]. Most importantly, '/'
in namespaces are now not escaped anymore (also see the lengthy
discussion at [2]), key names are lower-cased, and qualifiers are sorted
for comparability.

[1]: https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#how-to-build-purl-string-from-its-components
[2]: package-url/purl-spec#176

Signed-off-by: Sebastian Schuberth <[email protected]>
  • Loading branch information
sschuberth committed Oct 18, 2024
1 parent cbef993 commit 5d5215b
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 20 deletions.
25 changes: 15 additions & 10 deletions model/src/main/kotlin/utils/PurlUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
20 changes: 10 additions & 10 deletions model/src/test/kotlin/utils/PurlExtensionsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand All @@ -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" {
Expand All @@ -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" {
Expand Down Expand Up @@ -140,8 +140,8 @@ class PurlExtensionsTest : WordSpec({
val purl = id.toPurl(extras.qualifiers, extras.subpath)

purl shouldBe "pkg:maven/com.example/[email protected]?" +
"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
}

Expand All @@ -161,10 +161,10 @@ class PurlExtensionsTest : WordSpec({
val purl = id.toPurl(extras.qualifiers, extras.subpath)

purl shouldBe "pkg:maven/com.example/[email protected]?" +
"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
}
Expand Down

0 comments on commit 5d5215b

Please sign in to comment.