xref: /aosp_15_r20/external/spdx-tools/rdfloader/parser2v3/parse_package.go (revision ba677afa8f67bb56cbc794f4d0e378e0da058e16)
1// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
2
3package parser2v3
4
5import (
6	"fmt"
7	"strings"
8
9	gordfParser "github.com/spdx/gordf/rdfloader/parser"
10	"github.com/spdx/tools-golang/spdx/common"
11	"github.com/spdx/tools-golang/spdx/v2_3"
12)
13
14func (parser *rdfParser2_3) getPackageFromNode(packageNode *gordfParser.Node) (pkg *v2_3.Package, err error) {
15	pkg = &v2_3.Package{} // new package which will be returned
16
17	currState := parser.cache[packageNode.ID]
18	if currState == nil {
19		// there is no entry about the state of current package node.
20		// this is the first time we're seeing this node.
21		parser.cache[packageNode.ID] = &nodeState{
22			object: pkg,
23			Color:  WHITE,
24		}
25	} else if currState.Color == GREY {
26		// we have already started parsing this package node and we needn't parse it again.
27		return currState.object.(*v2_3.Package), nil
28	}
29
30	// setting color of the state to grey to indicate that we've started to
31	// parse this node once.
32	parser.cache[packageNode.ID].Color = GREY
33
34	// setting state color to black to indicate when we're done parsing this node.
35	defer func() { parser.cache[packageNode.ID].Color = BLACK }()
36
37	// setting the SPDXIdentifier for the package.
38	eId, err := ExtractElementID(getLastPartOfURI(packageNode.ID))
39	if err != nil {
40		return nil, fmt.Errorf("error extracting elementID of a package identifier: %v", err)
41	}
42	pkg.PackageSPDXIdentifier = eId // 3.2
43
44	// check if we already have a package initialized for this ID
45	existingPackageIndex := -1
46	for ii, existingPkg := range parser.doc.Packages {
47		if existingPkg != nil && existingPkg.PackageSPDXIdentifier == eId {
48			existingPackageIndex = ii
49			pkg = existingPkg
50			break
51		}
52	}
53
54	// iterate over all the triples associated with the provided package packageNode.
55	for _, subTriple := range parser.nodeToTriples(packageNode) {
56		switch subTriple.Predicate.ID {
57		case RDF_TYPE:
58			// cardinality: exactly 1
59			continue
60		case SPDX_NAME: // 7.1
61			// cardinality: exactly 1
62			pkg.PackageName = subTriple.Object.ID
63		case SPDX_VERSION_INFO: // 7.3
64			// cardinality: max 1
65			pkg.PackageVersion = subTriple.Object.ID
66		case SPDX_PACKAGE_FILE_NAME: // 7.4
67			// cardinality: max 1
68			pkg.PackageFileName = subTriple.Object.ID
69		case SPDX_SUPPLIER: // 7.5
70			// cardinality: max 1
71			err = setPackageSupplier(pkg, subTriple.Object.ID)
72		case SPDX_ORIGINATOR: // 7.6
73			// cardinality: max 1
74			err = setPackageOriginator(pkg, subTriple.Object.ID)
75		case SPDX_DOWNLOAD_LOCATION: // 7.7
76			// cardinality: exactly 1
77			err = setDocumentLocationFromURI(pkg, subTriple.Object.ID)
78		case SPDX_FILES_ANALYZED: // 7.8
79			// cardinality: max 1
80			err = setFilesAnalyzed(pkg, subTriple.Object.ID)
81		case SPDX_PACKAGE_VERIFICATION_CODE: // 7.9
82			// cardinality: max 1
83			err = parser.setPackageVerificationCode(pkg, subTriple.Object)
84		case SPDX_CHECKSUM: // 7.10
85			// cardinality: min 0
86			err = parser.setPackageChecksum(pkg, subTriple.Object)
87		case DOAP_HOMEPAGE: // 7.11
88			// cardinality: max 1
89			// homepage must be a valid Uri
90			if !isUriValid(subTriple.Object.ID) {
91				return nil, fmt.Errorf("invalid uri %s while parsing doap_homepage in a package", subTriple.Object.ID)
92			}
93			pkg.PackageHomePage = subTriple.Object.ID
94		case SPDX_SOURCE_INFO: // 7.12
95			// cardinality: max 1
96			pkg.PackageSourceInfo = subTriple.Object.ID
97		case SPDX_LICENSE_CONCLUDED: // 7.13
98			// cardinality: exactly 1
99			anyLicenseInfo, err := parser.getAnyLicenseFromNode(subTriple.Object)
100			if err != nil {
101				return nil, err
102			}
103			pkg.PackageLicenseConcluded = anyLicenseInfo.ToLicenseString()
104		case SPDX_LICENSE_INFO_FROM_FILES: // 7.14
105			// cardinality: min 0
106			pkg.PackageLicenseInfoFromFiles = append(pkg.PackageLicenseInfoFromFiles, getLicenseStringFromURI(subTriple.Object.ID))
107		case SPDX_LICENSE_DECLARED: // 7.15
108			// cardinality: exactly 1
109			anyLicenseInfo, err := parser.getAnyLicenseFromNode(subTriple.Object)
110			if err != nil {
111				return nil, err
112			}
113			pkg.PackageLicenseDeclared = anyLicenseInfo.ToLicenseString()
114		case SPDX_LICENSE_COMMENTS: // 7.16
115			// cardinality: max 1
116			pkg.PackageLicenseComments = subTriple.Object.ID
117		case SPDX_COPYRIGHT_TEXT: // 7.17
118			// cardinality: exactly 1
119			pkg.PackageCopyrightText = subTriple.Object.ID
120		case SPDX_SUMMARY: // 7.18
121			// cardinality: max 1
122			pkg.PackageSummary = subTriple.Object.ID
123		case SPDX_DESCRIPTION: // 7.19
124			// cardinality: max 1
125			pkg.PackageDescription = subTriple.Object.ID
126		case RDFS_COMMENT: // 7.20
127			// cardinality: max 1
128			pkg.PackageComment = subTriple.Object.ID
129		case SPDX_EXTERNAL_REF: // 7.21
130			// cardinality: min 0
131			externalDocRef, err := parser.getPackageExternalRef(subTriple.Object)
132			if err != nil {
133				return nil, fmt.Errorf("error parsing externalRef of a package: %v", err)
134			}
135			pkg.PackageExternalReferences = append(pkg.PackageExternalReferences, externalDocRef)
136		case SPDX_HAS_FILE: // 7.22
137			// cardinality: min 0
138			file, err := parser.getFileFromNode(subTriple.Object)
139			if err != nil {
140				return nil, fmt.Errorf("error setting file inside a package: %v", err)
141			}
142			parser.setFileToPackage(pkg, file)
143		case SPDX_PRIMARY_PACKAGE_PURPOSE: // 7.24
144			// cardinality: exactly 1
145			pkg.PrimaryPackagePurpose = getPrimaryPackagePurpose(subTriple.Object.ID)
146		case SPDX_RELEASE_DATE: // 7.25
147			// cardinality: exactly 1
148			pkg.ReleaseDate = subTriple.Object.ID
149		case SPDX_BUILT_DATE: // 7.26
150			// cardinality: exactly 1
151			pkg.BuiltDate = subTriple.Object.ID
152		case SPDX_VALID_UNTIL_DATE: // 7.27
153			// cardinality: exactly 1
154			pkg.ValidUntilDate = subTriple.Object.ID
155		case SPDX_RELATIONSHIP:
156			// cardinality: min 0
157			err = parser.parseRelationship(subTriple)
158		case SPDX_ATTRIBUTION_TEXT:
159			// cardinality: min 0
160			pkg.PackageAttributionTexts = append(pkg.PackageAttributionTexts, subTriple.Object.ID)
161		case SPDX_ANNOTATION:
162			// cardinality: min 0
163			err = parser.parseAnnotationFromNode(subTriple.Object)
164		default:
165			return nil, fmt.Errorf("unknown predicate id %s while parsing a package", subTriple.Predicate.ID)
166		}
167		if err != nil {
168			return nil, err
169		}
170	}
171
172	if existingPackageIndex != -1 {
173		parser.doc.Packages[existingPackageIndex] = pkg
174	} else {
175		parser.doc.Packages = append(parser.doc.Packages, pkg)
176	}
177
178	return pkg, nil
179}
180
181// parses externalReference found in the package by the associated triple.
182func (parser *rdfParser2_3) getPackageExternalRef(node *gordfParser.Node) (externalDocRef *v2_3.PackageExternalReference, err error) {
183	externalDocRef = &v2_3.PackageExternalReference{}
184	for _, triple := range parser.nodeToTriples(node) {
185		switch triple.Predicate.ID {
186		case SPDX_REFERENCE_CATEGORY:
187			// cardinality: exactly 1
188			switch triple.Object.ID {
189			case SPDX_REFERENCE_CATEGORY_SECURITY:
190				externalDocRef.Category = "SECURITY"
191			case SPDX_REFERENCE_CATEGORY_PACKAGE_MANAGER:
192				externalDocRef.Category = "PACKAGE-MANAGER"
193			case SPDX_REFERENCE_CATEGORY_OTHER:
194				externalDocRef.Category = "OTHER"
195			default:
196				return nil, fmt.Errorf("unknown packageManager uri %s", triple.Predicate.ID)
197			}
198		case RDF_TYPE:
199			continue
200		case SPDX_REFERENCE_TYPE:
201			// assumes: the reference type is associated with just the uri and
202			// 			other associated fields are ignored.
203			// other fields include:
204			//		1. contextualExample,
205			//		2. documentation and,
206			//		3. externalReferenceSite
207			externalDocRef.RefType = triple.Object.ID
208		case SPDX_REFERENCE_LOCATOR:
209			// cardinality: exactly 1
210			externalDocRef.Locator = triple.Object.ID
211		case RDFS_COMMENT:
212			// cardinality: max 1
213			externalDocRef.ExternalRefComment = triple.Object.ID
214		default:
215			return nil, fmt.Errorf("unknown package external reference predicate id %s", triple.Predicate.ID)
216		}
217	}
218	return
219}
220
221func getPrimaryPackagePurpose(purpose string) string {
222	value := strings.ReplaceAll(purpose, "packagePurpose_", "")
223	value = strings.ReplaceAll(value, "_", "-")
224	value = strings.ToUpper(value)
225	switch value {
226	case "APPLICATION", "FRAMEWORK", "LIBRARY", "CONTAINER", "OPERATING-SYSTEM", "DEVICE", "FIRMWARE", "SOURCE", "ARCHIVE", "FILE", "INSTALL", "OTHER":
227		return value
228	}
229	// invalid value
230	return ""
231}
232
233func (parser *rdfParser2_3) setPackageVerificationCode(pkg *v2_3.Package, node *gordfParser.Node) error {
234	if pkg.PackageVerificationCode == nil {
235		pkg.PackageVerificationCode = &common.PackageVerificationCode{}
236	}
237	for _, subTriple := range parser.nodeToTriples(node) {
238		switch subTriple.Predicate.ID {
239		case SPDX_PACKAGE_VERIFICATION_CODE_VALUE:
240			// cardinality: exactly 1
241			pkg.PackageVerificationCode.Value = subTriple.Object.ID
242		case SPDX_PACKAGE_VERIFICATION_CODE_EXCLUDED_FILE:
243			// cardinality: min 0
244			pkg.PackageVerificationCode.ExcludedFiles = append(pkg.PackageVerificationCode.ExcludedFiles, subTriple.Object.ID)
245		case RDF_TYPE:
246			// cardinality: exactly 1
247			continue
248		default:
249			return fmt.Errorf("unparsed predicate %s", subTriple.Predicate.ID)
250		}
251	}
252	return nil
253}
254
255// appends the file to the package and also sets the assocWithPackage for the
256// file to indicate the file is associated with a package
257func (parser *rdfParser2_3) setFileToPackage(pkg *v2_3.Package, file *v2_3.File) {
258	if pkg.Files == nil {
259		pkg.Files = []*v2_3.File{}
260	}
261	pkg.Files = append(pkg.Files, file)
262	parser.assocWithPackage[file.FileSPDXIdentifier] = true
263}
264
265// given a supplierObject, sets the PackageSupplier attribute of the pkg.
266// Args:
267//
268//	value: [NOASSERTION | [Person | Organization]: string]
269func setPackageSupplier(pkg *v2_3.Package, value string) error {
270	value = strings.TrimSpace(value)
271	supplier := &common.Supplier{}
272	if strings.ToUpper(value) == "NOASSERTION" {
273		supplier.Supplier = "NOASSERTION"
274		pkg.PackageSupplier = supplier
275		return nil
276	}
277
278	subKey, subValue, err := ExtractSubs(value, ":")
279	if err != nil {
280		return fmt.Errorf("package supplier must be of the form NOASSERTION or [Person|Organization]: string. found: %s", value)
281	}
282	switch subKey {
283	case "Person", "Organization":
284		supplier.Supplier = subValue
285		supplier.SupplierType = subKey
286	default:
287		return fmt.Errorf("unknown supplier %s", subKey)
288	}
289
290	pkg.PackageSupplier = supplier
291
292	return nil
293}
294
295// given a OriginatorObject, sets the PackageOriginator attribute of the pkg.
296// Args:
297//
298//	value: [NOASSERTION | [Person | Organization]: string]
299func setPackageOriginator(pkg *v2_3.Package, value string) error {
300	value = strings.TrimSpace(value)
301	originator := &common.Originator{}
302	if strings.ToUpper(value) == "NOASSERTION" {
303		originator.Originator = "NOASSERTION"
304		pkg.PackageOriginator = originator
305		return nil
306	}
307
308	subKey, subValue, err := ExtractSubs(value, ":")
309	if err != nil {
310		return fmt.Errorf("package Originator must be of the form NOASSERTION or [Person|Organization]: string. found: %s", value)
311	}
312	switch subKey {
313	case "Person", "Organization":
314		originator.Originator = subValue
315		originator.OriginatorType = subKey
316	default:
317		return fmt.Errorf("unknown Originator %s", subKey)
318	}
319
320	pkg.PackageOriginator = originator
321
322	return nil
323}
324
325// validates the uri and sets the location if it is valid
326func setDocumentLocationFromURI(pkg *v2_3.Package, locationURI string) error {
327	switch locationURI {
328	case SPDX_NOASSERTION_CAPS, SPDX_NOASSERTION_SMALL:
329		pkg.PackageDownloadLocation = "NOASSERTION"
330	case SPDX_NONE_CAPS, SPDX_NONE_SMALL:
331		pkg.PackageDownloadLocation = "NONE"
332	default:
333		if !isUriValid(locationURI) {
334			return fmt.Errorf("%s is not a valid uri", locationURI)
335		}
336		pkg.PackageDownloadLocation = locationURI
337	}
338	return nil
339}
340
341// sets the FilesAnalyzed attribute to the given package
342// boolValue is a string of type "true" or "false"
343func setFilesAnalyzed(pkg *v2_3.Package, boolValue string) (err error) {
344	pkg.IsFilesAnalyzedTagPresent = true
345	pkg.FilesAnalyzed, err = boolFromString(boolValue)
346	return err
347}
348
349func (parser *rdfParser2_3) setPackageChecksum(pkg *v2_3.Package, node *gordfParser.Node) error {
350	checksumAlgorithm, checksumValue, err := parser.getChecksumFromNode(node)
351	if err != nil {
352		return fmt.Errorf("error getting checksum algorithm and value from %v", node)
353	}
354	if pkg.PackageChecksums == nil {
355		pkg.PackageChecksums = make([]common.Checksum, 0, 1)
356	}
357	switch checksumAlgorithm {
358	case common.SHA1,
359		common.SHA224,
360		common.SHA256,
361		common.SHA384,
362		common.SHA512,
363		common.MD2,
364		common.MD4,
365		common.MD5,
366		common.MD6,
367		common.SHA3_256,
368		common.SHA3_384,
369		common.SHA3_512,
370		common.BLAKE2b_256,
371		common.BLAKE2b_384,
372		common.BLAKE2b_512,
373		common.BLAKE3,
374		common.ADLER32:
375		pkg.PackageChecksums = append(pkg.PackageChecksums, common.Checksum{Algorithm: checksumAlgorithm, Value: checksumValue})
376	default:
377		return fmt.Errorf("unknown checksumAlgorithm %s while parsing a package", checksumAlgorithm)
378	}
379	return nil
380}
381