@@ -29,12 +29,15 @@ Copyright (c) OWASP Foundation. All Rights Reserved.
2929import type { PackageURL } from 'packageurl-js'
3030import { PurlQualifierNames } from 'packageurl-js'
3131
32- import { tryCanonicalizeGitUrl } from "../_helpers/gitUrl"
32+ import { tryCanonicalizeGitUrl } from "../_helpers/gitUrl"
3333import { isNotUndefined } from '../_helpers/notUndefined'
3434import type { PackageJson } from '../_helpers/packageJson'
3535import { ExternalReferenceType } from '../enums/externalReferenceType'
36+ import { HashAlgorithm } from "../enums/hashAlogorithm" ;
3637import type { Component } from '../models/component'
3738import { ExternalReference } from '../models/externalReference'
39+ import { HashDictionary } from '../models/hash'
40+ import { defaultRegistryMatcher , parsePackageIntegrity } from '../utils/npmjsUtility.node'
3841import { PackageUrlFactory as PlainPackageUrlFactory } from './packageUrl'
3942
4043/**
@@ -47,6 +50,7 @@ export class ExternalReferenceFactory {
4750 try { refs . push ( this . makeVcs ( data ) ) } catch { /* pass */ }
4851 try { refs . push ( this . makeHomepage ( data ) ) } catch { /* pass */ }
4952 try { refs . push ( this . makeIssueTracker ( data ) ) } catch { /* pass */ }
53+ try { refs . push ( this . makeDist ( data ) ) } catch { /* pass */ }
5054
5155 return refs . filter ( isNotUndefined )
5256 }
@@ -100,13 +104,32 @@ export class ExternalReferenceFactory {
100104 ? new ExternalReference ( url , ExternalReferenceType . IssueTracker , { comment } )
101105 : undefined
102106 }
103- }
104107
105- /**
106- * The default repository is `https://registry.npmjs.org`.
107- * @see {@link https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#npm }
108- */
109- const npmDefaultRepositoryMatcher = / ^ h t t p s ? : \/ \/ r e g i s t r y \. n p m j s \. o r g ( : ? \/ | $ ) /
108+ makeDist ( data : PackageJson ) : ExternalReference | undefined {
109+ // "dist" might be used in bundled dependencies' manifests.
110+ // docs: https://blog.npmjs.org/post/172999548390/new-pgp-machinery
111+ /* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- acknowledged */
112+ const { tarball, integrity, shasum } = data . dist ?? { }
113+ if ( typeof tarball === 'string' ) {
114+ const hashes = new HashDictionary ( )
115+ let comment = 'as detected from PackageJson property "dist.tarball"'
116+ if ( typeof integrity === 'string' ) {
117+ try {
118+ // actually not the hash of the file, but more of an integrity-check -- lets use it anyway.
119+ // see https://blog.npmjs.org/post/172999548390/new-pgp-machinery
120+ hashes . set ( ...parsePackageIntegrity ( integrity ) )
121+ comment += ' and property "dist.integrity"'
122+ } catch { /* pass */ }
123+ }
124+ if ( typeof shasum === 'string' && shasum . length === 40 ) {
125+ hashes . set ( HashAlgorithm [ "SHA-1" ] , shasum )
126+ comment += ' and property "dist.shasum"'
127+ }
128+ return new ExternalReference ( tarball , ExternalReferenceType . Distribution , { hashes, comment } )
129+ }
130+ return undefined
131+ }
132+ }
110133
111134/**
112135 * Node-specific PackageUrlFactory.
@@ -134,13 +157,13 @@ export class PackageUrlFactory extends PlainPackageUrlFactory<'npm'> {
134157 * - "download_url" is stripped, if it is NPM's default registry ("registry.npmjs.org")
135158 * - "checksum" is stripped, unless a "download_url" or "vcs_url" is given.
136159 */
137- #finalizeQualifiers ( purl : PackageURL ) : PackageURL {
160+ #finalizeQualifiers( purl : PackageURL ) : PackageURL {
138161 const qualifiers = new Map ( Object . entries ( purl . qualifiers ?? { } ) )
139162
140163 const downloadUrl = qualifiers . get ( PurlQualifierNames . DownloadUrl )
141164 if ( downloadUrl !== undefined ) {
142165 qualifiers . delete ( PurlQualifierNames . VcsUrl )
143- if ( npmDefaultRepositoryMatcher . test ( downloadUrl ) ) {
166+ if ( defaultRegistryMatcher . test ( downloadUrl ) ) {
144167 qualifiers . delete ( PurlQualifierNames . DownloadUrl )
145168 }
146169 }
0 commit comments