Skip to content

Commit 3f440ea

Browse files
committed
Update implementation for spec changes
1 parent 67e16d8 commit 3f440ea

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+7294
-830
lines changed

src/encode.js

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ const { encodeURIComponent: encodeComponent } = globalThis
1212

1313
function encodeName(name) {
1414
return isNonEmptyString(name)
15-
? encodeComponent(name).replace(/%3A/g, ':')
15+
? encodeComponent(name).replaceAll('%3A', ':')
1616
: ''
1717
}
1818

1919
function encodeNamespace(namespace) {
2020
return isNonEmptyString(namespace)
21-
? encodeComponent(namespace).replace(/%3A/g, ':').replace(/%2F/g, '/')
21+
? encodeComponent(namespace).replaceAll('%3A', ':').replaceAll('%2F', '/')
2222
: ''
2323
}
2424

@@ -28,9 +28,9 @@ function encodeQualifierParam(param) {
2828
// 'application/x-www-form-urlencoded' and `spaceAsPlus` of `true`.
2929
// https://url.spec.whatwg.org/#urlencoded-serializing
3030
REUSED_SEARCH_PARAMS.set(REUSED_SEARCH_PARAMS_KEY, param)
31-
return replacePlusSignWithPercentEncodedSpace(
32-
REUSED_SEARCH_PARAMS.toString().slice(REUSED_SEARCH_PARAMS_OFFSET)
33-
)
31+
return REUSED_SEARCH_PARAMS.toString()
32+
.slice(REUSED_SEARCH_PARAMS_OFFSET)
33+
.replaceAll('+', '%2B')
3434
}
3535
return ''
3636
}
@@ -44,28 +44,23 @@ function encodeQualifiers(qualifiers) {
4444
const key = qualifiersKeys[i]
4545
searchParams.set(key, qualifiers[key])
4646
}
47-
return replacePlusSignWithPercentEncodedSpace(searchParams.toString())
47+
return searchParams.toString().replaceAll('+', '%2B')
4848
}
4949
return ''
5050
}
5151

5252
function encodeSubpath(subpath) {
5353
return isNonEmptyString(subpath)
54-
? encodeComponent(subpath).replace(/%2F/g, '/')
54+
? encodeComponent(subpath).replaceAll('%2F', '/')
5555
: ''
5656
}
5757

5858
function encodeVersion(version) {
5959
return isNonEmptyString(version)
60-
? encodeComponent(version).replace(/%3A/g, ':').replace(/%2B/g, '+')
60+
? encodeComponent(version).replaceAll('%3A', ':')
6161
: ''
6262
}
6363

64-
function replacePlusSignWithPercentEncodedSpace(str) {
65-
// Convert plus signs to %20 for better portability.
66-
return str.replace(/\+/g, '%20')
67-
}
68-
6964
module.exports = {
7065
encodeComponent,
7166
encodeName,

src/normalize.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ function normalizePath(pathname, callback) {
5050

5151
function normalizeQualifiers(rawQualifiers) {
5252
let qualifiers
53+
// Use for-of to work with entries iterators.
5354
for (const { 0: key, 1: value } of qualifiersToEntries(rawQualifiers)) {
5455
const strValue = typeof value === 'string' ? value : String(value)
5556
const trimmed = strValue.trim()
@@ -85,7 +86,8 @@ function normalizeVersion(rawVersion) {
8586

8687
function qualifiersToEntries(rawQualifiers) {
8788
if (isObject(rawQualifiers)) {
88-
return rawQualifiers instanceof URLSearchParams
89+
// URLSearchParams instances have an "entries" method that returns an iterator.
90+
return typeof rawQualifiers.entries === 'function'
8991
? rawQualifiers.entries()
9092
: Object.entries(rawQualifiers)
9193
}

src/package-url.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,17 @@ class PackageURL {
214214
}
215215

216216
let rawQualifiers
217-
const { searchParams } = url
218-
if (searchParams.size !== 0) {
219-
searchParams.forEach(value => decodePurlComponent('qualifiers', value))
217+
if (url.searchParams.size !== 0) {
218+
const search = url.search.slice(1)
219+
const searchParams = new URLSearchParams()
220+
const entries = search.split('&')
221+
for (let i = 0, { length } = entries; i < length; i += 1) {
222+
const pairs = entries[i].split('=')
223+
const value = decodePurlComponent('qualifiers', pairs.at(1) ?? '')
224+
// Use append preserves plus signs.
225+
// https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams#preserving_plus_signs
226+
searchParams.append(pairs[0], value)
227+
}
220228
// Split the remainder once from right on '?'.
221229
rawQualifiers = searchParams
222230
}

src/validate.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ function validateQualifiers(qualifiers, throws) {
3636
return false
3737
}
3838
const keysIterable =
39-
// URL searchParams have an "keys" method that returns an iterator.
39+
// URLSearchParams instances have a "keys" method that returns an iterator.
4040
typeof qualifiers.keys === 'function'
4141
? qualifiers.keys()
4242
: Object.keys(qualifiers)
43+
// Use for-of to work with URLSearchParams iterators
4344
for (const key of keysIterable) {
4445
if (!validateQualifierKey(key, throws)) {
4546
return false
@@ -136,7 +137,7 @@ function validateType(type, throws) {
136137
return false
137138
}
138139
// The package type is composed only of ASCII letters and numbers,
139-
// '.', '+' and '-' (period, plus, and dash)
140+
// '.' (period), and '-' (dash).
140141
for (let i = 0, { length } = type; i < length; i += 1) {
141142
const code = type.charCodeAt(i)
142143
// biome-ignore format: newlines
@@ -147,7 +148,6 @@ function validateType(type, throws) {
147148
(code >= 65 && code <= 90) || // A-Z
148149
(code >= 97 && code <= 122) || // a-z
149150
code === 46 || // .
150-
code === 43 || // +
151151
code === 45
152152
) // -
153153
)

test/benchmark.test.js

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,33 @@
11
'use strict'
22

33
const assert = require('node:assert/strict')
4+
const path = require('node:path')
45
const { describe, it } = require('node:test')
56

6-
const TEST_FILE = require('./data/test-suite-data.json')
7+
const { glob } = require('fast-glob')
8+
9+
const { readJson } = require('@socketsecurity/registry/lib/fs')
10+
const { isObject } = require('@socketsecurity/registry/lib/objects')
11+
712
const { PackageURL } = require('../src/package-url')
813

914
describe('PackageURL', () => {
10-
it('Benchmarking the library', () => {
15+
it('Benchmarking the library', async () => {
16+
const TEST_FILES = (
17+
await Promise.all(
18+
(
19+
await glob(['**/**.json'], {
20+
absolute: true,
21+
cwd: path.join(__dirname, 'data')
22+
})
23+
).map(p => readJson(p))
24+
)
25+
)
26+
.filter(Boolean)
27+
.flatMap(o => o.tests ?? [])
28+
1129
const iterations = 10000
12-
const data = TEST_FILE.filter(obj => !obj.is_invalid)
30+
const data = TEST_FILES.filter(obj => isObject(obj.expected_output))
1331
const { length: dataLength } = data
1432
const objects = []
1533
for (let i = 0; i < iterations; i += dataLength) {
@@ -23,13 +41,14 @@ describe('PackageURL', () => {
2341
const start = Date.now()
2442
for (let i = 0; i < iterations; i += 1) {
2543
const obj = objects[i]
44+
const { expected_output } = obj
2645
const purl = new PackageURL(
27-
obj.type,
28-
obj.namespace,
29-
obj.name,
30-
obj.version,
31-
obj.qualifiers,
32-
obj.subpath
46+
expected_output.type,
47+
expected_output.namespace,
48+
expected_output.name,
49+
expected_output.version,
50+
expected_output.qualifiers,
51+
expected_output.subpath
3352
)
3453
PackageURL.fromString(purl.toString())
3554
}

0 commit comments

Comments
 (0)