From 1596a82f080e968d45f942f80544a9d510e916a1 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 4 Jun 2024 16:41:14 +0200 Subject: [PATCH 01/45] refactor: convert to TS Signed-off-by: Jan Kowalleck --- .mocharc.js | 3 +- libs/universal-node-xml/stringify.js | 52 -------------- src/_optPlug.node/_wrapper.ts | 53 ++++++++++++++ .../_optPlug.node/errors.ts | 16 ++--- .../xmlStringify/__opts/xmlbuilder2.ts | 70 +++++++++++-------- .../_optPlug.node/xmlStringify/index.ts | 44 ++++-------- src/serialize/errors.ts | 22 ++++++ src/serialize/xmlSerializer.node.ts | 17 ++++- src/validation/errors.ts | 10 +-- .../internals/OpPlug.xmlStringify.spec.js | 6 +- .../OpPlug.xmlStringify.xmlbuilder2.spec.js | 22 +++--- tsconfig.d.json | 4 +- tsconfig.node.json | 3 + 13 files changed, 170 insertions(+), 152 deletions(-) delete mode 100644 libs/universal-node-xml/stringify.js create mode 100644 src/_optPlug.node/_wrapper.ts rename libs/universal-node-xml/stringify.d.ts => src/_optPlug.node/errors.ts (63%) rename libs/universal-node-xml/__stringifiers/xmlbuilder2.js => src/_optPlug.node/xmlStringify/__opts/xmlbuilder2.ts (54%) rename libs/universal-node-xml/__stringifiers/_helpers.js => src/_optPlug.node/xmlStringify/index.ts (50%) create mode 100644 src/serialize/errors.ts rename libs/universal-node-xml/stringify.test.js => tests/functional/internals/OpPlug.xmlStringify.spec.js (86%) rename libs/universal-node-xml/__stringifiers/xmlbuilder2.spec.js => tests/functional/internals/OpPlug.xmlStringify.xmlbuilder2.spec.js (86%) diff --git a/.mocharc.js b/.mocharc.js index 56d9d3f3f..218c0b2a0 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -26,8 +26,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved. module.exports = { timeout: 10000, spec: [ - 'tests', - 'libs' + 'tests' ], recursive: true, parallel: false, // if true, then some IDEs cannot run it diff --git a/libs/universal-node-xml/stringify.js b/libs/universal-node-xml/stringify.js deleted file mode 100644 index a512e8f64..000000000 --- a/libs/universal-node-xml/stringify.js +++ /dev/null @@ -1,52 +0,0 @@ -/*! -This file is part of CycloneDX JavaScript Library. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -SPDX-License-Identifier: Apache-2.0 -Copyright (c) OWASP Foundation. All Rights Reserved. -*/ - -/* eslint-disable jsdoc/valid-types -- - JSDoc is still missing support for tuples - https://github.com/jsdoc/jsdoc/issues/1703 -*/ -/** - * Prioritized list of possible implementations. - * @type {[string, function():(Function|*)][]} - */ -const possibleStringifiers = [ - ['xmlbuilder2', () => require('./__stringifiers/xmlbuilder2')] - // ... add others here, pull-requests welcome! -] -/* eslint-enable jsdoc/valid-types */ - -module.exports = function () { - throw new Error( - 'No stringifier available.' + - ' Please install any of the optional dependencies: ' + - possibleStringifiers.map(kv => kv[0]).join(', ') - ) -} -module.exports.fails = true - -for (const [, getStringifier] of possibleStringifiers) { - try { - const possibleStringifier = getStringifier() - if (typeof possibleStringifier === 'function') { - module.exports = possibleStringifier - break - } - } catch { - /* pass */ - } -} diff --git a/src/_optPlug.node/_wrapper.ts b/src/_optPlug.node/_wrapper.ts new file mode 100644 index 000000000..9c7dff66b --- /dev/null +++ b/src/_optPlug.node/_wrapper.ts @@ -0,0 +1,53 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +import { OptPlugError } from './errors' + +type WillThrow = (() => never) & { fails: true } +type WillNotFailRightAway = Omit +type PossibleFunctionalities = Array<[string, () => Functionality | undefined]> + +function makeWIllThrow (message: string): WillThrow { + const f: WillThrow = function (): never { + throw new OptPlugError(message) + } + f.fails = true + return Object.freeze(f) +} + +export default function > ( + name: string, + pf: PossibleFunctionalities +): Functionality | WillThrow { + for (const [, getF] of pf) { + try { + const f = getF() + if (f !== undefined) { + return f + } + } catch { + /* pass */ + } + } + return makeWIllThrow( + `No ${name} available.` + + ' Please install any of the optional dependencies: ' + + pf.map(kv => kv[0]).join(' || ') + ) +} diff --git a/libs/universal-node-xml/stringify.d.ts b/src/_optPlug.node/errors.ts similarity index 63% rename from libs/universal-node-xml/stringify.d.ts rename to src/_optPlug.node/errors.ts index 30753c63d..c88f9f1c0 100644 --- a/libs/universal-node-xml/stringify.d.ts +++ b/src/_optPlug.node/errors.ts @@ -17,15 +17,11 @@ SPDX-License-Identifier: Apache-2.0 Copyright (c) OWASP Foundation. All Rights Reserved. */ -import type { SerializerOptions } from '../../src/serialize/types' -import type { SimpleXml } from '../../src/serialize/xml/types' +export class OptPlugError extends Error { + readonly cause: any | undefined -declare interface ThrowError { - /** @throws {@link Error} */ - (..._: any[]): never - fails: true + constructor (message: string, cause?: any) { + super(message) + this.cause = cause + } } - -declare type Stringify = (element: SimpleXml.Element, options?: SerializerOptions) => string -declare const stringify: Stringify | ThrowError -export = stringify diff --git a/libs/universal-node-xml/__stringifiers/xmlbuilder2.js b/src/_optPlug.node/xmlStringify/__opts/xmlbuilder2.ts similarity index 54% rename from libs/universal-node-xml/__stringifiers/xmlbuilder2.js rename to src/_optPlug.node/xmlStringify/__opts/xmlbuilder2.ts index 257cf2f66..b4b57c020 100644 --- a/libs/universal-node-xml/__stringifiers/xmlbuilder2.js +++ b/src/_optPlug.node/xmlStringify/__opts/xmlbuilder2.ts @@ -17,32 +17,22 @@ SPDX-License-Identifier: Apache-2.0 Copyright (c) OWASP Foundation. All Rights Reserved. */ -const { create } = require('xmlbuilder2') -const { getNS, makeIndent } = require('./_helpers') +import { create } from 'xmlbuilder2' +import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces' -module.exports = typeof create === 'function' - ? stringify - /* c8 ignore next */ - : undefined +import type { SerializerOptions } from '../../../serialize/types' +import type { SimpleXml } from '../../../serialize/xml/types' +import type { Functionality } from '../' -/* eslint-disable jsdoc/valid-types */ - -/** - * @typedef {import('xmlbuilder2/lib/interfaces').XMLBuilder} XMLBuilder - */ - -/** - * @typedef {import('../../../src/serialize/xml/types').SimpleXml.Element} Element - */ - -/* eslint-enable jsdoc/valid-types */ +if (typeof create !== 'function') { + throw new Error('`create` is not a function') +} -/** - * @param {Element} rootElement - * @param {string|number|undefined} [space] - * @return {string} - */ -function stringify (rootElement, { space } = {}) { +/** @internal */ +export default (function ( + rootElement: SimpleXml.Element, + { space }: SerializerOptions = {} +): string { const indent = makeIndent(space) const doc = create({ encoding: 'UTF-8' }) addEle(doc, rootElement) @@ -52,15 +42,16 @@ function stringify (rootElement, { space } = {}) { prettyPrint: indent.length > 0, indent }) -} +}) satisfies Functionality -/** - * @param {XMLBuilder} parent - * @param {Element} element - * @param {?string} [parentNS] - */ -function addEle (parent, element, parentNS = null) { - if (element.type !== 'element') { return } +function addEle ( + parent: XMLBuilder, + element: SimpleXml.Element | SimpleXml.Comment, + parentNS: string | null = null +): void { + if (element.type !== 'element') { + return + } const ns = getNS(element) ?? parentNS const ele = parent.ele(ns, element.name, element.attributes) if (element.children === undefined) { @@ -73,3 +64,20 @@ function addEle (parent, element, parentNS = null) { } } } + +function getNS (element: SimpleXml.Element): string | null { + const ns = (element.namespace ?? element.attributes?.xmlns)?.toString() ?? '' + return ns.length > 0 + ? ns + : null +} + +function makeIndent (space: string | number | any): string { + if (typeof space === 'number') { + return ' '.repeat(Math.max(0, space)) + } + if (typeof space === 'string') { + return space + } + return '' +} diff --git a/libs/universal-node-xml/__stringifiers/_helpers.js b/src/_optPlug.node/xmlStringify/index.ts similarity index 50% rename from libs/universal-node-xml/__stringifiers/_helpers.js rename to src/_optPlug.node/xmlStringify/index.ts index 95cea7b12..cce5bb1c2 100644 --- a/libs/universal-node-xml/__stringifiers/_helpers.js +++ b/src/_optPlug.node/xmlStringify/index.ts @@ -17,35 +17,15 @@ SPDX-License-Identifier: Apache-2.0 Copyright (c) OWASP Foundation. All Rights Reserved. */ -/* eslint-disable jsdoc/valid-types */ - -/** - * @typedef {import('../../../src/serialize/xml/types').SimpleXml.Element} Element - */ - -/* eslint-enable jsdoc/valid-types */ - -/** - * @param {Element} element - * @return {string|string|null} - */ -module.exports.getNS = function (element) { - const ns = (element.namespace ?? element.attributes?.xmlns)?.toString() ?? '' - return ns.length > 0 - ? ns - : null -} - -/** - * @param {string|number|*} [space] - * @return {string} - */ -module.exports.makeIndent = function (space) { - if (typeof space === 'number') { - return ' '.repeat(Math.max(0, space)) - } - if (typeof space === 'string') { - return space - } - return '' -} +import type { SerializerOptions } from '../../serialize/types' +import type { SimpleXml } from '../../serialize/xml/types' +import opWrapper from '../_wrapper' + +export type Functionality = (element: SimpleXml.Element, options?: SerializerOptions) => string + +export default opWrapper('XmlStringifier', [ + /* eslint-disable @typescript-eslint/no-var-requires */ + ['xmlbuilder2', () => require('./__opts/xmlbuilder2').default] + // ... add others here, pull-requests welcome! + /* eslint-enable @typescript-eslint/no-var-requires */ +]) diff --git a/src/serialize/errors.ts b/src/serialize/errors.ts new file mode 100644 index 000000000..408fa3103 --- /dev/null +++ b/src/serialize/errors.ts @@ -0,0 +1,22 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +import { OptPlugError } from '../_optPlug.node/errors' + +export class MissingOptionalDependencyError extends OptPlugError {} diff --git a/src/serialize/xmlSerializer.node.ts b/src/serialize/xmlSerializer.node.ts index fa7540b49..608b5cd9f 100644 --- a/src/serialize/xmlSerializer.node.ts +++ b/src/serialize/xmlSerializer.node.ts @@ -17,7 +17,9 @@ SPDX-License-Identifier: Apache-2.0 Copyright (c) OWASP Foundation. All Rights Reserved. */ -import * as stringify from '../../libs/universal-node-xml/stringify' +import { OptPlugError } from '../_optPlug.node/errors' +import stringify from '../_optPlug.node/xmlStringify' +import { MissingOptionalDependencyError } from './errors' import type { SerializerOptions } from './types' import type { SimpleXml } from './xml/types' import { XmlBaseSerializer } from './xmlBaseSerializer' @@ -26,10 +28,21 @@ import { XmlBaseSerializer } from './xmlBaseSerializer' * XML serializer for node. */ export class XmlSerializer extends XmlBaseSerializer { + /** + * @throws {@link Serialize.MissingOptionalDependencyError | MissingOptionalDependencyError} + * @throws {@link Error} + */ protected _serialize ( normalizedBom: SimpleXml.Element, options: SerializerOptions = {} ): string { - return stringify(normalizedBom, options) + try { + return stringify(normalizedBom, options) + } catch (err) { + if (err instanceof OptPlugError) { + throw new MissingOptionalDependencyError(err.message, err) + } + throw err + } } } diff --git a/src/validation/errors.ts b/src/validation/errors.ts index 871491136..5530b65f4 100644 --- a/src/validation/errors.ts +++ b/src/validation/errors.ts @@ -17,6 +17,7 @@ SPDX-License-Identifier: Apache-2.0 Copyright (c) OWASP Foundation. All Rights Reserved. */ +import { OptPlugError } from '../_optPlug.node/errors' import type { Version } from '../spec' export class NotImplementedError extends Error { @@ -25,11 +26,4 @@ export class NotImplementedError extends Error { } } -export class MissingOptionalDependencyError extends Error { - readonly cause: any | undefined - - constructor (message: string, cause?: any) { - super(message) - this.cause = cause - } -} +export class MissingOptionalDependencyError extends OptPlugError {} diff --git a/libs/universal-node-xml/stringify.test.js b/tests/functional/internals/OpPlug.xmlStringify.spec.js similarity index 86% rename from libs/universal-node-xml/stringify.test.js rename to tests/functional/internals/OpPlug.xmlStringify.spec.js index 3c4822526..1e1ddd259 100644 --- a/libs/universal-node-xml/stringify.test.js +++ b/tests/functional/internals/OpPlug.xmlStringify.spec.js @@ -20,9 +20,9 @@ Copyright (c) OWASP Foundation. All Rights Reserved. const assert = require('assert') const { suite, test } = require('mocha') -const stringify = require('./stringify') +const stringify = require('../../../dist.node/_optPlug.node/xmlStringify/').default -suite('libs/universal-node-xml/stringify', () => { +suite('internals: OptPlug.xmlStringify::auto', () => { const dummyElem = Object.freeze({ type: 'element', name: 'foo' @@ -37,7 +37,7 @@ suite('libs/universal-node-xml/stringify', () => { }, (err) => { assert.ok(err instanceof Error) - assert.match(err.message, /no stringifier available/i) + assert.match(err.message, /no XmlStringifier available/i) return true } ) diff --git a/libs/universal-node-xml/__stringifiers/xmlbuilder2.spec.js b/tests/functional/internals/OpPlug.xmlStringify.xmlbuilder2.spec.js similarity index 86% rename from libs/universal-node-xml/__stringifiers/xmlbuilder2.spec.js rename to tests/functional/internals/OpPlug.xmlStringify.xmlbuilder2.spec.js index c71575ce4..3f2e29083 100644 --- a/libs/universal-node-xml/__stringifiers/xmlbuilder2.spec.js +++ b/tests/functional/internals/OpPlug.xmlStringify.xmlbuilder2.spec.js @@ -22,13 +22,13 @@ const { suite, test } = require('mocha') let stringify try { - stringify = require('./xmlbuilder2') + stringify = require('../../../dist.node/_optPlug.node/xmlStringify/__opts/xmlbuilder2').default } catch { stringify = undefined } -suite('libs/universal-node-xml', () => { - suite('stringifiers: xmlbuilder2', () => { +suite('internals: OptPlug.xmlStringify::xmlbuilder2', () => { + suite('xmlbuilder2', () => { if (stringify === undefined) { test('SKIPPED', () => {}) return @@ -81,14 +81,14 @@ suite('libs/universal-node-xml', () => { assert.strictEqual(stringified, '' + '' + - '' + - 'testing... \n' + - 'amp-encode? & \n' + - 'tag-encode? <b>foo<b> \n' + - '' + - '' + - '' + - '' + + '' + + 'testing... \n' + + 'amp-encode? & \n' + + 'tag-encode? <b>foo<b> \n' + + '' + + '' + + '' + + '' + '' ) }) diff --git a/tsconfig.d.json b/tsconfig.d.json index c7cd6b25d..98120e5a0 100644 --- a/tsconfig.d.json +++ b/tsconfig.d.json @@ -9,5 +9,7 @@ "declarationDir": "dist.d", "removeComments": false /* to keep annotations */ }, - "include": ["src/**.ts"] + "include": [ + "src/**.ts" + ] } diff --git a/tsconfig.node.json b/tsconfig.node.json index 799ff3574..f1579cbf9 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -1,6 +1,9 @@ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "./tsconfig.json", + "include": [ + "src/_optPlug.node/*/__opts" + ], "files": ["src/index.node.ts"], "compilerOptions": { "moduleSuffixes": [".node", ""], From 6168be25355d25c5cd7b60abc4319d4de781f2cb Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 4 Jun 2024 17:38:01 +0200 Subject: [PATCH 02/45] migrate pluggable xml validator Signed-off-by: Jan Kowalleck --- src/_optPlug.node/jsonValidate/index.ts | 31 ++++++++ .../xmlValidate/__opts/libxmljs2.ts | 49 ++++++++++++ src/_optPlug.node/xmlValidate/index.ts | 30 ++++++++ src/validation/xmlValidator.node.ts | 74 ++++--------------- 4 files changed, 126 insertions(+), 58 deletions(-) create mode 100644 src/_optPlug.node/jsonValidate/index.ts create mode 100644 src/_optPlug.node/xmlValidate/__opts/libxmljs2.ts create mode 100644 src/_optPlug.node/xmlValidate/index.ts diff --git a/src/_optPlug.node/jsonValidate/index.ts b/src/_optPlug.node/jsonValidate/index.ts new file mode 100644 index 000000000..ebea9d58b --- /dev/null +++ b/src/_optPlug.node/jsonValidate/index.ts @@ -0,0 +1,31 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +import type { SerializerOptions } from '../../serialize/types' +import type { SimpleXml } from '../../serialize/xml/types' +import opWrapper from '../_wrapper' + +export type Functionality = (element: SimpleXml.Element, options?: SerializerOptions) => string + +export default opWrapper('XmlStringifier', [ + /* eslint-disable @typescript-eslint/no-var-requires */ + ['(foo and bar)', () => require('./__opts/libxmljs2').default] + // ... add others here, pull-requests welcome! + /* eslint-enable @typescript-eslint/no-var-requires */ +]) diff --git a/src/_optPlug.node/xmlValidate/__opts/libxmljs2.ts b/src/_optPlug.node/xmlValidate/__opts/libxmljs2.ts new file mode 100644 index 000000000..4a653cc50 --- /dev/null +++ b/src/_optPlug.node/xmlValidate/__opts/libxmljs2.ts @@ -0,0 +1,49 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +import { readFileSync } from 'fs' +import { type Document, type ParserOptions, parseXml } from 'libxmljs2' +import { pathToFileURL } from 'url' + +import type { ValidationError } from '../../../validation/types' +import type { Functionality } from '../index' + +const xmlParseOptions: Readonly = Object.freeze({ + nonet: true, + compact: true, + // explicitly prevent XXE + // see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + noent: false, + dtdload: false +}) + +/** @internal */ +export default (function ( + data: string, + schemaPath: string, + schemaCache: Record = {} +): ValidationError { + const doc = parseXml(data, xmlParseOptions) + const schema = schemaCache[schemaPath] ?? (schemaCache[schemaPath] = + parseXml(readFileSync(schemaPath, 'utf-8'), + { ...xmlParseOptions, baseUrl: pathToFileURL(schemaPath).toString() })) + return doc.validate(schema) + ? null + : doc.validationErrors +}) satisfies Functionality diff --git a/src/_optPlug.node/xmlValidate/index.ts b/src/_optPlug.node/xmlValidate/index.ts new file mode 100644 index 000000000..9b095d9cf --- /dev/null +++ b/src/_optPlug.node/xmlValidate/index.ts @@ -0,0 +1,30 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +import type { ValidationError } from '../../validation/types' +import opWrapper from '../_wrapper' + +export type Functionality = (data: string, schemaPath: string, schemaCache: Exclude) => null | ValidationError + +export default opWrapper('XmlStringifier', [ + /* eslint-disable @typescript-eslint/no-var-requires */ + ['libxmljs2', () => require('./__opts/libxmljs2').default] + // ... add others here, pull-requests welcome! + /* eslint-enable @typescript-eslint/no-var-requires */ +]) diff --git a/src/validation/xmlValidator.node.ts b/src/validation/xmlValidator.node.ts index 91f9e4b06..9c4717964 100644 --- a/src/validation/xmlValidator.node.ts +++ b/src/validation/xmlValidator.node.ts @@ -17,64 +17,22 @@ SPDX-License-Identifier: Apache-2.0 Copyright (c) OWASP Foundation. All Rights Reserved. */ -import { readFile } from 'fs/promises' -import type { Document, ParserOptions, parseXml } from 'libxmljs2' -import { pathToFileURL } from 'url' - +import { OptPlugError } from '../_optPlug.node/errors' +import xmlValidate from '../_optPlug.node/xmlValidate' import { FILES } from '../resources.node' import { BaseValidator } from './baseValidator' import { MissingOptionalDependencyError, NotImplementedError } from './errors' import type { ValidationError } from './types' -let _parser: typeof parseXml | undefined - -async function getParser (): Promise { - if (_parser === undefined) { - let libxml - try { - libxml = await import('libxmljs2') - } catch (err) { - throw new MissingOptionalDependencyError( - 'No XML validator available.' + - ' Please install the optional dependency "libxmljs2".' + - ' Please make sure the system meets the requirements for "node-gyp", see https://github.com/TooTallNate/node-gyp#installation', - err - ) - } - _parser = libxml.parseXml - } - return _parser -} - -const xmlParseOptions: Readonly = Object.freeze({ - nonet: true, - compact: true, - // explicitly prevent XXE - // see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html - noent: false, - dtdload: false -}) - export class XmlValidator extends BaseValidator { - #schema: Document | undefined - - #getSchemaFile (): string | undefined { - return FILES.CDX.XML_SCHEMA[this.version] - } + readonly #schemaCache = {} - async #getSchema (): Promise { - if (undefined === this.#schema) { - const file = this.#getSchemaFile() - if (file === undefined) { - throw new NotImplementedError(this.version) - } - const [parse, schema] = await Promise.all([ - getParser(), - readFile(file, 'utf-8') - ]) - this.#schema = parse(schema, { ...xmlParseOptions, baseUrl: pathToFileURL(file).toString() }) + #getSchemaFilePath (): string { + const file = FILES.CDX.XML_SCHEMA[this.version] + if (file === undefined) { + throw new NotImplementedError(this.version) } - return this.#schema + return file } /** @@ -89,13 +47,13 @@ export class XmlValidator extends BaseValidator { * - {@link Validation.MissingOptionalDependencyError | MissingOptionalDependencyError}, when a required dependency was not installed */ async validate (data: string): Promise { - const [parse, schema] = await Promise.all([ - getParser(), - this.#getSchema() - ]) - const doc = parse(data, xmlParseOptions) - return doc.validate(schema) - ? null - : doc.validationErrors + try { + return xmlValidate(data, this.#getSchemaFilePath(), this.#schemaCache) + } catch (err) { + if (err instanceof OptPlugError) { + throw new MissingOptionalDependencyError(err.message, err) + } + throw err + } } } From f683ad0ce45a2022307a0b417f8e87902aebaeed Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 4 Jun 2024 18:33:52 +0200 Subject: [PATCH 03/45] wip Signed-off-by: Jan Kowalleck --- src/_optPlug.node/xmlValidate/index.ts | 2 +- .../Validation.XmlValidator.node.spec.js | 10 +- ...ec.js => OpPlug.node.xmlStringify.spec.js} | 10 +- ...lug.node.xmlStringify.xmlbuilder2.spec.js} | 126 +++++++++--------- .../internals/OpPlug.node.xmlValidate.spec.js | 57 ++++++++ 5 files changed, 125 insertions(+), 80 deletions(-) rename tests/functional/internals/{OpPlug.xmlStringify.spec.js => OpPlug.node.xmlStringify.spec.js} (83%) rename tests/functional/internals/{OpPlug.xmlStringify.xmlbuilder2.spec.js => OpPlug.node.xmlStringify.xmlbuilder2.spec.js} (54%) create mode 100644 tests/functional/internals/OpPlug.node.xmlValidate.spec.js diff --git a/src/_optPlug.node/xmlValidate/index.ts b/src/_optPlug.node/xmlValidate/index.ts index 9b095d9cf..35abe5dee 100644 --- a/src/_optPlug.node/xmlValidate/index.ts +++ b/src/_optPlug.node/xmlValidate/index.ts @@ -22,7 +22,7 @@ import opWrapper from '../_wrapper' export type Functionality = (data: string, schemaPath: string, schemaCache: Exclude) => null | ValidationError -export default opWrapper('XmlStringifier', [ +export default opWrapper('XmlValidator', [ /* eslint-disable @typescript-eslint/no-var-requires */ ['libxmljs2', () => require('./__opts/libxmljs2').default] // ... add others here, pull-requests welcome! diff --git a/tests/functional/Validation.XmlValidator.node.spec.js b/tests/functional/Validation.XmlValidator.node.spec.js index 63cbbed51..ad3aa64c4 100644 --- a/tests/functional/Validation.XmlValidator.node.spec.js +++ b/tests/functional/Validation.XmlValidator.node.spec.js @@ -23,19 +23,13 @@ const assert = require('assert') const { suite, it, xit } = require('mocha') const { globSync } = require('fast-glob') +const { default: { fails: skipTests } } = require('../../dist.node/_optPlug.node/xmlValidate/') const { Validation: { XmlValidator }, Spec: { Version } } = require('../../') -let hasDep = true -try { - require('libxmljs2') -} catch { - hasDep = false -} - -const test = hasDep ? it : xit +const test = skipTests ? xit : it suite('Validation.XmlValidator functional', function () { this.timeout(60000); diff --git a/tests/functional/internals/OpPlug.xmlStringify.spec.js b/tests/functional/internals/OpPlug.node.xmlStringify.spec.js similarity index 83% rename from tests/functional/internals/OpPlug.xmlStringify.spec.js rename to tests/functional/internals/OpPlug.node.xmlStringify.spec.js index 1e1ddd259..42734a65c 100644 --- a/tests/functional/internals/OpPlug.xmlStringify.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlStringify.spec.js @@ -20,20 +20,20 @@ Copyright (c) OWASP Foundation. All Rights Reserved. const assert = require('assert') const { suite, test } = require('mocha') -const stringify = require('../../../dist.node/_optPlug.node/xmlStringify/').default +const xmlStringify = require('../../../dist.node/_optPlug.node/xmlStringify/').default -suite('internals: OptPlug.xmlStringify::auto', () => { +suite('internals: OpPlug.node.xmlStringify', () => { const dummyElem = Object.freeze({ type: 'element', name: 'foo' }) const dummyElemStringifiedRE = /|><\/foo>)/ - if (stringify.fails) { + if (xmlStringify.fails) { test('call should fail/throw', () => { assert.throws( () => { - stringify(dummyElem) + xmlStringify(dummyElem) }, (err) => { assert.ok(err instanceof Error) @@ -44,7 +44,7 @@ suite('internals: OptPlug.xmlStringify::auto', () => { }) } else { test('call should pass', () => { - const stringified = stringify(dummyElem) + const stringified = xmlStringify(dummyElem) assert.match(stringified, dummyElemStringifiedRE) }) } diff --git a/tests/functional/internals/OpPlug.xmlStringify.xmlbuilder2.spec.js b/tests/functional/internals/OpPlug.node.xmlStringify.xmlbuilder2.spec.js similarity index 54% rename from tests/functional/internals/OpPlug.xmlStringify.xmlbuilder2.spec.js rename to tests/functional/internals/OpPlug.node.xmlStringify.xmlbuilder2.spec.js index 3f2e29083..9d398c516 100644 --- a/tests/functional/internals/OpPlug.xmlStringify.xmlbuilder2.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlStringify.xmlbuilder2.spec.js @@ -20,66 +20,61 @@ Copyright (c) OWASP Foundation. All Rights Reserved. const assert = require('assert') const { suite, test } = require('mocha') -let stringify +let xmlStringify try { - stringify = require('../../../dist.node/_optPlug.node/xmlStringify/__opts/xmlbuilder2').default + xmlStringify = require('../../../dist.node/_optPlug.node/xmlStringify/__opts/xmlbuilder2').default } catch { - stringify = undefined + xmlStringify = undefined } -suite('internals: OptPlug.xmlStringify::xmlbuilder2', () => { - suite('xmlbuilder2', () => { - if (stringify === undefined) { - test('SKIPPED', () => {}) - return - } - - assert.strictEqual(typeof stringify, 'function') - - const data = { - type: 'element', - name: 'some-children', - children: [ - { - type: 'element', - name: 'some-attributes', - attributes: { - string: 'some-value', - number: 1, - 'quote-encode': 'foo " bar' - } - }, - { - type: 'element', - name: 'some-text', - children: 'testing... \n' + +(xmlStringify === undefined + ? suite.skip + : suite +)('internals: OpPlug.node.xmlStringify :: xmlbuilder2', () => { + const data = { + type: 'element', + name: 'some-children', + children: [ + { + type: 'element', + name: 'some-attributes', + attributes: { + string: 'some-value', + number: 1, + 'quote-encode': 'foo " bar' + } + }, + { + type: 'element', + name: 'some-text', + children: 'testing... \n' + 'amp-encode? & \n' + 'tag-encode? foo \n' - }, - { - type: 'element', - namespace: 'https://example.com/ns1', - name: 'some-namespaced', - children: [ - { - type: 'element', - name: 'empty' - } - ] - }, - { - type: 'not-an-element', - namespace: 'https://example.com/ns1', - name: 'not-element', - children: 'omit this thing, it is not an element.' - } - ] - } + }, + { + type: 'element', + namespace: 'https://example.com/ns1', + name: 'some-namespaced', + children: [ + { + type: 'element', + name: 'empty' + } + ] + }, + { + type: 'not-an-element', + namespace: 'https://example.com/ns1', + name: 'not-element', + children: 'omit this thing, it is not an element.' + } + ] + } - test('data w/o spacing', () => { - const stringified = stringify(data) - assert.strictEqual(stringified, - '' + + test('data w/o spacing', () => { + const stringified = xmlStringify(data) + assert.strictEqual(stringified, + '' + '' + '' + 'testing... \n' + @@ -90,13 +85,13 @@ suite('internals: OptPlug.xmlStringify::xmlbuilder2', () => { '' + '' + '' - ) - }) + ) + }) - test('data with space=4', () => { - const stringified = stringify(data, { space: 4 }) - assert.strictEqual(stringified, - '\n' + + test('data with space=4', () => { + const stringified = xmlStringify(data, { space: 4 }) + assert.strictEqual(stringified, + '\n' + '\n' + ' \n' + ' testing... \n' + @@ -107,13 +102,13 @@ suite('internals: OptPlug.xmlStringify::xmlbuilder2', () => { ' \n' + ' \n' + '' - ) - }) + ) + }) - test('data with space=TAB', () => { - const stringified = stringify(data, { space: '\t' }) - assert.strictEqual(stringified, - '\n' + + test('data with space=TAB', () => { + const stringified = xmlStringify(data, { space: '\t' }) + assert.strictEqual(stringified, + '\n' + '\n' + '\t\n' + '\ttesting... \n' + @@ -124,6 +119,5 @@ suite('internals: OptPlug.xmlStringify::xmlbuilder2', () => { '\t\t\n' + '\t\n' + '') - }) }) }) diff --git a/tests/functional/internals/OpPlug.node.xmlValidate.spec.js b/tests/functional/internals/OpPlug.node.xmlValidate.spec.js new file mode 100644 index 000000000..384bc70dd --- /dev/null +++ b/tests/functional/internals/OpPlug.node.xmlValidate.spec.js @@ -0,0 +1,57 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +const assert = require('assert') +const { suite, test } = require('mocha') + +const xmlValidate = require('../../../dist.node/_optPlug.node/xmlValidate/').default +const { + _Resources: Resources, + Spec: { Version } +} = require('../../../') + +suite('internals: OpPlug.node.xmlValidate::auto', () => { + const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] + const validXML = '' + const invalidXML = ' xmlns="http://cyclonedx.org/schema/bom/1.6">' + + if (xmlValidate.fails) { + test('call should fail/throw', () => { + assert.throws( + () => { + xmlValidate(validXML, schemaPath, {}) + }, + (err) => { + assert.ok(err instanceof Error) + assert.match(err.message, /no XmlValidator available/i) + return true + } + ) + }) + } else { + test('call should return null', () => { + const validationError = xmlValidate(validXML, schemaPath, {}) + assert.strictEqual(validationError, null) + }) + test('call should return validationError', () => { + const validationError = xmlValidate(invalidXML, schemaPath, {}) + assert.notEqual(validationError, null) + }) + } +}) From 0cabf9e9bc49fd607a040df60302722735f5ba48 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 4 Jun 2024 18:39:46 +0200 Subject: [PATCH 04/45] wip Signed-off-by: Jan Kowalleck --- .../OpPlug.node.xmlValidate.libxmljs2.spec.js | 52 +++++++++++++++++++ .../internals/OpPlug.node.xmlValidate.spec.js | 5 +- 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js diff --git a/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js b/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js new file mode 100644 index 000000000..f3815ec17 --- /dev/null +++ b/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js @@ -0,0 +1,52 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +const assert = require('assert') +const { suite, test } = require('mocha') + +const { + _Resources: Resources, + Spec: { Version } +} = require('../../../') + +let xmlValidate +try { + xmlValidate = require('../../../dist.node/_optPlug.node/xmlValidate/__opts/libxmljs2').default +} catch { + xmlValidate = undefined +} + +(xmlValidate === undefined + ? suite.skip + : suite +)('internals: OpPlug.node.xmlValidate :: libxmljs2 ', () => { + const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] + const validXML = '' + const invalidXML = ' xmlns="http://cyclonedx.org/schema/bom/1.6">' + + test('call should return null', () => { + const validationError = xmlValidate(validXML, schemaPath, {}) + assert.strictEqual(validationError, null) + }) + + test('call should return validationError', () => { + const validationError = xmlValidate(invalidXML, schemaPath, {}) + assert.notEqual(validationError, null) + }) +}) diff --git a/tests/functional/internals/OpPlug.node.xmlValidate.spec.js b/tests/functional/internals/OpPlug.node.xmlValidate.spec.js index 384bc70dd..5596c356b 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidate.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidate.spec.js @@ -20,13 +20,13 @@ Copyright (c) OWASP Foundation. All Rights Reserved. const assert = require('assert') const { suite, test } = require('mocha') -const xmlValidate = require('../../../dist.node/_optPlug.node/xmlValidate/').default const { _Resources: Resources, Spec: { Version } } = require('../../../') +const xmlValidate = require('../../../dist.node/_optPlug.node/xmlValidate/').default -suite('internals: OpPlug.node.xmlValidate::auto', () => { +suite('internals: OpPlug.node.xmlValidate', () => { const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] const validXML = '' const invalidXML = ' xmlns="http://cyclonedx.org/schema/bom/1.6">' @@ -49,6 +49,7 @@ suite('internals: OpPlug.node.xmlValidate::auto', () => { const validationError = xmlValidate(validXML, schemaPath, {}) assert.strictEqual(validationError, null) }) + test('call should return validationError', () => { const validationError = xmlValidate(invalidXML, schemaPath, {}) assert.notEqual(validationError, null) From f436e4b8176e70ac55175a702d7f6d15f658526c Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 4 Jun 2024 19:00:00 +0200 Subject: [PATCH 05/45] wip Signed-off-by: Jan Kowalleck --- .../OpPlug.node.xmlValidate.libxmljs2.spec.js | 45 ++++++++++++++++--- .../internals/OpPlug.node.xmlValidate.spec.js | 17 ++++--- .../Validation.XmlValidator.test.js | 27 ----------- 3 files changed, 49 insertions(+), 40 deletions(-) diff --git a/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js b/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js index f3815ec17..c26efd03f 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js @@ -24,6 +24,9 @@ const { _Resources: Resources, Spec: { Version } } = require('../../../') +const { pathToFileURL } = require('url') +const { realpathSync } = require('fs') +const { join } = require('path') let xmlValidate try { @@ -36,17 +39,47 @@ try { ? suite.skip : suite )('internals: OpPlug.node.xmlValidate :: libxmljs2 ', () => { + const schemaCache = {} const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] - const validXML = '' - const invalidXML = ' xmlns="http://cyclonedx.org/schema/bom/1.6">' + const validXML = ` + ` + const invalidXML = ` + xmlns="http://cyclonedx.org/schema/bom/1.6">` - test('call should return null', () => { - const validationError = xmlValidate(validXML, schemaPath, {}) + test('valid return null', () => { + const validationError = xmlValidate(validXML, schemaPath, schemaCache) assert.strictEqual(validationError, null) }) - test('call should return validationError', () => { - const validationError = xmlValidate(invalidXML, schemaPath, {}) + test('invalid returns validationError', () => { + const validationError = xmlValidate(invalidXML, schemaPath, schemaCache) assert.notEqual(validationError, null) }) + + test('is not affected by XXE injection', async () => { + // see https://github.com/CycloneDX/cyclonedx-javascript-library/issues/1061 + const xxeFile = join(__dirname, '..', '..', '_data', 'xxe_flag.txt') + const input = ` + + ]> + + + + bar + 1.337 + + + &flag; + + + + + ` + const validationError = xmlValidate(input, schemaPath, schemaCache) + assert.doesNotMatch( + JSON.stringify(validationError), + /vaiquia2zoo3Im8ro9zahNg5mohwipouka2xieweed6ahChei3doox2fek3ise0lmohju3loh5oDu7eigh3jaeR2aiph2Voo/, + 'must not leak secrets') + }) }) diff --git a/tests/functional/internals/OpPlug.node.xmlValidate.spec.js b/tests/functional/internals/OpPlug.node.xmlValidate.spec.js index 5596c356b..bbb695022 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidate.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidate.spec.js @@ -27,15 +27,18 @@ const { const xmlValidate = require('../../../dist.node/_optPlug.node/xmlValidate/').default suite('internals: OpPlug.node.xmlValidate', () => { + const schemaCache = {} const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] - const validXML = '' - const invalidXML = ' xmlns="http://cyclonedx.org/schema/bom/1.6">' + const validXML = ` + ` + const invalidXML = ` + xmlns="http://cyclonedx.org/schema/bom/1.6">` if (xmlValidate.fails) { test('call should fail/throw', () => { assert.throws( () => { - xmlValidate(validXML, schemaPath, {}) + xmlValidate(validXML, schemaPath, schemaCache) }, (err) => { assert.ok(err instanceof Error) @@ -45,13 +48,13 @@ suite('internals: OpPlug.node.xmlValidate', () => { ) }) } else { - test('call should return null', () => { - const validationError = xmlValidate(validXML, schemaPath, {}) + test('valid returns null', () => { + const validationError = xmlValidate(validXML, schemaPath, schemaCache) assert.strictEqual(validationError, null) }) - test('call should return validationError', () => { - const validationError = xmlValidate(invalidXML, schemaPath, {}) + test('invalid returns validationError', () => { + const validationError = xmlValidate(invalidXML, schemaPath, schemaCache) assert.notEqual(validationError, null) }) } diff --git a/tests/integration/Validation.XmlValidator.test.js b/tests/integration/Validation.XmlValidator.test.js index fc54ee081..cd4b486fb 100644 --- a/tests/integration/Validation.XmlValidator.test.js +++ b/tests/integration/Validation.XmlValidator.test.js @@ -103,32 +103,5 @@ describe('Validation.XmlValidator', () => { const validationError = await validator.validate(input) assert.strictEqual(validationError, null) }) - - it('is not affected by XXE injection', async () => { - const validator = new XmlValidator(version) - const input = ` - - ]> - - - - bar - 1.337 - ${version === '1.0' ? 'false' : ''} - - - &flag; - - - - - ` - const validationError = await validator.validate(input) - assert.doesNotMatch( - JSON.stringify(validationError), - /vaiquia2zoo3Im8ro9zahNg5mohwipouka2xieweed6ahChei3doox2fek3ise0lmohju3loh5oDu7eigh3jaeR2aiph2Voo/ - ) - }) })) }) From 58be3953853bf4e233d681f1fed9ad2ff434b3fc Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 4 Jun 2024 19:24:14 +0200 Subject: [PATCH 06/45] wip Signed-off-by: Jan Kowalleck --- src/_optPlug.node/jsonValidate/index.ts | 31 ------------------- .../xmlValidate/__opts/libxmljs2.ts | 3 +- src/_optPlug.node/xmlValidate/index.ts | 2 +- .../OpPlug.node.xmlValidate.libxmljs2.spec.js | 2 +- .../Validation.XmlValidator.test.js | 3 -- 5 files changed, 4 insertions(+), 37 deletions(-) delete mode 100644 src/_optPlug.node/jsonValidate/index.ts diff --git a/src/_optPlug.node/jsonValidate/index.ts b/src/_optPlug.node/jsonValidate/index.ts deleted file mode 100644 index ebea9d58b..000000000 --- a/src/_optPlug.node/jsonValidate/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*! -This file is part of CycloneDX JavaScript Library. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -SPDX-License-Identifier: Apache-2.0 -Copyright (c) OWASP Foundation. All Rights Reserved. -*/ - -import type { SerializerOptions } from '../../serialize/types' -import type { SimpleXml } from '../../serialize/xml/types' -import opWrapper from '../_wrapper' - -export type Functionality = (element: SimpleXml.Element, options?: SerializerOptions) => string - -export default opWrapper('XmlStringifier', [ - /* eslint-disable @typescript-eslint/no-var-requires */ - ['(foo and bar)', () => require('./__opts/libxmljs2').default] - // ... add others here, pull-requests welcome! - /* eslint-enable @typescript-eslint/no-var-requires */ -]) diff --git a/src/_optPlug.node/xmlValidate/__opts/libxmljs2.ts b/src/_optPlug.node/xmlValidate/__opts/libxmljs2.ts index 4a653cc50..edba69278 100644 --- a/src/_optPlug.node/xmlValidate/__opts/libxmljs2.ts +++ b/src/_optPlug.node/xmlValidate/__opts/libxmljs2.ts @@ -29,6 +29,7 @@ const xmlParseOptions: Readonly = Object.freeze({ compact: true, // explicitly prevent XXE // see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + // see https://github.com/CycloneDX/cyclonedx-javascript-library/issues/1061 noent: false, dtdload: false }) @@ -38,7 +39,7 @@ export default (function ( data: string, schemaPath: string, schemaCache: Record = {} -): ValidationError { +): null | ValidationError { const doc = parseXml(data, xmlParseOptions) const schema = schemaCache[schemaPath] ?? (schemaCache[schemaPath] = parseXml(readFileSync(schemaPath, 'utf-8'), diff --git a/src/_optPlug.node/xmlValidate/index.ts b/src/_optPlug.node/xmlValidate/index.ts index 35abe5dee..05246a44a 100644 --- a/src/_optPlug.node/xmlValidate/index.ts +++ b/src/_optPlug.node/xmlValidate/index.ts @@ -20,7 +20,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved. import type { ValidationError } from '../../validation/types' import opWrapper from '../_wrapper' -export type Functionality = (data: string, schemaPath: string, schemaCache: Exclude) => null | ValidationError +export type Functionality = (data: string, schemaPath: string, schemaCache: NonNullable) => null | ValidationError export default opWrapper('XmlValidator', [ /* eslint-disable @typescript-eslint/no-var-requires */ diff --git a/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js b/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js index c26efd03f..ed9c05176 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js @@ -58,7 +58,7 @@ try { test('is not affected by XXE injection', async () => { // see https://github.com/CycloneDX/cyclonedx-javascript-library/issues/1061 - const xxeFile = join(__dirname, '..', '..', '_data', 'xxe_flag.txt') + const xxeFile = join(__dirname, '..', '..', '_data', 'xxe_flag.txt') const input = ` diff --git a/tests/integration/Validation.XmlValidator.test.js b/tests/integration/Validation.XmlValidator.test.js index cd4b486fb..dbed5d016 100644 --- a/tests/integration/Validation.XmlValidator.test.js +++ b/tests/integration/Validation.XmlValidator.test.js @@ -18,9 +18,6 @@ Copyright (c) OWASP Foundation. All Rights Reserved. */ const assert = require('assert') -const { realpathSync } = require('fs') -const { join } = require('path') -const { pathToFileURL } = require('url') const { describe, it } = require('mocha') From 01a9cbfbb32339be90d473c4a2374ac0f9ad1d16 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Tue, 4 Jun 2024 19:45:54 +0200 Subject: [PATCH 07/45] docs Signed-off-by: Jan Kowalleck --- src/serialize/xmlSerializer.node.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/serialize/xmlSerializer.node.ts b/src/serialize/xmlSerializer.node.ts index 608b5cd9f..bfc6ce056 100644 --- a/src/serialize/xmlSerializer.node.ts +++ b/src/serialize/xmlSerializer.node.ts @@ -28,6 +28,8 @@ import { XmlBaseSerializer } from './xmlBaseSerializer' * XML serializer for node. */ export class XmlSerializer extends XmlBaseSerializer { + // maybe override parent::serialize() and skip nonmalization and everything, in case `stringify.fails` is true + /** * @throws {@link Serialize.MissingOptionalDependencyError | MissingOptionalDependencyError} * @throws {@link Error} From e1cbc7922fa25ac6aca2b39fc8407df9f7b1eaa2 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 11:45:56 +0200 Subject: [PATCH 08/45] wip Signed-off-by: Jan Kowalleck --- .../__opts/libxmljs2.ts | 25 ++++++++----------- .../{xmlValidate => xmlValidator}/index.ts | 3 ++- src/validation/xmlValidator.node.ts | 22 +++++++++++++--- .../Validation.XmlValidator.node.spec.js | 2 +- .../OpPlug.node.xmlValidate.libxmljs2.spec.js | 15 ++++++----- .../internals/OpPlug.node.xmlValidate.spec.js | 13 +++++----- 6 files changed, 46 insertions(+), 34 deletions(-) rename src/_optPlug.node/{xmlValidate => xmlValidator}/__opts/libxmljs2.ts (67%) rename src/_optPlug.node/{xmlValidate => xmlValidator}/index.ts (89%) diff --git a/src/_optPlug.node/xmlValidate/__opts/libxmljs2.ts b/src/_optPlug.node/xmlValidator/__opts/libxmljs2.ts similarity index 67% rename from src/_optPlug.node/xmlValidate/__opts/libxmljs2.ts rename to src/_optPlug.node/xmlValidator/__opts/libxmljs2.ts index edba69278..23e7960b3 100644 --- a/src/_optPlug.node/xmlValidate/__opts/libxmljs2.ts +++ b/src/_optPlug.node/xmlValidator/__opts/libxmljs2.ts @@ -18,11 +18,11 @@ Copyright (c) OWASP Foundation. All Rights Reserved. */ import { readFileSync } from 'fs' -import { type Document, type ParserOptions, parseXml } from 'libxmljs2' +import { type ParserOptions, parseXml } from 'libxmljs2' import { pathToFileURL } from 'url' import type { ValidationError } from '../../../validation/types' -import type { Functionality } from '../index' +import type { Functionality, Validator } from '../index' const xmlParseOptions: Readonly = Object.freeze({ nonet: true, @@ -35,16 +35,13 @@ const xmlParseOptions: Readonly = Object.freeze({ }) /** @internal */ -export default (function ( - data: string, - schemaPath: string, - schemaCache: Record = {} -): null | ValidationError { - const doc = parseXml(data, xmlParseOptions) - const schema = schemaCache[schemaPath] ?? (schemaCache[schemaPath] = - parseXml(readFileSync(schemaPath, 'utf-8'), - { ...xmlParseOptions, baseUrl: pathToFileURL(schemaPath).toString() })) - return doc.validate(schema) - ? null - : doc.validationErrors +export default (function (schemaPath: string): Validator { + const schema = parseXml(readFileSync(schemaPath, 'utf-8'), + { ...xmlParseOptions, baseUrl: pathToFileURL(schemaPath).toString() }) + return function (data: string): null | ValidationError { + const doc = parseXml(data, xmlParseOptions) + return doc.validate(schema) + ? null + : doc.validationErrors + } }) satisfies Functionality diff --git a/src/_optPlug.node/xmlValidate/index.ts b/src/_optPlug.node/xmlValidator/index.ts similarity index 89% rename from src/_optPlug.node/xmlValidate/index.ts rename to src/_optPlug.node/xmlValidator/index.ts index 05246a44a..bf965ed33 100644 --- a/src/_optPlug.node/xmlValidate/index.ts +++ b/src/_optPlug.node/xmlValidator/index.ts @@ -20,7 +20,8 @@ Copyright (c) OWASP Foundation. All Rights Reserved. import type { ValidationError } from '../../validation/types' import opWrapper from '../_wrapper' -export type Functionality = (data: string, schemaPath: string, schemaCache: NonNullable) => null | ValidationError +export type Validator = (data: string) => null | ValidationError +export type Functionality = (schemaPath: string) => Validator export default opWrapper('XmlValidator', [ /* eslint-disable @typescript-eslint/no-var-requires */ diff --git a/src/validation/xmlValidator.node.ts b/src/validation/xmlValidator.node.ts index 9c4717964..b0e9dcf9e 100644 --- a/src/validation/xmlValidator.node.ts +++ b/src/validation/xmlValidator.node.ts @@ -18,15 +18,13 @@ Copyright (c) OWASP Foundation. All Rights Reserved. */ import { OptPlugError } from '../_optPlug.node/errors' -import xmlValidate from '../_optPlug.node/xmlValidate' +import makeValidator, { type Validator } from '../_optPlug.node/xmlValidator' import { FILES } from '../resources.node' import { BaseValidator } from './baseValidator' import { MissingOptionalDependencyError, NotImplementedError } from './errors' import type { ValidationError } from './types' export class XmlValidator extends BaseValidator { - readonly #schemaCache = {} - #getSchemaFilePath (): string { const file = FILES.CDX.XML_SCHEMA[this.version] if (file === undefined) { @@ -35,6 +33,22 @@ export class XmlValidator extends BaseValidator { return file } + #validatorCache?: Validator = undefined + + get #validator (): Validator { + if (this.#validatorCache === undefined) { + try { + this.#validatorCache = makeValidator(this.#getSchemaFilePath()) + } catch (err) { + if (err instanceof OptPlugError) { + throw new MissingOptionalDependencyError(err.message, err) + } + throw err + } + } + return this.#validatorCache + } + /** * Validate the data against CycloneDX spec of `this.version`. * @@ -48,7 +62,7 @@ export class XmlValidator extends BaseValidator { */ async validate (data: string): Promise { try { - return xmlValidate(data, this.#getSchemaFilePath(), this.#schemaCache) + return this.#validator(data) } catch (err) { if (err instanceof OptPlugError) { throw new MissingOptionalDependencyError(err.message, err) diff --git a/tests/functional/Validation.XmlValidator.node.spec.js b/tests/functional/Validation.XmlValidator.node.spec.js index ad3aa64c4..097cb1fc7 100644 --- a/tests/functional/Validation.XmlValidator.node.spec.js +++ b/tests/functional/Validation.XmlValidator.node.spec.js @@ -23,7 +23,7 @@ const assert = require('assert') const { suite, it, xit } = require('mocha') const { globSync } = require('fast-glob') -const { default: { fails: skipTests } } = require('../../dist.node/_optPlug.node/xmlValidate/') +const { default: { fails: skipTests } } = require('../../dist.node/_optPlug.node/xmlValidator/') const { Validation: { XmlValidator }, Spec: { Version } diff --git a/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js b/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js index ed9c05176..b1b3499b6 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js @@ -28,18 +28,17 @@ const { pathToFileURL } = require('url') const { realpathSync } = require('fs') const { join } = require('path') -let xmlValidate +let makeValidator try { - xmlValidate = require('../../../dist.node/_optPlug.node/xmlValidate/__opts/libxmljs2').default + makeValidator = require('../../../dist.node/_optPlug.node/xmlValidator/__opts/libxmljs2').default } catch { - xmlValidate = undefined + makeValidator = undefined } -(xmlValidate === undefined +(makeValidator === undefined ? suite.skip : suite )('internals: OpPlug.node.xmlValidate :: libxmljs2 ', () => { - const schemaCache = {} const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] const validXML = ` ` @@ -47,12 +46,12 @@ try { xmlns="http://cyclonedx.org/schema/bom/1.6">` test('valid return null', () => { - const validationError = xmlValidate(validXML, schemaPath, schemaCache) + const validationError = makeValidator(schemaPath)(validXML) assert.strictEqual(validationError, null) }) test('invalid returns validationError', () => { - const validationError = xmlValidate(invalidXML, schemaPath, schemaCache) + const validationError = makeValidator(schemaPath)(invalidXML) assert.notEqual(validationError, null) }) @@ -76,7 +75,7 @@ try { ` - const validationError = xmlValidate(input, schemaPath, schemaCache) + const validationError = makeValidator(schemaPath)(input) assert.doesNotMatch( JSON.stringify(validationError), /vaiquia2zoo3Im8ro9zahNg5mohwipouka2xieweed6ahChei3doox2fek3ise0lmohju3loh5oDu7eigh3jaeR2aiph2Voo/, diff --git a/tests/functional/internals/OpPlug.node.xmlValidate.spec.js b/tests/functional/internals/OpPlug.node.xmlValidate.spec.js index bbb695022..7d05085f4 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidate.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidate.spec.js @@ -24,21 +24,20 @@ const { _Resources: Resources, Spec: { Version } } = require('../../../') -const xmlValidate = require('../../../dist.node/_optPlug.node/xmlValidate/').default +const makeValidator = require('../../../dist.node/_optPlug.node/xmlValidator/').default suite('internals: OpPlug.node.xmlValidate', () => { - const schemaCache = {} const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] const validXML = ` ` const invalidXML = ` xmlns="http://cyclonedx.org/schema/bom/1.6">` - if (xmlValidate.fails) { + if (makeValidator.fails) { test('call should fail/throw', () => { assert.throws( () => { - xmlValidate(validXML, schemaPath, schemaCache) + makeValidator(schemaPath) }, (err) => { assert.ok(err instanceof Error) @@ -48,13 +47,15 @@ suite('internals: OpPlug.node.xmlValidate', () => { ) }) } else { + const validator = makeValidator(schemaPath) + test('valid returns null', () => { - const validationError = xmlValidate(validXML, schemaPath, schemaCache) + const validationError = validator(validXML) assert.strictEqual(validationError, null) }) test('invalid returns validationError', () => { - const validationError = xmlValidate(invalidXML, schemaPath, schemaCache) + const validationError = validator(invalidXML, schemaPath) assert.notEqual(validationError, null) }) } From 15e1aa385a7e047958aaa5f28e367832f6ca4fac Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 14:19:28 +0200 Subject: [PATCH 09/45] fix json schema 1.2 Signed-off-by: Jan Kowalleck --- res/schema/README.md | 15 ++++++++------- res/schema/bom-1.2-strict.SNAPSHOT.schema.json | 3 --- res/schema/bom-1.2.SNAPSHOT.schema.json | 4 ---- tools/schema-downloader/download.js | 1 + 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/res/schema/README.md b/res/schema/README.md index 3cf1c1b73..794409fa4 100644 --- a/res/schema/README.md +++ b/res/schema/README.md @@ -15,13 +15,13 @@ Currently using version | [`bom-1.4.SNAPSHOT.xsd`](bom-1.4.SNAPSHOT.xsd) | applied changes: 1 | | [`bom-1.5.SNAPSHOT.xsd`](bom-1.5.SNAPSHOT.xsd) | applied changes: 1 | | [`bom-1.6.SNAPSHOT.xsd`](bom-1.6.SNAPSHOT.xsd) | applied changes: 1 | -| [`bom-1.2.SNAPSHOT.schema.json`](bom-1.2.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 | -| [`bom-1.3.SNAPSHOT.schema.json`](bom-1.3.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 | -| [`bom-1.4.SNAPSHOT.schema.json`](bom-1.4.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 | -| [`bom-1.5.SNAPSHOT.schema.json`](bom-1.5.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 | -| [`bom-1.6.SNAPSHOT.schema.json`](bom-1.6.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 | -| [`bom-1.2-strict.SNAPSHOT.schema.json`](bom-1.2-strict.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 | -| [`bom-1.3-strict.SNAPSHOT.schema.json`](bom-1.3-strict.SNAPSHOT.schema.json) | applied changes: 2,3,4,5 | +| [`bom-1.2.SNAPSHOT.schema.json`](bom-1.2.SNAPSHOT.schema.json) | applied changes: 2,3,4,5,6 | +| [`bom-1.3.SNAPSHOT.schema.json`](bom-1.3.SNAPSHOT.schema.json) | applied changes: 2,3,4,5,6 | +| [`bom-1.4.SNAPSHOT.schema.json`](bom-1.4.SNAPSHOT.schema.json) | applied changes: 2,3,4,5,6 | +| [`bom-1.5.SNAPSHOT.schema.json`](bom-1.5.SNAPSHOT.schema.json) | applied changes: 2,3,4,5,6 | +| [`bom-1.6.SNAPSHOT.schema.json`](bom-1.6.SNAPSHOT.schema.json) | applied changes: 2,3,4,5,6 | +| [`bom-1.2-strict.SNAPSHOT.schema.json`](bom-1.2-strict.SNAPSHOT.schema.json) | applied changes: 2,3,4,5,6 | +| [`bom-1.3-strict.SNAPSHOT.schema.json`](bom-1.3-strict.SNAPSHOT.schema.json) | applied changes: 2,3,4,5,6 | | [`spdx.SNAPSHOT.xsd`](spdx.SNAPSHOT.xsd) | | | [`spdx.SNAPSHOT.schema.json`](spdx.SNAPSHOT.schema.json) | | | [`jsf-0.82.SNAPSHOT.schema.json`](jsf-0.82.SNAPSHOT.schema.json) | | @@ -32,3 +32,4 @@ changes: 3. `jsf-0.82.schema.json` was replaced with `jsf-0.82.SNAPSHOT.schema.json` 4. `properties.$schema.enum` was fixed to match `$id` 5. `required.version` removed, as it is actually optional with default value +6. `"format": "string"` removed, as it is unknown to JSON spec diff --git a/res/schema/bom-1.2-strict.SNAPSHOT.schema.json b/res/schema/bom-1.2-strict.SNAPSHOT.schema.json index a36fb4b6c..b85fecf94 100644 --- a/res/schema/bom-1.2-strict.SNAPSHOT.schema.json +++ b/res/schema/bom-1.2-strict.SNAPSHOT.schema.json @@ -135,19 +135,16 @@ "properties": { "vendor": { "type": "string", - "format": "string", "title": "Tool Vendor", "description": "The date and time (timestamp) when the document was created." }, "name": { "type": "string", - "format": "string", "title": "Tool Name", "description": "The date and time (timestamp) when the document was created." }, "version": { "type": "string", - "format": "string", "title": "Tool Version", "description": "The date and time (timestamp) when the document was created." }, diff --git a/res/schema/bom-1.2.SNAPSHOT.schema.json b/res/schema/bom-1.2.SNAPSHOT.schema.json index d23f683b5..44de67d01 100644 --- a/res/schema/bom-1.2.SNAPSHOT.schema.json +++ b/res/schema/bom-1.2.SNAPSHOT.schema.json @@ -129,19 +129,16 @@ "properties": { "vendor": { "type": "string", - "format": "string", "title": "Tool Vendor", "description": "The date and time (timestamp) when the document was created." }, "name": { "type": "string", - "format": "string", "title": "Tool Name", "description": "The date and time (timestamp) when the document was created." }, "version": { "type": "string", - "format": "string", "title": "Tool Version", "description": "The date and time (timestamp) when the document was created." }, @@ -837,7 +834,6 @@ "properties": { "ref": { "$ref": "#/definitions/refType", - "format": "string", "title": "Reference", "description": "References a component by the components bom-ref attribute" }, diff --git a/tools/schema-downloader/download.js b/tools/schema-downloader/download.js index 473d0a76d..ad7acd6d3 100644 --- a/tools/schema-downloader/download.js +++ b/tools/schema-downloader/download.js @@ -58,6 +58,7 @@ const BomJsonLax = Object.freeze({ replace: Object.freeze([ Object.freeze(['spdx.schema.json', 'spdx.SNAPSHOT.schema.json']), Object.freeze(['jsf-0.82.schema.json', 'jsf-0.82.SNAPSHOT.schema.json']), + Object.freeze([/,?\s*"format"\S*:\s*"string"/sg, '']), /* "$schema" is not required but optional. that enum constraint value there is complicated -> remove it. See https://github.com/CycloneDX/specification/issues/402 From 9135e7b40b476515d781b10a57f892eebf18bbfb Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 15:30:27 +0200 Subject: [PATCH 10/45] wip Signed-off-by: Jan Kowalleck --- src/_optPlug.node/_wrapper.ts | 2 +- src/_optPlug.node/jsonValidator/__opts/ajv.ts | 59 +++++++++++ src/_optPlug.node/jsonValidator/index.ts | 31 ++++++ .../xmlValidator/__opts/libxmljs2.ts | 7 +- src/_optPlug.node/xmlValidator/index.ts | 2 +- src/validation/jsonValidator.node.ts | 99 +++++-------------- src/validation/xmlValidator.node.ts | 12 +-- .../OpPlug.node.xmlValidate.libxmljs2.spec.js | 10 +- .../internals/OpPlug.node.xmlValidate.spec.js | 12 +-- 9 files changed, 137 insertions(+), 97 deletions(-) create mode 100644 src/_optPlug.node/jsonValidator/__opts/ajv.ts create mode 100644 src/_optPlug.node/jsonValidator/index.ts diff --git a/src/_optPlug.node/_wrapper.ts b/src/_optPlug.node/_wrapper.ts index 9c7dff66b..fc7cd6e7a 100644 --- a/src/_optPlug.node/_wrapper.ts +++ b/src/_optPlug.node/_wrapper.ts @@ -47,7 +47,7 @@ export default function kv[0]).join(' || ') ) } diff --git a/src/_optPlug.node/jsonValidator/__opts/ajv.ts b/src/_optPlug.node/jsonValidator/__opts/ajv.ts new file mode 100644 index 000000000..81754571e --- /dev/null +++ b/src/_optPlug.node/jsonValidator/__opts/ajv.ts @@ -0,0 +1,59 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +import Ajv, { type Options as AjvOptions } from 'ajv' +import addFormats from 'ajv-formats' +/* @ts-expect-error TS7016 */ +import * as addFormats2019 from 'ajv-formats-draft2019' +import { readFile } from 'fs/promises' + +import type { ValidationError } from '../../../validation/types' +import type { Functionality, Validator } from '../index' + +const ajvOptions: AjvOptions = Object.freeze({ + // no defaults => no data alteration + useDefaults: false, + strict: false, + strictSchema: false, + addUsedSchema: false +}) + +/** @internal */ +export default (async function (schemaPath: string, schemaMap: Record = {}): Promise { + const [schema, schemas] = await Promise.all([ + readFile(schemaPath, 'utf-8').then(c => JSON.parse(c)), + Promise.all(Object.entries(schemaMap).map( + async ([k, v]) => await readFile(v, 'utf-8').then(c => [k, JSON.parse(c)]) + )).then(es => Object.fromEntries(es)) + ]) + + const ajv = new Ajv({ ...ajvOptions, schemas }) + addFormats(ajv) + addFormats2019(ajv, { formats: ['idn-email'] }) + // there is just no working implementation for format "iri-reference": see https://github.com/luzlab/ajv-formats-draft2019/issues/22 + ajv.addFormat('iri-reference', true) + + const validator = ajv.compile(schema) + + return function (data: string): null | ValidationError { + return validator(JSON.parse(data)) + ? null + : validator.errors + } +}) satisfies Functionality diff --git a/src/_optPlug.node/jsonValidator/index.ts b/src/_optPlug.node/jsonValidator/index.ts new file mode 100644 index 000000000..266129d0c --- /dev/null +++ b/src/_optPlug.node/jsonValidator/index.ts @@ -0,0 +1,31 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +import type { ValidationError } from '../../validation/types' +import opWrapper from '../_wrapper' + +export type Validator = (data: string) => null | ValidationError +export type Functionality = (schemaPath: string, schemaMap: Record) => Promise + +export default opWrapper('JsonValidator', [ + /* eslint-disable @typescript-eslint/no-var-requires */ + ['( ajv && ajv-formats && ajv-formats-draft2019 )', () => require('./__opts/ajv').default] + // ... add others here, pull-requests welcome! + /* eslint-enable @typescript-eslint/no-var-requires */ +]) diff --git a/src/_optPlug.node/xmlValidator/__opts/libxmljs2.ts b/src/_optPlug.node/xmlValidator/__opts/libxmljs2.ts index 23e7960b3..cdf568658 100644 --- a/src/_optPlug.node/xmlValidator/__opts/libxmljs2.ts +++ b/src/_optPlug.node/xmlValidator/__opts/libxmljs2.ts @@ -17,7 +17,7 @@ SPDX-License-Identifier: Apache-2.0 Copyright (c) OWASP Foundation. All Rights Reserved. */ -import { readFileSync } from 'fs' +import { readFile } from 'fs/promises' import { type ParserOptions, parseXml } from 'libxmljs2' import { pathToFileURL } from 'url' @@ -35,9 +35,10 @@ const xmlParseOptions: Readonly = Object.freeze({ }) /** @internal */ -export default (function (schemaPath: string): Validator { - const schema = parseXml(readFileSync(schemaPath, 'utf-8'), +export default (async function (schemaPath: string): Promise { + const schema = parseXml(await readFile(schemaPath, 'utf-8'), { ...xmlParseOptions, baseUrl: pathToFileURL(schemaPath).toString() }) + return function (data: string): null | ValidationError { const doc = parseXml(data, xmlParseOptions) return doc.validate(schema) diff --git a/src/_optPlug.node/xmlValidator/index.ts b/src/_optPlug.node/xmlValidator/index.ts index bf965ed33..5e1fd8fd3 100644 --- a/src/_optPlug.node/xmlValidator/index.ts +++ b/src/_optPlug.node/xmlValidator/index.ts @@ -21,7 +21,7 @@ import type { ValidationError } from '../../validation/types' import opWrapper from '../_wrapper' export type Validator = (data: string) => null | ValidationError -export type Functionality = (schemaPath: string) => Validator +export type Functionality = (schemaPath: string) => Promise export default opWrapper('XmlValidator', [ /* eslint-disable @typescript-eslint/no-var-requires */ diff --git a/src/validation/jsonValidator.node.ts b/src/validation/jsonValidator.node.ts index abaf9c298..16b57c611 100644 --- a/src/validation/jsonValidator.node.ts +++ b/src/validation/jsonValidator.node.ts @@ -17,86 +17,41 @@ SPDX-License-Identifier: Apache-2.0 Copyright (c) OWASP Foundation. All Rights Reserved. */ -import type Ajv from 'ajv' -import { type ValidateFunction } from 'ajv' -import { readFile } from 'fs/promises' - +import { OptPlugError } from '../_optPlug.node/errors' +import makeValidator, { type Validator } from '../_optPlug.node/jsonValidator' import { FILES } from '../resources.node' import { BaseValidator } from './baseValidator' import { MissingOptionalDependencyError, NotImplementedError } from './errors' import type { ValidationError } from './types' -let _ajv: Ajv | undefined +abstract class BaseJsonValidator extends BaseValidator { + protected abstract _getSchemaFile (): string | undefined -/** - * @throws {@link Validation.MissingOptionalDependencyError | MissingOptionalDependencyError} - */ -async function getAjv (): Promise { - if (_ajv === undefined) { - let Ajv, addFormats, addFormats2019 - try { - [Ajv, addFormats, addFormats2019] = await Promise.all([ - import('ajv').then((m) => m.default), - import('ajv-formats').then((m) => m.default), - /* @ts-expect-error TS7016 */ - import('ajv-formats-draft2019') - ]) - } catch (err) { - throw new MissingOptionalDependencyError( - 'No JSON validator available.' + - ' Please install all of the optional dependencies:' + - ' ajv, ajv-formats, ajv-formats-draft2019', - err - ) + #getSchemaFilePath (): string { + const s = this._getSchemaFile() + if (s === undefined) { + throw new NotImplementedError(this.version) } - - const [spdxSchema, jsfSchema] = await Promise.all([ - readFile(FILES.SPDX.JSON_SCHEMA, 'utf-8').then(JSON.parse), - readFile(FILES.JSF.JSON_SCHEMA, 'utf-8').then(JSON.parse) - ]) - const ajv = new Ajv({ - // no defaults => no data alteration - useDefaults: false, - formats: { - // "string" was mistakenly set as format in CycloneDX1.2 - string: true - }, - strict: false, - strictSchema: false, - addUsedSchema: false, - schemas: { - 'http://cyclonedx.org/schema/spdx.SNAPSHOT.schema.json': spdxSchema, - 'http://cyclonedx.org/schema/jsf-0.82.SNAPSHOT.schema.json': jsfSchema - } - }) - addFormats(ajv) - addFormats2019(ajv, { formats: ['idn-email'] }) - // there is just no working implementation for format "iri-reference": see https://github.com/luzlab/ajv-formats-draft2019/issues/22 - ajv.addFormat('iri-reference', true) - _ajv = ajv + return s } - return _ajv -} - -abstract class BaseJsonValidator extends BaseValidator { - #validator: ValidateFunction | undefined - /** @internal */ - protected abstract _getSchemaFile (): string | undefined + #validatorCache?: Validator = undefined - async #getValidator (): Promise { - if (this.#validator === undefined) { - const schemaFile = this._getSchemaFile() - if (schemaFile === undefined) { - throw new NotImplementedError(this.version) + async #getValidator (): Promise { + if (this.#validatorCache === undefined) { + try { + this.#validatorCache = await makeValidator(this.#getSchemaFilePath(), { + 'http://cyclonedx.org/schema/spdx.SNAPSHOT.schema.json': FILES.SPDX.JSON_SCHEMA, + 'http://cyclonedx.org/schema/jsf-0.82.SNAPSHOT.schema.json': FILES.JSF.JSON_SCHEMA + }) + } catch (err) { + if (err instanceof OptPlugError) { + throw new MissingOptionalDependencyError(err.message, err) + } + throw err } - const [ajv, schema] = await Promise.all([ - getAjv(), - readFile(schemaFile, 'utf-8').then(JSON.parse) - ]) - this.#validator = ajv.compile(schema) } - return this.#validator + return this.#validatorCache } /** @@ -111,13 +66,7 @@ abstract class BaseJsonValidator extends BaseValidator { * - {@link Validation.MissingOptionalDependencyError | MissingOptionalDependencyError}, when a required dependency was not installed */ async validate (data: string): Promise { - const [doc, validator] = await Promise.all([ - (async () => JSON.parse(data))(), - this.#getValidator() - ]) - return validator(doc) - ? null - : validator.errors + return (await this.#getValidator())(data) } } export class JsonValidator extends BaseJsonValidator { diff --git a/src/validation/xmlValidator.node.ts b/src/validation/xmlValidator.node.ts index b0e9dcf9e..c3ac45a1e 100644 --- a/src/validation/xmlValidator.node.ts +++ b/src/validation/xmlValidator.node.ts @@ -26,19 +26,19 @@ import type { ValidationError } from './types' export class XmlValidator extends BaseValidator { #getSchemaFilePath (): string { - const file = FILES.CDX.XML_SCHEMA[this.version] - if (file === undefined) { + const s = FILES.CDX.XML_SCHEMA[this.version] + if (s === undefined) { throw new NotImplementedError(this.version) } - return file + return s } #validatorCache?: Validator = undefined - get #validator (): Validator { + async #getValidator (): Promise { if (this.#validatorCache === undefined) { try { - this.#validatorCache = makeValidator(this.#getSchemaFilePath()) + this.#validatorCache = await makeValidator(this.#getSchemaFilePath()) } catch (err) { if (err instanceof OptPlugError) { throw new MissingOptionalDependencyError(err.message, err) @@ -62,7 +62,7 @@ export class XmlValidator extends BaseValidator { */ async validate (data: string): Promise { try { - return this.#validator(data) + return (await this.#getValidator())(data) } catch (err) { if (err instanceof OptPlugError) { throw new MissingOptionalDependencyError(err.message, err) diff --git a/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js b/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js index b1b3499b6..2dd3dc376 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js @@ -45,13 +45,13 @@ try { const invalidXML = ` xmlns="http://cyclonedx.org/schema/bom/1.6">` - test('valid return null', () => { - const validationError = makeValidator(schemaPath)(validXML) + test('valid return null', async () => { + const validationError = (await makeValidator(schemaPath))(validXML) assert.strictEqual(validationError, null) }) - test('invalid returns validationError', () => { - const validationError = makeValidator(schemaPath)(invalidXML) + test('invalid returns validationError', async () => { + const validationError = (await makeValidator(schemaPath))(invalidXML) assert.notEqual(validationError, null) }) @@ -75,7 +75,7 @@ try { ` - const validationError = makeValidator(schemaPath)(input) + const validationError = (await makeValidator(schemaPath))(input) assert.doesNotMatch( JSON.stringify(validationError), /vaiquia2zoo3Im8ro9zahNg5mohwipouka2xieweed6ahChei3doox2fek3ise0lmohju3loh5oDu7eigh3jaeR2aiph2Voo/, diff --git a/tests/functional/internals/OpPlug.node.xmlValidate.spec.js b/tests/functional/internals/OpPlug.node.xmlValidate.spec.js index 7d05085f4..569a042aa 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidate.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidate.spec.js @@ -26,7 +26,7 @@ const { } = require('../../../') const makeValidator = require('../../../dist.node/_optPlug.node/xmlValidator/').default -suite('internals: OpPlug.node.xmlValidate', () => { +suite('internals: OpPlug.node.xmlValidate', async () => { const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] const validXML = ` ` @@ -35,9 +35,9 @@ suite('internals: OpPlug.node.xmlValidate', () => { if (makeValidator.fails) { test('call should fail/throw', () => { - assert.throws( - () => { - makeValidator(schemaPath) + assert.rejects( + async () => { + await makeValidator(schemaPath) }, (err) => { assert.ok(err instanceof Error) @@ -47,7 +47,7 @@ suite('internals: OpPlug.node.xmlValidate', () => { ) }) } else { - const validator = makeValidator(schemaPath) + const validator = await makeValidator(schemaPath) test('valid returns null', () => { const validationError = validator(validXML) @@ -55,7 +55,7 @@ suite('internals: OpPlug.node.xmlValidate', () => { }) test('invalid returns validationError', () => { - const validationError = validator(invalidXML, schemaPath) + const validationError = validator(invalidXML) assert.notEqual(validationError, null) }) } From a23c597bd8ec5c9fa724fc96466f845927054fe3 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 15:56:13 +0200 Subject: [PATCH 11/45] wip Signed-off-by: Jan Kowalleck --- ...pPlug.node.xmlValidator.libxmljs2.spec.js} | 17 ++++++++++---- ...ec.js => OpPlug.node.xmlValidator.spec.js} | 23 ++++++++++++------- 2 files changed, 28 insertions(+), 12 deletions(-) rename tests/functional/internals/{OpPlug.node.xmlValidate.libxmljs2.spec.js => OpPlug.node.xmlValidator.libxmljs2.spec.js} (83%) rename tests/functional/internals/{OpPlug.node.xmlValidate.spec.js => OpPlug.node.xmlValidator.spec.js} (69%) diff --git a/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js similarity index 83% rename from tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js rename to tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js index 2dd3dc376..02668303c 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidate.libxmljs2.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js @@ -38,23 +38,32 @@ try { (makeValidator === undefined ? suite.skip : suite -)('internals: OpPlug.node.xmlValidate :: libxmljs2 ', () => { +)('internals: OpPlug.node.xmlValidator :: libxmljs2 ', () => { const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] const validXML = ` ` const invalidXML = ` - xmlns="http://cyclonedx.org/schema/bom/1.6">` + ` + const brokenXML = ` + ` // not closed - test('valid return null', async () => { + test('valid causes no validationError', async () => { const validationError = (await makeValidator(schemaPath))(validXML) assert.strictEqual(validationError, null) }) - test('invalid returns validationError', async () => { + test('invalid causes validationError', async () => { const validationError = (await makeValidator(schemaPath))(invalidXML) assert.notEqual(validationError, null) }) + test('broken causes validationError', async () => { + const validator = await makeValidator(schemaPath) + assert.throws(() => { + validator(brokenXML) + }) + }) + test('is not affected by XXE injection', async () => { // see https://github.com/CycloneDX/cyclonedx-javascript-library/issues/1061 const xxeFile = join(__dirname, '..', '..', '_data', 'xxe_flag.txt') diff --git a/tests/functional/internals/OpPlug.node.xmlValidate.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js similarity index 69% rename from tests/functional/internals/OpPlug.node.xmlValidate.spec.js rename to tests/functional/internals/OpPlug.node.xmlValidator.spec.js index 569a042aa..f9e70f315 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidate.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js @@ -26,12 +26,14 @@ const { } = require('../../../') const makeValidator = require('../../../dist.node/_optPlug.node/xmlValidator/').default -suite('internals: OpPlug.node.xmlValidate', async () => { +suite('internals: OpPlug.node.xmlValidator', () => { const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] const validXML = ` ` const invalidXML = ` - xmlns="http://cyclonedx.org/schema/bom/1.6">` + ` + const brokenXML = ` + ` // not closed if (makeValidator.fails) { test('call should fail/throw', () => { @@ -47,16 +49,21 @@ suite('internals: OpPlug.node.xmlValidate', async () => { ) }) } else { - const validator = await makeValidator(schemaPath) - - test('valid returns null', () => { - const validationError = validator(validXML) + test('valid causes no validationError', async () => { + const validationError = (await makeValidator(schemaPath))(validXML) assert.strictEqual(validationError, null) }) - test('invalid returns validationError', () => { - const validationError = validator(invalidXML) + test('invalid causes validationError', async () => { + const validationError = (await makeValidator(schemaPath))(invalidXML) assert.notEqual(validationError, null) }) + + test('broken causes validationError', async () => { + const validator = await makeValidator(schemaPath) + assert.throws(() => { + validator(brokenXML) + }) + }) } }) From 7f60354198d608c1abb37567b2881fbd74f80312 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 16:10:07 +0200 Subject: [PATCH 12/45] wip Signed-off-by: Jan Kowalleck --- .../OpPlug.node.jsonValidator.ajv.spec.js | 62 +++++++++++++++++ .../OpPlug.node.jsonValidator.spec.js | 68 +++++++++++++++++++ ...OpPlug.node.xmlValidator.libxmljs2.spec.js | 4 +- .../OpPlug.node.xmlValidator.spec.js | 4 +- 4 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 tests/functional/internals/OpPlug.node.jsonValidator.ajv.spec.js create mode 100644 tests/functional/internals/OpPlug.node.jsonValidator.spec.js diff --git a/tests/functional/internals/OpPlug.node.jsonValidator.ajv.spec.js b/tests/functional/internals/OpPlug.node.jsonValidator.ajv.spec.js new file mode 100644 index 000000000..42636f87b --- /dev/null +++ b/tests/functional/internals/OpPlug.node.jsonValidator.ajv.spec.js @@ -0,0 +1,62 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +const assert = require('assert') +const { suite, test } = require('mocha') + +const { + _Resources: Resources, + Spec: { Version } +} = require('../../../') + +let makeValidator +try { + makeValidator = require('../../../dist.node/_optPlug.node/jsonValidator/__opts/ajv').default +} catch { + makeValidator = undefined +} + +(makeValidator === undefined + ? suite.skip + : suite +)('internals: OpPlug.node.jsonValidator :: ajv ', () => { + const schemaPath = Resources.FILES.CDX.JSON_SCHEMA[Version.v1dot6] + const schemaMap = { + 'http://cyclonedx.org/schema/spdx.SNAPSHOT.schema.json': Resources.FILES.SPDX.JSON_SCHEMA, + 'http://cyclonedx.org/schema/jsf-0.82.SNAPSHOT.schema.json': Resources.FILES.JSF.JSON_SCHEMA + } + const validJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"}' + const invalidJson = '{"bomFormat": "unexpected", "specVersion": "1.6"}' + const brokenJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"' // not closed + + test('valid causes no validationError', async () => { + const validationError = (await makeValidator(schemaPath, schemaMap))(validJson) + assert.strictEqual(validationError, null) + }) + + test('invalid causes validationError', async () => { + const validationError = (await makeValidator(schemaPath, schemaMap))(invalidJson) + assert.notEqual(validationError, null) + }) + + test('broken causes validationError', async () => { + const validator = await makeValidator(schemaPath, schemaMap) + assert.throws(() => { validator(brokenJson) }) + }) +}) diff --git a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js new file mode 100644 index 000000000..688a61037 --- /dev/null +++ b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js @@ -0,0 +1,68 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +const assert = require('assert') +const { suite, test } = require('mocha') + +const { + _Resources: Resources, + Spec: { Version } +} = require('../../../') +const makeValidator = require('../../../dist.node/_optPlug.node/jsonValidator/').default + +suite('internals: OpPlug.node.jsonValidator', () => { + const schemaPath = Resources.FILES.CDX.JSON_SCHEMA[Version.v1dot6] + const schemaMap = { + 'http://cyclonedx.org/schema/spdx.SNAPSHOT.schema.json': Resources.FILES.SPDX.JSON_SCHEMA, + 'http://cyclonedx.org/schema/jsf-0.82.SNAPSHOT.schema.json': Resources.FILES.JSF.JSON_SCHEMA + } + const validJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"}' + const invalidJson = '{"bomFormat": "unexpected", "specVersion": "1.6"}' + const brokenJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"' // not closed + + if (makeValidator.fails) { + test('call should fail/throw', () => { + assert.rejects( + async () => { + await makeValidator(schemaPath) + }, + (err) => { + assert.ok(err instanceof Error) + assert.match(err.message, /no JsonValidator available/i) + return true + } + ) + }) + } else { + test('valid causes no validationError', async () => { + const validationError = (await makeValidator(schemaPath, schemaMap))(validJson) + assert.strictEqual(validationError, null) + }) + + test('invalid causes validationError', async () => { + const validationError = (await makeValidator(schemaPath, schemaMap))(invalidJson) + assert.notEqual(validationError, null) + }) + + test('broken causes validationError', async () => { + const validator = await makeValidator(schemaPath, schemaMap) + assert.throws(() => { validator(brokenJson) }) + }) + } +}) diff --git a/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js index 02668303c..3eeadd25d 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js @@ -59,9 +59,7 @@ try { test('broken causes validationError', async () => { const validator = await makeValidator(schemaPath) - assert.throws(() => { - validator(brokenXML) - }) + assert.throws(() => { validator(brokenXML) }) }) test('is not affected by XXE injection', async () => { diff --git a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js index f9e70f315..919dd1495 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js @@ -61,9 +61,7 @@ suite('internals: OpPlug.node.xmlValidator', () => { test('broken causes validationError', async () => { const validator = await makeValidator(schemaPath) - assert.throws(() => { - validator(brokenXML) - }) + assert.throws(() => { validator(brokenXML) }) }) } }) From ecbddce74b12680b3adaf4c8ea1747705be7bec7 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 16:19:10 +0200 Subject: [PATCH 13/45] docs Signed-off-by: Jan Kowalleck --- src/_helpers/README.md | 4 ++-- src/_optPlug.node/README.md | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 src/_optPlug.node/README.md diff --git a/src/_helpers/README.md b/src/_helpers/README.md index d3a2b7a75..32444f4d7 100644 --- a/src/_helpers/README.md +++ b/src/_helpers/README.md @@ -2,5 +2,5 @@ These are _internal_ helpers, that are not intended to be exported/published. -The helpers shall **not** be marked as `@internal`, so that TypeScript might pick up on them and still render definitions for them. -The internal defined interfaces, classes, functions are required for proper type checking downstream, but should not be utilized/called downstream. +The helpers SHALL **NOT** be marked as `@internal`, so that TypeScript might pick up on them and still render definitions for them. +The internal defined interfaces, classes, functions are required for proper type checking downstream, but SHOULD NOT be utilized/called downstream. diff --git a/src/_optPlug.node/README.md b/src/_optPlug.node/README.md new file mode 100644 index 000000000..4a42ce88d --- /dev/null +++ b/src/_optPlug.node/README.md @@ -0,0 +1,10 @@ +# Optional Pluggable for node + +These are _internal_ helpers, that are not intended to be exported/published. + +The helpers SHALL **NOT** be marked as `@internal`, so that TypeScript might pick up on them and still render definitions for them. +The internal defined interfaces, classes, functions are required for proper type checking downstream, but SHOULD NOT be utilized/called downstream. + +Some functionality is private. +These exports **MUST** be marked as `@internal`. +These files **MUST** be prefixed with double-underscore ("dunder" - `__`). From 2ef6d30eb3ccec9d4dffab7c6db6fffef46bbea3 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 16:33:22 +0200 Subject: [PATCH 14/45] refactor Signed-off-by: Jan Kowalleck --- src/_optPlug.node/README.md | 3 ++- .../{jsonValidator/__opts => __jsonValidators}/ajv.ts | 4 ++-- .../__opts => __xmlStringifiers}/xmlbuilder2.ts | 6 +++--- .../{xmlValidator/__opts => __xmlValidators}/libxmljs2.ts | 4 ++-- .../{jsonValidator/index.ts => jsonValidator.ts} | 6 +++--- .../{xmlStringify/index.ts => xmlStringify.ts} | 8 ++++---- .../{xmlValidator/index.ts => xmlValidator.ts} | 6 +++--- tests/functional/Validation.XmlValidator.node.spec.js | 2 +- .../internals/OpPlug.node.jsonValidator.ajv.spec.js | 2 +- .../internals/OpPlug.node.jsonValidator.spec.js | 2 +- .../functional/internals/OpPlug.node.xmlStringify.spec.js | 2 +- .../OpPlug.node.xmlStringify.xmlbuilder2.spec.js | 2 +- .../internals/OpPlug.node.xmlValidator.libxmljs2.spec.js | 2 +- .../functional/internals/OpPlug.node.xmlValidator.spec.js | 2 +- tsconfig.node.json | 2 +- 15 files changed, 27 insertions(+), 26 deletions(-) rename src/_optPlug.node/{jsonValidator/__opts => __jsonValidators}/ajv.ts (94%) rename src/_optPlug.node/{xmlStringify/__opts => __xmlStringifiers}/xmlbuilder2.ts (92%) rename src/_optPlug.node/{xmlValidator/__opts => __xmlValidators}/libxmljs2.ts (92%) rename src/_optPlug.node/{jsonValidator/index.ts => jsonValidator.ts} (89%) rename src/_optPlug.node/{xmlStringify/index.ts => xmlStringify.ts} (81%) rename src/_optPlug.node/{xmlValidator/index.ts => xmlValidator.ts} (86%) diff --git a/src/_optPlug.node/README.md b/src/_optPlug.node/README.md index 4a42ce88d..6777295e3 100644 --- a/src/_optPlug.node/README.md +++ b/src/_optPlug.node/README.md @@ -7,4 +7,5 @@ The internal defined interfaces, classes, functions are required for proper type Some functionality is private. These exports **MUST** be marked as `@internal`. -These files **MUST** be prefixed with double-underscore ("dunder" - `__`). +Respective files **MUST NOT** export or declare any relevant types or symbols. +Respective files **MUST** be prefixed with double-underscore ("dunder" - `__`). diff --git a/src/_optPlug.node/jsonValidator/__opts/ajv.ts b/src/_optPlug.node/__jsonValidators/ajv.ts similarity index 94% rename from src/_optPlug.node/jsonValidator/__opts/ajv.ts rename to src/_optPlug.node/__jsonValidators/ajv.ts index 81754571e..4c1be88b0 100644 --- a/src/_optPlug.node/jsonValidator/__opts/ajv.ts +++ b/src/_optPlug.node/__jsonValidators/ajv.ts @@ -23,8 +23,8 @@ import addFormats from 'ajv-formats' import * as addFormats2019 from 'ajv-formats-draft2019' import { readFile } from 'fs/promises' -import type { ValidationError } from '../../../validation/types' -import type { Functionality, Validator } from '../index' +import type { ValidationError } from '../../validation/types' +import type { Functionality, Validator } from '../jsonValidator' const ajvOptions: AjvOptions = Object.freeze({ // no defaults => no data alteration diff --git a/src/_optPlug.node/xmlStringify/__opts/xmlbuilder2.ts b/src/_optPlug.node/__xmlStringifiers/xmlbuilder2.ts similarity index 92% rename from src/_optPlug.node/xmlStringify/__opts/xmlbuilder2.ts rename to src/_optPlug.node/__xmlStringifiers/xmlbuilder2.ts index b4b57c020..8fa542369 100644 --- a/src/_optPlug.node/xmlStringify/__opts/xmlbuilder2.ts +++ b/src/_optPlug.node/__xmlStringifiers/xmlbuilder2.ts @@ -20,9 +20,9 @@ Copyright (c) OWASP Foundation. All Rights Reserved. import { create } from 'xmlbuilder2' import type { XMLBuilder } from 'xmlbuilder2/lib/interfaces' -import type { SerializerOptions } from '../../../serialize/types' -import type { SimpleXml } from '../../../serialize/xml/types' -import type { Functionality } from '../' +import type { SerializerOptions } from '../../serialize/types' +import type { SimpleXml } from '../../serialize/xml/types' +import type { Functionality } from '../xmlStringify' if (typeof create !== 'function') { throw new Error('`create` is not a function') diff --git a/src/_optPlug.node/xmlValidator/__opts/libxmljs2.ts b/src/_optPlug.node/__xmlValidators/libxmljs2.ts similarity index 92% rename from src/_optPlug.node/xmlValidator/__opts/libxmljs2.ts rename to src/_optPlug.node/__xmlValidators/libxmljs2.ts index cdf568658..a72989722 100644 --- a/src/_optPlug.node/xmlValidator/__opts/libxmljs2.ts +++ b/src/_optPlug.node/__xmlValidators/libxmljs2.ts @@ -21,8 +21,8 @@ import { readFile } from 'fs/promises' import { type ParserOptions, parseXml } from 'libxmljs2' import { pathToFileURL } from 'url' -import type { ValidationError } from '../../../validation/types' -import type { Functionality, Validator } from '../index' +import type { ValidationError } from '../../validation/types' +import type { Functionality, Validator } from '../xmlValidator' const xmlParseOptions: Readonly = Object.freeze({ nonet: true, diff --git a/src/_optPlug.node/jsonValidator/index.ts b/src/_optPlug.node/jsonValidator.ts similarity index 89% rename from src/_optPlug.node/jsonValidator/index.ts rename to src/_optPlug.node/jsonValidator.ts index 266129d0c..a73ddb418 100644 --- a/src/_optPlug.node/jsonValidator/index.ts +++ b/src/_optPlug.node/jsonValidator.ts @@ -17,15 +17,15 @@ SPDX-License-Identifier: Apache-2.0 Copyright (c) OWASP Foundation. All Rights Reserved. */ -import type { ValidationError } from '../../validation/types' -import opWrapper from '../_wrapper' +import type { ValidationError } from '../validation/types' +import opWrapper from './_wrapper' export type Validator = (data: string) => null | ValidationError export type Functionality = (schemaPath: string, schemaMap: Record) => Promise export default opWrapper('JsonValidator', [ /* eslint-disable @typescript-eslint/no-var-requires */ - ['( ajv && ajv-formats && ajv-formats-draft2019 )', () => require('./__opts/ajv').default] + ['( ajv && ajv-formats && ajv-formats-draft2019 )', () => require('./__jsonValidators/ajv').default] // ... add others here, pull-requests welcome! /* eslint-enable @typescript-eslint/no-var-requires */ ]) diff --git a/src/_optPlug.node/xmlStringify/index.ts b/src/_optPlug.node/xmlStringify.ts similarity index 81% rename from src/_optPlug.node/xmlStringify/index.ts rename to src/_optPlug.node/xmlStringify.ts index cce5bb1c2..2c77335da 100644 --- a/src/_optPlug.node/xmlStringify/index.ts +++ b/src/_optPlug.node/xmlStringify.ts @@ -17,15 +17,15 @@ SPDX-License-Identifier: Apache-2.0 Copyright (c) OWASP Foundation. All Rights Reserved. */ -import type { SerializerOptions } from '../../serialize/types' -import type { SimpleXml } from '../../serialize/xml/types' -import opWrapper from '../_wrapper' +import type { SerializerOptions } from '../serialize/types' +import type { SimpleXml } from '../serialize/xml/types' +import opWrapper from './_wrapper' export type Functionality = (element: SimpleXml.Element, options?: SerializerOptions) => string export default opWrapper('XmlStringifier', [ /* eslint-disable @typescript-eslint/no-var-requires */ - ['xmlbuilder2', () => require('./__opts/xmlbuilder2').default] + ['xmlbuilder2', () => require('./__xmlStringifiers/xmlbuilder2').default] // ... add others here, pull-requests welcome! /* eslint-enable @typescript-eslint/no-var-requires */ ]) diff --git a/src/_optPlug.node/xmlValidator/index.ts b/src/_optPlug.node/xmlValidator.ts similarity index 86% rename from src/_optPlug.node/xmlValidator/index.ts rename to src/_optPlug.node/xmlValidator.ts index 5e1fd8fd3..be2f98785 100644 --- a/src/_optPlug.node/xmlValidator/index.ts +++ b/src/_optPlug.node/xmlValidator.ts @@ -17,15 +17,15 @@ SPDX-License-Identifier: Apache-2.0 Copyright (c) OWASP Foundation. All Rights Reserved. */ -import type { ValidationError } from '../../validation/types' -import opWrapper from '../_wrapper' +import type { ValidationError } from '../validation/types' +import opWrapper from './_wrapper' export type Validator = (data: string) => null | ValidationError export type Functionality = (schemaPath: string) => Promise export default opWrapper('XmlValidator', [ /* eslint-disable @typescript-eslint/no-var-requires */ - ['libxmljs2', () => require('./__opts/libxmljs2').default] + ['libxmljs2', () => require('./__xmlValidators/libxmljs2').default] // ... add others here, pull-requests welcome! /* eslint-enable @typescript-eslint/no-var-requires */ ]) diff --git a/tests/functional/Validation.XmlValidator.node.spec.js b/tests/functional/Validation.XmlValidator.node.spec.js index 097cb1fc7..4e85f365a 100644 --- a/tests/functional/Validation.XmlValidator.node.spec.js +++ b/tests/functional/Validation.XmlValidator.node.spec.js @@ -23,7 +23,7 @@ const assert = require('assert') const { suite, it, xit } = require('mocha') const { globSync } = require('fast-glob') -const { default: { fails: skipTests } } = require('../../dist.node/_optPlug.node/xmlValidator/') +const { default: { fails: skipTests } } = require('../../dist.node/_optPlug.node/xmlValidator') const { Validation: { XmlValidator }, Spec: { Version } diff --git a/tests/functional/internals/OpPlug.node.jsonValidator.ajv.spec.js b/tests/functional/internals/OpPlug.node.jsonValidator.ajv.spec.js index 42636f87b..092e4fcc5 100644 --- a/tests/functional/internals/OpPlug.node.jsonValidator.ajv.spec.js +++ b/tests/functional/internals/OpPlug.node.jsonValidator.ajv.spec.js @@ -27,7 +27,7 @@ const { let makeValidator try { - makeValidator = require('../../../dist.node/_optPlug.node/jsonValidator/__opts/ajv').default + makeValidator = require('../../../dist.node/_optPlug.node/__jsonValidators/ajv').default } catch { makeValidator = undefined } diff --git a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js index 688a61037..c6634843c 100644 --- a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js @@ -24,7 +24,7 @@ const { _Resources: Resources, Spec: { Version } } = require('../../../') -const makeValidator = require('../../../dist.node/_optPlug.node/jsonValidator/').default +const makeValidator = require('../../../dist.node/_optPlug.node/jsonValidator').default suite('internals: OpPlug.node.jsonValidator', () => { const schemaPath = Resources.FILES.CDX.JSON_SCHEMA[Version.v1dot6] diff --git a/tests/functional/internals/OpPlug.node.xmlStringify.spec.js b/tests/functional/internals/OpPlug.node.xmlStringify.spec.js index 42734a65c..d8be1027f 100644 --- a/tests/functional/internals/OpPlug.node.xmlStringify.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlStringify.spec.js @@ -20,7 +20,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved. const assert = require('assert') const { suite, test } = require('mocha') -const xmlStringify = require('../../../dist.node/_optPlug.node/xmlStringify/').default +const xmlStringify = require('../../../dist.node/_optPlug.node/xmlStringify').default suite('internals: OpPlug.node.xmlStringify', () => { const dummyElem = Object.freeze({ diff --git a/tests/functional/internals/OpPlug.node.xmlStringify.xmlbuilder2.spec.js b/tests/functional/internals/OpPlug.node.xmlStringify.xmlbuilder2.spec.js index 9d398c516..f9f6e5eeb 100644 --- a/tests/functional/internals/OpPlug.node.xmlStringify.xmlbuilder2.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlStringify.xmlbuilder2.spec.js @@ -22,7 +22,7 @@ const { suite, test } = require('mocha') let xmlStringify try { - xmlStringify = require('../../../dist.node/_optPlug.node/xmlStringify/__opts/xmlbuilder2').default + xmlStringify = require('../../../dist.node/_optPlug.node/__xmlStringifiers/xmlbuilder2').default } catch { xmlStringify = undefined } diff --git a/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js index 3eeadd25d..96e3e7a03 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js @@ -30,7 +30,7 @@ const { join } = require('path') let makeValidator try { - makeValidator = require('../../../dist.node/_optPlug.node/xmlValidator/__opts/libxmljs2').default + makeValidator = require('../../../dist.node/_optPlug.node/__xmlValidators/libxmljs2').default } catch { makeValidator = undefined } diff --git a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js index 919dd1495..dcf6a151f 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js @@ -24,7 +24,7 @@ const { _Resources: Resources, Spec: { Version } } = require('../../../') -const makeValidator = require('../../../dist.node/_optPlug.node/xmlValidator/').default +const makeValidator = require('../../../dist.node/_optPlug.node/xmlValidator').default suite('internals: OpPlug.node.xmlValidator', () => { const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] diff --git a/tsconfig.node.json b/tsconfig.node.json index f1579cbf9..95d746905 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/tsconfig", "extends": "./tsconfig.json", "include": [ - "src/_optPlug.node/*/__opts" + "src/_optPlug.node/__*s/*.ts" ], "files": ["src/index.node.ts"], "compilerOptions": { From aa0798eb17191e7b4e5f73581207709a31bdcbe2 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 16:34:50 +0200 Subject: [PATCH 15/45] tidy Signed-off-by: Jan Kowalleck --- .c8rc.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.c8rc.json b/.c8rc.json index 3767d45e3..22a947e1d 100644 --- a/.c8rc.json +++ b/.c8rc.json @@ -1,8 +1,7 @@ { "all": true, "src": [ - "src", - "libs/universal-node-xml" + "src" ], "exclude": [ "**/*.web.ts", From d26a611333e3647005d9ca01fe1e5e7afdcda617 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 16:36:11 +0200 Subject: [PATCH 16/45] tidy Signed-off-by: Jan Kowalleck --- .eslintignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index a98d5d618..49a90a7ae 100644 --- a/.eslintignore +++ b/.eslintignore @@ -12,7 +12,6 @@ /res/schema/ !/src/** -!/libs/** !/tools/schema-downloader/** From 3d30e5e393e4e91b7532aebd149500570655b416 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 16:41:39 +0200 Subject: [PATCH 17/45] tidy Signed-off-by: Jan Kowalleck --- src/validation/xmlValidator.node.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/validation/xmlValidator.node.ts b/src/validation/xmlValidator.node.ts index c3ac45a1e..ea5c4fc90 100644 --- a/src/validation/xmlValidator.node.ts +++ b/src/validation/xmlValidator.node.ts @@ -61,13 +61,6 @@ export class XmlValidator extends BaseValidator { * - {@link Validation.MissingOptionalDependencyError | MissingOptionalDependencyError}, when a required dependency was not installed */ async validate (data: string): Promise { - try { - return (await this.#getValidator())(data) - } catch (err) { - if (err instanceof OptPlugError) { - throw new MissingOptionalDependencyError(err.message, err) - } - throw err - } + return (await this.#getValidator())(data) } } From ba8da8b4e3b3e38974415a47bfca3f192c73860c Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 16:53:02 +0200 Subject: [PATCH 18/45] fix Signed-off-by: Jan Kowalleck --- tests/integration/Serialize.XmlSerialize.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/Serialize.XmlSerialize.test.js b/tests/integration/Serialize.XmlSerialize.test.js index fa2fe2584..fe86276b5 100644 --- a/tests/integration/Serialize.XmlSerialize.test.js +++ b/tests/integration/Serialize.XmlSerialize.test.js @@ -68,7 +68,7 @@ describe('Serialize.XmlSerialize', function () { }) } catch (err) { assert.ok(err instanceof Error) - assert.match(err.message, /no stringifier available\./i) + assert.match(err.message, /no XmlStringifier available\./i) return // skipped } @@ -141,7 +141,7 @@ describe('Serialize.XmlSerialize', function () { serializer.serialize(bom) } catch (err) { assert.ok(err instanceof Error) - assert.match(err.message, /no stringifier available./i) + assert.match(err.message, /no XmlStringifier available./i) } assert.strictEqual(normalizedBomRefs.has('testing'), true) From 8ea81d6f60da9ec4051b03255c93b83abf977eb6 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 18:59:58 +0200 Subject: [PATCH 19/45] tests Signed-off-by: Jan Kowalleck --- .github/workflows/nodejs.yml | 2 +- package.json | 2 +- tests/_data/specLoader.spec.js | 10 +--- .../Validation.JsonValidator.node.spec.js | 22 +++++---- .../Validation.XmlValidator.node.spec.js | 10 ++-- .../OpPlug.node.jsonValidator.ajv.spec.js | 13 ++++-- .../OpPlug.node.jsonValidator.spec.js | 15 +++--- .../OpPlug.node.xmlStringify.spec.js | 11 ++--- ...Plug.node.xmlStringify.xmlbuilder2.spec.js | 13 ++++-- ...OpPlug.node.xmlValidator.libxmljs2.spec.js | 13 ++++-- .../OpPlug.node.xmlValidator.spec.js | 15 +++--- .../Serialize.XmlSerialize.test.js | 46 ++++++++++--------- .../Validation.JsonStrictValidator.test.js | 14 +++--- .../Validation.JsonValidator.test.js | 14 +++--- .../Validation.XmlValidator.test.js | 13 ++---- tests/unit/Models.Bom.spec.js | 4 +- 16 files changed, 109 insertions(+), 108 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index d64abdad9..ca8b415e9 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -184,7 +184,7 @@ jobs: name: dist.node path: dist.node - name: test - run: npm run test:node + run: npm run test:node --forbid-pending - name: collect coverage if: ${{ failure() || success() }} run: > diff --git a/package.json b/package.json index 02d4ca0d1..e315b7723 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "eslint-plugin-simple-import-sort": "12.1.0", "eslint-plugin-tsdoc": "0.2.17", "fast-glob": "^3.3.1", - "mocha": "10.4.0", + "mocha": "^10.4.0", "npm-run-all2": "^6.1.2", "rimraf": "^5.0.7", "ts-loader": "9.5.1", diff --git a/tests/_data/specLoader.spec.js b/tests/_data/specLoader.spec.js index 5979da8fc..ff1f305e9 100644 --- a/tests/_data/specLoader.spec.js +++ b/tests/_data/specLoader.spec.js @@ -33,9 +33,7 @@ suite('test helpers: specLoader', () => { suite('loadSpec()', () => { test('unknown file', () => { assert.throws( - () => { - loadSpec('DOES-NOT-EXIST.schema.json') - }, + () => { loadSpec('DOES-NOT-EXIST.schema.json') }, Error, 'missing expected error' ) @@ -50,11 +48,7 @@ suite('test helpers: specLoader', () => { suite('getSpecElement()', () => { test('unknown path', () => { assert.throws( - () => { - getSpecElement( - 'bom-1.4.SNAPSHOT.schema.json', - 'properties', 'UNKNOWN_PROP') - }, + () => { getSpecElement('bom-1.4.SNAPSHOT.schema.json', 'properties', 'UNKNOWN_PROP') }, TypeError('undefined element: bom-1.4.SNAPSHOT.schema.json#properties.UNKNOWN_PROP'), 'missing expected error' ) diff --git a/tests/functional/Validation.JsonValidator.node.spec.js b/tests/functional/Validation.JsonValidator.node.spec.js index 696886b2c..7bbf05ce4 100644 --- a/tests/functional/Validation.JsonValidator.node.spec.js +++ b/tests/functional/Validation.JsonValidator.node.spec.js @@ -20,7 +20,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved. const fs = require('fs') const path = require('path') const assert = require('assert') -const { suite, it, xit } = require('mocha') +const { suite, test, before } = require('mocha') const { globSync } = require('fast-glob') const { @@ -28,16 +28,16 @@ const { Spec: { Version } } = require('../../') -let hasDep = true -try { - require('ajv') -} catch { - hasDep = false -} +before(function () { + const jsonValidator = require('../../dist.node/_optPlug.node/jsonValidator').default + if (jsonValidator.fails) { + this.skip() + } +}) -const test = hasDep ? it : xit +suite('Validation.JsonValidator functional', function () { + this.timeout(60000); -suite('Validation.JsonValidator functional', () => { [ Version.v1dot6, Version.v1dot5, @@ -65,7 +65,9 @@ suite('Validation.JsonValidator functional', () => { }) }) -suite('Validation.JsonStrictValidator functional', () => { +suite('Validation.JsonStrictValidator functional', function () { + this.timeout(60000); + [ Version.v1dot6, Version.v1dot5, diff --git a/tests/functional/Validation.XmlValidator.node.spec.js b/tests/functional/Validation.XmlValidator.node.spec.js index 4e85f365a..1ceed3b2f 100644 --- a/tests/functional/Validation.XmlValidator.node.spec.js +++ b/tests/functional/Validation.XmlValidator.node.spec.js @@ -20,16 +20,20 @@ Copyright (c) OWASP Foundation. All Rights Reserved. const fs = require('fs') const path = require('path') const assert = require('assert') -const { suite, it, xit } = require('mocha') +const { suite, test, before } = require('mocha') const { globSync } = require('fast-glob') -const { default: { fails: skipTests } } = require('../../dist.node/_optPlug.node/xmlValidator') const { Validation: { XmlValidator }, Spec: { Version } } = require('../../') -const test = skipTests ? xit : it +before(function () { + const xmlValidator = require('../../dist.node/_optPlug.node/xmlValidator').default + if (xmlValidator.fails) { + this.skip() + } +}) suite('Validation.XmlValidator functional', function () { this.timeout(60000); diff --git a/tests/functional/internals/OpPlug.node.jsonValidator.ajv.spec.js b/tests/functional/internals/OpPlug.node.jsonValidator.ajv.spec.js index 092e4fcc5..8fd2a39d3 100644 --- a/tests/functional/internals/OpPlug.node.jsonValidator.ajv.spec.js +++ b/tests/functional/internals/OpPlug.node.jsonValidator.ajv.spec.js @@ -18,7 +18,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved. */ const assert = require('assert') -const { suite, test } = require('mocha') +const { suite, test, before } = require('mocha') const { _Resources: Resources, @@ -32,10 +32,13 @@ try { makeValidator = undefined } -(makeValidator === undefined - ? suite.skip - : suite -)('internals: OpPlug.node.jsonValidator :: ajv ', () => { +before(function () { + if (typeof makeValidator !== 'function') { + this.skip() + } +}) + +suite('internals: OpPlug.node.jsonValidator :: ajv ', () => { const schemaPath = Resources.FILES.CDX.JSON_SCHEMA[Version.v1dot6] const schemaMap = { 'http://cyclonedx.org/schema/spdx.SNAPSHOT.schema.json': Resources.FILES.SPDX.JSON_SCHEMA, diff --git a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js index c6634843c..75bf8ee97 100644 --- a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js @@ -24,9 +24,10 @@ const { _Resources: Resources, Spec: { Version } } = require('../../../') -const makeValidator = require('../../../dist.node/_optPlug.node/jsonValidator').default +const { default: makeValidator } = require('../../../dist.node/_optPlug.node/jsonValidator') +const { OptPlugError } = require('../../../dist.node/_optPlug.node/errors') -suite('internals: OpPlug.node.jsonValidator', () => { +suite('internals: OpPlug.node.jsonValidator auto', () => { const schemaPath = Resources.FILES.CDX.JSON_SCHEMA[Version.v1dot6] const schemaMap = { 'http://cyclonedx.org/schema/spdx.SNAPSHOT.schema.json': Resources.FILES.SPDX.JSON_SCHEMA, @@ -37,13 +38,11 @@ suite('internals: OpPlug.node.jsonValidator', () => { const brokenJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"' // not closed if (makeValidator.fails) { - test('call should fail/throw', () => { - assert.rejects( - async () => { - await makeValidator(schemaPath) - }, + test('call should fail/throw', async () => { + await assert.rejects( + makeValidator(schemaPath), (err) => { - assert.ok(err instanceof Error) + assert.ok(err instanceof OptPlugError) assert.match(err.message, /no JsonValidator available/i) return true } diff --git a/tests/functional/internals/OpPlug.node.xmlStringify.spec.js b/tests/functional/internals/OpPlug.node.xmlStringify.spec.js index d8be1027f..5cb3e99c1 100644 --- a/tests/functional/internals/OpPlug.node.xmlStringify.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlStringify.spec.js @@ -20,9 +20,10 @@ Copyright (c) OWASP Foundation. All Rights Reserved. const assert = require('assert') const { suite, test } = require('mocha') -const xmlStringify = require('../../../dist.node/_optPlug.node/xmlStringify').default +const { default: xmlStringify } = require('../../../dist.node/_optPlug.node/xmlStringify') +const { OptPlugError } = require('../../../dist.node/_optPlug.node/errors') -suite('internals: OpPlug.node.xmlStringify', () => { +suite('internals: OpPlug.node.xmlStringify auto', () => { const dummyElem = Object.freeze({ type: 'element', name: 'foo' @@ -32,11 +33,9 @@ suite('internals: OpPlug.node.xmlStringify', () => { if (xmlStringify.fails) { test('call should fail/throw', () => { assert.throws( - () => { - xmlStringify(dummyElem) - }, + () => { xmlStringify(dummyElem) }, (err) => { - assert.ok(err instanceof Error) + assert.ok(err instanceof OptPlugError) assert.match(err.message, /no XmlStringifier available/i) return true } diff --git a/tests/functional/internals/OpPlug.node.xmlStringify.xmlbuilder2.spec.js b/tests/functional/internals/OpPlug.node.xmlStringify.xmlbuilder2.spec.js index f9f6e5eeb..3ba857e94 100644 --- a/tests/functional/internals/OpPlug.node.xmlStringify.xmlbuilder2.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlStringify.xmlbuilder2.spec.js @@ -18,7 +18,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved. */ const assert = require('assert') -const { suite, test } = require('mocha') +const { suite, test, before } = require('mocha') let xmlStringify try { @@ -27,10 +27,13 @@ try { xmlStringify = undefined } -(xmlStringify === undefined - ? suite.skip - : suite -)('internals: OpPlug.node.xmlStringify :: xmlbuilder2', () => { +before(function () { + if (typeof xmlStringify !== 'function') { + this.skip() + } +}) + +suite('internals: OpPlug.node.xmlStringify :: xmlbuilder2', () => { const data = { type: 'element', name: 'some-children', diff --git a/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js index 96e3e7a03..47aa12dd1 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.libxmljs2.spec.js @@ -18,7 +18,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved. */ const assert = require('assert') -const { suite, test } = require('mocha') +const { suite, test, before } = require('mocha') const { _Resources: Resources, @@ -35,10 +35,13 @@ try { makeValidator = undefined } -(makeValidator === undefined - ? suite.skip - : suite -)('internals: OpPlug.node.xmlValidator :: libxmljs2 ', () => { +before(function () { + if (typeof makeValidator !== 'function') { + this.skip() + } +}) + +suite('internals: OpPlug.node.xmlValidator :: libxmljs2 ', () => { const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] const validXML = ` ` diff --git a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js index dcf6a151f..3fbef8def 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js @@ -24,9 +24,10 @@ const { _Resources: Resources, Spec: { Version } } = require('../../../') -const makeValidator = require('../../../dist.node/_optPlug.node/xmlValidator').default +const { default: makeValidator } = require('../../../dist.node/_optPlug.node/xmlValidator') +const { OptPlugError } = require('../../../dist.node/_optPlug.node/errors') -suite('internals: OpPlug.node.xmlValidator', () => { +suite('internals: OpPlug.node.xmlValidator auto', () => { const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] const validXML = ` ` @@ -36,13 +37,11 @@ suite('internals: OpPlug.node.xmlValidator', () => { ` // not closed if (makeValidator.fails) { - test('call should fail/throw', () => { - assert.rejects( - async () => { - await makeValidator(schemaPath) - }, + test('call should fail/throw', async () => { + await assert.rejects( + makeValidator(schemaPath), (err) => { - assert.ok(err instanceof Error) + assert.ok(err instanceof OptPlugError) assert.match(err.message, /no XmlValidator available/i) return true } diff --git a/tests/integration/Serialize.XmlSerialize.test.js b/tests/integration/Serialize.XmlSerialize.test.js index fe86276b5..9e70d877a 100644 --- a/tests/integration/Serialize.XmlSerialize.test.js +++ b/tests/integration/Serialize.XmlSerialize.test.js @@ -27,16 +27,17 @@ const { Models, Enums, Serialize: { XML: { Normalize: { Factory: XmlNormalizeFactory } }, - XmlSerializer + XmlSerializer, MissingOptionalDependencyError }, Spec, - Validation: { - MissingOptionalDependencyError, - XmlValidator - } + Validation } = require('../../') +const xmlStringify = require('../../dist.node/_optPlug.node/xmlStringify').default + describe('Serialize.XmlSerialize', function () { + const expectMissingDepError = xmlStringify.fails ?? false + this.timeout(60000); [ @@ -56,30 +57,33 @@ describe('Serialize.XmlSerialize', function () { delete this.bom }) + if (expectMissingDepError) { + it('throws MissingOptionalDependencyError', function () { + const serializer = new XmlSerializer(normalizerFactory) + assert.throws( + () => { serializer.serialize(this.bom, {}) }, + (err) => err instanceof MissingOptionalDependencyError + ) + }) + return // skip other tests + } + it('serialize', async function () { const serializer = new XmlSerializer(normalizerFactory) + const serialized = await serializer.serialize( + this.bom, { + sortLists: true, + space: 4 + }) - let serialized - try { - serialized = serializer.serialize( - this.bom, { - sortLists: true, - space: 4 - }) - } catch (err) { - assert.ok(err instanceof Error) - assert.match(err.message, /no XmlStringifier available\./i) - return // skipped - } - - const validator = new XmlValidator(spec.version) + const validator = new Validation.XmlValidator(spec.version) try { const validationError = await validator.validate(serialized) assert.strictEqual(validationError, null) } catch (err) { - if (!(err instanceof MissingOptionalDependencyError)) { + if (!(err instanceof Validation.MissingOptionalDependencyError)) { // unexpected error - assert.fail(err) + throw err } } diff --git a/tests/integration/Validation.JsonStrictValidator.test.js b/tests/integration/Validation.JsonStrictValidator.test.js index a7c06ee64..c8db5c469 100644 --- a/tests/integration/Validation.JsonStrictValidator.test.js +++ b/tests/integration/Validation.JsonStrictValidator.test.js @@ -18,14 +18,8 @@ Copyright (c) OWASP Foundation. All Rights Reserved. */ const assert = require('assert') -const { describe, it } = require('mocha') -let hasDep = true -try { - require('ajv') -} catch { - hasDep = false -} +const { describe, it } = require('mocha') const { Spec: { Version }, @@ -35,7 +29,11 @@ const { } } = require('../../') +const jsonValidator = require('../../dist.node/_optPlug.node/jsonValidator').default + describe('Validation.JsonStrictValidator', () => { + const expectMissingDepError = jsonValidator.fails ?? false; + [ Version.v1dot0, Version.v1dot1 @@ -56,7 +54,7 @@ describe('Validation.JsonStrictValidator', () => { Version.v1dot3, Version.v1dot2 ].forEach((version) => describe(version, () => { - if (!hasDep) { + if (expectMissingDepError) { it('throws MissingOptionalDependencyError', async () => { const validator = new JsonStrictValidator(version) await assert.rejects( diff --git a/tests/integration/Validation.JsonValidator.test.js b/tests/integration/Validation.JsonValidator.test.js index 0b417e570..d98838a26 100644 --- a/tests/integration/Validation.JsonValidator.test.js +++ b/tests/integration/Validation.JsonValidator.test.js @@ -18,14 +18,8 @@ Copyright (c) OWASP Foundation. All Rights Reserved. */ const assert = require('assert') -const { describe, it } = require('mocha') -let hasDep = true -try { - require('ajv') -} catch { - hasDep = false -} +const { describe, it } = require('mocha') const { Spec: { Version }, @@ -35,7 +29,11 @@ const { } } = require('../../') +const jsonValidator = require('../../dist.node/_optPlug.node/jsonValidator').default + describe('Validation.JsonValidator', () => { + const expectMissingDepError = jsonValidator.fails ?? false; + [ Version.v1dot0, Version.v1dot1 @@ -56,7 +54,7 @@ describe('Validation.JsonValidator', () => { Version.v1dot3, Version.v1dot2 ].forEach((version) => describe(version, () => { - if (!hasDep) { + if (expectMissingDepError) { it('throws MissingOptionalDependencyError', async () => { const validator = new JsonValidator(version) await assert.rejects( diff --git a/tests/integration/Validation.XmlValidator.test.js b/tests/integration/Validation.XmlValidator.test.js index dbed5d016..d40c62260 100644 --- a/tests/integration/Validation.XmlValidator.test.js +++ b/tests/integration/Validation.XmlValidator.test.js @@ -21,13 +21,6 @@ const assert = require('assert') const { describe, it } = require('mocha') -let hasDep = true -try { - require('libxmljs2') -} catch { - hasDep = false -} - const { Spec: { Version }, Validation: { @@ -36,7 +29,11 @@ const { } } = require('../../') +const xmlValidator = require('../../dist.node/_optPlug.node/xmlValidator').default + describe('Validation.XmlValidator', () => { + const expectMissingDepError = xmlValidator.fails ?? false; + [ // none so far ].forEach((version) => describe(version, () => { @@ -58,7 +55,7 @@ describe('Validation.XmlValidator', () => { Version.v1dot1, Version.v1dot0 ].forEach((version) => describe(version, () => { - if (!hasDep) { + if (expectMissingDepError) { it('throws MissingOptionalDependencyError', async () => { const validator = new XmlValidator(version) await assert.rejects( diff --git a/tests/unit/Models.Bom.spec.js b/tests/unit/Models.Bom.spec.js index 9b8072d2a..3d1efaa01 100644 --- a/tests/unit/Models.Bom.spec.js +++ b/tests/unit/Models.Bom.spec.js @@ -88,9 +88,7 @@ suite('Models.Bom', () => { const bom = new Bom() assert.notStrictEqual(bom.version, newVersion) assert.throws( - () => { - bom.version = newVersion - }, + () => { bom.version = newVersion }, /not PositiveInteger/i ) }) From a571fa6a2d5f8e5d6511e371857f1819b08d8644 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:04:03 +0200 Subject: [PATCH 20/45] tests Signed-off-by: Jan Kowalleck --- .../OpPlug.node.jsonValidator.spec.js | 47 ++++++++++--------- .../OpPlug.node.xmlStringify.spec.js | 23 ++++----- .../OpPlug.node.xmlValidator.spec.js | 45 +++++++++--------- 3 files changed, 59 insertions(+), 56 deletions(-) diff --git a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js index 75bf8ee97..4d8645b6a 100644 --- a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js @@ -28,15 +28,6 @@ const { default: makeValidator } = require('../../../dist.node/_optPlug.node/jso const { OptPlugError } = require('../../../dist.node/_optPlug.node/errors') suite('internals: OpPlug.node.jsonValidator auto', () => { - const schemaPath = Resources.FILES.CDX.JSON_SCHEMA[Version.v1dot6] - const schemaMap = { - 'http://cyclonedx.org/schema/spdx.SNAPSHOT.schema.json': Resources.FILES.SPDX.JSON_SCHEMA, - 'http://cyclonedx.org/schema/jsf-0.82.SNAPSHOT.schema.json': Resources.FILES.JSF.JSON_SCHEMA - } - const validJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"}' - const invalidJson = '{"bomFormat": "unexpected", "specVersion": "1.6"}' - const brokenJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"' // not closed - if (makeValidator.fails) { test('call should fail/throw', async () => { await assert.rejects( @@ -48,20 +39,30 @@ suite('internals: OpPlug.node.jsonValidator auto', () => { } ) }) - } else { - test('valid causes no validationError', async () => { - const validationError = (await makeValidator(schemaPath, schemaMap))(validJson) - assert.strictEqual(validationError, null) - }) - - test('invalid causes validationError', async () => { - const validationError = (await makeValidator(schemaPath, schemaMap))(invalidJson) - assert.notEqual(validationError, null) - }) + return + } - test('broken causes validationError', async () => { - const validator = await makeValidator(schemaPath, schemaMap) - assert.throws(() => { validator(brokenJson) }) - }) + const schemaPath = Resources.FILES.CDX.JSON_SCHEMA[Version.v1dot6] + const schemaMap = { + 'http://cyclonedx.org/schema/spdx.SNAPSHOT.schema.json': Resources.FILES.SPDX.JSON_SCHEMA, + 'http://cyclonedx.org/schema/jsf-0.82.SNAPSHOT.schema.json': Resources.FILES.JSF.JSON_SCHEMA } + const validJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"}' + const invalidJson = '{"bomFormat": "unexpected", "specVersion": "1.6"}' + const brokenJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"' // not closed + + test('valid causes no validationError', async () => { + const validationError = (await makeValidator(schemaPath, schemaMap))(validJson) + assert.strictEqual(validationError, null) + }) + + test('invalid causes validationError', async () => { + const validationError = (await makeValidator(schemaPath, schemaMap))(invalidJson) + assert.notEqual(validationError, null) + }) + + test('broken causes validationError', async () => { + const validator = await makeValidator(schemaPath, schemaMap) + assert.throws(() => { validator(brokenJson) }) + }) }) diff --git a/tests/functional/internals/OpPlug.node.xmlStringify.spec.js b/tests/functional/internals/OpPlug.node.xmlStringify.spec.js index 5cb3e99c1..d917b691e 100644 --- a/tests/functional/internals/OpPlug.node.xmlStringify.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlStringify.spec.js @@ -24,12 +24,6 @@ const { default: xmlStringify } = require('../../../dist.node/_optPlug.node/xmlS const { OptPlugError } = require('../../../dist.node/_optPlug.node/errors') suite('internals: OpPlug.node.xmlStringify auto', () => { - const dummyElem = Object.freeze({ - type: 'element', - name: 'foo' - }) - const dummyElemStringifiedRE = /|><\/foo>)/ - if (xmlStringify.fails) { test('call should fail/throw', () => { assert.throws( @@ -41,10 +35,17 @@ suite('internals: OpPlug.node.xmlStringify auto', () => { } ) }) - } else { - test('call should pass', () => { - const stringified = xmlStringify(dummyElem) - assert.match(stringified, dummyElemStringifiedRE) - }) + return } + + const dummyElem = Object.freeze({ + type: 'element', + name: 'foo' + }) + const dummyElemStringifiedRE = /|><\/foo>)/ + + test('call should pass', () => { + const stringified = xmlStringify(dummyElem) + assert.match(stringified, dummyElemStringifiedRE) + }) }) diff --git a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js index 3fbef8def..259d37f77 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js @@ -28,14 +28,6 @@ const { default: makeValidator } = require('../../../dist.node/_optPlug.node/xml const { OptPlugError } = require('../../../dist.node/_optPlug.node/errors') suite('internals: OpPlug.node.xmlValidator auto', () => { - const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] - const validXML = ` - ` - const invalidXML = ` - ` - const brokenXML = ` - ` // not closed - if (makeValidator.fails) { test('call should fail/throw', async () => { await assert.rejects( @@ -47,20 +39,29 @@ suite('internals: OpPlug.node.xmlValidator auto', () => { } ) }) - } else { - test('valid causes no validationError', async () => { - const validationError = (await makeValidator(schemaPath))(validXML) - assert.strictEqual(validationError, null) - }) + return + } - test('invalid causes validationError', async () => { - const validationError = (await makeValidator(schemaPath))(invalidXML) - assert.notEqual(validationError, null) - }) + const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] + const validXML = ` + ` + const invalidXML = ` + ` + const brokenXML = ` + ` // not closed - test('broken causes validationError', async () => { - const validator = await makeValidator(schemaPath) - assert.throws(() => { validator(brokenXML) }) - }) - } + test('valid causes no validationError', async () => { + const validationError = (await makeValidator(schemaPath))(validXML) + assert.strictEqual(validationError, null) + }) + + test('invalid causes validationError', async () => { + const validationError = (await makeValidator(schemaPath))(invalidXML) + assert.notEqual(validationError, null) + }) + + test('broken causes validationError', async () => { + const validator = await makeValidator(schemaPath) + assert.throws(() => { validator(brokenXML) }) + }) }) From a725e8fa17a99b27f08a1f4da611834ec433cb51 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:10:32 +0200 Subject: [PATCH 21/45] tests Signed-off-by: Jan Kowalleck --- tests/integration/Validation.JsonValidator.test.js | 1 + tests/integration/Validation.XmlValidator.test.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/Validation.JsonValidator.test.js b/tests/integration/Validation.JsonValidator.test.js index d98838a26..64922a7b4 100644 --- a/tests/integration/Validation.JsonValidator.test.js +++ b/tests/integration/Validation.JsonValidator.test.js @@ -35,6 +35,7 @@ describe('Validation.JsonValidator', () => { const expectMissingDepError = jsonValidator.fails ?? false; [ + 'somthing-unexpected', Version.v1dot0, Version.v1dot1 ].forEach((version) => describe(version, () => { diff --git a/tests/integration/Validation.XmlValidator.test.js b/tests/integration/Validation.XmlValidator.test.js index d40c62260..549bd165c 100644 --- a/tests/integration/Validation.XmlValidator.test.js +++ b/tests/integration/Validation.XmlValidator.test.js @@ -35,7 +35,7 @@ describe('Validation.XmlValidator', () => { const expectMissingDepError = xmlValidator.fails ?? false; [ - // none so far + 'somthing-unexpected' ].forEach((version) => describe(version, () => { it('throws not implemented', async () => { const validator = new XmlValidator(version) From 640f3c721c4ed8a4168181e31897f01e990e3721 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:18:53 +0200 Subject: [PATCH 22/45] ci Signed-off-by: Jan Kowalleck --- .github/workflows/nodejs.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index ca8b415e9..ffb81e568 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -152,12 +152,14 @@ jobs: # do not use caching, use fresh version always, since some deps are not pinned -- since this is a library. - name: setup project shell: bash + env: + NODE_VERSION: '${{ matrix.node-version }}' run: | set -ex dep_constraints='' dev_requirements='c8 mocha npm-run-all fast-glob' # as long as npm cannot auto-resolve engine-constraints, we need to help here - case '${{ matrix.node-version }}' in + case "$NODE_VERSION" in '16') # for some stupid reason, NPM tries to resolve dev-packages, event hey are to be omitted. # this is frustrating when NPM is not resolving to compatible versions ...so drop them here @@ -184,7 +186,16 @@ jobs: name: dist.node path: dist.node - name: test - run: npm run test:node --forbid-pending + shell: bash + env: + NODE_VERSION: '${{ matrix.node-version }}' + run: + if [[ "$NODE_VERSION" = "$NODE_ACTIVE_LTS" ]] + then + npm run -- test:node --forbid-pending + else + npm run -- test:node + fi - name: collect coverage if: ${{ failure() || success() }} run: > From ab3972abb0fa25575d1ebb89c64b0a2b62ad001e Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:21:48 +0200 Subject: [PATCH 23/45] ci Signed-off-by: Jan Kowalleck --- .github/workflows/nodejs.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index ffb81e568..d1d266959 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -190,12 +190,13 @@ jobs: env: NODE_VERSION: '${{ matrix.node-version }}' run: + test_opts='' if [[ "$NODE_VERSION" = "$NODE_ACTIVE_LTS" ]] then - npm run -- test:node --forbid-pending + echo 'forbid pending tests!' >&2 + test_opts+=' --forbid-pending' else - npm run -- test:node - fi + npm run -- test:node $test_opts - name: collect coverage if: ${{ failure() || success() }} run: > From a6b6febcb0afc54830513ec2bfafcd5b452693eb Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:24:11 +0200 Subject: [PATCH 24/45] ci Signed-off-by: Jan Kowalleck --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index d1d266959..800c307af 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -189,7 +189,7 @@ jobs: shell: bash env: NODE_VERSION: '${{ matrix.node-version }}' - run: + run: | test_opts='' if [[ "$NODE_VERSION" = "$NODE_ACTIVE_LTS" ]] then From e4631211b010190c0901a9f6363a2c4e11cb4c4e Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:25:43 +0200 Subject: [PATCH 25/45] dings Signed-off-by: Jan Kowalleck --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e315b7723..02d4ca0d1 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "eslint-plugin-simple-import-sort": "12.1.0", "eslint-plugin-tsdoc": "0.2.17", "fast-glob": "^3.3.1", - "mocha": "^10.4.0", + "mocha": "10.4.0", "npm-run-all2": "^6.1.2", "rimraf": "^5.0.7", "ts-loader": "9.5.1", From 87116f994ca9cb334879f7bb3a32f54985602450 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:36:43 +0200 Subject: [PATCH 26/45] tidy Signed-off-by: Jan Kowalleck --- tests/functional/Validation.JsonValidator.node.spec.js | 2 +- tests/functional/Validation.XmlValidator.node.spec.js | 2 +- tests/integration/Serialize.XmlSerialize.test.js | 2 +- tests/integration/Validation.JsonStrictValidator.test.js | 2 +- tests/integration/Validation.JsonValidator.test.js | 2 +- tests/integration/Validation.XmlValidator.test.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/functional/Validation.JsonValidator.node.spec.js b/tests/functional/Validation.JsonValidator.node.spec.js index 7bbf05ce4..fb8e18036 100644 --- a/tests/functional/Validation.JsonValidator.node.spec.js +++ b/tests/functional/Validation.JsonValidator.node.spec.js @@ -29,7 +29,7 @@ const { } = require('../../') before(function () { - const jsonValidator = require('../../dist.node/_optPlug.node/jsonValidator').default + const { default: jsonValidator } = require('../../dist.node/_optPlug.node/jsonValidator') if (jsonValidator.fails) { this.skip() } diff --git a/tests/functional/Validation.XmlValidator.node.spec.js b/tests/functional/Validation.XmlValidator.node.spec.js index 1ceed3b2f..7e6f05888 100644 --- a/tests/functional/Validation.XmlValidator.node.spec.js +++ b/tests/functional/Validation.XmlValidator.node.spec.js @@ -29,7 +29,7 @@ const { } = require('../../') before(function () { - const xmlValidator = require('../../dist.node/_optPlug.node/xmlValidator').default + const { default: xmlValidator } = require('../../dist.node/_optPlug.node/xmlValidator') if (xmlValidator.fails) { this.skip() } diff --git a/tests/integration/Serialize.XmlSerialize.test.js b/tests/integration/Serialize.XmlSerialize.test.js index 9e70d877a..bc8964d28 100644 --- a/tests/integration/Serialize.XmlSerialize.test.js +++ b/tests/integration/Serialize.XmlSerialize.test.js @@ -33,7 +33,7 @@ const { Validation } = require('../../') -const xmlStringify = require('../../dist.node/_optPlug.node/xmlStringify').default +const { default: xmlStringify } = require('../../dist.node/_optPlug.node/xmlStringify') describe('Serialize.XmlSerialize', function () { const expectMissingDepError = xmlStringify.fails ?? false diff --git a/tests/integration/Validation.JsonStrictValidator.test.js b/tests/integration/Validation.JsonStrictValidator.test.js index c8db5c469..83ab389d0 100644 --- a/tests/integration/Validation.JsonStrictValidator.test.js +++ b/tests/integration/Validation.JsonStrictValidator.test.js @@ -29,7 +29,7 @@ const { } } = require('../../') -const jsonValidator = require('../../dist.node/_optPlug.node/jsonValidator').default +const { default: jsonValidator } = require('../../dist.node/_optPlug.node/jsonValidator') describe('Validation.JsonStrictValidator', () => { const expectMissingDepError = jsonValidator.fails ?? false; diff --git a/tests/integration/Validation.JsonValidator.test.js b/tests/integration/Validation.JsonValidator.test.js index 64922a7b4..2874345a8 100644 --- a/tests/integration/Validation.JsonValidator.test.js +++ b/tests/integration/Validation.JsonValidator.test.js @@ -29,7 +29,7 @@ const { } } = require('../../') -const jsonValidator = require('../../dist.node/_optPlug.node/jsonValidator').default +const { default: jsonValidator } = require('../../dist.node/_optPlug.node/jsonValidator') describe('Validation.JsonValidator', () => { const expectMissingDepError = jsonValidator.fails ?? false; diff --git a/tests/integration/Validation.XmlValidator.test.js b/tests/integration/Validation.XmlValidator.test.js index 549bd165c..74a146fc4 100644 --- a/tests/integration/Validation.XmlValidator.test.js +++ b/tests/integration/Validation.XmlValidator.test.js @@ -29,7 +29,7 @@ const { } } = require('../../') -const xmlValidator = require('../../dist.node/_optPlug.node/xmlValidator').default +const { default: xmlValidator } = require('../../dist.node/_optPlug.node/xmlValidator') describe('Validation.XmlValidator', () => { const expectMissingDepError = xmlValidator.fails ?? false; From e10e52c393a3dce132b33778fad54567260de164 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:39:07 +0200 Subject: [PATCH 27/45] ci Signed-off-by: Jan Kowalleck --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 800c307af..558a35614 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -195,7 +195,7 @@ jobs: then echo 'forbid pending tests!' >&2 test_opts+=' --forbid-pending' - else + fi npm run -- test:node $test_opts - name: collect coverage if: ${{ failure() || success() }} From e0103c4bab23d471528bc569d899aaed7ec59a11 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:42:34 +0200 Subject: [PATCH 28/45] ci Signed-off-by: Jan Kowalleck --- .github/workflows/nodejs.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 558a35614..9a0f10f34 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -186,17 +186,9 @@ jobs: name: dist.node path: dist.node - name: test - shell: bash - env: - NODE_VERSION: '${{ matrix.node-version }}' - run: | - test_opts='' - if [[ "$NODE_VERSION" = "$NODE_ACTIVE_LTS" ]] - then - echo 'forbid pending tests!' >&2 - test_opts+=' --forbid-pending' - fi - npm run -- test:node $test_opts + run: > + npm run -- test:node + ${{ matrix.node-version == env.NODE_ACTIVE_LTS && ' --forbid-pending' || '' }} - name: collect coverage if: ${{ failure() || success() }} run: > From 441bdc9f2de16f2d1bb1dc71a43f542ae1fe5965 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:44:29 +0200 Subject: [PATCH 29/45] ci Signed-off-by: Jan Kowalleck --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 9a0f10f34..d02065d44 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -188,7 +188,7 @@ jobs: - name: test run: > npm run -- test:node - ${{ matrix.node-version == env.NODE_ACTIVE_LTS && ' --forbid-pending' || '' }} + ${{ startsWith(matrix.node-version, env.NODE_ACTIVE_LTS) && ' --forbid-pending' || '' }} - name: collect coverage if: ${{ failure() || success() }} run: > From fed8af0e51af2985dfd8b15a1b01721095f9084e Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:51:04 +0200 Subject: [PATCH 30/45] tests Signed-off-by: Jan Kowalleck --- .../internals/OpPlug.node.jsonValidator.spec.js | 13 +++++++------ .../internals/OpPlug.node.xmlValidator.spec.js | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js index 4d8645b6a..753a049b3 100644 --- a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js @@ -28,10 +28,16 @@ const { default: makeValidator } = require('../../../dist.node/_optPlug.node/jso const { OptPlugError } = require('../../../dist.node/_optPlug.node/errors') suite('internals: OpPlug.node.jsonValidator auto', () => { + const schemaPath = Resources.FILES.CDX.JSON_SCHEMA[Version.v1dot6] + const schemaMap = { + 'http://cyclonedx.org/schema/spdx.SNAPSHOT.schema.json': Resources.FILES.SPDX.JSON_SCHEMA, + 'http://cyclonedx.org/schema/jsf-0.82.SNAPSHOT.schema.json': Resources.FILES.JSF.JSON_SCHEMA + } + if (makeValidator.fails) { test('call should fail/throw', async () => { await assert.rejects( - makeValidator(schemaPath), + makeValidator(schemaPath, schemaMap), (err) => { assert.ok(err instanceof OptPlugError) assert.match(err.message, /no JsonValidator available/i) @@ -42,11 +48,6 @@ suite('internals: OpPlug.node.jsonValidator auto', () => { return } - const schemaPath = Resources.FILES.CDX.JSON_SCHEMA[Version.v1dot6] - const schemaMap = { - 'http://cyclonedx.org/schema/spdx.SNAPSHOT.schema.json': Resources.FILES.SPDX.JSON_SCHEMA, - 'http://cyclonedx.org/schema/jsf-0.82.SNAPSHOT.schema.json': Resources.FILES.JSF.JSON_SCHEMA - } const validJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"}' const invalidJson = '{"bomFormat": "unexpected", "specVersion": "1.6"}' const brokenJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"' // not closed diff --git a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js index 259d37f77..a60ca2ffc 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js @@ -28,6 +28,8 @@ const { default: makeValidator } = require('../../../dist.node/_optPlug.node/xml const { OptPlugError } = require('../../../dist.node/_optPlug.node/errors') suite('internals: OpPlug.node.xmlValidator auto', () => { + const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] + if (makeValidator.fails) { test('call should fail/throw', async () => { await assert.rejects( @@ -42,7 +44,6 @@ suite('internals: OpPlug.node.xmlValidator auto', () => { return } - const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] const validXML = ` ` const invalidXML = ` From 6f0b285f4990ac71708d760cef098615dc3ed3ce Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:52:35 +0200 Subject: [PATCH 31/45] tests Signed-off-by: Jan Kowalleck --- .../internals/OpPlug.node.jsonValidator.spec.js | 13 ++++++------- .../internals/OpPlug.node.xmlValidator.spec.js | 5 ++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js index 753a049b3..3a70212da 100644 --- a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js @@ -28,16 +28,10 @@ const { default: makeValidator } = require('../../../dist.node/_optPlug.node/jso const { OptPlugError } = require('../../../dist.node/_optPlug.node/errors') suite('internals: OpPlug.node.jsonValidator auto', () => { - const schemaPath = Resources.FILES.CDX.JSON_SCHEMA[Version.v1dot6] - const schemaMap = { - 'http://cyclonedx.org/schema/spdx.SNAPSHOT.schema.json': Resources.FILES.SPDX.JSON_SCHEMA, - 'http://cyclonedx.org/schema/jsf-0.82.SNAPSHOT.schema.json': Resources.FILES.JSF.JSON_SCHEMA - } - if (makeValidator.fails) { test('call should fail/throw', async () => { await assert.rejects( - makeValidator(schemaPath, schemaMap), + makeValidator(), (err) => { assert.ok(err instanceof OptPlugError) assert.match(err.message, /no JsonValidator available/i) @@ -48,6 +42,11 @@ suite('internals: OpPlug.node.jsonValidator auto', () => { return } + const schemaPath = Resources.FILES.CDX.JSON_SCHEMA[Version.v1dot6] + const schemaMap = { + 'http://cyclonedx.org/schema/spdx.SNAPSHOT.schema.json': Resources.FILES.SPDX.JSON_SCHEMA, + 'http://cyclonedx.org/schema/jsf-0.82.SNAPSHOT.schema.json': Resources.FILES.JSF.JSON_SCHEMA + } const validJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"}' const invalidJson = '{"bomFormat": "unexpected", "specVersion": "1.6"}' const brokenJson = '{"bomFormat": "CycloneDX", "specVersion": "1.6"' // not closed diff --git a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js index a60ca2ffc..d74a5ec99 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js @@ -28,12 +28,10 @@ const { default: makeValidator } = require('../../../dist.node/_optPlug.node/xml const { OptPlugError } = require('../../../dist.node/_optPlug.node/errors') suite('internals: OpPlug.node.xmlValidator auto', () => { - const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] - if (makeValidator.fails) { test('call should fail/throw', async () => { await assert.rejects( - makeValidator(schemaPath), + makeValidator(), (err) => { assert.ok(err instanceof OptPlugError) assert.match(err.message, /no XmlValidator available/i) @@ -44,6 +42,7 @@ suite('internals: OpPlug.node.xmlValidator auto', () => { return } + const schemaPath = Resources.FILES.CDX.XML_SCHEMA[Version.v1dot6] const validXML = ` ` const invalidXML = ` From ee7c22e4959de64a663e1206f203d3b2381f3dd9 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 19:53:02 +0200 Subject: [PATCH 32/45] tests Signed-off-by: Jan Kowalleck --- tests/functional/internals/OpPlug.node.xmlStringify.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/internals/OpPlug.node.xmlStringify.spec.js b/tests/functional/internals/OpPlug.node.xmlStringify.spec.js index d917b691e..817defc39 100644 --- a/tests/functional/internals/OpPlug.node.xmlStringify.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlStringify.spec.js @@ -27,7 +27,7 @@ suite('internals: OpPlug.node.xmlStringify auto', () => { if (xmlStringify.fails) { test('call should fail/throw', () => { assert.throws( - () => { xmlStringify(dummyElem) }, + () => { xmlStringify() }, (err) => { assert.ok(err instanceof OptPlugError) assert.match(err.message, /no XmlStringifier available/i) From c08e1c538b19705fc11f6298663a01d43be5728b Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 21:30:47 +0200 Subject: [PATCH 33/45] fix Signed-off-by: Jan Kowalleck --- src/serialize/index.common.ts | 1 + .../functional/internals/OpPlug.node.jsonValidator.spec.js | 6 +++--- tests/functional/internals/OpPlug.node.xmlValidator.spec.js | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/serialize/index.common.ts b/src/serialize/index.common.ts index 1b26c1de0..69799debd 100644 --- a/src/serialize/index.common.ts +++ b/src/serialize/index.common.ts @@ -20,6 +20,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved. // !!! not everything is public, yet export * from './bomRefDiscriminator' +export * from './errors' export * as Types from './types' // region base diff --git a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js index 3a70212da..9175e9351 100644 --- a/tests/functional/internals/OpPlug.node.jsonValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.jsonValidator.spec.js @@ -29,9 +29,9 @@ const { OptPlugError } = require('../../../dist.node/_optPlug.node/errors') suite('internals: OpPlug.node.jsonValidator auto', () => { if (makeValidator.fails) { - test('call should fail/throw', async () => { - await assert.rejects( - makeValidator(), + test('call should fail/throw', () => { + assert.throws( + () => { makeValidator() }, (err) => { assert.ok(err instanceof OptPlugError) assert.match(err.message, /no JsonValidator available/i) diff --git a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js index d74a5ec99..ec5fd8115 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidator.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.spec.js @@ -29,9 +29,9 @@ const { OptPlugError } = require('../../../dist.node/_optPlug.node/errors') suite('internals: OpPlug.node.xmlValidator auto', () => { if (makeValidator.fails) { - test('call should fail/throw', async () => { - await assert.rejects( - makeValidator(), + test('call should fail/throw', () => { + assert.throws( + () => { makeValidator() }, (err) => { assert.ok(err instanceof OptPlugError) assert.match(err.message, /no XmlValidator available/i) From c1136d3a4e9bbb0d9897aa5ed9215b1bc9460616 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 23:12:40 +0200 Subject: [PATCH 34/45] fix Signed-off-by: Jan Kowalleck --- src/validation/jsonValidator.node.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/validation/jsonValidator.node.ts b/src/validation/jsonValidator.node.ts index 16b57c611..106afff1f 100644 --- a/src/validation/jsonValidator.node.ts +++ b/src/validation/jsonValidator.node.ts @@ -25,6 +25,7 @@ import { MissingOptionalDependencyError, NotImplementedError } from './errors' import type { ValidationError } from './types' abstract class BaseJsonValidator extends BaseValidator { + /** @internal */ protected abstract _getSchemaFile (): string | undefined #getSchemaFilePath (): string { From 2ed07abd1c39fad7b9fd4c2e415bf78fec764b59 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 23:39:22 +0200 Subject: [PATCH 35/45] types Signed-off-by: Jan Kowalleck --- src/_optPlug.node/_wrapper.ts | 11 +++++------ src/_optPlug.node/jsonValidator.ts | 4 ++-- src/_optPlug.node/xmlStringify.ts | 4 ++-- src/_optPlug.node/xmlValidator.ts | 4 ++-- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/_optPlug.node/_wrapper.ts b/src/_optPlug.node/_wrapper.ts index fc7cd6e7a..de7856fba 100644 --- a/src/_optPlug.node/_wrapper.ts +++ b/src/_optPlug.node/_wrapper.ts @@ -19,9 +19,10 @@ Copyright (c) OWASP Foundation. All Rights Reserved. import { OptPlugError } from './errors' -type WillThrow = (() => never) & { fails: true } +export type WillThrow = (() => never) & { fails: true } + type WillNotFailRightAway = Omit -type PossibleFunctionalities = Array<[string, () => Functionality | undefined]> +type PossibleFunctionalities = Array<[string, () => Functionality]> function makeWIllThrow (message: string): WillThrow { const f: WillThrow = function (): never { @@ -31,16 +32,14 @@ function makeWIllThrow (message: string): WillThrow { return Object.freeze(f) } +/** @internal */ export default function > ( name: string, pf: PossibleFunctionalities ): Functionality | WillThrow { for (const [, getF] of pf) { try { - const f = getF() - if (f !== undefined) { - return f - } + return getF() } catch { /* pass */ } diff --git a/src/_optPlug.node/jsonValidator.ts b/src/_optPlug.node/jsonValidator.ts index a73ddb418..7117ef1d4 100644 --- a/src/_optPlug.node/jsonValidator.ts +++ b/src/_optPlug.node/jsonValidator.ts @@ -18,7 +18,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved. */ import type { ValidationError } from '../validation/types' -import opWrapper from './_wrapper' +import opWrapper, { type WillThrow } from './_wrapper' export type Validator = (data: string) => null | ValidationError export type Functionality = (schemaPath: string, schemaMap: Record) => Promise @@ -28,4 +28,4 @@ export default opWrapper('JsonValidator', [ ['( ajv && ajv-formats && ajv-formats-draft2019 )', () => require('./__jsonValidators/ajv').default] // ... add others here, pull-requests welcome! /* eslint-enable @typescript-eslint/no-var-requires */ -]) +]) satisfies Functionality | WillThrow diff --git a/src/_optPlug.node/xmlStringify.ts b/src/_optPlug.node/xmlStringify.ts index 2c77335da..a8595aff8 100644 --- a/src/_optPlug.node/xmlStringify.ts +++ b/src/_optPlug.node/xmlStringify.ts @@ -19,7 +19,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved. import type { SerializerOptions } from '../serialize/types' import type { SimpleXml } from '../serialize/xml/types' -import opWrapper from './_wrapper' +import opWrapper, { type WillThrow } from './_wrapper' export type Functionality = (element: SimpleXml.Element, options?: SerializerOptions) => string @@ -28,4 +28,4 @@ export default opWrapper('XmlStringifier', [ ['xmlbuilder2', () => require('./__xmlStringifiers/xmlbuilder2').default] // ... add others here, pull-requests welcome! /* eslint-enable @typescript-eslint/no-var-requires */ -]) +]) satisfies Functionality | WillThrow diff --git a/src/_optPlug.node/xmlValidator.ts b/src/_optPlug.node/xmlValidator.ts index be2f98785..da4d6cfd4 100644 --- a/src/_optPlug.node/xmlValidator.ts +++ b/src/_optPlug.node/xmlValidator.ts @@ -18,7 +18,7 @@ Copyright (c) OWASP Foundation. All Rights Reserved. */ import type { ValidationError } from '../validation/types' -import opWrapper from './_wrapper' +import opWrapper, { type WillThrow } from './_wrapper' export type Validator = (data: string) => null | ValidationError export type Functionality = (schemaPath: string) => Promise @@ -28,4 +28,4 @@ export default opWrapper('XmlValidator', [ ['libxmljs2', () => require('./__xmlValidators/libxmljs2').default] // ... add others here, pull-requests welcome! /* eslint-enable @typescript-eslint/no-var-requires */ -]) +]) satisfies Functionality | WillThrow From e8671919914145ba61a36a582351356b41457410 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 5 Jun 2024 23:54:06 +0200 Subject: [PATCH 36/45] docs Signed-off-by: Jan Kowalleck --- HISTORY.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 680225255..918728272 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,6 +6,14 @@ All notable changes to this project will be documented in this file. +* Misc + * Refactored functionality around optional/pluggable dependencies (via [#1083], [#1084]) + This was done in preparation for [#1079] + +[#1079]: https://github.com/CycloneDX/cyclonedx-javascript-library/issues/1079 +[#1083]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/1083 +[#1084]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/1084 + ## 6.9.5 -- 2024-05-23 Maintenance release. From 7ba6c8098dc5e53d7b980859560d8828ccf50636 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 6 Jun 2024 00:13:50 +0200 Subject: [PATCH 37/45] docs Signed-off-by: Jan Kowalleck --- HISTORY.md | 5 +++++ src/serialize/types.ts | 1 + src/validation/types.ts | 1 + 3 files changed, 7 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 918728272..6b13ea739 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,6 +6,11 @@ All notable changes to this project will be documented in this file. +* Changed + * Exising `Serialize.XmlSerializer.serialize()` for _Node.js_ may throw `Serialize.MissingOptionalDependencyError` (via [#1084]) + This is considered a non-breaking change, as the docs always told that any `Error` may be thrown. +* Added + * New class `Serialize.MissingOptionalDependencyError` (via [#1084]) * Misc * Refactored functionality around optional/pluggable dependencies (via [#1083], [#1084]) This was done in preparation for [#1079] diff --git a/src/serialize/types.ts b/src/serialize/types.ts index 242e276fb..7864bab5f 100644 --- a/src/serialize/types.ts +++ b/src/serialize/types.ts @@ -35,6 +35,7 @@ export interface SerializerOptions { export interface Serializer { /** + * @throws {@link Serialize.MissingOptionalDependencyError | MissingOptionalDependencyError}, when a required dependency was not installed * @throws {@link Error} */ serialize: (bom: Bom, options?: SerializerOptions & NormalizerOptions) => string diff --git a/src/validation/types.ts b/src/validation/types.ts index 52ba18d64..df66bd75e 100644 --- a/src/validation/types.ts +++ b/src/validation/types.ts @@ -31,6 +31,7 @@ export interface Validator { * Promise rejects with one of the following: * - {@link Validation.NotImplementedError | NotImplementedError}, when there is no validator available * - {@link Validation.MissingOptionalDependencyError | MissingOptionalDependencyError}, when a required dependency was not installed + * - {@link Error} */ validate: (data: string) => Promise } From 4d8ead53400398010a24ee1c34bab488a7452af6 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 6 Jun 2024 00:21:10 +0200 Subject: [PATCH 38/45] docs Signed-off-by: Jan Kowalleck --- src/serialize/baseSerializer.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/serialize/baseSerializer.ts b/src/serialize/baseSerializer.ts index 0623bc6dd..93838c0af 100644 --- a/src/serialize/baseSerializer.ts +++ b/src/serialize/baseSerializer.ts @@ -58,10 +58,7 @@ export abstract class BaseSerializer implements Serializer { } } - /** - * @readonly - * @throws {@link Error} - */ + /** {@inheritDoc Serialize.Types.Validator.validate} */ public serialize (bom: Bom, options?: SerializerOptions & NormalizerOptions): string { return this._serialize( this.#normalize(bom, options), From 63665566e893301715a6ef20349bc34cceb00211 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 6 Jun 2024 00:21:27 +0200 Subject: [PATCH 39/45] docs Signed-off-by: Jan Kowalleck --- src/serialize/baseSerializer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serialize/baseSerializer.ts b/src/serialize/baseSerializer.ts index 93838c0af..8816c1343 100644 --- a/src/serialize/baseSerializer.ts +++ b/src/serialize/baseSerializer.ts @@ -58,7 +58,7 @@ export abstract class BaseSerializer implements Serializer { } } - /** {@inheritDoc Serialize.Types.Validator.validate} */ + /** {@inheritDoc Serialize.Types.Serializer.serialize} */ public serialize (bom: Bom, options?: SerializerOptions & NormalizerOptions): string { return this._serialize( this.#normalize(bom, options), From 0d71a47ec28e5a2273eff74a008495f2d8f29aa6 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 6 Jun 2024 00:39:24 +0200 Subject: [PATCH 40/45] types Signed-off-by: Jan Kowalleck --- src/serialize/baseSerializer.ts | 1 - src/serialize/jsonSerializer.ts | 2 +- src/serialize/xmlBaseSerializer.ts | 2 +- src/validation/baseValidator.ts | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/serialize/baseSerializer.ts b/src/serialize/baseSerializer.ts index 8816c1343..e60b441cc 100644 --- a/src/serialize/baseSerializer.ts +++ b/src/serialize/baseSerializer.ts @@ -58,7 +58,6 @@ export abstract class BaseSerializer implements Serializer { } } - /** {@inheritDoc Serialize.Types.Serializer.serialize} */ public serialize (bom: Bom, options?: SerializerOptions & NormalizerOptions): string { return this._serialize( this.#normalize(bom, options), diff --git a/src/serialize/jsonSerializer.ts b/src/serialize/jsonSerializer.ts index ace47aae4..f85948d04 100644 --- a/src/serialize/jsonSerializer.ts +++ b/src/serialize/jsonSerializer.ts @@ -32,7 +32,7 @@ export class JsonSerializer extends BaseSerializer { readonly #normalizerFactory: NormalizerFactory /** - * @throws {@link UnsupportedFormatError} if `normalizerFactory.spec` does not support {@link Format.JSON}. + * @throws {@link Spec.UnsupportedFormatError | UnsupportedFormatError} if `normalizerFactory.spec` does not support {@link Format.JSON}. */ constructor (normalizerFactory: JsonSerializer['normalizerFactory']) { if (!normalizerFactory.spec.supportsFormat(Format.JSON)) { diff --git a/src/serialize/xmlBaseSerializer.ts b/src/serialize/xmlBaseSerializer.ts index b6c401f18..48938a63c 100644 --- a/src/serialize/xmlBaseSerializer.ts +++ b/src/serialize/xmlBaseSerializer.ts @@ -32,7 +32,7 @@ export abstract class XmlBaseSerializer extends BaseSerializer } From c3a7decd17099be3f34ac630ae99c973bac69303 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 6 Jun 2024 01:02:51 +0200 Subject: [PATCH 41/45] tidy Signed-off-by: Jan Kowalleck --- .eslintignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.eslintignore b/.eslintignore index 49a90a7ae..b0c92f705 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,8 +13,5 @@ !/src/** -!/tools/schema-downloader/** - - **/.idea/** **/.vscode/** From ec954984f79951b2fa8d0b9099198635091cabe9 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 6 Jun 2024 01:10:49 +0200 Subject: [PATCH 42/45] tidy Signed-off-by: Jan Kowalleck --- .eslintignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.eslintignore b/.eslintignore index b0c92f705..49a90a7ae 100644 --- a/.eslintignore +++ b/.eslintignore @@ -13,5 +13,8 @@ !/src/** +!/tools/schema-downloader/** + + **/.idea/** **/.vscode/** From 36cbb6d9e753baec592f757da949d9ae76d6a767 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 6 Jun 2024 10:59:40 +0200 Subject: [PATCH 43/45] tidy Signed-off-by: Jan Kowalleck --- src/_optPlug.node/_wrapper.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/_optPlug.node/_wrapper.ts b/src/_optPlug.node/_wrapper.ts index de7856fba..119076955 100644 --- a/src/_optPlug.node/_wrapper.ts +++ b/src/_optPlug.node/_wrapper.ts @@ -21,9 +21,6 @@ import { OptPlugError } from './errors' export type WillThrow = (() => never) & { fails: true } -type WillNotFailRightAway = Omit -type PossibleFunctionalities = Array<[string, () => Functionality]> - function makeWIllThrow (message: string): WillThrow { const f: WillThrow = function (): never { throw new OptPlugError(message) @@ -32,8 +29,10 @@ function makeWIllThrow (message: string): WillThrow { return Object.freeze(f) } +type PossibleFunctionalities = Array<[string, () => Functionality]> + /** @internal */ -export default function > ( +export default function ( name: string, pf: PossibleFunctionalities ): Functionality | WillThrow { From 528c0a2a7036b2dcc429d2df196439a1ad6cd127 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 6 Jun 2024 11:01:33 +0200 Subject: [PATCH 44/45] tidy Signed-off-by: Jan Kowalleck --- src/_optPlug.node/_wrapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_optPlug.node/_wrapper.ts b/src/_optPlug.node/_wrapper.ts index 119076955..40d457d11 100644 --- a/src/_optPlug.node/_wrapper.ts +++ b/src/_optPlug.node/_wrapper.ts @@ -45,7 +45,7 @@ export default function ( } return makeWIllThrow( `No ${name} available.` + - ' Please install the optional dependency groups: ' + + ' Please install one of the optional dependencies: ' + pf.map(kv => kv[0]).join(' || ') ) } From 359b5b59f8e473b90923a2395243cce41b18f26c Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Thu, 6 Jun 2024 11:02:49 +0200 Subject: [PATCH 45/45] tidy Signed-off-by: Jan Kowalleck --- src/_optPlug.node/_wrapper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_optPlug.node/_wrapper.ts b/src/_optPlug.node/_wrapper.ts index 40d457d11..b34359269 100644 --- a/src/_optPlug.node/_wrapper.ts +++ b/src/_optPlug.node/_wrapper.ts @@ -44,8 +44,8 @@ export default function ( } } return makeWIllThrow( - `No ${name} available.` + - ' Please install one of the optional dependencies: ' + + `No ${name} available.\n` + + 'Please install one of the optional dependencies: ' + pf.map(kv => kv[0]).join(' || ') ) }