Socket
Socket
Sign inDemoInstall

packageurl-js

Package Overview
Dependencies
Maintainers
0
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

packageurl-js - npm Package Compare versions

Comparing version 1.2.1 to 2.0.0

src/constants.js

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
}

16

package.json
{
"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"
]
}
# 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
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc