From 16036094884a1b569756c43234d153762af37548 Mon Sep 17 00:00:00 2001 From: Leon Grave <l.grave@gmail.com> Date: Tue, 26 Nov 2024 11:41:30 +0100 Subject: [PATCH 1/8] Switch to libxml2-wasm for XML validation Due to libxmljs2 not being maintained and contains a vulnerability, a replacement needed to be found. This commit replaces it with libxml2-wasm, which is a new, but maintained library, which serves the purpose of validating XML. The implementation is as close the the previous library in regards to flags passed to libxml2, but only adapted to a different interface and the recommendation to dispose all objects. This is my first contribution to this project, and typescript isn't my usual language, so comments are welcome. Resolves: #1079 Signed-off-by: Leon Grave <l.grave@gmail.com> --- docs/dev/decisions/XmlValidator.md | 7 ++- package.json | 2 +- .../__xmlValidators/libxml2-wasm.ts | 53 +++++++++++++++++++ .../__xmlValidators/libxmljs2.ts | 48 ----------------- src/_optPlug.node/xmlValidator.ts | 2 +- 5 files changed, 61 insertions(+), 51 deletions(-) create mode 100644 src/_optPlug.node/__xmlValidators/libxml2-wasm.ts delete mode 100644 src/_optPlug.node/__xmlValidators/libxmljs2.ts diff --git a/docs/dev/decisions/XmlValidator.md b/docs/dev/decisions/XmlValidator.md index 7be0de95b..01a437953 100644 --- a/docs/dev/decisions/XmlValidator.md +++ b/docs/dev/decisions/XmlValidator.md @@ -22,7 +22,8 @@ There are several implementations for this: * [`libxmljs3`](https://www.npmjs.com/package/libxmljs3) * unmaintained copy of `libxmljs2` * ! DO NOT USE ! -* Any alternative? Please open a pull-request to add them. +* [`libxml2-wasm`](https://www.npmjs.com/package/libxml2-wasm) + * maintained WASM implementation of a libxml2 wrapper At the moment of writing (2023-04-21), `libxmljs` and `libxmljs2` are both working on several test environments. Both had the needed capabilities. @@ -38,6 +39,10 @@ as it was more popular/used and had a more active community. Decided to replace `libxmljs2`, as it is end of life. +#### 2024-11-26 + +Decided to replace `libxmljs2` with `libxml2-wasm`, since it's maintained and a functioning XML validator. + ## WebBrowsers there seams to exist no solution for validating XML according to XSD diff --git a/package.json b/package.json index 7887fa6b2..0075b9b54 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "ajv-formats-draft2019": "^1.6.1", - "libxmljs2": "^0.31 || ^0.32 || ^0.33 || ^0.35", + "libxml2-wasm": "^0.4.1", "xmlbuilder2": "^3.0.2" }, "devDependencies": { diff --git a/src/_optPlug.node/__xmlValidators/libxml2-wasm.ts b/src/_optPlug.node/__xmlValidators/libxml2-wasm.ts new file mode 100644 index 000000000..463858641 --- /dev/null +++ b/src/_optPlug.node/__xmlValidators/libxml2-wasm.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 { readFile } from 'fs/promises'; +import { ParseOption, XmlDocument, XsdValidator } from 'libxml2-wasm'; +import { pathToFileURL } from 'url'; + +import type { ValidationError } from '../../validation/types'; +import type { Functionality, Validator } from '../xmlValidator'; + +/** @internal */ +export default (async function (schemaPath: string): Promise<Validator> { + const schema = XmlDocument.fromString( + await readFile(schemaPath, 'utf-8'), + { + option: ParseOption.XML_PARSE_NONET | ParseOption.XML_PARSE_COMPACT, + url: pathToFileURL(schemaPath).toString() + }); + const validator = XsdValidator.fromDoc(schema); + + return function (data: string): null | ValidationError { + const doc = XmlDocument.fromString(data, { option: ParseOption.XML_PARSE_NONET | ParseOption.XML_PARSE_COMPACT }); + let errors = null; + try { + validator.validate(doc); + } + catch (validationErrors) { + errors = validationErrors; + } + + doc.dispose(); + validator.dispose(); + schema.dispose(); + + return errors; + } +}) satisfies Functionality diff --git a/src/_optPlug.node/__xmlValidators/libxmljs2.ts b/src/_optPlug.node/__xmlValidators/libxmljs2.ts deleted file mode 100644 index a72989722..000000000 --- a/src/_optPlug.node/__xmlValidators/libxmljs2.ts +++ /dev/null @@ -1,48 +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 { 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 '../xmlValidator' - -const xmlParseOptions: Readonly<ParserOptions> = Object.freeze({ - nonet: true, - 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 -}) - -/** @internal */ -export default (async function (schemaPath: string): Promise<Validator> { - 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) - ? null - : doc.validationErrors - } -}) satisfies Functionality diff --git a/src/_optPlug.node/xmlValidator.ts b/src/_optPlug.node/xmlValidator.ts index 67b92a2be..1f4aed70e 100644 --- a/src/_optPlug.node/xmlValidator.ts +++ b/src/_optPlug.node/xmlValidator.ts @@ -27,7 +27,7 @@ export default opWrapper<Functionality>('XmlValidator', [ /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-require-imports -- needed */ - ['libxmljs2', () => require('./__xmlValidators/libxmljs2').default] + ['libxml2-wasm', () => require('./__xmlValidators/libxml2-wasm').default] // ... add others here, pull-requests welcome! /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-require-imports */ From a4c45c150bbfb45a5b75fc40fa651b60b37595aa Mon Sep 17 00:00:00 2001 From: Leon Grave <l.grave@gmail.com> Date: Tue, 26 Nov 2024 12:54:23 +0100 Subject: [PATCH 2/8] Processed review comments Signed-off-by: Leon Grave <l.grave@gmail.com> --- HISTORY.md | 1 + package.json | 1 + src/_optPlug.node/xmlValidator.ts | 1 + .../internals/OpPlug.node.xmlValidator.implementation.spec.js | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index fe1e0e801..649435ad3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -18,6 +18,7 @@ All notable changes to this project will be documented in this file. * Serializers and `Bom`-Normalizers will take changed `Models.Bom.tools` into account ([#1152] via [#1163]) * Dependencies * Support `libxmljs2@^0.35` (via [#1173]) + * Support `libxml2-wasm@^0.41` as an alternative for `libxmljs2` (via [#1184]) * Use `packageurl-js@^2.0.1`, was `@>=0.0.6 <0.0.8 || ^1` (via [#1142]) [#1142]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/1142 diff --git a/package.json b/package.json index 0075b9b54..97f5ef7d5 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "ajv-formats-draft2019": "^1.6.1", + "libxmljs2": "^0.31 || ^0.32 || ^0.33 || ^0.35", "libxml2-wasm": "^0.4.1", "xmlbuilder2": "^3.0.2" }, diff --git a/src/_optPlug.node/xmlValidator.ts b/src/_optPlug.node/xmlValidator.ts index 1f4aed70e..6c2b9700f 100644 --- a/src/_optPlug.node/xmlValidator.ts +++ b/src/_optPlug.node/xmlValidator.ts @@ -27,6 +27,7 @@ export default opWrapper<Functionality>('XmlValidator', [ /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-require-imports -- needed */ + ['libxmljs2', () => require('./__xmlValidators/libxmljs2').default], ['libxml2-wasm', () => require('./__xmlValidators/libxml2-wasm').default] // ... add others here, pull-requests welcome! diff --git a/tests/functional/internals/OpPlug.node.xmlValidator.implementation.spec.js b/tests/functional/internals/OpPlug.node.xmlValidator.implementation.spec.js index ba6a08206..9137252a3 100644 --- a/tests/functional/internals/OpPlug.node.xmlValidator.implementation.spec.js +++ b/tests/functional/internals/OpPlug.node.xmlValidator.implementation.spec.js @@ -29,7 +29,7 @@ const { realpathSync } = require('fs') const { join } = require('path') suite('functional: internals: OpPlug.node.xmlValidator implementation', () => { - for (const impl of ['libxmljs2']) { + for (const impl of ['libxmljs2','libxml2-wasm']) { suite(impl, () => { let makeValidator try { From 818b1e5a4a5ad4e07c58b0bd2902e1ffaa54f32d Mon Sep 17 00:00:00 2001 From: Leon Grave <l.grave@gmail.com> Date: Tue, 26 Nov 2024 13:11:08 +0100 Subject: [PATCH 3/8] Revert file deletion, removed decision Signed-off-by: Leon Grave <l.grave@gmail.com> --- docs/dev/decisions/XmlValidator.md | 4 -- .../__xmlValidators/libxmljs2.ts | 44 +++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 src/_optPlug.node/__xmlValidators/libxmljs2.ts diff --git a/docs/dev/decisions/XmlValidator.md b/docs/dev/decisions/XmlValidator.md index 01a437953..4beee855f 100644 --- a/docs/dev/decisions/XmlValidator.md +++ b/docs/dev/decisions/XmlValidator.md @@ -39,10 +39,6 @@ as it was more popular/used and had a more active community. Decided to replace `libxmljs2`, as it is end of life. -#### 2024-11-26 - -Decided to replace `libxmljs2` with `libxml2-wasm`, since it's maintained and a functioning XML validator. - ## WebBrowsers there seams to exist no solution for validating XML according to XSD diff --git a/src/_optPlug.node/__xmlValidators/libxmljs2.ts b/src/_optPlug.node/__xmlValidators/libxmljs2.ts new file mode 100644 index 000000000..68a27873e --- /dev/null +++ b/src/_optPlug.node/__xmlValidators/libxmljs2.ts @@ -0,0 +1,44 @@ +/*! +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 { 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 '../xmlValidator' + +const xmlParseOptions: Readonly<ParserOptions> = Object.freeze({ + nonet: true, + 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 +}) + +/** @internal */ +export default (async function (schemaPath: string): Promise<Validator> { + 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) + ? null + : doc.validationErrors + } +}) satisfies Functionality From caa8cd2c5c3f8ba240b2bb39949235e71f1a7aa5 Mon Sep 17 00:00:00 2001 From: Leon Grave <l.grave@gmail.com> Date: Tue, 26 Nov 2024 13:13:11 +0100 Subject: [PATCH 4/8] Revert removal of line breaks in header Signed-off-by: Leon Grave <l.grave@gmail.com> --- src/_optPlug.node/__xmlValidators/libxmljs2.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/_optPlug.node/__xmlValidators/libxmljs2.ts b/src/_optPlug.node/__xmlValidators/libxmljs2.ts index 68a27873e..a72989722 100644 --- a/src/_optPlug.node/__xmlValidators/libxmljs2.ts +++ b/src/_optPlug.node/__xmlValidators/libxmljs2.ts @@ -1,14 +1,18 @@ /*! 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. */ From d329805d53b024b483334a70e103136383eb19ad Mon Sep 17 00:00:00 2001 From: Leon Grave <l.grave@gmail.com> Date: Tue, 26 Nov 2024 21:04:08 +0100 Subject: [PATCH 5/8] Review comments Signed-off-by: Leon Grave <l.grave@gmail.com> --- docs/dev/decisions/XmlValidator.md | 1 + src/_optPlug.node/__xmlValidators/libxml2-wasm.ts | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/dev/decisions/XmlValidator.md b/docs/dev/decisions/XmlValidator.md index 4beee855f..8a60cdc8f 100644 --- a/docs/dev/decisions/XmlValidator.md +++ b/docs/dev/decisions/XmlValidator.md @@ -24,6 +24,7 @@ There are several implementations for this: * ! DO NOT USE ! * [`libxml2-wasm`](https://www.npmjs.com/package/libxml2-wasm) * maintained WASM implementation of a libxml2 wrapper +* Any alternative? Please open a pull-request to add them. At the moment of writing (2023-04-21), `libxmljs` and `libxmljs2` are both working on several test environments. Both had the needed capabilities. diff --git a/src/_optPlug.node/__xmlValidators/libxml2-wasm.ts b/src/_optPlug.node/__xmlValidators/libxml2-wasm.ts index 463858641..12b57a1e4 100644 --- a/src/_optPlug.node/__xmlValidators/libxml2-wasm.ts +++ b/src/_optPlug.node/__xmlValidators/libxml2-wasm.ts @@ -26,16 +26,17 @@ import type { Functionality, Validator } from '../xmlValidator'; /** @internal */ export default (async function (schemaPath: string): Promise<Validator> { + const options = ParseOption.XML_PARSE_NONET | ParseOption.XML_PARSE_COMPACT; const schema = XmlDocument.fromString( await readFile(schemaPath, 'utf-8'), { - option: ParseOption.XML_PARSE_NONET | ParseOption.XML_PARSE_COMPACT, + option: options, url: pathToFileURL(schemaPath).toString() }); const validator = XsdValidator.fromDoc(schema); return function (data: string): null | ValidationError { - const doc = XmlDocument.fromString(data, { option: ParseOption.XML_PARSE_NONET | ParseOption.XML_PARSE_COMPACT }); + const doc = XmlDocument.fromString(data, { option: options }); let errors = null; try { validator.validate(doc); From 0e59ff4f2083bfdc7d34ce5fed562163fa3c200b Mon Sep 17 00:00:00 2001 From: Leon Grave <l.grave@gmail.com> Date: Fri, 10 Jan 2025 16:52:39 +0100 Subject: [PATCH 6/8] No disposing of schema and validator Signed-off-by: Leon Grave <l.grave@gmail.com> --- src/_optPlug.node/__xmlValidators/libxml2-wasm.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/_optPlug.node/__xmlValidators/libxml2-wasm.ts b/src/_optPlug.node/__xmlValidators/libxml2-wasm.ts index 12b57a1e4..5be0279ce 100644 --- a/src/_optPlug.node/__xmlValidators/libxml2-wasm.ts +++ b/src/_optPlug.node/__xmlValidators/libxml2-wasm.ts @@ -46,8 +46,6 @@ export default (async function (schemaPath: string): Promise<Validator> { } doc.dispose(); - validator.dispose(); - schema.dispose(); return errors; } From c5f922fa79f17a825cd614fcc0cf9398299776d6 Mon Sep 17 00:00:00 2001 From: Leon Grave <l.grave@gmail.com> Date: Fri, 10 Jan 2025 16:53:07 +0100 Subject: [PATCH 7/8] Fix documentation after 7.0.0 release Signed-off-by: Leon Grave <l.grave@gmail.com> --- HISTORY.md | 3 ++- README.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 34a16cdbe..c521bf814 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. ## unreleased <!-- add unreleased items here --> +* Dependencies + * Support `libxml2-wasm@^0.41` as an alternative for `libxmljs2` (via [#1184]) ## 7.0.0 -- 2024-11-26 @@ -22,7 +24,6 @@ All notable changes to this project will be documented in this file. * Apply latest code style guide (via [#1170], [#1181]) * Dependencies * Support `libxmljs2@^0.35` (via [#1173]) - * Support `libxml2-wasm@^0.41` as an alternative for `libxmljs2` (via [#1184]) * Use `packageurl-js@^2.0.1`, was `@>=0.0.6 <0.0.8 || ^1` (via [#1142]) * Build * Use _TypeScript_ `v5.7.2` now, was `v5.6.3` (via [#1182]) diff --git a/README.md b/README.md index 9fe0c2364..a2c351a78 100644 --- a/README.md +++ b/README.md @@ -126,8 +126,9 @@ See the shipped `package.json` for version constraints. * [`ajv`](https://www.npmjs.com/package/ajv) * [`ajv-formats`](https://www.npmjs.com/package/ajv-formats) * [`ajv-formats-draft2019`](https://www.npmjs.com/package/ajv-formats-draft2019) -* Validation of XML on _Node.js_ requires all of: +* Validation of XML on _Node.js_ requires any of: * [`libxmljs2`](https://www.npmjs.com/package/libxmljs2) + * [`libxml2-wasm@`](https://www.npmjs.com/package/libxml2-wasm@) * the system might need to meet the requirements for [`node-gyp`](https://github.com/TooTallNate/node-gyp#installation), in certain cases. ## Usage From ef3bd628a496f3604f715e5dd5e66d7316c2fe2c Mon Sep 17 00:00:00 2001 From: Leon Grave <l.grave@gmail.com> Date: Sun, 12 Jan 2025 09:29:47 +0100 Subject: [PATCH 8/8] Bump libxml2-wasm to 0.5.0 Signed-off-by: Leon Grave <l.grave@gmail.com> --- HISTORY.md | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 192fcc3e7..1e1054867 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,12 +6,13 @@ All notable changes to this project will be documented in this file. <!-- add unreleased items here --> * Dependencies - * Support `libxml2-wasm@^0.41` as an alternative for `libxmljs2` (via [#1184]) + * Support `libxml2-wasm@^0.5.0` as an alternative for `libxmljs2` (via [#1184]) * Build * Use _TypeScript_ `v5.7.3` now, was `v5.7.2` (via [#1204]) [#1204]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/1204 +[#1184]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/1184 ## 7.1.0 -- 2025-01-09 diff --git a/package.json b/package.json index 7380fa5c2..a3d197cc0 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "ajv-formats": "^3.0.1", "ajv-formats-draft2019": "^1.6.1", "libxmljs2": "^0.31 || ^0.32 || ^0.33 || ^0.35", - "libxml2-wasm": "^0.4.1", + "libxml2-wasm": "^0.5.0", "xmlbuilder2": "^3.0.2" }, "devDependencies": {