Skip to content

Commit

Permalink
feat: encode qualifiers with URLSearchParams
Browse files Browse the repository at this point in the history
  • Loading branch information
jdalton committed Aug 14, 2024
1 parent 09d8ebf commit ff590d2
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 36 deletions.
53 changes: 37 additions & 16 deletions src/encode.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
'use strict'

const { isObject } = require('./objects')
const { isNonEmptyString } = require('./strings')

const reusedSearchParams = new URLSearchParams()

const { encodeURIComponent } = globalThis

function encodeWithColonAndForwardSlash(str) {
Expand All @@ -15,40 +20,54 @@ function encodeWithForwardSlash(str) {
}

function encodeNamespace(namespace) {
return typeof namespace === 'string' && namespace.length
return isNonEmptyString(namespace)
? encodeWithColonAndForwardSlash(namespace)
: ''
}

function encodeVersion(version) {
return typeof version === 'string' && version.length
? encodeWithColonAndPlusSign(version)
: ''
return isNonEmptyString(version) ? encodeWithColonAndPlusSign(version) : ''
}

function encodeQualifiers(qualifiers) {
let query = ''
if (qualifiers !== null && typeof qualifiers === 'object') {
if (isObject(qualifiers)) {
// Sort this list of qualifier strings lexicographically.
const qualifiersKeys = Object.keys(qualifiers).sort()
const searchParams = new URLSearchParams()
for (let i = 0, { length } = qualifiersKeys; i < length; i += 1) {
const key = qualifiersKeys[i]
query = `${query}${i === 0 ? '' : '&'}${key}=${encodeQualifierValue(qualifiers[key])}`
searchParams.set(key, qualifiers[key])
}
return replacePlusSignWithPercentEncodedSpace(searchParams.toString())
}
return query
return ''
}

function encodeQualifierValue(qualifierValue) {
return typeof qualifierValue === 'string' && qualifierValue.length
? encodeWithColonAndForwardSlash(qualifierValue)
function encodeQualifierParam(qualifierValue) {
return isNonEmptyString
? encodeURLSearchParamWithPercentEncodedSpace(param)
: ''
}

function encodeSubpath(subpath) {
return typeof subpath === 'string' && subpath.length
? encodeWithForwardSlash(subpath)
: ''
return isNonEmptyString(subpath) ? encodeWithForwardSlash(subpath) : ''
}

function encodeURLSearchParam(param) {
// Param key and value are encoded with `percentEncodeSet` of
// 'application/x-www-form-urlencoded' and `spaceAsPlus` of `true`.
// https://url.spec.whatwg.org/#urlencoded-serializing
reusedSearchParams.set('_', qualifierValue)
return reusedSearchParams.toString().slice(2)
}

function encodeURLSearchParamWithPercentEncodedSpace(str) {
return replacePlusSignWithPercentEncodedSpace(encodeURLSearchParam(str))
}

function replacePlusSignWithPercentEncodedSpace(str) {
// Convert plus signs to %20 for better portability.
return str.replace(/\+/g, '%20')
}

module.exports = {
Expand All @@ -58,7 +77,9 @@ module.exports = {
encodeNamespace,
encodeVersion,
encodeQualifiers,
encodeQualifierValue,
encodeQualifierParam,
encodeSubpath,
encodeURIComponent
encodeURIComponent,
encodeURLSearchParam,
encodeURLSearchParamWithPercentEncodedSpace
}
2 changes: 2 additions & 0 deletions src/purl-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
encodeNamespace,
encodeVersion,
encodeQualifiers,
encodeQualifierKey,
encodeQualifierValue,
encodeSubpath,
encodeURIComponent
Expand Down Expand Up @@ -69,6 +70,7 @@ module.exports = {
namespace: encodeNamespace,
version: encodeVersion,
qualifiers: encodeQualifiers,
qualifierKey: encodeQualifierKey,
qualifierValue: encodeQualifierValue,
subpath: encodeSubpath
},
Expand Down
16 changes: 14 additions & 2 deletions test/data/contrib-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@
"subpath": null,
"is_invalid": false
},
{
"description": "maven requires a namespace",
"purl": "pkg:maven/[email protected]",
"canonical_purl": "pkg:maven/[email protected]",
"type": "maven",
"namespace": null,
"name": null,
"version": null,
"qualifiers": null,
"subpath": null,
"is_invalid": true
},
{
"description": "improperly encoded version string",
"purl": "pkg:maven/org.apache.commons/[email protected]$@",
Expand All @@ -120,8 +132,8 @@
"is_invalid": true
},
{
"description": "In namespace, leading and trailing slashes '/' are not significant and should be stripped in the canonical form",
"purl": "pkg:golang//github.com/ll/[email protected]",
"description": "leading and trailing slashes '/' are not significant and should be stripped in the canonical form",
"purl": "pkg:golang//github.com///ll////[email protected]",
"canonical_purl": "pkg:golang/github.com/ll/[email protected]",
"type": "golang",
"namespace": "github.com/ll",
Expand Down
24 changes: 6 additions & 18 deletions test/data/test-suite-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
{
"description": "maven often uses qualifiers",
"purl": "pkg:Maven/org.apache.xmlgraphics/[email protected]?classifier=sources&repositorY_url=repo.spring.io/release",
"canonical_purl": "pkg:maven/org.apache.xmlgraphics/[email protected]?classifier=sources&repository_url=repo.spring.io/release",
"canonical_purl": "pkg:maven/org.apache.xmlgraphics/[email protected]?classifier=sources&repository_url=repo.spring.io%2Frelease",
"type": "maven",
"namespace": "org.apache.xmlgraphics",
"name": "batik-anim",
Expand All @@ -122,7 +122,7 @@
{
"description": "maven pom reference",
"purl": "pkg:Maven/org.apache.xmlgraphics/[email protected]?extension=pom&repositorY_url=repo.spring.io/release",
"canonical_purl": "pkg:maven/org.apache.xmlgraphics/[email protected]?extension=pom&repository_url=repo.spring.io/release",
"canonical_purl": "pkg:maven/org.apache.xmlgraphics/[email protected]?extension=pom&repository_url=repo.spring.io%2Frelease",
"type": "maven",
"namespace": "org.apache.xmlgraphics",
"name": "batik-anim",
Expand All @@ -143,18 +143,6 @@
"subpath": null,
"is_invalid": false
},
{
"description": "maven requires a namespace",
"purl": "pkg:maven/[email protected]",
"canonical_purl": "pkg:maven/[email protected]",
"type": "maven",
"namespace": null,
"name": null,
"version": null,
"qualifiers": null,
"subpath": null,
"is_invalid": true
},
{
"description": "npm can be scoped",
"purl": "pkg:npm/%40angular/[email protected]",
Expand Down Expand Up @@ -494,7 +482,7 @@
{
"description": "Hugging Face model with staging endpoint",
"purl": "pkg:huggingface/microsoft/deberta-v3-base@559062ad13d311b87b2c455e67dcd5f1c8f65111?repository_url=https://hub-ci.huggingface.co",
"canonical_purl": "pkg:huggingface/microsoft/deberta-v3-base@559062ad13d311b87b2c455e67dcd5f1c8f65111?repository_url=https://hub-ci.huggingface.co",
"canonical_purl": "pkg:huggingface/microsoft/deberta-v3-base@559062ad13d311b87b2c455e67dcd5f1c8f65111?repository_url=https%3A%2F%2Fhub-ci.huggingface.co",
"type": "huggingface",
"namespace": "microsoft",
"name": "deberta-v3-base",
Expand All @@ -518,7 +506,7 @@
{
"description": "MLflow model tracked in Azure Databricks (case insensitive)",
"purl": "pkg:mlflow/CreditFraud@3?repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow",
"canonical_purl": "pkg:mlflow/creditfraud@3?repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow",
"canonical_purl": "pkg:mlflow/creditfraud@3?repository_url=https%3A%2F%2Fadb-5245952564735461.0.azuredatabricks.net%2Fapi%2F2.0%2Fmlflow",
"type": "mlflow",
"namespace": null,
"name": "creditfraud",
Expand All @@ -530,7 +518,7 @@
{
"description": "MLflow model tracked in Azure ML (case sensitive)",
"purl": "pkg:mlflow/CreditFraud@3?repository_url=https://westus2.api.azureml.ms/mlflow/v1.0/subscriptions/a50f2011-fab8-4164-af23-c62881ef8c95/resourceGroups/TestResourceGroup/providers/Microsoft.MachineLearningServices/workspaces/TestWorkspace",
"canonical_purl": "pkg:mlflow/CreditFraud@3?repository_url=https://westus2.api.azureml.ms/mlflow/v1.0/subscriptions/a50f2011-fab8-4164-af23-c62881ef8c95/resourceGroups/TestResourceGroup/providers/Microsoft.MachineLearningServices/workspaces/TestWorkspace",
"canonical_purl": "pkg:mlflow/CreditFraud@3?repository_url=https%3A%2F%2Fwestus2.api.azureml.ms%2Fmlflow%2Fv1.0%2Fsubscriptions%2Fa50f2011-fab8-4164-af23-c62881ef8c95%2FresourceGroups%2FTestResourceGroup%2Fproviders%2FMicrosoft.MachineLearningServices%2Fworkspaces%2FTestWorkspace",
"type": "mlflow",
"namespace": null,
"name": "CreditFraud",
Expand All @@ -542,7 +530,7 @@
{
"description": "MLflow model with unique identifiers",
"purl": "pkg:mlflow/trafficsigns@10?model_uuid=36233173b22f4c89b451f1228d700d49&run_id=410a3121-2709-4f88-98dd-dba0ef056b0a&repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow",
"canonical_purl": "pkg:mlflow/trafficsigns@10?model_uuid=36233173b22f4c89b451f1228d700d49&repository_url=https://adb-5245952564735461.0.azuredatabricks.net/api/2.0/mlflow&run_id=410a3121-2709-4f88-98dd-dba0ef056b0a",
"canonical_purl": "pkg:mlflow/trafficsigns@10?model_uuid=36233173b22f4c89b451f1228d700d49&repository_url=https%3A%2F%2Fadb-5245952564735461.0.azuredatabricks.net%2Fapi%2F2.0%2Fmlflow&run_id=410a3121-2709-4f88-98dd-dba0ef056b0a",
"type": "mlflow",
"namespace": null,
"name": "trafficsigns",
Expand Down

0 comments on commit ff590d2

Please sign in to comment.