packageurl-js
Advanced tools
Comparing version 1.2.1 to 2.0.0
15
index.js
@@ -22,7 +22,16 @@ /*! | ||
*/ | ||
'use strict' | ||
const PackageURL = require('./src/package-url'); | ||
const { | ||
PackageURL, | ||
PurlComponent, | ||
PurlQualifierNames, | ||
PurlType | ||
} = require('./src/package-url') | ||
module.exports = { | ||
PackageURL | ||
}; | ||
PackageURL, | ||
PurlComponent, | ||
PurlQualifierNames, | ||
PurlType | ||
} |
{ | ||
"name": "packageurl-js", | ||
"version": "1.2.1", | ||
"version": "2.0.0", | ||
"description": "JavaScript library to parse and build \"purl\" aka. package URLs. This is a microlibrary implementing the purl spec at https://github.com/package-url", | ||
@@ -21,7 +21,15 @@ "keywords": [ | ||
"scripts": { | ||
"test": "mocha" | ||
"test": "mocha", | ||
"prettify": "prettier --write ./**/*.js" | ||
}, | ||
"devDependencies": { | ||
"mocha": "^10.2.0" | ||
} | ||
"mocha": "^10.4.0", | ||
"prettier": "^3.2.5" | ||
}, | ||
"files": [ | ||
"*{.js,.ts}", | ||
"src/**/*{.js,.ts}", | ||
"LICENSE", | ||
"README.md" | ||
] | ||
} |
133
README.md
# packageurl-js | ||
### Installing: | ||
### Installing | ||
To install `packageurl-js` in your project, simply run: | ||
``` | ||
```bash | ||
npm install packageurl-js | ||
@@ -11,7 +12,8 @@ ``` | ||
### Local Development: | ||
Clone the `packageurl-js` repo and `cd` into the directory. | ||
### Local Development | ||
Then run: | ||
``` | ||
Clone the `packageurl-js` repo and `cd` into the directory. | ||
Then run: | ||
```bash | ||
npm install | ||
@@ -21,4 +23,5 @@ ``` | ||
### Testing | ||
To run the test suite: | ||
``` | ||
To run the test suite: | ||
```bash | ||
npm test | ||
@@ -29,31 +32,32 @@ ``` | ||
#### Import ES6 Module | ||
#### Importing | ||
As an ES6 module | ||
```js | ||
import { PackageURL } from 'packageurl-js' | ||
``` | ||
import { PackageURL } from 'packageurl-js'; | ||
``` | ||
#### Import CommonJs Module | ||
As a CommonJS module | ||
```js | ||
const { PackageURL } = require('packageurl-js') | ||
``` | ||
const { PackageURL } = require('packageurl-js'); | ||
``` | ||
#### Parsing from a string | ||
#### Parsing | ||
```js | ||
const purlStr = 'pkg:maven/org.springframework.integration/spring-integration-jms@5.5.5' | ||
console.log(PackageURL.fromString(purlStr)) | ||
console.log(new PackageURL(...PackageURL.parseString(purlStr))) | ||
``` | ||
const pkg = PackageURL.fromString('pkg:maven/org.springframework.integration/spring-integration-jms@5.5.5'); | ||
console.log(pkg); | ||
``` | ||
=> | ||
will both log | ||
``` | ||
PackageURL { | ||
type: 'maven', | ||
name: 'spring-integration-jms', | ||
namespace: 'org.springframework.integration', | ||
version: '5.5.5', | ||
qualifiers: null, | ||
subpath: null | ||
type: 'maven', | ||
name: 'spring-integration-jms', | ||
namespace: 'org.springframework.integration', | ||
version: '5.5.5', | ||
qualifiers: undefined, | ||
subpath: undefined | ||
} | ||
@@ -64,3 +68,3 @@ ``` | ||
``` | ||
```js | ||
const pkg = new PackageURL( | ||
@@ -70,7 +74,5 @@ 'maven', | ||
'spring-integration-jms', | ||
'5.5.5', | ||
undefined, | ||
undefined); | ||
console.log(pkg.toString()); | ||
'5.5.5' | ||
) | ||
console.log(pkg.toString()) | ||
``` | ||
@@ -86,7 +88,7 @@ | ||
``` | ||
```js | ||
try { | ||
PackageURL.fromString('not-a-purl'); | ||
} catch(ex) { | ||
console.error(ex.message); | ||
PackageURL.fromString('not-a-purl') | ||
} catch (e) { | ||
console.error(e.message) | ||
} | ||
@@ -98,3 +100,60 @@ ``` | ||
``` | ||
purl is missing the required "pkg" scheme component. | ||
Invalid purl: missing required "pkg" scheme component | ||
``` | ||
#### Helper Objects | ||
Helpers for encoding, normalizing, and validating purl components and types can | ||
be imported directly from the module or found on the PackageURL class as static | ||
properties. | ||
```js | ||
import { | ||
PackageURL, | ||
PurlComponent, | ||
PurlType | ||
} from 'packageurl-js' | ||
PurlComponent === PackageURL.Component // => true | ||
PurlType === PackageURL.Type // => true | ||
``` | ||
#### PurlComponent | ||
Contains the following properties each with their own `encode`, `normalize`, | ||
and `validate` methods, e.g. `PurlComponent.name.validate(nameStr)`: | ||
- type | ||
- namespace | ||
- name | ||
- version | ||
- qualifiers | ||
- qualifierKey | ||
- qualifierValue | ||
- subpath | ||
#### PurlType | ||
Contains the following properties each with their own `normalize`, and `validate` | ||
methods, e.g. `PurlType.npm.validate(purlObj)`: | ||
- alpm | ||
- apk | ||
- bitbucket | ||
- bitnami | ||
- composer | ||
- conan | ||
- cran | ||
- deb | ||
- github | ||
- gitlab | ||
- golang | ||
- hex | ||
- huggingface | ||
- luarocks | ||
- maven | ||
- mlflow | ||
- npm | ||
- oci | ||
- pub | ||
- pypi | ||
- qpkg | ||
- rpm | ||
- swift |
@@ -25,74 +25,170 @@ /*! | ||
export type PurlQualifiers = { [key: string]: string } | ||
export type PurlComponentEncoder = (comp: any) => string | ||
export type PurlComponentQualifiersNormalizer = (comp: any) => PurlQualifiers | undefined | ||
export type PurlComponentStringNormalizer = (comp: any) => string | undefined | ||
export type PurlComponentValidator = (comp: any, throws: boolean) => boolean | ||
export type PurlTypNormalizer = <T extends PackageURL>(purl: T) => T | ||
export type PurlTypeValidator = (purl: PackageURL, throws: boolean) => boolean | ||
export type PurlComponentEntry = Readonly<{ | ||
encode: PurlComponentEncoder | ||
normalize: PurlComponentStringNormalizer | ||
validate: PurlComponentValidator | ||
}> | ||
export type PurlTypeEntry = Readonly<{ | ||
normalize: PurlTypNormalizer | ||
validate: PurlTypeValidator | ||
}> | ||
/** | ||
* Collection of PURL component encode, normalize, and validate methods. | ||
* @see {@link https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#rules-for-each-purl-component specification} | ||
*/ | ||
export type PurlComponent = Readonly<{ | ||
type: PurlComponentEntry | ||
namespace: PurlComponentEntry | ||
name: PurlComponentEntry | ||
version: PurlComponentEntry | ||
qualifierKey: PurlComponentEntry | ||
qualifiers: PurlComponentEntry | ||
qualifierValue: PurlComponentEntry | ||
subpath: PurlComponentEntry | ||
}> | ||
/** | ||
* Known qualifiers names. | ||
* @see {@link https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#known-qualifiers-keyvalue-pairs specification} | ||
*/ | ||
export type PurlQualifierNames = Readonly<{ | ||
RepositoryUrl:'repository_url', | ||
DownloadUrl: 'download_url', | ||
VcsUrl: 'vcs_url', | ||
FileName: 'file_name', | ||
Checksum: 'checksum' | ||
}> | ||
/** | ||
* Collection of PURL type normalize and validate methods. | ||
* @see {@link https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#known-purl-types specification} | ||
*/ | ||
export type PurlType = Readonly<{ | ||
alpm: PurlTypeEntry | ||
apk: PurlTypeEntry | ||
bitbucket: PurlTypeEntry | ||
bitnami: PurlTypeEntry | ||
composer: PurlTypeEntry | ||
conan: PurlTypeEntry | ||
cran: PurlTypeEntry | ||
deb: PurlTypeEntry | ||
github: PurlTypeEntry | ||
gitlab: PurlTypeEntry | ||
golang: PurlTypeEntry | ||
hex: PurlTypeEntry | ||
huggingface: PurlTypeEntry | ||
luarocks: PurlTypeEntry | ||
maven: PurlTypeEntry | ||
mlflow: PurlTypeEntry | ||
npm: PurlTypeEntry | ||
oci: PurlTypeEntry | ||
pub: PurlTypeEntry | ||
pypi: PurlTypeEntry | ||
qpkg: PurlTypeEntry | ||
rpm: PurlTypeEntry | ||
swift: PurlTypeEntry | ||
}> | ||
/** | ||
* A purl or package URL is an attempt to standardize existing approaches to reliably identify and locate software packages. | ||
* A purl is a URL string used to identify and locate a software package in a mostly universal and uniform way across programing languages, | ||
* package managers, packaging conventions, tools, APIs and databases. | ||
* Such a package URL is useful to reliably reference the same software package using a simple and expressive syntax and conventions based on familiar URLs. | ||
* A purl is a URL string used to identify and locate a software package in a mostly universal and uniform way across | ||
* programming languages, package managers, packaging conventions, tools, APIs and databases. Such a package URL is | ||
* useful to reliably reference the same software package using a simple and expressive syntax and conventions based | ||
* on familiar URLs. | ||
*/ | ||
class PackageURL { | ||
export class PackageURL { | ||
/** | ||
* Known qualifiers names. | ||
* @see {@link https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#known-qualifiers-keyvalue-pairs specification} | ||
*/ | ||
static KnownQualifierNames: Readonly<{ | ||
RepositoryUrl:'repository_url', | ||
DownloadUrl: 'download_url', | ||
VcsUrl: 'vcs_url', | ||
FileName: 'file_name', | ||
Checksum: 'checksum' | ||
}> | ||
static Component: PurlComponent | ||
static KnownQualifierNames: PurlQualifierNames | ||
static Type: PurlType | ||
/** | ||
* the package "type" or package "protocol" such as maven, npm, nuget, gem, pypi, etc. Required. | ||
* The package "type" or package "protocol" such as maven, npm, nuget, gem, | ||
* pypi, etc. Required. | ||
*/ | ||
type: string; | ||
type: string | ||
/** | ||
* some name prefix such as a Maven groupid, a Docker image owner, a GitHub user or organization. Optional and type-specific. | ||
* Some name prefix such as a Maven groupid, a Docker image owner, a GitHub | ||
* user or organization. Optional and type-specific. | ||
*/ | ||
namespace: string | undefined | null; | ||
namespace: string | undefined | ||
/** | ||
* the name of the package. Required. | ||
* The name of the package. Required. | ||
*/ | ||
name: string; | ||
name: string | ||
/** | ||
* the version of the package. Optional. | ||
* The version of the package. Optional. | ||
*/ | ||
version: string | undefined | null; | ||
version: string | undefined | ||
/** | ||
* extra qualifying data for a package such as an OS, architecture, a distro, etc. Optional and type-specific. | ||
* Extra qualifying data for a package such as an OS, architecture, a distro, | ||
* etc. Optional and type-specific. | ||
*/ | ||
qualifiers: { | ||
[key: string]: string; | ||
} | undefined | null; | ||
qualifiers: PurlQualifiers | undefined | ||
/** | ||
* extra subpath within a package, relative to the package root. Optional. | ||
* Extra subpath within a package, relative to the package root. Optional. | ||
*/ | ||
subpath: string | undefined | null; | ||
subpath: string | undefined | ||
constructor(type: string, | ||
constructor( | ||
type: string, | ||
namespace: string | undefined | null, | ||
name: string, | ||
version: string | undefined | null, | ||
qualifiers: { [key: string]: string; } | undefined | null, | ||
subpath: string | undefined | null); | ||
version?: string | undefined | null, | ||
qualifiers?: PurlQualifiers | string | undefined | null, | ||
subpath?: string | undefined | null | ||
) | ||
/** | ||
* Converts the PackageURL to a string | ||
* Converts the PackageURL to a string. | ||
*/ | ||
toString(): string; | ||
toString(): string | ||
/** | ||
* Parses a string tp a PackageURL | ||
* @param purl string to parse | ||
* Parses a purl string into a PackageURL instance. | ||
*/ | ||
static fromString(purl: string): PackageURL | ||
static fromString(purlStr: string): PackageURL | ||
/** | ||
* Parses a purl string into a PackageURL arguments array. | ||
*/ | ||
static parseString(purlStr: string): [ | ||
type: string | undefined, | ||
namespace: string | undefined, | ||
name: string | undefined, | ||
version: string | undefined, | ||
qualifiers: PurlQualifiers | undefined, | ||
subpath: string | undefined | ||
] | ||
} | ||
// @ts-ignore | ||
export const PurlComponent = <PurlComponent>{} | ||
// @ts-ignore | ||
export const PurlQualifierNames = <PurlQualifierNames>{} | ||
// @ts-ignore | ||
export const PurlType = <PurlType>{} | ||
} |
@@ -22,205 +22,242 @@ /*! | ||
*/ | ||
'use strict' | ||
const KnownQualifierNames = Object.freeze({ | ||
// known qualifiers as defined here: | ||
// https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#known-qualifiers-keyvalue-pairs | ||
RepositoryUrl: 'repository_url', | ||
DownloadUrl: 'download_url', | ||
VcsUrl: 'vcs_url', | ||
FileName: 'file_name', | ||
Checksum: 'checksum' | ||
}); | ||
const { isObject, recursiveFreeze } = require('./objects') | ||
const { isBlank, isNonEmptyString, trimLeadingSlashes } = require('./strings') | ||
const { PurlComponent } = require('./purl-component') | ||
const { PurlQualifierNames } = require('./purl-qualifier-names') | ||
const { PurlType } = require('./purl-type') | ||
class PackageURL { | ||
static Component = recursiveFreeze(PurlComponent) | ||
static KnownQualifierNames = recursiveFreeze(PurlQualifierNames) | ||
static Type = recursiveFreeze(PurlType) | ||
static get KnownQualifierNames() { | ||
return KnownQualifierNames; | ||
} | ||
constructor( | ||
rawType, | ||
rawNamespace, | ||
rawName, | ||
rawVersion, | ||
rawQualifiers, | ||
rawSubpath | ||
) { | ||
const type = isNonEmptyString(rawType) | ||
? PurlComponent.type.normalize(rawType) | ||
: rawType | ||
PurlComponent.type.validate(type, true) | ||
constructor(type, namespace, name, version, qualifiers, subpath) { | ||
let required = { 'type': type, 'name': name }; | ||
Object.keys(required).forEach(key => { | ||
if (!required[key]) { | ||
throw new Error('Invalid purl: "' + key + '" is a required field.'); | ||
} | ||
}); | ||
const namespace = isNonEmptyString(rawNamespace) | ||
? PurlComponent.namespace.normalize(rawNamespace) | ||
: rawNamespace | ||
PurlComponent.namespace.validate(namespace, true) | ||
let strings = { 'type': type, 'namespace': namespace, 'name': name, 'versions': version, 'subpath': subpath }; | ||
Object.keys(strings).forEach(key => { | ||
if (strings[key] && typeof strings[key] === 'string' || !strings[key]) { | ||
return; | ||
} | ||
throw new Error('Invalid purl: "' + key + '" argument must be a string.'); | ||
}); | ||
const name = isNonEmptyString(rawName) | ||
? PurlComponent.name.normalize(rawName) | ||
: rawName | ||
PurlComponent.name.validate(name, true) | ||
if (qualifiers) { | ||
if (typeof qualifiers !== 'object') { | ||
throw new Error('Invalid purl: "qualifiers" argument must be a dictionary.'); | ||
} | ||
Object.keys(qualifiers).forEach(key => { | ||
if (!/^[a-z]+$/i.test(key) && !/[\.-_]/.test(key)) { | ||
throw new Error('Invalid purl: qualifier "' + key + '" contains an illegal character.'); | ||
} | ||
}); | ||
} | ||
const version = isNonEmptyString(rawVersion) | ||
? PurlComponent.version.normalize(rawVersion) | ||
: rawVersion | ||
PurlComponent.version.validate(version, true) | ||
this.type = type; | ||
this.name = name; | ||
this.namespace = namespace; | ||
this.version = version; | ||
this.qualifiers = qualifiers; | ||
this.subpath = subpath; | ||
} | ||
const qualifiers = | ||
typeof rawQualifiers === 'string' || isObject(rawQualifiers) | ||
? PurlComponent.qualifiers.normalize(rawQualifiers) | ||
: rawQualifiers | ||
PurlComponent.qualifiers.validate(qualifiers, true) | ||
_handlePyPi() { | ||
this.name = this.name.toLowerCase().replace(/_/g, '-'); | ||
} | ||
_handlePub() { | ||
this.name = this.name.toLowerCase(); | ||
if (!/^[a-z0-9_]+$/i.test(this.name)) { | ||
throw new Error('Invalid purl: contains an illegal character.'); | ||
} | ||
} | ||
const subpath = isNonEmptyString(rawSubpath) | ||
? PurlComponent.subpath.normalize(rawSubpath) | ||
: rawSubpath | ||
PurlComponent.subpath.validate(subpath, true) | ||
toString() { | ||
var purl = ['pkg:', encodeURIComponent(this.type), '/']; | ||
this.type = type | ||
this.name = name | ||
this.namespace = namespace ?? undefined | ||
this.version = version ?? undefined | ||
this.qualifiers = qualifiers ?? undefined | ||
this.subpath = subpath ?? undefined | ||
if (this.type === 'pypi') { | ||
this._handlePyPi(); | ||
const typeHelpers = PurlType[type] | ||
if (typeHelpers) { | ||
typeHelpers.normalize(this) | ||
typeHelpers.validate(this, true) | ||
} | ||
} | ||
if (this.type === 'pub') { | ||
this._handlePub(); | ||
} | ||
if (this.namespace) { | ||
purl.push( | ||
encodeURIComponent(this.namespace) | ||
.replace(/%3A/g, ':') | ||
.replace(/%2F/g, '/') | ||
); | ||
purl.push('/'); | ||
toString() { | ||
const { namespace, name, version, qualifiers, subpath, type } = this | ||
let purlStr = `pkg:${PurlComponent.type.encode(type)}/` | ||
if (namespace) { | ||
purlStr = `${purlStr}${PurlComponent.namespace.encode(namespace)}/` | ||
} | ||
purlStr = `${purlStr}${PurlComponent.name.encode(name)}` | ||
if (version) { | ||
purlStr = `${purlStr}@${PurlComponent.version.encode(version)}` | ||
} | ||
if (qualifiers) { | ||
purlStr = `${purlStr}?${PurlComponent.qualifiers.encode(qualifiers)}` | ||
} | ||
if (subpath) { | ||
purlStr = `${purlStr}#${PurlComponent.subpath.encode(subpath)}` | ||
} | ||
return purlStr | ||
} | ||
purl.push(encodeURIComponent(this.name).replace(/%3A/g, ':')); | ||
if (this.version) { | ||
purl.push('@'); | ||
purl.push(encodeURIComponent(this.version).replace(/%3A/g, ':').replace(/%2B/g,'+')); | ||
static fromString(purlStr) { | ||
return new PackageURL(...PackageURL.parseString(purlStr)) | ||
} | ||
if (this.qualifiers) { | ||
purl.push('?'); | ||
static parseString(purlStr) { | ||
// https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#how-to-parse-a-purl-string-in-its-components | ||
if (typeof purlStr !== 'string') { | ||
throw new Error('A purl string argument is required.') | ||
} | ||
if (isBlank(purlStr)) { | ||
return [ | ||
undefined, | ||
undefined, | ||
undefined, | ||
undefined, | ||
undefined, | ||
undefined | ||
] | ||
} | ||
let qualifiers = this.qualifiers; | ||
let qualifierString = []; | ||
Object.keys(qualifiers).sort().forEach(key => { | ||
qualifierString.push( | ||
encodeURIComponent(key).replace(/%3A/g, ':') | ||
+ '=' | ||
+ encodeURIComponent(qualifiers[key]).replace(/%2F/g, '/') | ||
); | ||
}); | ||
// Split the remainder once from left on ':'. | ||
const colonIndex = purlStr.indexOf(':') | ||
// Use WHATWG URL to split up the purl string. | ||
// - Split the purl string once from right on '#' | ||
// - Split the remainder once from right on '?' | ||
// - Split the remainder once from left on ':' | ||
let url | ||
let maybeUrlWithAuth | ||
if (colonIndex !== -1) { | ||
try { | ||
// Since a purl never contains a URL Authority, its scheme | ||
// must not be suffixed with double slash as in 'pkg://' | ||
// and should use instead 'pkg:'. Purl parsers must accept | ||
// URLs such as 'pkg://' and must ignore the '//' | ||
const beforeColon = purlStr.slice(0, colonIndex) | ||
const afterColon = purlStr.slice(colonIndex + 1) | ||
const trimmedAfterColon = trimLeadingSlashes(afterColon) | ||
url = new URL(`${beforeColon}:${trimmedAfterColon}`) | ||
maybeUrlWithAuth = | ||
afterColon.length === trimmedAfterColon.length | ||
? url | ||
: new URL(purlStr) | ||
} catch (e) { | ||
throw new Error('Invalid purl: failed to parse as URL', { | ||
cause: e | ||
}) | ||
} | ||
} | ||
// The scheme is a constant with the value "pkg". | ||
if (url?.protocol !== 'pkg:') { | ||
throw new Error( | ||
'Invalid purl: missing required "pkg" scheme component' | ||
) | ||
} | ||
// A purl must NOT contain a URL Authority i.e. there is no support for | ||
// username, password, host and port components. | ||
if ( | ||
maybeUrlWithAuth.username !== '' || | ||
maybeUrlWithAuth.password !== '' | ||
) { | ||
throw new Error( | ||
'Invalid purl: cannot contain a "user:pass@host:port"' | ||
) | ||
} | ||
purl.push(qualifierString.join('&')); | ||
} | ||
const { pathname } = url | ||
const firstSlashIndex = pathname.indexOf('/') | ||
const rawType = | ||
firstSlashIndex === -1 | ||
? pathname | ||
: pathname.slice(0, firstSlashIndex) | ||
if (firstSlashIndex < 1) { | ||
return [ | ||
rawType, | ||
undefined, | ||
undefined, | ||
undefined, | ||
undefined, | ||
undefined | ||
] | ||
} | ||
if (this.subpath) { | ||
purl.push('#'); | ||
purl.push(encodeURIComponent(this.subpath) | ||
.replace(/%3A/g, ':') | ||
.replace(/%2F/g, '/')); | ||
} | ||
let rawVersion | ||
let atSignIndex = pathname.lastIndexOf('@') | ||
// Handle unencoded leading '@' characters. This is a small break from | ||
// the specification to make parsing more forgiving so that users don't | ||
// have to deal with it. | ||
if ( | ||
atSignIndex !== -1 && | ||
pathname.charCodeAt(atSignIndex - 1) === 47 /*'/'*/ | ||
) { | ||
atSignIndex = -1 | ||
} | ||
const beforeVersion = pathname.slice( | ||
rawType.length + 1, | ||
atSignIndex === -1 ? pathname.length : atSignIndex | ||
) | ||
if (atSignIndex !== -1) { | ||
// Split the remainder once from right on '@'. | ||
rawVersion = pathname.slice(atSignIndex + 1) | ||
} | ||
return purl.join(''); | ||
} | ||
let rawNamespace | ||
let rawName | ||
const lastSlashIndex = beforeVersion.lastIndexOf('/') | ||
if (lastSlashIndex === -1) { | ||
// Split the remainder once from right on '/'. | ||
rawName = beforeVersion | ||
} else { | ||
// Split the remainder once from right on '/'. | ||
rawName = beforeVersion.slice(lastSlashIndex + 1) | ||
// Split the remainder on '/'. | ||
rawNamespace = beforeVersion.slice(0, lastSlashIndex) | ||
} | ||
static fromString(purl) { | ||
if (!purl || typeof purl !== 'string' || !purl.trim()) { | ||
throw new Error('A purl string argument is required.'); | ||
} | ||
let rawQualifiers | ||
const { searchParams } = url | ||
if (searchParams.size !== 0) { | ||
// Split the remainder once from right on '?'. | ||
rawQualifiers = searchParams | ||
} | ||
let scheme = purl.slice(0, purl.indexOf(':')) | ||
let remainder = purl.slice(purl.indexOf(':') + 1) | ||
if (scheme !== 'pkg') { | ||
throw new Error('purl is missing the required "pkg" scheme component.'); | ||
} | ||
// this strip '/, // and /// as possible in :// or :/// | ||
// from https://gist.github.com/refo/47632c8a547f2d9b6517#file-remove-leading-slash | ||
remainder = remainder.trim().replace(/^\/+/g, ''); | ||
let rawSubpath | ||
const { hash } = url | ||
if (hash.length !== 0) { | ||
// Split the purl string once from right on '#'. | ||
rawSubpath = hash.slice(1) | ||
} | ||
let type | ||
[type, remainder] = remainder.split('/', 2); | ||
if (!type || !remainder) { | ||
throw new Error('purl is missing the required "type" component.'); | ||
return [ | ||
rawType, | ||
rawNamespace, | ||
rawName, | ||
rawVersion, | ||
rawQualifiers, | ||
rawSubpath | ||
] | ||
} | ||
type = decodeURIComponent(type) | ||
} | ||
let url = new URL(purl); | ||
for (const staticProp of ['Component', 'KnownQualifierNames', 'Type']) { | ||
Reflect.defineProperty(PackageURL, staticProp, { | ||
...Reflect.getOwnPropertyDescriptor(PackageURL, staticProp), | ||
writable: false | ||
}) | ||
} | ||
let qualifiers = null; | ||
url.searchParams.forEach((value, key) => { | ||
if (!qualifiers) { | ||
qualifiers = {}; | ||
} | ||
qualifiers[key] = value; | ||
}); | ||
let subpath = url.hash; | ||
if (subpath.indexOf('#') === 0) { | ||
subpath = subpath.substring(1); | ||
} | ||
subpath = subpath.length === 0 | ||
? null | ||
: decodeURIComponent(subpath) | ||
Reflect.setPrototypeOf(PackageURL.prototype, null) | ||
if (url.username !== '' || url.password !== '') { | ||
throw new Error('Invalid purl: cannot contain a "user:pass@host:port"'); | ||
} | ||
// this strip '/, // and /// as possible in :// or :/// | ||
// from https://gist.github.com/refo/47632c8a547f2d9b6517#file-remove-leading-slash | ||
let path = url.pathname.trim().replace(/^\/+/g, ''); | ||
// version is optional - check for existence | ||
let version = null; | ||
if (path.includes('@')) { | ||
let index = path.indexOf('@'); | ||
let rawVersion= path.substring(index + 1); | ||
version = decodeURIComponent(rawVersion); | ||
// Convert percent-encoded colons (:) back, to stay in line with the `toString` | ||
// implementation of this library. | ||
// https://github.com/package-url/packageurl-js/blob/58026c86978c6e356e5e07f29ecfdccbf8829918/src/package-url.js#L98C10-L98C10 | ||
let versionEncoded = encodeURIComponent(version).replace(/%3A/g, ':').replace(/%2B/g,'+'); | ||
if (rawVersion !== versionEncoded) { | ||
throw new Error('Invalid purl: version must be percent-encoded'); | ||
} | ||
remainder = path.substring(0, index); | ||
} else { | ||
remainder = path; | ||
} | ||
// The 'remainder' should now consist of an optional namespace and the name | ||
let remaining = remainder.split('/').slice(1); | ||
let name = null; | ||
let namespace = null; | ||
if (remaining.length > 1) { | ||
let nameIndex = remaining.length - 1; | ||
let namespaceComponents = remaining.slice(0, nameIndex); | ||
name = decodeURIComponent(remaining[nameIndex]); | ||
namespace = decodeURIComponent(namespaceComponents.join('/')); | ||
} else if (remaining.length === 1) { | ||
name = decodeURIComponent(remaining[0]); | ||
} | ||
if (name === '') { | ||
throw new Error('purl is missing the required "name" component.'); | ||
} | ||
return new PackageURL(type, namespace, name, version, qualifiers, subpath); | ||
} | ||
}; | ||
module.exports = PackageURL; | ||
module.exports = { | ||
PackageURL, | ||
PurlComponent, | ||
PurlQualifierNames, | ||
PurlType | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
55459
18
1391
153
2
1