@cyclonedx/cyclonedx-library 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +5 -0
- package/README.md +152 -0
- package/dist.node/_index.node.js +53 -0
- package/dist.node/_index.node.js.map +1 -0
- package/dist.node/enums/attachmentEncoding.js +26 -0
- package/dist.node/enums/attachmentEncoding.js.map +1 -0
- package/dist.node/enums/componentScope.js +28 -0
- package/dist.node/enums/componentScope.js.map +1 -0
- package/dist.node/enums/componentType.js +33 -0
- package/dist.node/enums/componentType.js.map +1 -0
- package/dist.node/enums/externalReferenceType.js +41 -0
- package/dist.node/enums/externalReferenceType.js.map +1 -0
- package/dist.node/enums/hashAlogorithm.js +37 -0
- package/dist.node/enums/hashAlogorithm.js.map +1 -0
- package/dist.node/enums/index.js +40 -0
- package/dist.node/enums/index.js.map +1 -0
- package/dist.node/factories/index.js +36 -0
- package/dist.node/factories/index.js.map +1 -0
- package/dist.node/factories/licenseFactory.js +56 -0
- package/dist.node/factories/licenseFactory.js.map +1 -0
- package/dist.node/helpers/types.js +26 -0
- package/dist.node/helpers/types.js.map +1 -0
- package/dist.node/models/attachment.js +30 -0
- package/dist.node/models/attachment.js.map +1 -0
- package/dist.node/models/bom.js +67 -0
- package/dist.node/models/bom.js.map +1 -0
- package/dist.node/models/bomRef.js +37 -0
- package/dist.node/models/bomRef.js.map +1 -0
- package/dist.node/models/component.js +96 -0
- package/dist.node/models/component.js.map +1 -0
- package/dist.node/models/externalReference.js +40 -0
- package/dist.node/models/externalReference.js.map +1 -0
- package/dist.node/models/hash.js +29 -0
- package/dist.node/models/hash.js.map +1 -0
- package/dist.node/models/index.js +47 -0
- package/dist.node/models/index.js.map +1 -0
- package/dist.node/models/license.js +103 -0
- package/dist.node/models/license.js.map +1 -0
- package/dist.node/models/metadata.js +35 -0
- package/dist.node/models/metadata.js.map +1 -0
- package/dist.node/models/organizationalContact.js +41 -0
- package/dist.node/models/organizationalContact.js.map +1 -0
- package/dist.node/models/organizationalEntity.js +31 -0
- package/dist.node/models/organizationalEntity.js.map +1 -0
- package/dist.node/models/swid.js +58 -0
- package/dist.node/models/swid.js.map +1 -0
- package/dist.node/models/tool.js +45 -0
- package/dist.node/models/tool.js.map +1 -0
- package/dist.node/resources.node.js +55 -0
- package/dist.node/resources.node.js.map +1 -0
- package/dist.node/serialize/_index.node.js +37 -0
- package/dist.node/serialize/_index.node.js.map +1 -0
- package/dist.node/serialize/baseSerializer.js +56 -0
- package/dist.node/serialize/baseSerializer.js.map +1 -0
- package/dist.node/serialize/bomRefDiscriminator.js +66 -0
- package/dist.node/serialize/bomRefDiscriminator.js.map +1 -0
- package/dist.node/serialize/index.js +55 -0
- package/dist.node/serialize/index.js.map +1 -0
- package/dist.node/serialize/json/index.js +47 -0
- package/dist.node/serialize/json/index.js.map +1 -0
- package/dist.node/serialize/json/normalize.js +431 -0
- package/dist.node/serialize/json/normalize.js.map +1 -0
- package/dist.node/serialize/json/types.js +35 -0
- package/dist.node/serialize/json/types.js.map +1 -0
- package/dist.node/serialize/jsonSerializer.js +55 -0
- package/dist.node/serialize/jsonSerializer.js.map +1 -0
- package/dist.node/serialize/types.js +21 -0
- package/dist.node/serialize/types.js.map +1 -0
- package/dist.node/serialize/xml/index.js +47 -0
- package/dist.node/serialize/xml/index.js.map +1 -0
- package/dist.node/serialize/xml/normalize.js +560 -0
- package/dist.node/serialize/xml/normalize.js.map +1 -0
- package/dist.node/serialize/xml/types.js +31 -0
- package/dist.node/serialize/xml/types.js.map +1 -0
- package/dist.node/serialize/xmlBaseSerializer.js +52 -0
- package/dist.node/serialize/xmlBaseSerializer.js.map +1 -0
- package/dist.node/serialize/xmlSerializer.node.js +30 -0
- package/dist.node/serialize/xmlSerializer.node.js.map +1 -0
- package/dist.node/spdx.js +35 -0
- package/dist.node/spdx.js.map +1 -0
- package/dist.node/spec.js +229 -0
- package/dist.node/spec.js.map +1 -0
- package/dist.node/types/cpe.js +28 -0
- package/dist.node/types/cpe.js.map +1 -0
- package/dist.node/types/index.js +39 -0
- package/dist.node/types/index.js.map +1 -0
- package/dist.node/types/integer.js +36 -0
- package/dist.node/types/integer.js.map +1 -0
- package/dist.node/types/mimeType.js +28 -0
- package/dist.node/types/mimeType.js.map +1 -0
- package/dist.node/types/urn.js +28 -0
- package/dist.node/types/urn.js.map +1 -0
- package/dist.web/lib.dev.js +3487 -0
- package/dist.web/lib.dev.js.map +1 -0
- package/dist.web/lib.js +2 -0
- package/dist.web/lib.js.LICENSE.txt +18 -0
- package/libs/universal-node-xml/index.d.ts +33 -0
- package/libs/universal-node-xml/index.js +42 -0
- package/libs/universal-node-xml/stringifiers/helpers.js +17 -0
- package/libs/universal-node-xml/stringifiers/xmlbuilder2.js +51 -0
- package/package.json +86 -0
- package/res/README.md +27 -0
- package/res/bom-1.0.SNAPSHOT.xsd +247 -0
- package/res/bom-1.1.SNAPSHOT.xsd +731 -0
- package/res/bom-1.2-strict.SNAPSHOT.schema.json +1026 -0
- package/res/bom-1.2.SNAPSHOT.schema.json +997 -0
- package/res/bom-1.2.SNAPSHOT.xsd +1418 -0
- package/res/bom-1.3-strict.SNAPSHOT.schema.json +1085 -0
- package/res/bom-1.3.SNAPSHOT.schema.json +1054 -0
- package/res/bom-1.3.SNAPSHOT.xsd +1631 -0
- package/res/bom-1.4.SNAPSHOT.schema.json +1697 -0
- package/res/bom-1.4.SNAPSHOT.xsd +2407 -0
- package/res/jsf-0.82.SNAPSHOT.schema.json +244 -0
- package/res/spdx.SNAPSHOT.schema.json +533 -0
- package/res/spdx.SNAPSHOT.xsd +2639 -0
- package/src/_index.node.ts +31 -0
- package/src/_index.web.ts +27 -0
- package/src/enums/attachmentEncoding.ts +22 -0
- package/src/enums/componentScope.ts +24 -0
- package/src/enums/componentType.ts +29 -0
- package/src/enums/externalReferenceType.ts +37 -0
- package/src/enums/hashAlogorithm.ts +33 -0
- package/src/enums/index.ts +24 -0
- package/src/factories/index.ts +20 -0
- package/src/factories/licenseFactory.ts +62 -0
- package/src/helpers/README.md +3 -0
- package/src/helpers/types.ts +28 -0
- package/src/models/attachment.ts +37 -0
- package/src/models/bom.ts +85 -0
- package/src/models/bomRef.ts +41 -0
- package/src/models/component.ts +136 -0
- package/src/models/externalReference.ts +48 -0
- package/src/models/hash.ts +38 -0
- package/src/models/index.ts +31 -0
- package/src/models/license.ts +133 -0
- package/src/models/metadata.ts +50 -0
- package/src/models/organizationalContact.ts +49 -0
- package/src/models/organizationalEntity.ts +38 -0
- package/src/models/swid.ts +71 -0
- package/src/models/tool.ts +58 -0
- package/src/resources.node.ts +59 -0
- package/src/serialize/_index.node.ts +23 -0
- package/src/serialize/_index.web.ts +23 -0
- package/src/serialize/baseSerializer.ts +52 -0
- package/src/serialize/bomRefDiscriminator.ts +69 -0
- package/src/serialize/index.ts +35 -0
- package/src/serialize/json/index.ts +23 -0
- package/src/serialize/json/normalize.ts +450 -0
- package/src/serialize/json/types.ts +187 -0
- package/src/serialize/jsonSerializer.ts +59 -0
- package/src/serialize/types.ts +38 -0
- package/src/serialize/xml/index.ts +23 -0
- package/src/serialize/xml/normalize.ts +590 -0
- package/src/serialize/xml/types.ts +112 -0
- package/src/serialize/xmlBaseSerializer.ts +52 -0
- package/src/serialize/xmlSerializer.node.ts +35 -0
- package/src/serialize/xmlSerializer.web.ts +89 -0
- package/src/spdx.ts +48 -0
- package/src/spec.ts +289 -0
- package/src/types/cpe.ts +33 -0
- package/src/types/index.ts +23 -0
- package/src/types/integer.ts +50 -0
- package/src/types/mimeType.ts +31 -0
- package/src/types/urn.ts +33 -0
- package/tsconfig.json +108 -0
- package/tsconfig.node.json +8 -0
- package/tsconfig.web.json +5 -0
- package/webpack.config.js +74 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
This file is part of CycloneDX JavaScript Library.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
16
|
+
SPDX-License-Identifier: Apache-2.0
|
|
17
|
+
Copyright (c) OWASP Foundation. All Rights Reserved.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { isNotUndefined, Stringable } from '../../helpers/types'
|
|
21
|
+
import * as Models from '../../models'
|
|
22
|
+
import { Protocol as Spec, Version as SpecVersion } from '../../spec'
|
|
23
|
+
import { NormalizerOptions } from '../types'
|
|
24
|
+
import { SimpleXml, XmlSchema } from './types'
|
|
25
|
+
|
|
26
|
+
export class Factory {
|
|
27
|
+
readonly #spec: Spec
|
|
28
|
+
|
|
29
|
+
constructor (spec: Spec) {
|
|
30
|
+
this.#spec = spec
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get spec (): Spec {
|
|
34
|
+
return this.#spec
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
makeForBom (): BomNormalizer {
|
|
38
|
+
return new BomNormalizer(this)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
makeForMetadata (): MetadataNormalizer {
|
|
42
|
+
return new MetadataNormalizer(this)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
makeForComponent (): ComponentNormalizer {
|
|
46
|
+
return new ComponentNormalizer(this)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
makeForTool (): ToolNormalizer {
|
|
50
|
+
return new ToolNormalizer(this)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
makeForOrganizationalContact (): OrganizationalContactNormalizer {
|
|
54
|
+
return new OrganizationalContactNormalizer(this)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
makeForOrganizationalEntity (): OrganizationalEntityNormalizer {
|
|
58
|
+
return new OrganizationalEntityNormalizer(this)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
makeForHash (): HashNormalizer {
|
|
62
|
+
return new HashNormalizer(this)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
makeForLicense (): LicenseNormalizer {
|
|
66
|
+
return new LicenseNormalizer(this)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
makeForSWID (): SWIDNormalizer {
|
|
70
|
+
return new SWIDNormalizer(this)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
makeForExternalReference (): ExternalReferenceNormalizer {
|
|
74
|
+
return new ExternalReferenceNormalizer(this)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
makeForAttachment (): AttachmentNormalizer {
|
|
78
|
+
return new AttachmentNormalizer(this)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
makeForDependencyGraph (): DependencyGraphNormalizer {
|
|
82
|
+
return new DependencyGraphNormalizer(this)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const xmlNamespace: ReadonlyMap<SpecVersion, string> = new Map([
|
|
87
|
+
[SpecVersion.v1dot2, 'http://cyclonedx.org/schema/bom/1.2'],
|
|
88
|
+
[SpecVersion.v1dot3, 'http://cyclonedx.org/schema/bom/1.3'],
|
|
89
|
+
[SpecVersion.v1dot4, 'http://cyclonedx.org/schema/bom/1.4']
|
|
90
|
+
])
|
|
91
|
+
|
|
92
|
+
interface Normalizer {
|
|
93
|
+
normalize: (data: object, options: NormalizerOptions, elementName?: string) => object | undefined
|
|
94
|
+
|
|
95
|
+
normalizeIter?: (data: Iterable<object>, options: NormalizerOptions, elementName: string) => object[]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
abstract class Base implements Normalizer {
|
|
99
|
+
protected readonly _factory: Factory
|
|
100
|
+
|
|
101
|
+
constructor (factory: Factory) {
|
|
102
|
+
this._factory = factory
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {*} data
|
|
107
|
+
* @param {NormalizerOptions} options
|
|
108
|
+
* @param {string} [elementName] element name. XML defines structures; the element's name is defined on usage of a structure.
|
|
109
|
+
*/
|
|
110
|
+
abstract normalize (data: object, options: NormalizerOptions, elementName?: string): object | undefined
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/strict-boolean-expressions --
|
|
114
|
+
* since empty strings need to be treated as undefined/null
|
|
115
|
+
*/
|
|
116
|
+
|
|
117
|
+
export class BomNormalizer extends Base {
|
|
118
|
+
normalize (data: Models.Bom, options: NormalizerOptions): SimpleXml.Element {
|
|
119
|
+
const components: SimpleXml.Element = {
|
|
120
|
+
// spec < 1.4 always requires a 'components' element
|
|
121
|
+
type: 'element',
|
|
122
|
+
name: 'components',
|
|
123
|
+
children: data.components.size > 0
|
|
124
|
+
? this._factory.makeForComponent().normalizeIter(data.components, options, 'component')
|
|
125
|
+
: undefined
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
type: 'element',
|
|
129
|
+
// the element's name is hardcoded in the XSD
|
|
130
|
+
name: 'bom',
|
|
131
|
+
namespace: xmlNamespace.get(this._factory.spec.version),
|
|
132
|
+
attributes: {
|
|
133
|
+
version: data.version,
|
|
134
|
+
serialNumber: data.serialNumber
|
|
135
|
+
},
|
|
136
|
+
children: [
|
|
137
|
+
data.metadata
|
|
138
|
+
? this._factory.makeForMetadata().normalize(data.metadata, options, 'metadata')
|
|
139
|
+
: undefined,
|
|
140
|
+
components,
|
|
141
|
+
this._factory.spec.supportsDependencyGraph
|
|
142
|
+
? this._factory.makeForDependencyGraph().normalize(data, options, 'dependencies')
|
|
143
|
+
: undefined
|
|
144
|
+
].filter(isNotUndefined)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export class MetadataNormalizer extends Base {
|
|
150
|
+
normalize (data: Models.Metadata, options: NormalizerOptions, elementName: string): SimpleXml.Element {
|
|
151
|
+
const orgEntityNormalizer = this._factory.makeForOrganizationalEntity()
|
|
152
|
+
const timestamp: SimpleXml.Element | undefined = data.timestamp === undefined
|
|
153
|
+
? undefined
|
|
154
|
+
: {
|
|
155
|
+
type: 'element',
|
|
156
|
+
name: 'timestamp',
|
|
157
|
+
children: data.timestamp.toISOString()
|
|
158
|
+
}
|
|
159
|
+
const tools: SimpleXml.Element | undefined = data.tools.size > 0
|
|
160
|
+
? {
|
|
161
|
+
type: 'element',
|
|
162
|
+
name: 'tools',
|
|
163
|
+
children: this._factory.makeForTool().normalizeIter(data.tools, options, 'tool')
|
|
164
|
+
}
|
|
165
|
+
: undefined
|
|
166
|
+
const authors: SimpleXml.Element | undefined = data.authors.size > 0
|
|
167
|
+
? {
|
|
168
|
+
type: 'element',
|
|
169
|
+
name: 'authors',
|
|
170
|
+
children: this._factory.makeForOrganizationalContact()
|
|
171
|
+
.normalizeIter(data.authors, options, 'author')
|
|
172
|
+
}
|
|
173
|
+
: undefined
|
|
174
|
+
return {
|
|
175
|
+
type: 'element',
|
|
176
|
+
name: elementName,
|
|
177
|
+
children: [
|
|
178
|
+
timestamp,
|
|
179
|
+
tools,
|
|
180
|
+
authors,
|
|
181
|
+
data.component === undefined
|
|
182
|
+
? undefined
|
|
183
|
+
: this._factory.makeForComponent().normalize(data.component, options, 'component'),
|
|
184
|
+
data.manufacture === undefined
|
|
185
|
+
? undefined
|
|
186
|
+
: orgEntityNormalizer.normalize(data.manufacture, options, 'manufacture'),
|
|
187
|
+
data.supplier === undefined
|
|
188
|
+
? undefined
|
|
189
|
+
: orgEntityNormalizer.normalize(data.supplier, options, 'supplier')
|
|
190
|
+
].filter(isNotUndefined)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export class ToolNormalizer extends Base {
|
|
196
|
+
normalize (data: Models.Tool, options: NormalizerOptions, elementName: string): SimpleXml.Element {
|
|
197
|
+
const hashes: SimpleXml.Element | undefined = data.hashes.size > 0
|
|
198
|
+
? {
|
|
199
|
+
type: 'element',
|
|
200
|
+
name: 'hashes',
|
|
201
|
+
children: this._factory.makeForHash().normalizeIter(data.hashes, options, 'hash')
|
|
202
|
+
}
|
|
203
|
+
: undefined
|
|
204
|
+
const externalReferences: SimpleXml.Element | undefined =
|
|
205
|
+
this._factory.spec.supportsToolReferences && data.externalReferences.size > 0
|
|
206
|
+
? {
|
|
207
|
+
type: 'element',
|
|
208
|
+
name: 'externalReferences',
|
|
209
|
+
children: this._factory.makeForExternalReference()
|
|
210
|
+
.normalizeIter(data.externalReferences, options, 'reference')
|
|
211
|
+
}
|
|
212
|
+
: undefined
|
|
213
|
+
return {
|
|
214
|
+
type: 'element',
|
|
215
|
+
name: elementName,
|
|
216
|
+
children: [
|
|
217
|
+
makeOptionalTextElement(data.vendor, 'vendor'),
|
|
218
|
+
makeOptionalTextElement(data.name, 'name'),
|
|
219
|
+
makeOptionalTextElement(data.version, 'version'),
|
|
220
|
+
hashes,
|
|
221
|
+
externalReferences
|
|
222
|
+
].filter(isNotUndefined)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
normalizeIter (data: Iterable<Models.Tool>, options: NormalizerOptions, elementName: string): SimpleXml.Element[] {
|
|
227
|
+
const tools = Array.from(data)
|
|
228
|
+
if (options.sortLists) {
|
|
229
|
+
tools.sort(Models.ToolRepository.compareItems)
|
|
230
|
+
}
|
|
231
|
+
return tools.map(t => this.normalize(t, options, elementName))
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export class HashNormalizer extends Base {
|
|
236
|
+
normalize ([algorithm, content]: Models.Hash, options: NormalizerOptions, elementName: string): SimpleXml.Element | undefined {
|
|
237
|
+
const spec = this._factory.spec
|
|
238
|
+
return spec.supportsHashAlgorithm(algorithm) && spec.supportsHashValue(content)
|
|
239
|
+
? {
|
|
240
|
+
type: 'element',
|
|
241
|
+
name: elementName,
|
|
242
|
+
attributes: { alg: algorithm },
|
|
243
|
+
children: content
|
|
244
|
+
}
|
|
245
|
+
: undefined
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
normalizeIter (data: Iterable<Models.Hash>, options: NormalizerOptions, elementName: string): SimpleXml.Element[] {
|
|
249
|
+
const hashes = Array.from(data)
|
|
250
|
+
if (options.sortLists ?? false) {
|
|
251
|
+
hashes.sort(Models.HashRepository.compareItems)
|
|
252
|
+
}
|
|
253
|
+
return hashes.map(h => this.normalize(h, options, elementName))
|
|
254
|
+
.filter(isNotUndefined)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export class OrganizationalContactNormalizer extends Base {
|
|
259
|
+
normalize (data: Models.OrganizationalContact, options: NormalizerOptions, elementName: string): SimpleXml.Element {
|
|
260
|
+
return {
|
|
261
|
+
type: 'element',
|
|
262
|
+
name: elementName,
|
|
263
|
+
children: [
|
|
264
|
+
makeOptionalTextElement(data.name, 'name'),
|
|
265
|
+
makeOptionalTextElement(data.email, 'email'),
|
|
266
|
+
makeOptionalTextElement(data.phone, 'phone')
|
|
267
|
+
].filter(isNotUndefined)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
normalizeIter (data: Iterable<Models.OrganizationalContact>, options: NormalizerOptions, elementName: string): SimpleXml.Element[] {
|
|
272
|
+
const contacts = Array.from(data)
|
|
273
|
+
if (options.sortLists ?? false) {
|
|
274
|
+
contacts.sort(Models.OrganizationalContactRepository.compareItems)
|
|
275
|
+
}
|
|
276
|
+
return contacts.map(c => this.normalize(c, options, elementName))
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export class OrganizationalEntityNormalizer extends Base {
|
|
281
|
+
normalize (data: Models.OrganizationalEntity, options: NormalizerOptions, elementName: string): SimpleXml.Element {
|
|
282
|
+
return {
|
|
283
|
+
type: 'element',
|
|
284
|
+
name: elementName,
|
|
285
|
+
children: [
|
|
286
|
+
makeOptionalTextElement(data.name, 'name'),
|
|
287
|
+
...makeTextElementIter(data.url, options, 'url')
|
|
288
|
+
.filter(({ children: u }) => XmlSchema.isAnyURI(u)),
|
|
289
|
+
...this._factory.makeForOrganizationalContact().normalizeIter(data.contact, options, 'contact')
|
|
290
|
+
].filter(isNotUndefined)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export class ComponentNormalizer extends Base {
|
|
296
|
+
normalize (data: Models.Component, options: NormalizerOptions, elementName: string): SimpleXml.Element | undefined {
|
|
297
|
+
if (!this._factory.spec.supportsComponentType(data.type)) {
|
|
298
|
+
return undefined
|
|
299
|
+
}
|
|
300
|
+
const supplier: SimpleXml.Element | undefined = data.supplier === undefined
|
|
301
|
+
? undefined
|
|
302
|
+
: this._factory.makeForOrganizationalEntity().normalize(data.supplier, options, 'supplier')
|
|
303
|
+
const hashes: SimpleXml.Element | undefined = data.hashes.size > 0
|
|
304
|
+
? {
|
|
305
|
+
type: 'element',
|
|
306
|
+
name: 'hashes',
|
|
307
|
+
children: this._factory.makeForHash().normalizeIter(data.hashes, options, 'hash')
|
|
308
|
+
}
|
|
309
|
+
: undefined
|
|
310
|
+
const licenses: SimpleXml.Element | undefined = data.licenses.size > 0
|
|
311
|
+
? {
|
|
312
|
+
type: 'element',
|
|
313
|
+
name: 'licenses',
|
|
314
|
+
children: this._factory.makeForLicense().normalizeIter(data.licenses, options)
|
|
315
|
+
}
|
|
316
|
+
: undefined
|
|
317
|
+
const swid: SimpleXml.Element | undefined = data.swid === undefined
|
|
318
|
+
? undefined
|
|
319
|
+
: this._factory.makeForSWID().normalize(data.swid, options, 'swid')
|
|
320
|
+
const extRefs: SimpleXml.Element | undefined = data.externalReferences.size > 0
|
|
321
|
+
? {
|
|
322
|
+
type: 'element',
|
|
323
|
+
name: 'externalReferences',
|
|
324
|
+
children: this._factory.makeForExternalReference()
|
|
325
|
+
.normalizeIter(data.externalReferences, options, 'reference')
|
|
326
|
+
}
|
|
327
|
+
: undefined
|
|
328
|
+
return {
|
|
329
|
+
type: 'element',
|
|
330
|
+
name: elementName,
|
|
331
|
+
attributes: {
|
|
332
|
+
type: data.type,
|
|
333
|
+
'bom-ref': data.bomRef.value
|
|
334
|
+
},
|
|
335
|
+
children: [
|
|
336
|
+
supplier,
|
|
337
|
+
makeOptionalTextElement(data.author, 'author'),
|
|
338
|
+
makeOptionalTextElement(data.publisher, 'publisher'),
|
|
339
|
+
makeOptionalTextElement(data.group, 'group'),
|
|
340
|
+
makeTextElement(data.name, 'name'),
|
|
341
|
+
makeTextElement(
|
|
342
|
+
// version fallback to string for spec < 1.4
|
|
343
|
+
data.version ?? '',
|
|
344
|
+
'version'
|
|
345
|
+
),
|
|
346
|
+
makeOptionalTextElement(data.description, 'description'),
|
|
347
|
+
makeOptionalTextElement(data.scope, 'description'),
|
|
348
|
+
hashes,
|
|
349
|
+
licenses,
|
|
350
|
+
makeOptionalTextElement(data.copyright, 'copyright'),
|
|
351
|
+
makeOptionalTextElement(data.cpe, 'cpe'),
|
|
352
|
+
makeOptionalTextElement(data.purl, 'purl'),
|
|
353
|
+
swid,
|
|
354
|
+
extRefs
|
|
355
|
+
].filter(isNotUndefined)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
normalizeIter (data: Iterable<Models.Component>, options: NormalizerOptions, elementName: string): SimpleXml.Element[] {
|
|
360
|
+
const components = Array.from(data)
|
|
361
|
+
if (options.sortLists ?? false) {
|
|
362
|
+
components.sort(Models.ComponentRepository.compareItems)
|
|
363
|
+
}
|
|
364
|
+
return components.map(c => this.normalize(c, options, elementName))
|
|
365
|
+
.filter(isNotUndefined)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export class LicenseNormalizer extends Base {
|
|
370
|
+
normalize (data: Models.License, options: NormalizerOptions): SimpleXml.Element {
|
|
371
|
+
switch (true) {
|
|
372
|
+
case data instanceof Models.NamedLicense:
|
|
373
|
+
return this.#normalizeNamedLicense(data as Models.NamedLicense, options)
|
|
374
|
+
case data instanceof Models.SpdxLicense:
|
|
375
|
+
return this.#normalizeSpdxLicense(data as Models.SpdxLicense, options)
|
|
376
|
+
case data instanceof Models.LicenseExpression:
|
|
377
|
+
return this.#normalizeLicenseExpression(data as Models.LicenseExpression)
|
|
378
|
+
default:
|
|
379
|
+
// this case is not expected to happen - and therefore is undocumented
|
|
380
|
+
throw new TypeError('Unexpected LicenseChoice')
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
#normalizeNamedLicense (data: Models.NamedLicense, options: NormalizerOptions): SimpleXml.Element {
|
|
385
|
+
const url = data.url?.toString()
|
|
386
|
+
return {
|
|
387
|
+
type: 'element',
|
|
388
|
+
name: 'license',
|
|
389
|
+
children: [
|
|
390
|
+
makeTextElement(data.name, 'name'),
|
|
391
|
+
data.text === undefined
|
|
392
|
+
? undefined
|
|
393
|
+
: this._factory.makeForAttachment().normalize(data.text, options, 'text'),
|
|
394
|
+
XmlSchema.isAnyURI(url)
|
|
395
|
+
? makeTextElement(url, 'url')
|
|
396
|
+
: undefined
|
|
397
|
+
].filter(isNotUndefined)
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
#normalizeSpdxLicense (data: Models.SpdxLicense, options: NormalizerOptions): SimpleXml.Element {
|
|
402
|
+
const url = data.url?.toString()
|
|
403
|
+
return {
|
|
404
|
+
type: 'element',
|
|
405
|
+
name: 'license',
|
|
406
|
+
children: [
|
|
407
|
+
makeTextElement(data.id, 'id'),
|
|
408
|
+
data.text === undefined
|
|
409
|
+
? undefined
|
|
410
|
+
: this._factory.makeForAttachment().normalize(data.text, options, 'text'),
|
|
411
|
+
XmlSchema.isAnyURI(url)
|
|
412
|
+
? makeTextElement(url, 'url')
|
|
413
|
+
: undefined
|
|
414
|
+
].filter(isNotUndefined)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
#normalizeLicenseExpression (data: Models.LicenseExpression): SimpleXml.Element {
|
|
419
|
+
return makeTextElement(data.expression, 'expression')
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
normalizeIter (data: Models.LicenseRepository, options: NormalizerOptions): SimpleXml.Element[] {
|
|
423
|
+
const licenses = Array.from(data)
|
|
424
|
+
if (options.sortLists ?? false) {
|
|
425
|
+
licenses.sort(Models.LicenseRepository.compareItems)
|
|
426
|
+
}
|
|
427
|
+
return licenses.map(c => this.normalize(c, options))
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export class SWIDNormalizer extends Base {
|
|
432
|
+
normalize (data: Models.SWID, options: NormalizerOptions, elementName: string): SimpleXml.Element {
|
|
433
|
+
const url = data.url?.toString()
|
|
434
|
+
return {
|
|
435
|
+
type: 'element',
|
|
436
|
+
name: elementName,
|
|
437
|
+
attributes: {
|
|
438
|
+
tagId: data.tagId,
|
|
439
|
+
name: data.name,
|
|
440
|
+
version: data.version || undefined,
|
|
441
|
+
tagVersion: data.tagVersion,
|
|
442
|
+
patch: data.patch === undefined
|
|
443
|
+
? undefined
|
|
444
|
+
: (data.patch ? 'true' : 'false')
|
|
445
|
+
},
|
|
446
|
+
children: [
|
|
447
|
+
data.text === undefined
|
|
448
|
+
? undefined
|
|
449
|
+
: this._factory.makeForAttachment().normalize(data.text, options, 'text'),
|
|
450
|
+
XmlSchema.isAnyURI(url)
|
|
451
|
+
? makeTextElement(url, 'url')
|
|
452
|
+
: undefined
|
|
453
|
+
].filter(isNotUndefined)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export class ExternalReferenceNormalizer extends Base {
|
|
459
|
+
normalize (data: Models.ExternalReference, options: NormalizerOptions, elementName: string): SimpleXml.Element | undefined {
|
|
460
|
+
const url = data.url.toString()
|
|
461
|
+
return this._factory.spec.supportsExternalReferenceType(data.type) &&
|
|
462
|
+
XmlSchema.isAnyURI(url)
|
|
463
|
+
? {
|
|
464
|
+
type: 'element',
|
|
465
|
+
name: elementName,
|
|
466
|
+
attributes: {
|
|
467
|
+
type: data.type
|
|
468
|
+
},
|
|
469
|
+
children: [
|
|
470
|
+
makeTextElement(url, 'url'),
|
|
471
|
+
makeOptionalTextElement(data.comment, 'comment')
|
|
472
|
+
].filter(isNotUndefined)
|
|
473
|
+
}
|
|
474
|
+
: undefined
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
normalizeIter (data: Iterable<Models.ExternalReference>, options: NormalizerOptions, elementName: string): SimpleXml.Element[] {
|
|
478
|
+
const references = Array.from(data)
|
|
479
|
+
if (options.sortLists ?? false) {
|
|
480
|
+
references.sort(Models.ExternalReferenceRepository.compareItems)
|
|
481
|
+
}
|
|
482
|
+
return references.map(r => this.normalize(r, options, elementName))
|
|
483
|
+
.filter(isNotUndefined)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export class AttachmentNormalizer extends Base {
|
|
488
|
+
normalize (data: Models.Attachment, options: NormalizerOptions, elementName: string): SimpleXml.Element {
|
|
489
|
+
return {
|
|
490
|
+
type: 'element',
|
|
491
|
+
name: elementName,
|
|
492
|
+
attributes: {
|
|
493
|
+
'content-type': data.contentType || undefined,
|
|
494
|
+
encoding: data.encoding || undefined
|
|
495
|
+
},
|
|
496
|
+
children: data.content
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export class DependencyGraphNormalizer extends Base {
|
|
502
|
+
normalize (data: Models.Bom, options: NormalizerOptions, elementName: string): SimpleXml.Element | undefined {
|
|
503
|
+
if (!data.metadata.component?.bomRef.value) {
|
|
504
|
+
// the graph is missing the entry point -> omit the graph
|
|
505
|
+
return undefined
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const allRefs = new Map<Models.BomRef, Models.BomRefRepository>()
|
|
509
|
+
for (const c of data.components) {
|
|
510
|
+
allRefs.set(c.bomRef, new Models.BomRefRepository(c.dependencies))
|
|
511
|
+
}
|
|
512
|
+
allRefs.set(data.metadata.component.bomRef, data.metadata.component.dependencies)
|
|
513
|
+
|
|
514
|
+
const normalized: Array<(SimpleXml.Element & { attributes: { ref: string } })> = []
|
|
515
|
+
for (const [ref, deps] of allRefs) {
|
|
516
|
+
const dep = this.#normalizeDependency(ref, deps, allRefs, options)
|
|
517
|
+
if (isNotUndefined(dep)) {
|
|
518
|
+
normalized.push(dep)
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (options.sortLists ?? false) {
|
|
523
|
+
normalized.sort(
|
|
524
|
+
({ attributes: { ref: a } }, { attributes: { ref: b } }) => a.localeCompare(b))
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return {
|
|
528
|
+
type: 'element',
|
|
529
|
+
name: elementName,
|
|
530
|
+
children: normalized
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
#normalizeDependency (
|
|
535
|
+
ref: Models.BomRef,
|
|
536
|
+
deps: Models.BomRefRepository,
|
|
537
|
+
allRefs: Map<Models.BomRef, Models.BomRefRepository>,
|
|
538
|
+
options: NormalizerOptions
|
|
539
|
+
): undefined | (SimpleXml.Element & { attributes: { ref: string } }) {
|
|
540
|
+
const bomRef = ref.toString()
|
|
541
|
+
if (bomRef.length === 0) {
|
|
542
|
+
// no value -> cannot render
|
|
543
|
+
return undefined
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const dependsOn: string[] = Array.from(deps).filter(d => allRefs.has(d) && d !== ref)
|
|
547
|
+
.map(d => d.toString()).filter(d => d.length > 0)
|
|
548
|
+
if (options.sortLists ?? false) {
|
|
549
|
+
dependsOn.sort((a, b) => a.localeCompare(b))
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return {
|
|
553
|
+
type: 'element',
|
|
554
|
+
name: 'dependency',
|
|
555
|
+
attributes: { ref: bomRef },
|
|
556
|
+
children: dependsOn.map(d => ({
|
|
557
|
+
type: 'element',
|
|
558
|
+
name: 'dependency',
|
|
559
|
+
attributes: { ref: d }
|
|
560
|
+
}))
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/* eslint-enable @typescript-eslint/prefer-nullish-coalescing, @typescript-eslint/strict-boolean-expressions */
|
|
566
|
+
|
|
567
|
+
type StrictTextElement = SimpleXml.TextElement & { children: string }
|
|
568
|
+
|
|
569
|
+
function makeOptionalTextElement (data: null | undefined | Stringable, elementName: string): undefined | StrictTextElement {
|
|
570
|
+
const s = data?.toString() ?? ''
|
|
571
|
+
return s.length > 0
|
|
572
|
+
? makeTextElement(s, elementName)
|
|
573
|
+
: undefined
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function makeTextElement (data: Stringable, elementName: string): StrictTextElement {
|
|
577
|
+
return {
|
|
578
|
+
type: 'element',
|
|
579
|
+
name: elementName,
|
|
580
|
+
children: data.toString()
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function makeTextElementIter (data: Iterable<Stringable>, options: NormalizerOptions, elementName: string): StrictTextElement[] {
|
|
585
|
+
const r: StrictTextElement[] = Array.from(data, d => makeTextElement(d, elementName))
|
|
586
|
+
if (options.sortLists ?? false) {
|
|
587
|
+
r.sort(({ children: a }, { children: b }) => a.localeCompare(b))
|
|
588
|
+
}
|
|
589
|
+
return r
|
|
590
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
This file is part of CycloneDX JavaScript Library.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
16
|
+
SPDX-License-Identifier: Apache-2.0
|
|
17
|
+
Copyright (c) OWASP Foundation. All Rights Reserved.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
21
|
+
export namespace XmlSchema {
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @see isAnyURI
|
|
25
|
+
*/
|
|
26
|
+
export type AnyURI = string
|
|
27
|
+
/**
|
|
28
|
+
* Test whether format is XML::anyURI - best-effort.
|
|
29
|
+
*
|
|
30
|
+
* @see {@link http://www.w3.org/TR/xmlschema-2/#anyURI}
|
|
31
|
+
* @see {@link http://www.datypic.com/sc/xsd/t-xsd_anyURI.html}
|
|
32
|
+
*/
|
|
33
|
+
export function isAnyURI (value: AnyURI | any): value is AnyURI {
|
|
34
|
+
return typeof value === 'string' &&
|
|
35
|
+
value.length > 0 &&
|
|
36
|
+
Array.from(value).filter(c => c === '#').length <= 1
|
|
37
|
+
// TODO add more validation according to spec
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
43
|
+
export namespace SimpleXml {
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Attribute's name.
|
|
47
|
+
*
|
|
48
|
+
* Must be alphanumeric.
|
|
49
|
+
* Must start with alpha.
|
|
50
|
+
* Must not contain whitespace characters.
|
|
51
|
+
* Should not be literal "xmlns".
|
|
52
|
+
*/
|
|
53
|
+
export type AttributeName = string
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Element's name.
|
|
57
|
+
*
|
|
58
|
+
* Must be alphanumeric.
|
|
59
|
+
* Must start with alpha.
|
|
60
|
+
* Must not contain whitespace characters.
|
|
61
|
+
*/
|
|
62
|
+
export type ElementName = string
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Textual representation.
|
|
66
|
+
*
|
|
67
|
+
* Be aware that low-/high-bytes could be represented as numbers.
|
|
68
|
+
* They might need to be converted on serialization.
|
|
69
|
+
*/
|
|
70
|
+
export type Text = string | number
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Unset representation.
|
|
74
|
+
*
|
|
75
|
+
* Do NOT allow null here, as it is context-aware sometimes an empty string or unset,
|
|
76
|
+
* in a space where context is unknown.
|
|
77
|
+
*/
|
|
78
|
+
export type Unset = undefined
|
|
79
|
+
|
|
80
|
+
export interface ElementAttributes {
|
|
81
|
+
[key: AttributeName]: Text | Unset
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export type ElementChildren = Iterable<Comment | Element> | Text | Unset
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Element node.
|
|
88
|
+
*/
|
|
89
|
+
export interface Element {
|
|
90
|
+
type: 'element'
|
|
91
|
+
name: ElementName
|
|
92
|
+
namespace?: string | URL
|
|
93
|
+
attributes?: ElementAttributes
|
|
94
|
+
children?: ElementChildren
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Element node with textual content
|
|
99
|
+
*/
|
|
100
|
+
export interface TextElement extends Element {
|
|
101
|
+
children: Text
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Comment node.
|
|
106
|
+
*/
|
|
107
|
+
export interface Comment {
|
|
108
|
+
type: 'comment'
|
|
109
|
+
text?: Text
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
}
|