// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later package parser2v3 import ( "fmt" "strings" gordfParser "github.com/spdx/gordf/rdfloader/parser" "github.com/spdx/tools-golang/spdx/common" "github.com/spdx/tools-golang/spdx/v2_3" ) func (parser *rdfParser2_3) getPackageFromNode(packageNode *gordfParser.Node) (pkg *v2_3.Package, err error) { pkg = &v2_3.Package{} // new package which will be returned currState := parser.cache[packageNode.ID] if currState == nil { // there is no entry about the state of current package node. // this is the first time we're seeing this node. parser.cache[packageNode.ID] = &nodeState{ object: pkg, Color: WHITE, } } else if currState.Color == GREY { // we have already started parsing this package node and we needn't parse it again. return currState.object.(*v2_3.Package), nil } // setting color of the state to grey to indicate that we've started to // parse this node once. parser.cache[packageNode.ID].Color = GREY // setting state color to black to indicate when we're done parsing this node. defer func() { parser.cache[packageNode.ID].Color = BLACK }() // setting the SPDXIdentifier for the package. eId, err := ExtractElementID(getLastPartOfURI(packageNode.ID)) if err != nil { return nil, fmt.Errorf("error extracting elementID of a package identifier: %v", err) } pkg.PackageSPDXIdentifier = eId // 3.2 // check if we already have a package initialized for this ID existingPackageIndex := -1 for ii, existingPkg := range parser.doc.Packages { if existingPkg != nil && existingPkg.PackageSPDXIdentifier == eId { existingPackageIndex = ii pkg = existingPkg break } } // iterate over all the triples associated with the provided package packageNode. for _, subTriple := range parser.nodeToTriples(packageNode) { switch subTriple.Predicate.ID { case RDF_TYPE: // cardinality: exactly 1 continue case SPDX_NAME: // 7.1 // cardinality: exactly 1 pkg.PackageName = subTriple.Object.ID case SPDX_VERSION_INFO: // 7.3 // cardinality: max 1 pkg.PackageVersion = subTriple.Object.ID case SPDX_PACKAGE_FILE_NAME: // 7.4 // cardinality: max 1 pkg.PackageFileName = subTriple.Object.ID case SPDX_SUPPLIER: // 7.5 // cardinality: max 1 err = setPackageSupplier(pkg, subTriple.Object.ID) case SPDX_ORIGINATOR: // 7.6 // cardinality: max 1 err = setPackageOriginator(pkg, subTriple.Object.ID) case SPDX_DOWNLOAD_LOCATION: // 7.7 // cardinality: exactly 1 err = setDocumentLocationFromURI(pkg, subTriple.Object.ID) case SPDX_FILES_ANALYZED: // 7.8 // cardinality: max 1 err = setFilesAnalyzed(pkg, subTriple.Object.ID) case SPDX_PACKAGE_VERIFICATION_CODE: // 7.9 // cardinality: max 1 err = parser.setPackageVerificationCode(pkg, subTriple.Object) case SPDX_CHECKSUM: // 7.10 // cardinality: min 0 err = parser.setPackageChecksum(pkg, subTriple.Object) case DOAP_HOMEPAGE: // 7.11 // cardinality: max 1 // homepage must be a valid Uri if !isUriValid(subTriple.Object.ID) { return nil, fmt.Errorf("invalid uri %s while parsing doap_homepage in a package", subTriple.Object.ID) } pkg.PackageHomePage = subTriple.Object.ID case SPDX_SOURCE_INFO: // 7.12 // cardinality: max 1 pkg.PackageSourceInfo = subTriple.Object.ID case SPDX_LICENSE_CONCLUDED: // 7.13 // cardinality: exactly 1 anyLicenseInfo, err := parser.getAnyLicenseFromNode(subTriple.Object) if err != nil { return nil, err } pkg.PackageLicenseConcluded = anyLicenseInfo.ToLicenseString() case SPDX_LICENSE_INFO_FROM_FILES: // 7.14 // cardinality: min 0 pkg.PackageLicenseInfoFromFiles = append(pkg.PackageLicenseInfoFromFiles, getLicenseStringFromURI(subTriple.Object.ID)) case SPDX_LICENSE_DECLARED: // 7.15 // cardinality: exactly 1 anyLicenseInfo, err := parser.getAnyLicenseFromNode(subTriple.Object) if err != nil { return nil, err } pkg.PackageLicenseDeclared = anyLicenseInfo.ToLicenseString() case SPDX_LICENSE_COMMENTS: // 7.16 // cardinality: max 1 pkg.PackageLicenseComments = subTriple.Object.ID case SPDX_COPYRIGHT_TEXT: // 7.17 // cardinality: exactly 1 pkg.PackageCopyrightText = subTriple.Object.ID case SPDX_SUMMARY: // 7.18 // cardinality: max 1 pkg.PackageSummary = subTriple.Object.ID case SPDX_DESCRIPTION: // 7.19 // cardinality: max 1 pkg.PackageDescription = subTriple.Object.ID case RDFS_COMMENT: // 7.20 // cardinality: max 1 pkg.PackageComment = subTriple.Object.ID case SPDX_EXTERNAL_REF: // 7.21 // cardinality: min 0 externalDocRef, err := parser.getPackageExternalRef(subTriple.Object) if err != nil { return nil, fmt.Errorf("error parsing externalRef of a package: %v", err) } pkg.PackageExternalReferences = append(pkg.PackageExternalReferences, externalDocRef) case SPDX_HAS_FILE: // 7.22 // cardinality: min 0 file, err := parser.getFileFromNode(subTriple.Object) if err != nil { return nil, fmt.Errorf("error setting file inside a package: %v", err) } parser.setFileToPackage(pkg, file) case SPDX_PRIMARY_PACKAGE_PURPOSE: // 7.24 // cardinality: exactly 1 pkg.PrimaryPackagePurpose = getPrimaryPackagePurpose(subTriple.Object.ID) case SPDX_RELEASE_DATE: // 7.25 // cardinality: exactly 1 pkg.ReleaseDate = subTriple.Object.ID case SPDX_BUILT_DATE: // 7.26 // cardinality: exactly 1 pkg.BuiltDate = subTriple.Object.ID case SPDX_VALID_UNTIL_DATE: // 7.27 // cardinality: exactly 1 pkg.ValidUntilDate = subTriple.Object.ID case SPDX_RELATIONSHIP: // cardinality: min 0 err = parser.parseRelationship(subTriple) case SPDX_ATTRIBUTION_TEXT: // cardinality: min 0 pkg.PackageAttributionTexts = append(pkg.PackageAttributionTexts, subTriple.Object.ID) case SPDX_ANNOTATION: // cardinality: min 0 err = parser.parseAnnotationFromNode(subTriple.Object) default: return nil, fmt.Errorf("unknown predicate id %s while parsing a package", subTriple.Predicate.ID) } if err != nil { return nil, err } } if existingPackageIndex != -1 { parser.doc.Packages[existingPackageIndex] = pkg } else { parser.doc.Packages = append(parser.doc.Packages, pkg) } return pkg, nil } // parses externalReference found in the package by the associated triple. func (parser *rdfParser2_3) getPackageExternalRef(node *gordfParser.Node) (externalDocRef *v2_3.PackageExternalReference, err error) { externalDocRef = &v2_3.PackageExternalReference{} for _, triple := range parser.nodeToTriples(node) { switch triple.Predicate.ID { case SPDX_REFERENCE_CATEGORY: // cardinality: exactly 1 switch triple.Object.ID { case SPDX_REFERENCE_CATEGORY_SECURITY: externalDocRef.Category = "SECURITY" case SPDX_REFERENCE_CATEGORY_PACKAGE_MANAGER: externalDocRef.Category = "PACKAGE-MANAGER" case SPDX_REFERENCE_CATEGORY_OTHER: externalDocRef.Category = "OTHER" default: return nil, fmt.Errorf("unknown packageManager uri %s", triple.Predicate.ID) } case RDF_TYPE: continue case SPDX_REFERENCE_TYPE: // assumes: the reference type is associated with just the uri and // other associated fields are ignored. // other fields include: // 1. contextualExample, // 2. documentation and, // 3. externalReferenceSite externalDocRef.RefType = triple.Object.ID case SPDX_REFERENCE_LOCATOR: // cardinality: exactly 1 externalDocRef.Locator = triple.Object.ID case RDFS_COMMENT: // cardinality: max 1 externalDocRef.ExternalRefComment = triple.Object.ID default: return nil, fmt.Errorf("unknown package external reference predicate id %s", triple.Predicate.ID) } } return } func getPrimaryPackagePurpose(purpose string) string { value := strings.ReplaceAll(purpose, "packagePurpose_", "") value = strings.ReplaceAll(value, "_", "-") value = strings.ToUpper(value) switch value { case "APPLICATION", "FRAMEWORK", "LIBRARY", "CONTAINER", "OPERATING-SYSTEM", "DEVICE", "FIRMWARE", "SOURCE", "ARCHIVE", "FILE", "INSTALL", "OTHER": return value } // invalid value return "" } func (parser *rdfParser2_3) setPackageVerificationCode(pkg *v2_3.Package, node *gordfParser.Node) error { if pkg.PackageVerificationCode == nil { pkg.PackageVerificationCode = &common.PackageVerificationCode{} } for _, subTriple := range parser.nodeToTriples(node) { switch subTriple.Predicate.ID { case SPDX_PACKAGE_VERIFICATION_CODE_VALUE: // cardinality: exactly 1 pkg.PackageVerificationCode.Value = subTriple.Object.ID case SPDX_PACKAGE_VERIFICATION_CODE_EXCLUDED_FILE: // cardinality: min 0 pkg.PackageVerificationCode.ExcludedFiles = append(pkg.PackageVerificationCode.ExcludedFiles, subTriple.Object.ID) case RDF_TYPE: // cardinality: exactly 1 continue default: return fmt.Errorf("unparsed predicate %s", subTriple.Predicate.ID) } } return nil } // appends the file to the package and also sets the assocWithPackage for the // file to indicate the file is associated with a package func (parser *rdfParser2_3) setFileToPackage(pkg *v2_3.Package, file *v2_3.File) { if pkg.Files == nil { pkg.Files = []*v2_3.File{} } pkg.Files = append(pkg.Files, file) parser.assocWithPackage[file.FileSPDXIdentifier] = true } // given a supplierObject, sets the PackageSupplier attribute of the pkg. // Args: // value: [NOASSERTION | [Person | Organization]: string] func setPackageSupplier(pkg *v2_3.Package, value string) error { value = strings.TrimSpace(value) supplier := &common.Supplier{} if strings.ToUpper(value) == "NOASSERTION" { supplier.Supplier = "NOASSERTION" pkg.PackageSupplier = supplier return nil } subKey, subValue, err := ExtractSubs(value, ":") if err != nil { return fmt.Errorf("package supplier must be of the form NOASSERTION or [Person|Organization]: string. found: %s", value) } switch subKey { case "Person", "Organization": supplier.Supplier = subValue supplier.SupplierType = subKey default: return fmt.Errorf("unknown supplier %s", subKey) } pkg.PackageSupplier = supplier return nil } // given a OriginatorObject, sets the PackageOriginator attribute of the pkg. // Args: // value: [NOASSERTION | [Person | Organization]: string] func setPackageOriginator(pkg *v2_3.Package, value string) error { value = strings.TrimSpace(value) originator := &common.Originator{} if strings.ToUpper(value) == "NOASSERTION" { originator.Originator = "NOASSERTION" pkg.PackageOriginator = originator return nil } subKey, subValue, err := ExtractSubs(value, ":") if err != nil { return fmt.Errorf("package Originator must be of the form NOASSERTION or [Person|Organization]: string. found: %s", value) } switch subKey { case "Person", "Organization": originator.Originator = subValue originator.OriginatorType = subKey default: return fmt.Errorf("unknown Originator %s", subKey) } pkg.PackageOriginator = originator return nil } // validates the uri and sets the location if it is valid func setDocumentLocationFromURI(pkg *v2_3.Package, locationURI string) error { switch locationURI { case SPDX_NOASSERTION_CAPS, SPDX_NOASSERTION_SMALL: pkg.PackageDownloadLocation = "NOASSERTION" case SPDX_NONE_CAPS, SPDX_NONE_SMALL: pkg.PackageDownloadLocation = "NONE" default: if !isUriValid(locationURI) { return fmt.Errorf("%s is not a valid uri", locationURI) } pkg.PackageDownloadLocation = locationURI } return nil } // sets the FilesAnalyzed attribute to the given package // boolValue is a string of type "true" or "false" func setFilesAnalyzed(pkg *v2_3.Package, boolValue string) (err error) { pkg.IsFilesAnalyzedTagPresent = true pkg.FilesAnalyzed, err = boolFromString(boolValue) return err } func (parser *rdfParser2_3) setPackageChecksum(pkg *v2_3.Package, node *gordfParser.Node) error { checksumAlgorithm, checksumValue, err := parser.getChecksumFromNode(node) if err != nil { return fmt.Errorf("error getting checksum algorithm and value from %v", node) } if pkg.PackageChecksums == nil { pkg.PackageChecksums = make([]common.Checksum, 0, 1) } switch checksumAlgorithm { case common.MD5, common.SHA1, common.SHA256: pkg.PackageChecksums = append(pkg.PackageChecksums, common.Checksum{Algorithm: checksumAlgorithm, Value: checksumValue}) default: return fmt.Errorf("unknown checksumAlgorithm %s while parsing a package", checksumAlgorithm) } return nil }