Skip to content

Commit 1a3a2ad

Browse files
authored
feat: Factories.FromNodePackageJson.PackageUrlFactory to omits PURL's npm defaults (#207)
feat: `Factories.FromNodePackageJson.PackageUrlFactory` Signed-off-by: Jan Kowalleck <[email protected]>
1 parent 6ff2022 commit 1a3a2ad

File tree

6 files changed

+401
-6
lines changed

6 files changed

+401
-6
lines changed

HISTORY.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@ All notable changes to this project will be documented in this file.
44

55
## unreleased
66

7+
* Added
8+
* New class `Factories.FromNodePackageJson.PackageUrlFactory` that acts like `Factories.PackageUrlFactory`, but
9+
omits PackageUrl's npm-specific "default derived" qualifier values for `download_url` & `vcs_url`. ([#204] via [#207])
710
* Build
811
* Use _TypeScript_ `v4.8.2` now, was `v4.7.4`. (via [#190])
912

13+
[#204]: https://github.com/CycloneDX/cyclonedx-javascript-library/issues/178
14+
[#207]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/207
1015
[#190]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/190
1116

1217
## 1.3.4 - 2022-08-16

src/factories/fromNodePackageJson.node.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,23 @@ SPDX-License-Identifier: Apache-2.0
1717
Copyright (c) OWASP Foundation. All Rights Reserved.
1818
*/
1919

20+
import { PackageURL } from 'packageurl-js'
21+
2022
import * as Models from '../models'
2123
import * as Enums from '../enums'
24+
import { PackageUrlFactory as PlainPackageUrlFactory } from './packageUrl'
2225
import { isNotUndefined } from '../helpers/notUndefined'
2326
import { PackageJson } from '../helpers/packageJson'
27+
import { PackageUrlQualifierNames } from '../helpers/packageUrl'
2428

2529
/**
30+
* Node-specifics.
2631
* @see {@link https://docs.npmjs.com/cli/v8/configuring-npm/package-json PackageJson spec}
2732
*/
2833

34+
/**
35+
* Node-specific ExternalReferenceFactory.
36+
*/
2937
export class ExternalReferenceFactory {
3038
makeExternalReferences (data: PackageJson): Models.ExternalReference[] {
3139
const refs: Array<Models.ExternalReference | undefined> = []
@@ -86,3 +94,49 @@ export class ExternalReferenceFactory {
8694
: undefined
8795
}
8896
}
97+
98+
const npmDefaultRegistryMatcher = /^https?:\/\/registry\.npmjs\.org/
99+
100+
/**
101+
* Node-specific PackageUrlFactory.
102+
*/
103+
export class PackageUrlFactory extends PlainPackageUrlFactory {
104+
override makeFromComponent (component: Models.Component, sort: boolean = false): PackageURL | undefined {
105+
const purl = super.makeFromComponent(component, sort)
106+
return purl === undefined
107+
? undefined
108+
: this.#finalizeQualifiers(purl)
109+
}
110+
111+
/**
112+
* Will strip unnecessary qualifiers according to {@link https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst#known-qualifiers-keyvalue-pairs PURL-SPECIFICATION}:
113+
* > Do not abuse qualifiers: it can be tempting to use many qualifier keys but their usage should be limited
114+
* > to the bare minimum for proper package identification to ensure that a purl stays compact and readable
115+
* > in most cases.
116+
*
117+
* Therefore:
118+
* - "vcs_url" is stripped, if a "download_url" is given.
119+
* - "download_url" is stripped, if it is NPM's default registry ("registry.npmjs.org")
120+
* - "checksum" is stripped, unless a "download_url" or "vcs_url" is given.
121+
*/
122+
#finalizeQualifiers (purl: PackageURL): PackageURL {
123+
const qualifiers = new Map(Object.entries(purl.qualifiers ?? {}))
124+
125+
const downloadUrl = qualifiers.get(PackageUrlQualifierNames.DownloadURL)
126+
if (downloadUrl !== undefined) {
127+
qualifiers.delete(PackageUrlQualifierNames.VcsUrl)
128+
if (npmDefaultRegistryMatcher.test(downloadUrl)) {
129+
qualifiers.delete(PackageUrlQualifierNames.DownloadURL)
130+
}
131+
}
132+
if (!qualifiers.has(PackageUrlQualifierNames.DownloadURL) && !qualifiers.has(PackageUrlQualifierNames.VcsUrl)) {
133+
// nothing to base a checksum on
134+
qualifiers.delete(PackageUrlQualifierNames.Checksum)
135+
}
136+
purl.qualifiers = qualifiers.size > 0
137+
? Object.fromEntries(qualifiers.entries())
138+
: undefined
139+
140+
return purl
141+
}
142+
}

src/factories/index.node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ export * from './index.common'
2121

2222
/** @since 1.2.0 */
2323
export * as FromNodePackageJson from './fromNodePackageJson.node'
24+
2425
/** @deprecated use {@link FromNodePackageJson} instead of {@link FromPackageJson} */
2526
export * as FromPackageJson from './fromNodePackageJson.node'

src/factories/packageUrl.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ SPDX-License-Identifier: Apache-2.0
1717
Copyright (c) OWASP Foundation. All Rights Reserved.
1818
*/
1919

20-
import { Component } from '../models'
2120
import { PackageURL } from 'packageurl-js'
21+
2222
import { ExternalReferenceType } from '../enums'
23+
import { Component } from '../models'
24+
import { PackageUrlQualifierNames } from '../helpers/packageUrl'
2325

2426
export class PackageUrlFactory {
2527
readonly #type: PackageURL['type']
@@ -55,25 +57,27 @@ export class PackageUrlFactory {
5557
// Everything is possible: URL-encoded, not encoded, with schema, without schema
5658
switch (extRef.type) {
5759
case ExternalReferenceType.VCS:
58-
[qualifiers.vcs_url, subpath] = url.split('#', 2)
60+
[qualifiers[PackageUrlQualifierNames.VcsUrl], subpath] = url.split('#', 2)
5961
break
6062
case ExternalReferenceType.Distribution:
61-
qualifiers.download_url = url
63+
qualifiers[PackageUrlQualifierNames.DownloadURL] = url
6264
break
6365
}
6466
}
6567

6668
const hashes = component.hashes
6769
if (hashes.size > 0) {
68-
qualifiers.checksum = Array.from(
69-
sort ? hashes.sorted() : hashes,
70+
qualifiers[PackageUrlQualifierNames.Checksum] = Array.from(
71+
sort
72+
? hashes.sorted()
73+
: hashes,
7074
([hashAlgo, hashCont]) => `${hashAlgo.toLowerCase()}:${hashCont.toLowerCase()}`
7175
).join(',')
7276
}
7377

7478
try {
7579
// Do not beautify the parameters here, because that is in the domain of PackageURL and its representation.
76-
// No need to convert an empty `subpath` string to `undefined` and such.
80+
// No need to convert an empty "subpath" string to `undefined` and such.
7781
return new PackageURL(
7882
this.#type,
7983
component.group,

src/helpers/packageUrl.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*!
2+
This file is part of CycloneDX JavaScript Library.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache-2.0
17+
Copyright (c) OWASP Foundation. All Rights Reserved.
18+
*/
19+
20+
/**
21+
* Known PURL qualifier names.
22+
* To be used until {@link https://github.com/package-url/packageurl-js/pull/34} gets merged
23+
* and {@link https://github.com/package-url/packageurl-js/issues/35} gets sorted out.
24+
*/
25+
export const enum PackageUrlQualifierNames {
26+
DownloadURL = 'download_url',
27+
VcsUrl = 'vcs_url',
28+
Checksum = 'checksum',
29+
}

0 commit comments

Comments
 (0)