Skip to content

Commit 4843b9a

Browse files
authored
feat: use CDX-library's license evidence gathering (#1398)
Signed-off-by: Jan Kowalleck <[email protected]>
1 parent e1a8512 commit 4843b9a

File tree

5 files changed

+34
-88
lines changed

5 files changed

+34
-88
lines changed

HISTORY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ All notable changes to this project will be documented in this file.
66

77
<!-- unreleased changes go here -->
88

9+
10+
* Changed
11+
* Utilizes license file gatherer of `@cyclonedx/cyclonedx-library`, previously used own implementation (via [#1398])
12+
* Dependencies
13+
* Upgraded runtime-dependency `@cyclonedx/cyclonedx-library@^8.4.0`, was `@^8.0.0` (via [#1398])
914
* Build
1015
* Use _TypeScript_ `v5.8.3` now, was `v5.8.2` (via [#1382])
1116

1217
[#1382]: https://github.com/CycloneDX/cyclonedx-webpack-plugin/pull/1382
18+
[#1398]: https://github.com/CycloneDX/cyclonedx-webpack-plugin/pull/1398
1319

1420
## 5.0.1 - 2025-03-17
1521

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"node": ">=20.18.0"
7272
},
7373
"dependencies": {
74-
"@cyclonedx/cyclonedx-library": "^8.0.0",
74+
"@cyclonedx/cyclonedx-library": "^8.4.0",
7575
"normalize-package-data": "^7.0.0",
7676
"xmlbuilder2": "^3.0.2"
7777
},

src/_helpers.ts

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved.
1818
*/
1919

2020
import { existsSync, readFileSync } from 'node:fs'
21-
import { dirname, extname, isAbsolute, join, parse, sep } from 'node:path'
21+
import { dirname, isAbsolute, join, sep } from 'node:path'
2222

2323
export function isNonNullable<T>(value: T): value is NonNullable<T> {
2424
// NonNullable: not null and not undefined
@@ -103,49 +103,6 @@ export function loadJsonFile(path: string): any {
103103
// see https://github.com/tc39/proposal-import-attributes
104104
}
105105

106-
// region MIME
107-
108-
export type MimeType = string
109-
110-
const MIME_TEXT_PLAIN: MimeType = 'text/plain'
111-
112-
const MAP_TEXT_EXTENSION_MIME: Readonly<Record<string, MimeType>> = {
113-
'': MIME_TEXT_PLAIN,
114-
// https://www.iana.org/assignments/media-types/media-types.xhtml
115-
'.csv': 'text/csv',
116-
'.htm': 'text/html',
117-
'.html': 'text/html',
118-
'.md': 'text/markdown',
119-
'.txt': MIME_TEXT_PLAIN,
120-
'.rst': 'text/prs.fallenstein.rst',
121-
'.xml': 'text/xml', // not `application/xml` -- our scope is text!
122-
// add more mime types above this line. pull-requests welcome!
123-
// license-specific files
124-
'.license': MIME_TEXT_PLAIN,
125-
'.licence': MIME_TEXT_PLAIN
126-
} as const
127-
128-
export function getMimeForTextFile(filename: string): MimeType | undefined {
129-
return MAP_TEXT_EXTENSION_MIME[extname(filename).toLowerCase()]
130-
}
131-
132-
const LICENSE_FILENAME_BASE = new Set(['licence', 'license'])
133-
const LICENSE_FILENAME_EXT = new Set([
134-
'.apache',
135-
'.bsd',
136-
'.gpl',
137-
'.mit'
138-
])
139-
140-
export function getMimeForLicenseFile(filename: string): MimeType | undefined {
141-
const {name, ext} = parse(filename.toLowerCase())
142-
return LICENSE_FILENAME_BASE.has(name) && LICENSE_FILENAME_EXT.has(ext)
143-
? MIME_TEXT_PLAIN
144-
: MAP_TEXT_EXTENSION_MIME[ext]
145-
}
146-
147-
// endregion MIME
148-
149106
// region polyfills
150107

151108
/** Polyfill for Iterator.some() */

src/extractor.ts

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

20-
import { type Dirent,readdirSync, readFileSync } from 'node:fs'
21-
import { dirname, join } from 'node:path'
20+
import { dirname } from 'node:path'
2221

2322
import * as CDX from '@cyclonedx/cyclonedx-library'
2423
import normalizePackageJson from 'normalize-package-data'
2524
import type { Compilation, Module } from 'webpack'
2625

2726
import {
28-
getMimeForLicenseFile,
2927
getPackageDescription,
3028
isNonNullable,
3129
type PackageDescription,
@@ -38,15 +36,18 @@ export class Extractor {
3836
readonly #compilation: Compilation
3937
readonly #componentBuilder: CDX.Builders.FromNodePackageJson.ComponentBuilder
4038
readonly #purlFactory: CDX.Factories.FromNodePackageJson.PackageUrlFactory
39+
readonly #leGatherer: CDX.Utils.LicenseUtility.LicenseEvidenceGatherer
4140

4241
constructor (
4342
compilation: Compilation,
4443
componentBuilder: CDX.Builders.FromNodePackageJson.ComponentBuilder,
45-
purlFactory: CDX.Factories.FromNodePackageJson.PackageUrlFactory
44+
purlFactory: CDX.Factories.FromNodePackageJson.PackageUrlFactory,
45+
leFetcher: CDX.Utils.LicenseUtility.LicenseEvidenceGatherer
4646
) {
4747
this.#compilation = compilation
4848
this.#componentBuilder = componentBuilder
4949
this.#purlFactory = purlFactory
50+
this.#leGatherer = leFetcher
5051
}
5152

5253
generateComponents (modules: Iterable<Module>, collectEvidence: boolean, logger?: WebpackLogger): Iterable<CDX.Models.Component> {
@@ -146,47 +147,24 @@ export class Extractor {
146147
}
147148
}
148149

149-
readonly #LICENSE_FILENAME_PATTERN = /^(?:UN)?LICEN[CS]E|.\.LICEN[CS]E$|^NOTICE$/i
150-
151150
public * getLicenseEvidence (packageDir: string, logger?: WebpackLogger): Generator<CDX.Models.License> {
152-
let pcis: Dirent[] = []
153-
try {
154-
pcis = readdirSync(packageDir, { withFileTypes: true })
155-
} catch (e) {
156-
logger?.warn('collecting license evidence in', packageDir, 'failed:', e)
157-
return
158-
}
159-
for (const pci of pcis) {
160-
if (
161-
// Ignore all directories - they are not files :-)
162-
// Don't follow symlinks for security reasons!
163-
!pci.isFile() ||
164-
!this.#LICENSE_FILENAME_PATTERN.test(pci.name)
165-
) {
166-
continue
151+
const files = this.#leGatherer.getFileAttachments(
152+
packageDir,
153+
(error: Error): void => {
154+
/* c8 ignore next 2 */
155+
logger?.info(error.message)
156+
logger?.debug(error.message, error)
167157
}
168-
169-
const contentType = getMimeForLicenseFile(pci.name)
170-
if (contentType === undefined) {
171-
continue
172-
}
173-
174-
const fp = join(packageDir, pci.name)
175-
try {
176-
yield new CDX.Models.NamedLicense(
177-
`file: ${pci.name}`,
178-
{
179-
text: new CDX.Models.Attachment(
180-
readFileSync(fp).toString('base64'),
181-
{
182-
contentType,
183-
encoding: CDX.Enums.AttachmentEncoding.Base64
184-
}
185-
)
186-
})
187-
} catch (e) { // may throw if `readFileSync()` fails
188-
logger?.warn('collecting license evidence from', fp, 'failed:', e)
158+
)
159+
try {
160+
for (const {file, text} of files) {
161+
yield new CDX.Models.NamedLicense(`file: ${file}`, { text })
189162
}
190163
}
164+
/* c8 ignore next 3 */
165+
catch (e) {
166+
// generator will not throw before first `.nest()` is called ...
167+
logger?.warn('collecting license evidence in', packageDir, 'failed:', e)
168+
}
191169
}
192170
}

src/plugin.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,12 @@ export class CycloneDxWebpackPlugin {
254254
pluginName,
255255
(_, modules) => {
256256
const thisLogger = logger.getChildLogger('ComponentFetcher')
257-
const extractor = new Extractor(compilation, cdxComponentBuilder, cdxPurlFactory)
257+
const extractor = new Extractor(
258+
compilation,
259+
cdxComponentBuilder,
260+
cdxPurlFactory,
261+
new CDX.Utils.LicenseUtility.LicenseEvidenceGatherer()
262+
)
258263

259264
thisLogger.log('generating components...')
260265
for (const component of extractor.generateComponents(modules, this.collectEvidence, thisLogger.getChildLogger('Extractor'))) {

0 commit comments

Comments
 (0)