@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.
Files changed (169) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +5 -0
  3. package/README.md +152 -0
  4. package/dist.node/_index.node.js +53 -0
  5. package/dist.node/_index.node.js.map +1 -0
  6. package/dist.node/enums/attachmentEncoding.js +26 -0
  7. package/dist.node/enums/attachmentEncoding.js.map +1 -0
  8. package/dist.node/enums/componentScope.js +28 -0
  9. package/dist.node/enums/componentScope.js.map +1 -0
  10. package/dist.node/enums/componentType.js +33 -0
  11. package/dist.node/enums/componentType.js.map +1 -0
  12. package/dist.node/enums/externalReferenceType.js +41 -0
  13. package/dist.node/enums/externalReferenceType.js.map +1 -0
  14. package/dist.node/enums/hashAlogorithm.js +37 -0
  15. package/dist.node/enums/hashAlogorithm.js.map +1 -0
  16. package/dist.node/enums/index.js +40 -0
  17. package/dist.node/enums/index.js.map +1 -0
  18. package/dist.node/factories/index.js +36 -0
  19. package/dist.node/factories/index.js.map +1 -0
  20. package/dist.node/factories/licenseFactory.js +56 -0
  21. package/dist.node/factories/licenseFactory.js.map +1 -0
  22. package/dist.node/helpers/types.js +26 -0
  23. package/dist.node/helpers/types.js.map +1 -0
  24. package/dist.node/models/attachment.js +30 -0
  25. package/dist.node/models/attachment.js.map +1 -0
  26. package/dist.node/models/bom.js +67 -0
  27. package/dist.node/models/bom.js.map +1 -0
  28. package/dist.node/models/bomRef.js +37 -0
  29. package/dist.node/models/bomRef.js.map +1 -0
  30. package/dist.node/models/component.js +96 -0
  31. package/dist.node/models/component.js.map +1 -0
  32. package/dist.node/models/externalReference.js +40 -0
  33. package/dist.node/models/externalReference.js.map +1 -0
  34. package/dist.node/models/hash.js +29 -0
  35. package/dist.node/models/hash.js.map +1 -0
  36. package/dist.node/models/index.js +47 -0
  37. package/dist.node/models/index.js.map +1 -0
  38. package/dist.node/models/license.js +103 -0
  39. package/dist.node/models/license.js.map +1 -0
  40. package/dist.node/models/metadata.js +35 -0
  41. package/dist.node/models/metadata.js.map +1 -0
  42. package/dist.node/models/organizationalContact.js +41 -0
  43. package/dist.node/models/organizationalContact.js.map +1 -0
  44. package/dist.node/models/organizationalEntity.js +31 -0
  45. package/dist.node/models/organizationalEntity.js.map +1 -0
  46. package/dist.node/models/swid.js +58 -0
  47. package/dist.node/models/swid.js.map +1 -0
  48. package/dist.node/models/tool.js +45 -0
  49. package/dist.node/models/tool.js.map +1 -0
  50. package/dist.node/resources.node.js +55 -0
  51. package/dist.node/resources.node.js.map +1 -0
  52. package/dist.node/serialize/_index.node.js +37 -0
  53. package/dist.node/serialize/_index.node.js.map +1 -0
  54. package/dist.node/serialize/baseSerializer.js +56 -0
  55. package/dist.node/serialize/baseSerializer.js.map +1 -0
  56. package/dist.node/serialize/bomRefDiscriminator.js +66 -0
  57. package/dist.node/serialize/bomRefDiscriminator.js.map +1 -0
  58. package/dist.node/serialize/index.js +55 -0
  59. package/dist.node/serialize/index.js.map +1 -0
  60. package/dist.node/serialize/json/index.js +47 -0
  61. package/dist.node/serialize/json/index.js.map +1 -0
  62. package/dist.node/serialize/json/normalize.js +431 -0
  63. package/dist.node/serialize/json/normalize.js.map +1 -0
  64. package/dist.node/serialize/json/types.js +35 -0
  65. package/dist.node/serialize/json/types.js.map +1 -0
  66. package/dist.node/serialize/jsonSerializer.js +55 -0
  67. package/dist.node/serialize/jsonSerializer.js.map +1 -0
  68. package/dist.node/serialize/types.js +21 -0
  69. package/dist.node/serialize/types.js.map +1 -0
  70. package/dist.node/serialize/xml/index.js +47 -0
  71. package/dist.node/serialize/xml/index.js.map +1 -0
  72. package/dist.node/serialize/xml/normalize.js +560 -0
  73. package/dist.node/serialize/xml/normalize.js.map +1 -0
  74. package/dist.node/serialize/xml/types.js +31 -0
  75. package/dist.node/serialize/xml/types.js.map +1 -0
  76. package/dist.node/serialize/xmlBaseSerializer.js +52 -0
  77. package/dist.node/serialize/xmlBaseSerializer.js.map +1 -0
  78. package/dist.node/serialize/xmlSerializer.node.js +30 -0
  79. package/dist.node/serialize/xmlSerializer.node.js.map +1 -0
  80. package/dist.node/spdx.js +35 -0
  81. package/dist.node/spdx.js.map +1 -0
  82. package/dist.node/spec.js +229 -0
  83. package/dist.node/spec.js.map +1 -0
  84. package/dist.node/types/cpe.js +28 -0
  85. package/dist.node/types/cpe.js.map +1 -0
  86. package/dist.node/types/index.js +39 -0
  87. package/dist.node/types/index.js.map +1 -0
  88. package/dist.node/types/integer.js +36 -0
  89. package/dist.node/types/integer.js.map +1 -0
  90. package/dist.node/types/mimeType.js +28 -0
  91. package/dist.node/types/mimeType.js.map +1 -0
  92. package/dist.node/types/urn.js +28 -0
  93. package/dist.node/types/urn.js.map +1 -0
  94. package/dist.web/lib.dev.js +3487 -0
  95. package/dist.web/lib.dev.js.map +1 -0
  96. package/dist.web/lib.js +2 -0
  97. package/dist.web/lib.js.LICENSE.txt +18 -0
  98. package/libs/universal-node-xml/index.d.ts +33 -0
  99. package/libs/universal-node-xml/index.js +42 -0
  100. package/libs/universal-node-xml/stringifiers/helpers.js +17 -0
  101. package/libs/universal-node-xml/stringifiers/xmlbuilder2.js +51 -0
  102. package/package.json +86 -0
  103. package/res/README.md +27 -0
  104. package/res/bom-1.0.SNAPSHOT.xsd +247 -0
  105. package/res/bom-1.1.SNAPSHOT.xsd +731 -0
  106. package/res/bom-1.2-strict.SNAPSHOT.schema.json +1026 -0
  107. package/res/bom-1.2.SNAPSHOT.schema.json +997 -0
  108. package/res/bom-1.2.SNAPSHOT.xsd +1418 -0
  109. package/res/bom-1.3-strict.SNAPSHOT.schema.json +1085 -0
  110. package/res/bom-1.3.SNAPSHOT.schema.json +1054 -0
  111. package/res/bom-1.3.SNAPSHOT.xsd +1631 -0
  112. package/res/bom-1.4.SNAPSHOT.schema.json +1697 -0
  113. package/res/bom-1.4.SNAPSHOT.xsd +2407 -0
  114. package/res/jsf-0.82.SNAPSHOT.schema.json +244 -0
  115. package/res/spdx.SNAPSHOT.schema.json +533 -0
  116. package/res/spdx.SNAPSHOT.xsd +2639 -0
  117. package/src/_index.node.ts +31 -0
  118. package/src/_index.web.ts +27 -0
  119. package/src/enums/attachmentEncoding.ts +22 -0
  120. package/src/enums/componentScope.ts +24 -0
  121. package/src/enums/componentType.ts +29 -0
  122. package/src/enums/externalReferenceType.ts +37 -0
  123. package/src/enums/hashAlogorithm.ts +33 -0
  124. package/src/enums/index.ts +24 -0
  125. package/src/factories/index.ts +20 -0
  126. package/src/factories/licenseFactory.ts +62 -0
  127. package/src/helpers/README.md +3 -0
  128. package/src/helpers/types.ts +28 -0
  129. package/src/models/attachment.ts +37 -0
  130. package/src/models/bom.ts +85 -0
  131. package/src/models/bomRef.ts +41 -0
  132. package/src/models/component.ts +136 -0
  133. package/src/models/externalReference.ts +48 -0
  134. package/src/models/hash.ts +38 -0
  135. package/src/models/index.ts +31 -0
  136. package/src/models/license.ts +133 -0
  137. package/src/models/metadata.ts +50 -0
  138. package/src/models/organizationalContact.ts +49 -0
  139. package/src/models/organizationalEntity.ts +38 -0
  140. package/src/models/swid.ts +71 -0
  141. package/src/models/tool.ts +58 -0
  142. package/src/resources.node.ts +59 -0
  143. package/src/serialize/_index.node.ts +23 -0
  144. package/src/serialize/_index.web.ts +23 -0
  145. package/src/serialize/baseSerializer.ts +52 -0
  146. package/src/serialize/bomRefDiscriminator.ts +69 -0
  147. package/src/serialize/index.ts +35 -0
  148. package/src/serialize/json/index.ts +23 -0
  149. package/src/serialize/json/normalize.ts +450 -0
  150. package/src/serialize/json/types.ts +187 -0
  151. package/src/serialize/jsonSerializer.ts +59 -0
  152. package/src/serialize/types.ts +38 -0
  153. package/src/serialize/xml/index.ts +23 -0
  154. package/src/serialize/xml/normalize.ts +590 -0
  155. package/src/serialize/xml/types.ts +112 -0
  156. package/src/serialize/xmlBaseSerializer.ts +52 -0
  157. package/src/serialize/xmlSerializer.node.ts +35 -0
  158. package/src/serialize/xmlSerializer.web.ts +89 -0
  159. package/src/spdx.ts +48 -0
  160. package/src/spec.ts +289 -0
  161. package/src/types/cpe.ts +33 -0
  162. package/src/types/index.ts +23 -0
  163. package/src/types/integer.ts +50 -0
  164. package/src/types/mimeType.ts +31 -0
  165. package/src/types/urn.ts +33 -0
  166. package/tsconfig.json +108 -0
  167. package/tsconfig.node.json +8 -0
  168. package/tsconfig.web.json +5 -0
  169. 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
+ }