diff --git a/builder/builder2v1/build_package.go b/builder/builder2v1/build_package.go index f39bb536..127fc810 100644 --- a/builder/builder2v1/build_package.go +++ b/builder/builder2v1/build_package.go @@ -52,7 +52,7 @@ func BuildPackageSection2_1(packageName string, dirRoot string, pathsIgnore []st fileNumber++ } // get the verification code - code, err := utils.GetVerificationCode2_1(files, "") + code, err := spdx.MakePackageVerificationCode2_1(files, "") if err != nil { return nil, err } diff --git a/builder/builder2v2/build_package.go b/builder/builder2v2/build_package.go index 9c460da8..98ca44d1 100644 --- a/builder/builder2v2/build_package.go +++ b/builder/builder2v2/build_package.go @@ -53,7 +53,7 @@ func BuildPackageSection2_2(packageName string, dirRoot string, pathsIgnore []st } // get the verification code - code, err := utils.GetVerificationCode2_2(files, "") + code, err := spdx.MakePackageVerificationCode2_2(files, "") if err != nil { return nil, err } diff --git a/json/json_test.go b/json/json_test.go index c78013cb..845986c6 100644 --- a/json/json_test.go +++ b/json/json_test.go @@ -61,7 +61,7 @@ func TestWrite2_2(t *testing.T) { var want = spdx.Document2_2{ DataLicense: "CC0-1.0", SPDXVersion: "SPDX-2.2", - SPDXIdentifier: "SPDXRef-DOCUMENT", + SPDXIdentifier: "DOCUMENT", DocumentName: "SPDX-Tools-v2.0", DocumentNamespace: "http://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301", CreationInfo: &spdx.CreationInfo2_2{ @@ -77,7 +77,7 @@ var want = spdx.Document2_2{ DocumentComment: "This document was created using SPDX 2.0 using licenses from the web site.", ExternalDocumentReferences: []spdx.ExternalDocumentRef2_2{ { - DocumentRefID: "DocumentRef-spdx-tool-1.2", + DocumentRefID: spdx.MakeDocElementID("spdx-tool-1.2", ""), URI: "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301", Checksum: spdx.Checksum{ Algorithm: spdx.SHA1, @@ -148,7 +148,7 @@ var want = spdx.Document2_2{ Packages: []*spdx.Package2_2{ { PackageName: "glibc", - PackageSPDXIdentifier: "SPDXRef-Package", + PackageSPDXIdentifier: "Package", PackageVersion: "2.11.1", PackageFileName: "glibc-2.11.1.tar.gz", PackageSupplier: &spdx.Supplier{ @@ -223,7 +223,7 @@ var want = spdx.Document2_2{ }, }, { - PackageSPDXIdentifier: "SPDXRef-fromDoap-1", + PackageSPDXIdentifier: "fromDoap-1", PackageCopyrightText: "NOASSERTION", PackageDownloadLocation: "NOASSERTION", FilesAnalyzed: false, @@ -234,7 +234,7 @@ var want = spdx.Document2_2{ }, { PackageName: "Jena", - PackageSPDXIdentifier: "SPDXRef-fromDoap-0", + PackageSPDXIdentifier: "fromDoap-0", PackageCopyrightText: "NOASSERTION", PackageDownloadLocation: "https://search.maven.org/remotecontent?filepath=org/apache/jena/apache-jena/3.12.0/apache-jena-3.12.0.tar.gz", PackageExternalReferences: []*spdx.PackageExternalReference2_2{ @@ -251,7 +251,7 @@ var want = spdx.Document2_2{ PackageVersion: "3.12.0", }, { - PackageSPDXIdentifier: "SPDXRef-Saxon", + PackageSPDXIdentifier: "Saxon", PackageChecksums: []spdx.Checksum{ { Algorithm: "SHA1", @@ -274,7 +274,7 @@ var want = spdx.Document2_2{ Files: []*spdx.File2_2{ { FileName: "./src/org/spdx/parser/DOAPProject.java", - FileSPDXIdentifier: "SPDXRef-DoapSource", + FileSPDXIdentifier: "DoapSource", FileTypes: []string{ "SOURCE", }, @@ -298,7 +298,7 @@ var want = spdx.Document2_2{ }, }, { - FileSPDXIdentifier: "SPDXRef-CommonsLangSrc", + FileSPDXIdentifier: "CommonsLangSrc", Checksums: []spdx.Checksum{ { Algorithm: "SHA1", @@ -315,7 +315,7 @@ var want = spdx.Document2_2{ FileNotice: "Apache Commons Lang\nCopyright 2001-2011 The Apache Software Foundation\n\nThis product includes software developed by\nThe Apache Software Foundation (http://www.apache.org/).\n\nThis product includes software from the Spring Framework,\nunder the Apache License 2.0 (see: StringUtils.containsWhitespace())", }, { - FileSPDXIdentifier: "SPDXRef-JenaLib", + FileSPDXIdentifier: "JenaLib", Checksums: []spdx.Checksum{ { Algorithm: "SHA1", @@ -332,7 +332,7 @@ var want = spdx.Document2_2{ LicenseInfoInFiles: []string{"LicenseRef-1"}, }, { - FileSPDXIdentifier: "SPDXRef-File", + FileSPDXIdentifier: "File", Annotations: []spdx.Annotation2_2{ { Annotator: spdx.Annotator{ @@ -367,27 +367,27 @@ var want = spdx.Document2_2{ }, Snippets: []spdx.Snippet2_2{ { - SnippetSPDXIdentifier: "SPDXRef-Snippet", - SnippetFromFileSPDXIdentifier: "SPDXRef-DoapSource", + SnippetSPDXIdentifier: "Snippet", + SnippetFromFileSPDXIdentifier: "DoapSource", Ranges: []spdx.SnippetRange{ { StartPointer: spdx.SnippetRangePointer{ Offset: 310, - FileSPDXIdentifier: "SPDXRef-DoapSource", + FileSPDXIdentifier: "DoapSource", }, EndPointer: spdx.SnippetRangePointer{ Offset: 420, - FileSPDXIdentifier: "SPDXRef-DoapSource", + FileSPDXIdentifier: "DoapSource", }, }, { StartPointer: spdx.SnippetRangePointer{ LineNumber: 5, - FileSPDXIdentifier: "SPDXRef-DoapSource", + FileSPDXIdentifier: "DoapSource", }, EndPointer: spdx.SnippetRangePointer{ LineNumber: 23, - FileSPDXIdentifier: "SPDXRef-DoapSource", + FileSPDXIdentifier: "DoapSource", }, }, }, diff --git a/rdfloader/parser2v2/parse_annotation.go b/rdfloader/parser2v2/parse_annotation.go index 18e4533d..5925aca0 100644 --- a/rdfloader/parser2v2/parse_annotation.go +++ b/rdfloader/parser2v2/parse_annotation.go @@ -7,6 +7,7 @@ import ( "fmt" gordfParser "github.com/spdx/gordf/rdfloader/parser" "github.com/spdx/tools-golang/spdx" + "github.com/spdx/tools-golang/utils" ) // creates a new instance of annotation and sets the annotation attributes @@ -54,7 +55,7 @@ func setAnnotationToParser(parser *rdfParser2_2, annotation *spdx.Annotation2_2) // annotator is of type [Person|Organization|Tool]:String func setAnnotatorFromString(annotatorString string, ann *spdx.Annotation2_2) error { - subkey, subvalue, err := ExtractSubs(annotatorString, ":") + subkey, subvalue, err := utils.ExtractSubs(annotatorString) if err != nil { return err } diff --git a/rdfloader/parser2v2/parse_creation_info.go b/rdfloader/parser2v2/parse_creation_info.go index dc4da77e..73499977 100644 --- a/rdfloader/parser2v2/parse_creation_info.go +++ b/rdfloader/parser2v2/parse_creation_info.go @@ -6,6 +6,7 @@ import ( "fmt" gordfParser "github.com/spdx/gordf/rdfloader/parser" "github.com/spdx/tools-golang/spdx" + "github.com/spdx/tools-golang/utils" ) // Cardinality: Mandatory, one. @@ -36,7 +37,7 @@ func (parser *rdfParser2_2) parseCreationInfoFromNode(ci *spdx.CreationInfo2_2, } func setCreator(creatorStr string, ci *spdx.CreationInfo2_2) error { - entityType, entity, err := ExtractSubs(creatorStr, ":") + entityType, entity, err := utils.ExtractSubs(creatorStr) if err != nil { return fmt.Errorf("error setting creator of a creation info: %s", err) } diff --git a/rdfloader/parser2v2/parse_file.go b/rdfloader/parser2v2/parse_file.go index a149712d..b9c0f8db 100644 --- a/rdfloader/parser2v2/parse_file.go +++ b/rdfloader/parser2v2/parse_file.go @@ -189,7 +189,7 @@ func (parser *rdfParser2_2) setUnpackagedFiles() { func setFileIdentifier(idURI string, file *spdx.File2_2) (err error) { idURI = strings.TrimSpace(idURI) uriFragment := getLastPartOfURI(idURI) - file.FileSPDXIdentifier, err = ExtractElementID(uriFragment) + err = file.FileSPDXIdentifier.FromString(uriFragment) if err != nil { return fmt.Errorf("error setting file identifier: %s", err) } diff --git a/rdfloader/parser2v2/parse_license.go b/rdfloader/parser2v2/parse_license.go index 4b4ad46b..009cef03 100644 --- a/rdfloader/parser2v2/parse_license.go +++ b/rdfloader/parser2v2/parse_license.go @@ -7,6 +7,7 @@ import ( "fmt" gordfParser "github.com/spdx/gordf/rdfloader/parser" "github.com/spdx/gordf/rdfwriter" + "strconv" "strings" ) @@ -37,7 +38,7 @@ func (parser *rdfParser2_2) getAnyLicenseFromNode(node *gordfParser.Node) (AnyLi parser.cache[node.ID].Color = GREY // setting state color to black when we're done parsing this node. - defer func(){parser.cache[node.ID].Color = BLACK}() + defer func() { parser.cache[node.ID].Color = BLACK }() associatedTriples := rdfwriter.FilterTriples(parser.gordfParserObj.Triples, &node.ID, nil, nil) if len(associatedTriples) == 0 { @@ -256,7 +257,7 @@ func (parser *rdfParser2_2) getLicenseFromNode(node *gordfParser.Node) (lic Lice value := triple.Object.ID switch triple.Predicate.ID { case SPDX_IS_OSI_APPROVED: - lic.isOsiApproved, err = boolFromString(value) + lic.isOsiApproved, err = strconv.ParseBool(value) if err != nil { return lic, fmt.Errorf("error parsing isOsiApproved attribute of a License: %v", err) } @@ -269,12 +270,12 @@ func (parser *rdfParser2_2) getLicenseFromNode(node *gordfParser.Node) (lic Lice case SPDX_STANDARD_LICENSE_HEADER_TEMPLATE: lic.standardLicenseHeaderTemplate = value case SPDX_IS_DEPRECATED_LICENSE_ID: - lic.isDeprecatedLicenseID, err = boolFromString(value) + lic.isDeprecatedLicenseID, err = strconv.ParseBool(value) if err != nil { return lic, fmt.Errorf("error parsing isDeprecatedLicenseId attribute of a License: %v", err) } case SPDX_IS_FSF_LIBRE: - lic.isFsfLibre, err = boolFromString(value) + lic.isFsfLibre, err = strconv.ParseBool(value) if err != nil { return lic, fmt.Errorf("error parsing isFsfLibre attribute of a License: %v", err) } diff --git a/rdfloader/parser2v2/parse_package.go b/rdfloader/parser2v2/parse_package.go index 41ccab30..ef059d91 100644 --- a/rdfloader/parser2v2/parse_package.go +++ b/rdfloader/parser2v2/parse_package.go @@ -4,6 +4,8 @@ package parser2v2 import ( "fmt" + "github.com/spdx/tools-golang/utils" + "strconv" "strings" gordfParser "github.com/spdx/gordf/rdfloader/parser" @@ -34,16 +36,16 @@ func (parser *rdfParser2_2) getPackageFromNode(packageNode *gordfParser.Node) (p defer func() { parser.cache[packageNode.ID].Color = BLACK }() // setting the SPDXIdentifier for the package. - eId, err := ExtractElementID(getLastPartOfURI(packageNode.ID)) + // 3.2 + err = pkg.PackageSPDXIdentifier.FromString(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 { + if existingPkg != nil && existingPkg.PackageSPDXIdentifier == pkg.PackageSPDXIdentifier { existingPackageIndex = ii pkg = existingPkg break @@ -246,7 +248,7 @@ func setPackageSupplier(pkg *spdx.Package2_2, value string) error { return nil } - subKey, subValue, err := ExtractSubs(value, ":") + subKey, subValue, err := utils.ExtractSubs(value) if err != nil { return fmt.Errorf("package supplier must be of the form NOASSERTION or [Person|Organization]: string. found: %s", value) } @@ -275,7 +277,7 @@ func setPackageOriginator(pkg *spdx.Package2_2, value string) error { return nil } - subKey, subValue, err := ExtractSubs(value, ":") + subKey, subValue, err := utils.ExtractSubs(value) if err != nil { return fmt.Errorf("package Originator must be of the form NOASSERTION or [Person|Organization]: string. found: %s", value) } @@ -312,7 +314,7 @@ func setDocumentLocationFromURI(pkg *spdx.Package2_2, locationURI string) error // boolValue is a string of type "true" or "false" func setFilesAnalyzed(pkg *spdx.Package2_2, boolValue string) (err error) { pkg.IsFilesAnalyzedTagPresent = true - pkg.FilesAnalyzed, err = boolFromString(boolValue) + pkg.FilesAnalyzed, err = strconv.ParseBool(boolValue) return err } diff --git a/rdfloader/parser2v2/parse_relationship.go b/rdfloader/parser2v2/parse_relationship.go index c4e85403..f997eea3 100644 --- a/rdfloader/parser2v2/parse_relationship.go +++ b/rdfloader/parser2v2/parse_relationship.go @@ -40,7 +40,7 @@ func (parser *rdfParser2_2) parseRelationship(triple *gordfParser.Triple) (err e parser.cache[triple.Object.ID].Color = GREY // setting state color to black to indicate when we're done parsing this node. - defer func(){parser.cache[triple.Object.ID].Color = BLACK}(); + defer func() { parser.cache[triple.Object.ID].Color = BLACK }() for _, subTriple := range parser.nodeToTriples(triple.Object) { switch subTriple.Predicate.ID { @@ -112,7 +112,7 @@ func (parser *rdfParser2_2) parseRelatedElementFromTriple(reln *spdx.Relationshi case SPDX_SPDX_ELEMENT: // it shouldn't be associated with any other triple. // it must be a uri reference. - reln.RefB, err = ExtractDocElementID(getLastPartOfURI(triple.Subject.ID)) + err = reln.RefB.FromString(getLastPartOfURI(triple.Subject.ID)) if err != nil { return err } @@ -124,15 +124,10 @@ func (parser *rdfParser2_2) parseRelatedElementFromTriple(reln *spdx.Relationshi // references like RefA and RefB of any relationship func getReferenceFromURI(uri string) (spdx.DocElementID, error) { + var docElementID spdx.DocElementID fragment := getLastPartOfURI(uri) - switch strings.ToLower(strings.TrimSpace(fragment)) { - case "noassertion", "none": - return spdx.DocElementID{ - DocumentRefID: "", - ElementRefID: spdx.ElementID(strings.ToUpper(fragment)), - }, nil - } - return ExtractDocElementID(fragment) + err := docElementID.FromString(fragment) + return docElementID, err } // note: relationshipType is case sensitive. diff --git a/rdfloader/parser2v2/parse_relationship_test.go b/rdfloader/parser2v2/parse_relationship_test.go index 14f4c129..c26e51cc 100644 --- a/rdfloader/parser2v2/parse_relationship_test.go +++ b/rdfloader/parser2v2/parse_relationship_test.go @@ -18,7 +18,7 @@ func Test_getReferenceFromURI(t *testing.T) { if ref.DocumentRefID != "" { t.Errorf("reference's documentRefID should've been empty, found %s", ref.DocumentRefID) } - if ref.ElementRefID != "NOASSERTION" { + if ref.SpecialID != "NOASSERTION" { t.Errorf("mismatching elementRefID. Found %s, expected %s", ref.ElementRefID, "NOASSERTION") } @@ -30,7 +30,7 @@ func Test_getReferenceFromURI(t *testing.T) { if ref.DocumentRefID != "" { t.Errorf("reference's documentRefID should've been empty, found %s", ref.DocumentRefID) } - if ref.ElementRefID != "NONE" { + if ref.SpecialID != "NONE" { t.Errorf("mismatching elementRefID. Found %s, expected %s", ref.ElementRefID, "NONE") } diff --git a/rdfloader/parser2v2/parse_review.go b/rdfloader/parser2v2/parse_review.go index 437042d5..4de6fb4d 100644 --- a/rdfloader/parser2v2/parse_review.go +++ b/rdfloader/parser2v2/parse_review.go @@ -6,6 +6,7 @@ import ( "fmt" gordfParser "github.com/spdx/gordf/rdfloader/parser" "github.com/spdx/tools-golang/spdx" + "github.com/spdx/tools-golang/utils" ) func (parser *rdfParser2_2) setReviewFromNode(reviewedNode *gordfParser.Node) error { @@ -24,7 +25,7 @@ func (parser *rdfParser2_2) setReviewFromNode(reviewedNode *gordfParser.Node) er case SPDX_REVIEWER: // cardinality: max 1 var err error - review.ReviewerType, review.Reviewer, err = ExtractSubs(triple.Object.ID, ":") + review.ReviewerType, review.Reviewer, err = utils.ExtractSubs(triple.Object.ID) if err != nil { return fmt.Errorf("error parsing reviewer: %v", err) } diff --git a/rdfloader/parser2v2/parse_snippet_info.go b/rdfloader/parser2v2/parse_snippet_info.go index a09d6716..1c115639 100644 --- a/rdfloader/parser2v2/parse_snippet_info.go +++ b/rdfloader/parser2v2/parse_snippet_info.go @@ -31,7 +31,11 @@ func (parser *rdfParser2_2) getSnippetInformationFromNode2_2(node *gordfParser.N if err != nil { return nil, err } - docElemID, err := ExtractDocElementID(getLastPartOfURI(siTriple.Object.ID)) + var docElemID spdx.DocElementID + err = docElemID.FromString(getLastPartOfURI(siTriple.Object.ID)) + if err != nil { + return nil, err + } si.SnippetFromFileSPDXIdentifier = docElemID.ElementRefID case SPDX_RANGE: // cardinality: min 1 @@ -189,7 +193,7 @@ func (parser *rdfParser2_2) parseRangeReference(node *gordfParser.Node, snippet func setSnippetID(uri string, si *spdx.Snippet2_2) (err error) { fragment := getLastPartOfURI(uri) - si.SnippetSPDXIdentifier, err = ExtractElementID(fragment) + err = si.SnippetSPDXIdentifier.FromString(fragment) if err != nil { return fmt.Errorf("error setting snippet identifier: %v", uri) } diff --git a/rdfloader/parser2v2/parse_spdx_document.go b/rdfloader/parser2v2/parse_spdx_document.go index 61593172..8a992370 100644 --- a/rdfloader/parser2v2/parse_spdx_document.go +++ b/rdfloader/parser2v2/parse_spdx_document.go @@ -6,6 +6,7 @@ import ( "fmt" gordfParser "github.com/spdx/gordf/rdfloader/parser" "github.com/spdx/tools-golang/spdx" + "github.com/spdx/tools-golang/utils" ) func (parser *rdfParser2_2) parseSpdxDocumentNode(spdxDocNode *gordfParser.Node) (err error) { @@ -14,7 +15,7 @@ func (parser *rdfParser2_2) parseSpdxDocumentNode(spdxDocNode *gordfParser.Node) // parse the document header information (SPDXID and document namespace) // the Subject.ID is of type baseURI#spdxID - baseUri, offset, err := ExtractSubs(spdxDocNode.ID, "#") + baseUri, offset, err := utils.ExtractSubsWithSeparator(spdxDocNode.ID, "#") if err != nil { return err } @@ -94,7 +95,7 @@ func (parser *rdfParser2_2) getExternalDocumentRefFromNode(node *gordfParser.Nod switch triple.Predicate.ID { case SPDX_EXTERNAL_DOCUMENT_ID: // cardinality: exactly 1 - edr.DocumentRefID = triple.Object.ID + edr.DocumentRefID = spdx.MakeDocElementID(triple.Object.ID, "") case SPDX_SPDX_DOCUMENT: // cardinality: exactly 1 // assumption: "spdxDocument" property of an external document diff --git a/rdfloader/parser2v2/utils.go b/rdfloader/parser2v2/utils.go index 27207d25..85e0027a 100644 --- a/rdfloader/parser2v2/utils.go +++ b/rdfloader/parser2v2/utils.go @@ -8,7 +8,6 @@ import ( gordfParser "github.com/spdx/gordf/rdfloader/parser" "github.com/spdx/gordf/rdfwriter" urilib "github.com/spdx/gordf/uri" - "github.com/spdx/tools-golang/spdx" "strings" ) @@ -20,7 +19,7 @@ func getLastPartOfURI(uri string) string { return parts[len(parts)-1] } parts := strings.Split(uri, "/") - return parts[len(parts)-1] + return strings.TrimSpace(parts[len(parts)-1]) } func isUriValid(uri string) bool { @@ -49,106 +48,3 @@ func (parser *rdfParser2_2) nodeToTriples(node *gordfParser.Node) []*gordfParser } return parser.nodeStringToTriples[node.String()] } - -// returns which boolean was given as an input -// string(bool) is the only possible input for which it will not raise any error. -func boolFromString(boolString string) (bool, error) { - switch strings.ToLower(boolString) { - case "true": - return true, nil - case "false": - return false, nil - default: - return false, fmt.Errorf("boolean string can be either true/false") - } -} - -/* Function Below this line is taken from the tvloader/parser2v2/utils.go */ - -// used to extract DocumentRef and SPDXRef values from an SPDX Identifier -// which can point either to this document or to a different one -func ExtractDocElementID(value string) (spdx.DocElementID, error) { - docRefID := "" - idStr := value - - // check prefix to see if it's a DocumentRef ID - if strings.HasPrefix(idStr, "DocumentRef-") { - // extract the part that comes between "DocumentRef-" and ":" - strs := strings.Split(idStr, ":") - // should be exactly two, part before and part after - if len(strs) < 2 { - return spdx.DocElementID{}, fmt.Errorf("no colon found although DocumentRef- prefix present") - } - if len(strs) > 2 { - return spdx.DocElementID{}, fmt.Errorf("more than one colon found") - } - - // trim the prefix and confirm non-empty - docRefID = strings.TrimPrefix(strs[0], "DocumentRef-") - if docRefID == "" { - return spdx.DocElementID{}, fmt.Errorf("document identifier has nothing after prefix") - } - // and use remainder for element ID parsing - idStr = strs[1] - } - - // check prefix to confirm it's got the right prefix for element IDs - if !strings.HasPrefix(idStr, "SPDXRef-") { - return spdx.DocElementID{}, fmt.Errorf("missing SPDXRef- prefix for element identifier") - } - - // make sure no colons are present - if strings.Contains(idStr, ":") { - // we know this means there was no DocumentRef- prefix, because - // we would have handled multiple colons above if it was - return spdx.DocElementID{}, fmt.Errorf("invalid colon in element identifier") - } - - // trim the prefix and confirm non-empty - eltRefID := strings.TrimPrefix(idStr, "SPDXRef-") - if eltRefID == "" { - return spdx.DocElementID{}, fmt.Errorf("element identifier has nothing after prefix") - } - - // we're good - return spdx.DocElementID{DocumentRefID: docRefID, ElementRefID: spdx.ElementID(eltRefID)}, nil -} - -// used to extract SPDXRef values only from an SPDX Identifier which can point -// to this document only. Use extractDocElementID for parsing IDs that can -// refer either to this document or a different one. -func ExtractElementID(value string) (spdx.ElementID, error) { - // check prefix to confirm it's got the right prefix for element IDs - if !strings.HasPrefix(value, "SPDXRef-") { - return spdx.ElementID(""), fmt.Errorf("missing SPDXRef- prefix for element identifier") - } - - // make sure no colons are present - if strings.Contains(value, ":") { - return spdx.ElementID(""), fmt.Errorf("invalid colon in element identifier") - } - - // trim the prefix and confirm non-empty - eltRefID := strings.TrimPrefix(value, "SPDXRef-") - if eltRefID == "" { - return spdx.ElementID(""), fmt.Errorf("element identifier has nothing after prefix") - } - - // we're good - return spdx.ElementID(eltRefID), nil -} - -// used to extract key / value from embedded substrings -// returns subkey, subvalue, nil if no error, or "", "", error otherwise -func ExtractSubs(value string, sep string) (string, string, error) { - // parse the value to see if it's a valid subvalue format - sp := strings.SplitN(value, sep, 2) - if len(sp) == 1 { - return "", "", fmt.Errorf("invalid subvalue format for %s (no %s found)", value, sep) - } - - subkey := strings.TrimSpace(sp[0]) - subvalue := strings.TrimSpace(sp[1]) - - return subkey, subvalue, nil -} diff --git a/rdfloader/parser2v2/utils_test.go b/rdfloader/parser2v2/utils_test.go index 797143be..d1466e03 100644 --- a/rdfloader/parser2v2/utils_test.go +++ b/rdfloader/parser2v2/utils_test.go @@ -4,7 +4,6 @@ package parser2v2 import ( gordfParser "github.com/spdx/gordf/rdfloader/parser" - "github.com/spdx/tools-golang/spdx" "reflect" "testing" ) @@ -89,38 +88,6 @@ func Test_rdfParser2_2_nodeToTriples(t *testing.T) { } } -func Test_boolFromString(t *testing.T) { - // TestCase 1: Valid Input: "true" - // mustn't raise any error - input := "true" - val, err := boolFromString(input) - if err != nil { - t.Errorf("function raised an error for a valid input(%s): %s", input, err) - } - if val != true { - t.Errorf("invalid output. Expected %v, found %v", true, val) - } - - // TestCase 2: Valid Input: "true" - // mustn't raise any error - input = "false" - val, err = boolFromString(input) - if err != nil { - t.Errorf("function raised an error for a valid input(%s): %s", input, err) - } - if val != false { - t.Errorf("invalid output. Expected %v, found %v", false, val) - } - - // TestCase 3: invalid input: "" - // it must raise an error - input = "" - val, err = boolFromString(input) - if err == nil { - t.Errorf("invalid input should've raised an error") - } -} - func Test_getNodeTypeFromTriples(t *testing.T) { var err error var node *gordfParser.Node @@ -184,106 +151,3 @@ func Test_getNodeTypeFromTriples(t *testing.T) { t.Errorf("expected an error saying more than one rdf:type found, got %v", err) } } - -// following tests are copy pasted from tvloader/parser2v2/util_test.go - -func TestCanExtractDocumentAndElementRefsFromID(t *testing.T) { - // test with valid ID in this document - helperForExtractDocElementID(t, "SPDXRef-file1", false, "", "file1") - // test with valid ID in another document - helperForExtractDocElementID(t, "DocumentRef-doc2:SPDXRef-file2", false, "doc2", "file2") - // test with invalid ID in this document - helperForExtractDocElementID(t, "a:SPDXRef-file1", true, "", "") - helperForExtractDocElementID(t, "file1", true, "", "") - helperForExtractDocElementID(t, "SPDXRef-", true, "", "") - helperForExtractDocElementID(t, "SPDXRef-file1:", true, "", "") - // test with invalid ID in another document - helperForExtractDocElementID(t, "DocumentRef-doc2", true, "", "") - helperForExtractDocElementID(t, "DocumentRef-doc2:", true, "", "") - helperForExtractDocElementID(t, "DocumentRef-doc2:SPDXRef-", true, "", "") - helperForExtractDocElementID(t, "DocumentRef-doc2:a", true, "", "") - helperForExtractDocElementID(t, "DocumentRef-:", true, "", "") - helperForExtractDocElementID(t, "DocumentRef-:SPDXRef-file1", true, "", "") - // test with invalid formats - helperForExtractDocElementID(t, "DocumentRef-doc2:SPDXRef-file1:file2", true, "", "") -} - -func helperForExtractDocElementID(t *testing.T, tst string, wantErr bool, wantDoc string, wantElt string) { - deID, err := ExtractDocElementID(tst) - if err != nil && wantErr == false { - t.Errorf("testing %v: expected nil error, got %v", tst, err) - } - if err == nil && wantErr == true { - t.Errorf("testing %v: expected non-nil error, got nil", tst) - } - if deID.DocumentRefID != wantDoc { - if wantDoc == "" { - t.Errorf("testing %v: want empty string for DocumentRefID, got %v", tst, deID.DocumentRefID) - } else { - t.Errorf("testing %v: want %v for DocumentRefID, got %v", tst, wantDoc, deID.DocumentRefID) - } - } - if deID.ElementRefID != spdx.ElementID(wantElt) { - if wantElt == "" { - t.Errorf("testing %v: want emptyString for ElementRefID, got %v", tst, deID.ElementRefID) - } else { - t.Errorf("testing %v: want %v for ElementRefID, got %v", tst, wantElt, deID.ElementRefID) - } - } -} - -func TestCanExtractElementRefsOnlyFromID(t *testing.T) { - // test with valid ID in this document - helperForExtractElementID(t, "SPDXRef-file1", false, "file1") - // test with valid ID in another document - helperForExtractElementID(t, "DocumentRef-doc2:SPDXRef-file2", true, "") - // test with invalid ID in this document - helperForExtractElementID(t, "a:SPDXRef-file1", true, "") - helperForExtractElementID(t, "file1", true, "") - helperForExtractElementID(t, "SPDXRef-", true, "") - helperForExtractElementID(t, "SPDXRef-file1:", true, "") - // test with invalid ID in another document - helperForExtractElementID(t, "DocumentRef-doc2", true, "") - helperForExtractElementID(t, "DocumentRef-doc2:", true, "") - helperForExtractElementID(t, "DocumentRef-doc2:SPDXRef-", true, "") - helperForExtractElementID(t, "DocumentRef-doc2:a", true, "") - helperForExtractElementID(t, "DocumentRef-:", true, "") - helperForExtractElementID(t, "DocumentRef-:SPDXRef-file1", true, "") -} - -func helperForExtractElementID(t *testing.T, tst string, wantErr bool, wantElt string) { - eID, err := ExtractElementID(tst) - if err != nil && wantErr == false { - t.Errorf("testing %v: expected nil error, got %v", tst, err) - } - if err == nil && wantErr == true { - t.Errorf("testing %v: expected non-nil error, got nil", tst) - } - if eID != spdx.ElementID(wantElt) { - if wantElt == "" { - t.Errorf("testing %v: want emptyString for ElementRefID, got %v", tst, eID) - } else { - t.Errorf("testing %v: want %v for ElementRefID, got %v", tst, wantElt, eID) - } - } -} - -func TestCanExtractSubvalues(t *testing.T) { - subkey, subvalue, err := ExtractSubs("SHA1: abc123", ":") - if err != nil { - t.Errorf("got error when calling extractSubs: %v", err) - } - if subkey != "SHA1" { - t.Errorf("got %v for subkey", subkey) - } - if subvalue != "abc123" { - t.Errorf("got %v for subvalue", subvalue) - } -} - -func TestReturnsErrorForInvalidSubvalueFormat(t *testing.T) { - _, _, err := ExtractSubs("blah", ":") - if err == nil { - t.Errorf("expected error when calling extractSubs for invalid format (0 colons), got nil") - } -} diff --git a/spdx/annotation.go b/spdx/annotation.go index 560b6f00..a5999d52 100644 --- a/spdx/annotation.go +++ b/spdx/annotation.go @@ -5,6 +5,7 @@ package spdx import ( "encoding/json" "fmt" + "github.com/spdx/tools-golang/utils" "strings" ) @@ -14,6 +15,34 @@ type Annotator struct { AnnotatorType string } +// Validate verifies that all the required fields are present. +// Returns an error if the object is invalid. +func (a Annotator) Validate() error { + if a.Annotator == "" || a.AnnotatorType == "" { + return fmt.Errorf("invalid Annotator, missing fields. %+v", a) + } + + return nil +} + +// FromString parses an Annotator string into an Annotator struct. +func (a *Annotator) FromString(value string) error { + annotatorType, annotator, err := utils.ExtractSubs(value) + if err != nil { + return err + } + + a.AnnotatorType = annotatorType + a.Annotator = annotator + + return nil +} + +// String converts the receiver into a string. +func (a Annotator) String() string { + return fmt.Sprintf("%s: %s", a.AnnotatorType, a.Annotator) +} + // UnmarshalJSON takes an annotator in the typical one-line format and parses it into an Annotator struct. // This function is also used when unmarshalling YAML func (a *Annotator) UnmarshalJSON(data []byte) error { @@ -21,26 +50,17 @@ func (a *Annotator) UnmarshalJSON(data []byte) error { annotatorStr := string(data) annotatorStr = strings.Trim(annotatorStr, "\"") - annotatorFields := strings.SplitN(annotatorStr, ": ", 2) - - if len(annotatorFields) != 2 { - return fmt.Errorf("failed to parse Annotator '%s'", annotatorStr) - } - - a.AnnotatorType = annotatorFields[0] - a.Annotator = annotatorFields[1] - - return nil + return a.FromString(annotatorStr) } // MarshalJSON converts the receiver into a slice of bytes representing an Annotator in string form. // This function is also used when marshalling to YAML func (a Annotator) MarshalJSON() ([]byte, error) { - if a.Annotator != "" { - return json.Marshal(fmt.Sprintf("%s: %s", a.AnnotatorType, a.Annotator)) + if err := a.Validate(); err != nil { + return nil, err } - return []byte{}, nil + return json.Marshal(a.String()) } // Annotation2_1 is an Annotation section of an SPDX Document for version 2.1 of the spec. diff --git a/spdx/checksum.go b/spdx/checksum.go index 3295969a..fa8661cb 100644 --- a/spdx/checksum.go +++ b/spdx/checksum.go @@ -2,6 +2,11 @@ package spdx +import ( + "fmt" + "github.com/spdx/tools-golang/utils" +) + // ChecksumAlgorithm represents the algorithm used to generate the file checksum in the Checksum struct. type ChecksumAlgorithm string @@ -24,3 +29,34 @@ type Checksum struct { Algorithm ChecksumAlgorithm `json:"algorithm"` Value string `json:"checksumValue"` } + +// FromString parses a Checksum string into a spdx.Checksum. +// These strings take the following form: +// SHA1: d6a770ba38583ed4bb4525bd96e50461655d2759 +func (c *Checksum) FromString(value string) error { + algorithm, value, err := utils.ExtractSubs(value) + if err != nil { + return err + } + + c.Algorithm = ChecksumAlgorithm(algorithm) + c.Value = value + + return nil +} + +// String converts the Checksum to its string form. +// e.g. "SHA1: d6a770ba38583ed4bb4525bd96e50461655d2759" +func (c Checksum) String() string { + return fmt.Sprintf("%s: %s", c.Algorithm, c.Value) +} + +// Validate verifies that all the required fields are present. +// Returns an error if the object is invalid. +func (c Checksum) Validate() error { + if c.Algorithm == "" || c.Value == "" { + return fmt.Errorf("invalid checksum, missing field(s). %+v", c) + } + + return nil +} diff --git a/spdx/creation_info.go b/spdx/creation_info.go index c0b6f636..50cb25a1 100644 --- a/spdx/creation_info.go +++ b/spdx/creation_info.go @@ -5,6 +5,7 @@ package spdx import ( "encoding/json" "fmt" + "github.com/spdx/tools-golang/utils" "strings" ) @@ -16,31 +17,50 @@ type Creator struct { CreatorType string } -// UnmarshalJSON takes an annotator in the typical one-line format and parses it into a Creator struct. -// This function is also used when unmarshalling YAML -func (c *Creator) UnmarshalJSON(data []byte) error { - str := string(data) - str = strings.Trim(str, "\"") - fields := strings.SplitN(str, ": ", 2) +// Validate verifies that all the required fields are present. +// Returns an error if the object is invalid. +func (c Creator) Validate() error { + if c.CreatorType == "" || c.Creator == "" { + return fmt.Errorf("invalid Creator, missing fields. %+v", c) + } - if len(fields) != 2 { - return fmt.Errorf("failed to parse Creator '%s'", str) + return nil +} + +// FromString takes a Creator in the typical one-line format and parses it into a Creator struct. +func (c *Creator) FromString(str string) error { + creatorType, creator, err := utils.ExtractSubs(str) + if err != nil { + return err } - c.CreatorType = fields[0] - c.Creator = fields[1] + c.CreatorType = creatorType + c.Creator = creator return nil } +// String converts the Creator into a string. +func (c Creator) String() string { + return fmt.Sprintf("%s: %s", c.CreatorType, c.Creator) +} + +// UnmarshalJSON takes a Creator in the typical one-line string format and parses it into a Creator struct. +func (c *Creator) UnmarshalJSON(data []byte) error { + str := string(data) + str = strings.Trim(str, "\"") + + return c.FromString(str) +} + // MarshalJSON converts the receiver into a slice of bytes representing a Creator in string form. // This function is also used with marshalling to YAML func (c Creator) MarshalJSON() ([]byte, error) { - if c.Creator != "" { - return json.Marshal(fmt.Sprintf("%s: %s", c.CreatorType, c.Creator)) + if err := c.Validate(); err != nil { + return nil, err } - return []byte{}, nil + return json.Marshal(c.String()) } // CreationInfo2_1 is a Document Creation Information section of an @@ -48,7 +68,7 @@ func (c Creator) MarshalJSON() ([]byte, error) { type CreationInfo2_1 struct { // 2.7: License List Version // Cardinality: optional, one - LicenseListVersion string `json:"licenseListVersion"` + LicenseListVersion string `json:"licenseListVersion,omitempty"` // 2.8: Creators: may have multiple keys for Person, Organization // and/or Tool @@ -61,7 +81,7 @@ type CreationInfo2_1 struct { // 2.10: Creator Comment // Cardinality: optional, one - CreatorComment string `json:"comment"` + CreatorComment string `json:"comment,omitempty"` } // CreationInfo2_2 is a Document Creation Information section of an diff --git a/spdx/document.go b/spdx/document.go index a3117cb7..dec597cf 100644 --- a/spdx/document.go +++ b/spdx/document.go @@ -3,13 +3,19 @@ // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later package spdx +import ( + "fmt" + "github.com/spdx/tools-golang/utils" + "strings" +) + // ExternalDocumentRef2_1 is a reference to an external SPDX document // as defined in section 2.6 for version 2.1 of the spec. type ExternalDocumentRef2_1 struct { // DocumentRefID is the ID string defined in the start of the // reference. It should _not_ contain the "DocumentRef-" part // of the mandatory ID string. - DocumentRefID string `json:"externalDocumentId"` + DocumentRefID DocElementID `json:"externalDocumentId"` // URI is the URI defined for the external document URI string `json:"spdxDocument"` @@ -24,7 +30,7 @@ type ExternalDocumentRef2_2 struct { // DocumentRefID is the ID string defined in the start of the // reference. It should _not_ contain the "DocumentRef-" part // of the mandatory ID string. - DocumentRefID string `json:"externalDocumentId"` + DocumentRefID DocElementID `json:"externalDocumentId"` // URI is the URI defined for the external document URI string `json:"spdxDocument"` @@ -33,6 +39,120 @@ type ExternalDocumentRef2_2 struct { Checksum Checksum `json:"checksum"` } +// Validate verifies that all the required fields are present. +// Returns true if the object is valid, returns false and an error if it is invalid. +func (e ExternalDocumentRef2_1) Validate() error { + if err := e.Checksum.Validate(); err != nil { + return fmt.Errorf("invalid Checksum in DocElementID: %w", err) + } + + if e.DocumentRefID.Validate() != nil || e.URI == "" { + return fmt.Errorf("invalid DocElementID, missing fields. %+v", e) + } + + return nil +} + +// Validate verifies that all the required fields are present. +// Returns true if the object is valid, returns false and an error if it is invalid. +func (e ExternalDocumentRef2_2) Validate() error { + if err := e.Checksum.Validate(); err != nil { + return fmt.Errorf("invalid Checksum in DocElementID: %w", err) + } + + if e.DocumentRefID.Validate() != nil || e.URI == "" { + return fmt.Errorf("invalid DocElementID, missing fields. %+v", e) + } + + return nil +} + +// String converts a ExternalDocumentRef2_1 object to a string. +// These strings take the form: " " +func (e ExternalDocumentRef2_1) String() string { + return fmt.Sprintf("%s %s %s", e.DocumentRefID, e.URI, e.Checksum) +} + +// String converts a ExternalDocumentRef2_2 object to a string. +// These strings take the form: " " +func (e ExternalDocumentRef2_2) String() string { + return fmt.Sprintf("%s %s %s", e.DocumentRefID, e.URI, e.Checksum) +} + +func extractExternalDocumentReference(value string) (DocElementID, string, string, string, error) { + sp := strings.Split(value, " ") + // remove any that are just whitespace + keepSp := []string{} + for _, s := range sp { + ss := strings.TrimSpace(s) + if ss != "" { + keepSp = append(keepSp, ss) + } + } + + var documentRefID DocElementID + var uri, alg, checksum string + + // now, should have 4 items (or 3, if Alg and Checksum were joined) + // and should be able to map them + if len(keepSp) == 4 { + documentRefID = MakeDocElementID(keepSp[0], "") + uri = keepSp[1] + alg = keepSp[2] + // check that colon is present for alg, and remove it + if !strings.HasSuffix(alg, ":") { + return documentRefID, "", "", "", fmt.Errorf("algorithm does not end with colon") + } + alg = strings.TrimSuffix(alg, ":") + checksum = keepSp[3] + } else if len(keepSp) == 3 { + documentRefID = MakeDocElementID(keepSp[0], "") + uri = keepSp[1] + // split on colon into alg and checksum + var err error + alg, checksum, err = utils.ExtractSubs(keepSp[2]) + if err != nil { + return documentRefID, "", "", "", err + } + } else { + return documentRefID, "", "", "", fmt.Errorf("expected 4 elements, got %d", len(keepSp)) + } + + return documentRefID, uri, alg, checksum, nil +} + +// FromString parses a string into a spdx.ExternalDocumentRef2_1. +// These strings take the following form: " " +func (e *ExternalDocumentRef2_1) FromString(value string) error { + documentRefID, uri, alg, checksum, err := extractExternalDocumentReference(value) + if err != nil { + return err + } + + e.DocumentRefID = documentRefID + e.URI = uri + e.Checksum.Algorithm = ChecksumAlgorithm(alg) + e.Checksum.Value = checksum + + return nil +} + +// FromString parses a string into a spdx.ExternalDocumentRef2_2. +// These strings take the following form: " " +func (e *ExternalDocumentRef2_2) FromString(value string) error { + documentRefID, uri, alg, checksum, err := extractExternalDocumentReference(value) + if err != nil { + return err + } + + e.DocumentRefID = documentRefID + e.URI = uri + e.Checksum.Algorithm = ChecksumAlgorithm(alg) + e.Checksum.Value = checksum + + return nil +} + // Document2_1 is an SPDX Document for version 2.1 of the spec. // See https://spdx.org/sites/cpstandard/files/pages/files/spdxversion2.1.pdf type Document2_1 struct { diff --git a/spdx/document_test.go b/spdx/document_test.go new file mode 100644 index 00000000..c159d984 --- /dev/null +++ b/spdx/document_test.go @@ -0,0 +1,73 @@ +package spdx + +import "testing" + +func TestCanExtractExternalDocumentReference(t *testing.T) { + refstring := "DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 SHA1:d6a770ba38583ed4bb4525bd96e50461655d2759" + wantDocumentRefID := "DocumentRef-spdx-tool-1.2" + wantURI := "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301" + wantAlg := ChecksumAlgorithm("SHA1") + wantChecksum := "d6a770ba38583ed4bb4525bd96e50461655d2759" + + var edr ExternalDocumentRef2_2 + err := edr.FromString(refstring) + if err != nil { + t.Errorf("got non-nil error: %v", err) + } + if wantDocumentRefID != edr.DocumentRefID.String() { + t.Errorf("wanted document ref ID %s, got %s", wantDocumentRefID, edr.DocumentRefID) + } + if wantURI != edr.URI { + t.Errorf("wanted URI %s, got %s", wantURI, edr.URI) + } + if wantAlg != edr.Checksum.Algorithm { + t.Errorf("wanted alg %s, got %s", wantAlg, edr.Checksum.Algorithm) + } + if wantChecksum != edr.Checksum.Value { + t.Errorf("wanted checksum %s, got %s", wantChecksum, edr.Checksum.Value) + } +} + +func TestCanExtractExternalDocumentReferenceWithExtraWhitespace(t *testing.T) { + refstring := " DocumentRef-spdx-tool-1.2 \t http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 \t SHA1: \t d6a770ba38583ed4bb4525bd96e50461655d2759" + wantDocumentRefID := "DocumentRef-spdx-tool-1.2" + wantURI := "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301" + wantAlg := ChecksumAlgorithm("SHA1") + wantChecksum := "d6a770ba38583ed4bb4525bd96e50461655d2759" + + var edr ExternalDocumentRef2_2 + err := edr.FromString(refstring) + if err != nil { + t.Errorf("got non-nil error: %v", err) + } + if wantDocumentRefID != edr.DocumentRefID.String() { + t.Errorf("wanted document ref ID %s, got %s", wantDocumentRefID, edr.DocumentRefID) + } + if wantURI != edr.URI { + t.Errorf("wanted URI %s, got %s", wantURI, edr.URI) + } + if wantAlg != edr.Checksum.Algorithm { + t.Errorf("wanted alg %s, got %s", wantAlg, edr.Checksum.Algorithm) + } + if wantChecksum != edr.Checksum.Value { + t.Errorf("wanted checksum %s, got %s", wantChecksum, edr.Checksum.Value) + } +} + +func TestFailsExternalDocumentReferenceWithInvalidFormats(t *testing.T) { + invalidRefs := []string{ + "whoops", + "DocumentRef-", + "DocumentRef- ", + "DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301", + "DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 d6a770ba38583ed4bb4525bd96e50461655d2759", + "DocumentRef-spdx-tool-1.2", + } + var edr ExternalDocumentRef2_2 + for _, refstring := range invalidRefs { + err := edr.FromString(refstring) + if err == nil { + t.Errorf("expected non-nil error for %s, got nil", refstring) + } + } +} diff --git a/spdx/identifier.go b/spdx/identifier.go index 56f8ffc8..927bef8c 100644 --- a/spdx/identifier.go +++ b/spdx/identifier.go @@ -14,16 +14,75 @@ import ( // ElementIDs should NOT contain the mandatory 'SPDXRef-' portion. type ElementID string +// Validate verifies that all the required fields are present. +// Returns an error if the object is invalid. +func (e ElementID) Validate() error { + if e == "" { + return fmt.Errorf("invalid ElementID, must not be blank") + } + + return nil +} + +func (e ElementID) String() string { + return fmt.Sprintf("SPDXRef-%s", string(e)) +} + +// FromString parses an SPDX Identifier string into an ElementID. +// These strings take the form: "SPDXRef-some-identifier" +func (e *ElementID) FromString(value string) error { + // check prefix to confirm it's got the right prefix for element IDs + if !strings.HasPrefix(value, "SPDXRef-") { + return fmt.Errorf("missing SPDXRef- prefix for element identifier") + } + + // make sure no colons are present + if strings.Contains(value, ":") { + return fmt.Errorf("invalid colon in element identifier") + } + + // trim the prefix and confirm non-empty + eltRefID := strings.TrimPrefix(value, "SPDXRef-") + if eltRefID == "" { + return fmt.Errorf("element identifier has nothing after prefix") + } + + *e = ElementID(eltRefID) + + return nil +} + +// UnmarshalJSON takes a SPDX Identifier string parses it into an ElementID. +// This function is also used when unmarshalling YAML +func (e *ElementID) UnmarshalJSON(data []byte) error { + // SPDX identifier will simply be a string + idStr := string(data) + idStr = strings.Trim(idStr, "\"") + + return e.FromString(idStr) +} + +// MarshalJSON converts the receiver into a slice of bytes representing an ElementID in string form. +// This function is also used when marshalling to YAML +func (e ElementID) MarshalJSON() ([]byte, error) { + if err := e.Validate(); err != nil { + return nil, err + } + + return json.Marshal(e.String()) +} + // DocElementID represents an SPDX element identifier that could be defined // in a different SPDX document, and therefore could have a "DocumentRef-" // portion, such as Relationships and Annotations. +// +// DocumentRefID is used to reference other documents. This will be an empty string for elements defined in the +// present document. This value should not contain the 'DocumentRef-' prefix. +// // ElementID is used for attributes in which a "DocumentRef-" portion cannot // appear, such as a Package or File definition (since it is necessarily -// being defined in the present document). -// DocumentRefID will be the empty string for elements defined in the -// present document. -// DocElementIDs should NOT contain the mandatory 'DocumentRef-' or -// 'SPDXRef-' portions. +// being defined in the present document). This value should not contain the 'SPDXRef-' prefix. +// // SpecialID is used ONLY if the DocElementID matches a defined set of // permitted special values for a particular field, e.g. "NONE" or // "NOASSERTION" for the right-hand side of Relationships. If SpecialID @@ -34,13 +93,22 @@ type DocElementID struct { SpecialID string } -// UnmarshalJSON takes a SPDX Identifier string parses it into a DocElementID struct. -// This function is also used when unmarshalling YAML -func (d *DocElementID) UnmarshalJSON(data []byte) error { - // SPDX identifier will simply be a string - idStr := string(data) - idStr = strings.Trim(idStr, "\"") +// Validate verifies that all the required fields are present. +// Returns an error if the object is invalid. +func (d DocElementID) Validate() error { + if d.DocumentRefID == "" && d.ElementRefID.Validate() != nil && d.SpecialID == "" { + return fmt.Errorf("invalid DocElementID, missing fields. %+v", d) + } + + return nil +} +// FromString parses an SPDX Identifier string into a DocElementID struct. +// These strings take one of the following forms: +// - "DocumentRef-other-document:SPDXRef-some-identifier" +// - "SPDXRef-some-identifier" +// - "NOASSERTION" or "NONE" +func (d *DocElementID) FromString(idStr string) error { // handle special cases if idStr == "NONE" || idStr == "NOASSERTION" { d.SpecialID = idStr @@ -54,6 +122,11 @@ func (d *DocElementID) UnmarshalJSON(data []byte) error { idFields = strings.SplitN(idStr, "DocumentRef-", 2) idStr = idFields[1] + // edge case - no text after DocumentRef, for example: DocumentRef-:SPDXRef-package + if strings.HasPrefix(idFields[1], ":") { + return fmt.Errorf("invalid DocumentRef '%s'", idStr) + } + // an SPDXRef can appear after a DocumentRef, separated by a colon idFields = strings.SplitN(idStr, ":", 2) d.DocumentRefID = idFields[0] @@ -66,37 +139,57 @@ func (d *DocElementID) UnmarshalJSON(data []byte) error { } // handle SPDXRef- - idFields = strings.SplitN(idStr, "SPDXRef-", 2) - if len(idFields) != 2 { - return fmt.Errorf("failed to parse SPDX Identifier '%s'", idStr) + err := d.ElementRefID.FromString(idStr) + if err != nil { + return err } - d.ElementRefID = ElementID(idFields[1]) - return nil } -// MarshalJSON converts the receiver into a slice of bytes representing a DocElementID in string form. -// This function is also used when marshalling to YAML -func (d DocElementID) MarshalJSON() ([]byte, error) { +// MarshalString converts the receiver into a string representing a DocElementID. +// This is used when writing a spreadsheet SPDX file, for example. +func (d DocElementID) String() string { if d.DocumentRefID != "" && d.ElementRefID != "" { - return json.Marshal(fmt.Sprintf("DocumentRef-%s:SPDXRef-%s", d.DocumentRefID, d.ElementRefID)) + return fmt.Sprintf("DocumentRef-%s:%s", d.DocumentRefID, d.ElementRefID) + } else if d.DocumentRefID != "" { + return fmt.Sprintf("DocumentRef-%s", d.DocumentRefID) } else if d.ElementRefID != "" { - return json.Marshal(fmt.Sprintf("SPDXRef-%s", d.ElementRefID)) + return d.ElementRefID.String() } else if d.SpecialID != "" { - return json.Marshal(d.SpecialID) + return d.SpecialID } - return []byte{}, fmt.Errorf("failed to marshal empty DocElementID") + return "" } -// TODO: add equivalents for LicenseRef- identifiers +// UnmarshalJSON takes a SPDX Identifier string parses it into a DocElementID struct. +// This function is also used when unmarshalling YAML +func (d *DocElementID) UnmarshalJSON(data []byte) error { + // SPDX identifier will simply be a string + idStr := string(data) + idStr = strings.Trim(idStr, "\"") -// MakeDocElementID takes strings (without prefixes) for the DocumentRef- -// and SPDXRef- identifiers, and returns a DocElementID. An empty string -// should be used for the DocumentRef- portion if it is referring to the -// present document. + return d.FromString(idStr) +} + +// MarshalJSON converts the receiver into a slice of bytes representing a DocElementID in string form. +// This function is also used when marshalling to YAML +func (d DocElementID) MarshalJSON() ([]byte, error) { + if err := d.Validate(); err != nil { + return nil, err + } + + return json.Marshal(d.String()) +} + +// MakeDocElementID takes strings for the DocumentRef- and SPDXRef- identifiers (these prefixes will be stripped if present), +// and returns a DocElementID. +// An empty string should be used for the DocumentRef- portion if it is referring to the present document. func MakeDocElementID(docRef string, eltRef string) DocElementID { + docRef = strings.Replace(docRef, "DocumentRef-", "", 1) + eltRef = strings.Replace(eltRef, "SPDXRef-", "", 1) + return DocElementID{ DocumentRefID: docRef, ElementRefID: ElementID(eltRef), @@ -110,24 +203,3 @@ func MakeDocElementID(docRef string, eltRef string) DocElementID { func MakeDocElementSpecial(specialID string) DocElementID { return DocElementID{SpecialID: specialID} } - -// RenderElementID takes an ElementID and returns the string equivalent, -// with the SPDXRef- prefix reinserted. -func RenderElementID(eID ElementID) string { - return "SPDXRef-" + string(eID) -} - -// RenderDocElementID takes a DocElementID and returns the string equivalent, -// with the SPDXRef- prefix (and, if applicable, the DocumentRef- prefix) -// reinserted. If a SpecialID is present, it will be rendered verbatim and -// DocumentRefID and ElementRefID will be ignored. -func RenderDocElementID(deID DocElementID) string { - if deID.SpecialID != "" { - return deID.SpecialID - } - prefix := "" - if deID.DocumentRefID != "" { - prefix = "DocumentRef-" + deID.DocumentRefID + ":" - } - return prefix + "SPDXRef-" + string(deID.ElementRefID) -} diff --git a/tvloader/parser2v2/util_test.go b/spdx/identifier_test.go similarity index 67% rename from tvloader/parser2v2/util_test.go rename to spdx/identifier_test.go index e2f75d7b..2e7a9247 100644 --- a/tvloader/parser2v2/util_test.go +++ b/spdx/identifier_test.go @@ -1,31 +1,76 @@ -// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later -package parser2v2 +package spdx import ( + "encoding/json" "testing" - - "github.com/spdx/tools-golang/spdx" ) -// ===== Helper function tests ===== +func TestMakeDocElementID(t *testing.T) { + // without DocRef + docElementID := MakeDocElementID("", "Package") + if docElementID.String() != "SPDXRef-Package" { + t.Errorf("expected 'SPDXRef-Package', got %s", docElementID) + return + } -func TestCanExtractSubvalues(t *testing.T) { - subkey, subvalue, err := extractSubs("SHA1: abc123") - if err != nil { - t.Errorf("got error when calling extractSubs: %v", err) + // with DocRef + docElementID = MakeDocElementID("OtherDoc", "Package") + if docElementID.String() != "DocumentRef-OtherDoc:SPDXRef-Package" { + t.Errorf("expected 'DocumentRef-OtherDoc:SPDXRef-Package', got %s", docElementID) + return } - if subkey != "SHA1" { - t.Errorf("got %v for subkey", subkey) +} + +func TestDocElementID_UnmarshalJSON(t *testing.T) { + rawJSON := json.RawMessage("\"DocumentRef-some-doc\"") + docElementID := DocElementID{} + + err := json.Unmarshal(rawJSON, &docElementID) + if err != nil { + t.Errorf(err.Error()) + return } - if subvalue != "abc123" { - t.Errorf("got %v for subvalue", subvalue) + + if docElementID.DocumentRefID != "some-doc" { + t.Errorf("Bad!") + return } } -func TestReturnsErrorForInvalidSubvalueFormat(t *testing.T) { - _, _, err := extractSubs("blah") - if err == nil { - t.Errorf("expected error when calling extractSubs for invalid format (0 colons), got nil") +func TestCanExtractElementRefsOnlyFromID(t *testing.T) { + // test with valid ID in this document + helperForExtractElementID(t, "SPDXRef-file1", false, "file1") + // test with valid ID in another document + helperForExtractElementID(t, "DocumentRef-doc2:SPDXRef-file2", true, "") + // test with invalid ID in this document + helperForExtractElementID(t, "a:SPDXRef-file1", true, "") + helperForExtractElementID(t, "file1", true, "") + helperForExtractElementID(t, "SPDXRef-", true, "") + helperForExtractElementID(t, "SPDXRef-file1:", true, "") + // test with invalid ID in another document + helperForExtractElementID(t, "DocumentRef-doc2", true, "") + helperForExtractElementID(t, "DocumentRef-doc2:", true, "") + helperForExtractElementID(t, "DocumentRef-doc2:SPDXRef-", true, "") + helperForExtractElementID(t, "DocumentRef-doc2:a", true, "") + helperForExtractElementID(t, "DocumentRef-:", true, "") + helperForExtractElementID(t, "DocumentRef-:SPDXRef-file1", true, "") +} + +func helperForExtractElementID(t *testing.T, tst string, wantErr bool, wantElt string) { + var eID ElementID + err := eID.FromString(tst) + if err != nil && wantErr == false { + t.Errorf("testing %v: expected nil error, got %v", tst, err) + } + if err == nil && wantErr == true { + t.Errorf("testing %v: expected non-nil error, got nil", tst) + } + if eID != ElementID(wantElt) { + if wantElt == "" { + t.Errorf("testing %v: want emptyString for ElementRefID, got %v", tst, eID) + } else { + t.Errorf("testing %v: want %v for ElementRefID, got %v", tst, wantElt, eID) + } } } @@ -33,6 +78,7 @@ func TestCanExtractDocumentAndElementRefsFromID(t *testing.T) { // test with valid ID in this document helperForExtractDocElementID(t, "SPDXRef-file1", false, "", "file1") // test with valid ID in another document + helperForExtractDocElementID(t, "DocumentRef-doc2", false, "doc2", "") helperForExtractDocElementID(t, "DocumentRef-doc2:SPDXRef-file2", false, "doc2", "file2") // test with invalid ID in this document helperForExtractDocElementID(t, "a:SPDXRef-file1", true, "", "") @@ -40,7 +86,6 @@ func TestCanExtractDocumentAndElementRefsFromID(t *testing.T) { helperForExtractDocElementID(t, "SPDXRef-", true, "", "") helperForExtractDocElementID(t, "SPDXRef-file1:", true, "", "") // test with invalid ID in another document - helperForExtractDocElementID(t, "DocumentRef-doc2", true, "", "") helperForExtractDocElementID(t, "DocumentRef-doc2:", true, "", "") helperForExtractDocElementID(t, "DocumentRef-doc2:SPDXRef-", true, "", "") helperForExtractDocElementID(t, "DocumentRef-doc2:a", true, "", "") @@ -51,13 +96,18 @@ func TestCanExtractDocumentAndElementRefsFromID(t *testing.T) { } func helperForExtractDocElementID(t *testing.T, tst string, wantErr bool, wantDoc string, wantElt string) { - deID, err := extractDocElementID(tst) - if err != nil && wantErr == false { + var deID DocElementID + err := deID.FromString(tst) + if err != nil && !wantErr { t.Errorf("testing %v: expected nil error, got %v", tst, err) + } else if err == nil && wantErr { + t.Errorf("testing %v: expected non-nil error, got nil: %+v", tst, deID) + return + } else if wantErr { + // bail early if an error was expected; the behavior when an error occurs is undefined + return } - if err == nil && wantErr == true { - t.Errorf("testing %v: expected non-nil error, got nil", tst) - } + if deID.DocumentRefID != wantDoc { if wantDoc == "" { t.Errorf("testing %v: want empty string for DocumentRefID, got %v", tst, deID.DocumentRefID) @@ -65,7 +115,7 @@ func helperForExtractDocElementID(t *testing.T, tst string, wantErr bool, wantDo t.Errorf("testing %v: want %v for DocumentRefID, got %v", tst, wantDoc, deID.DocumentRefID) } } - if deID.ElementRefID != spdx.ElementID(wantElt) { + if deID.ElementRefID != ElementID(wantElt) { if wantElt == "" { t.Errorf("testing %v: want empty string for ElementRefID, got %v", tst, deID.ElementRefID) } else { @@ -75,21 +125,15 @@ func helperForExtractDocElementID(t *testing.T, tst string, wantErr bool, wantDo } func TestCanExtractSpecialDocumentIDs(t *testing.T) { - permittedSpecial := []string{"NONE", "NOASSERTION"} - // test with valid special values - helperForExtractDocElementSpecial(t, permittedSpecial, "NONE", false, "", "", "NONE") - helperForExtractDocElementSpecial(t, permittedSpecial, "NOASSERTION", false, "", "", "NOASSERTION") - // test with valid regular IDs - helperForExtractDocElementSpecial(t, permittedSpecial, "SPDXRef-file1", false, "", "file1", "") - helperForExtractDocElementSpecial(t, permittedSpecial, "DocumentRef-doc2:SPDXRef-file2", false, "doc2", "file2", "") - helperForExtractDocElementSpecial(t, permittedSpecial, "a:SPDXRef-file1", true, "", "", "") - helperForExtractDocElementSpecial(t, permittedSpecial, "DocumentRef-doc2", true, "", "", "") + helperForExtractDocElementSpecial(t, "NONE", false, "", "", "NONE") + helperForExtractDocElementSpecial(t, "NOASSERTION", false, "", "", "NOASSERTION") // test with invalid other words not on permitted list - helperForExtractDocElementSpecial(t, permittedSpecial, "FOO", true, "", "", "") + helperForExtractDocElementSpecial(t, "FOO", true, "", "", "") } -func helperForExtractDocElementSpecial(t *testing.T, permittedSpecial []string, tst string, wantErr bool, wantDoc string, wantElt string, wantSpecial string) { - deID, err := extractDocElementSpecial(tst, permittedSpecial) +func helperForExtractDocElementSpecial(t *testing.T, tst string, wantErr bool, wantDoc string, wantElt string, wantSpecial string) { + var deID DocElementID + err := deID.FromString(tst) if err != nil && wantErr == false { t.Errorf("testing %v: expected nil error, got %v", tst, err) } @@ -103,7 +147,7 @@ func helperForExtractDocElementSpecial(t *testing.T, permittedSpecial []string, t.Errorf("testing %v: want %v for DocumentRefID, got %v", tst, wantDoc, deID.DocumentRefID) } } - if deID.ElementRefID != spdx.ElementID(wantElt) { + if deID.ElementRefID != ElementID(wantElt) { if wantElt == "" { t.Errorf("testing %v: want empty string for ElementRefID, got %v", tst, deID.ElementRefID) } else { @@ -118,39 +162,3 @@ func helperForExtractDocElementSpecial(t *testing.T, permittedSpecial []string, } } } - -func TestCanExtractElementRefsOnlyFromID(t *testing.T) { - // test with valid ID in this document - helperForExtractElementID(t, "SPDXRef-file1", false, "file1") - // test with valid ID in another document - helperForExtractElementID(t, "DocumentRef-doc2:SPDXRef-file2", true, "") - // test with invalid ID in this document - helperForExtractElementID(t, "a:SPDXRef-file1", true, "") - helperForExtractElementID(t, "file1", true, "") - helperForExtractElementID(t, "SPDXRef-", true, "") - helperForExtractElementID(t, "SPDXRef-file1:", true, "") - // test with invalid ID in another document - helperForExtractElementID(t, "DocumentRef-doc2", true, "") - helperForExtractElementID(t, "DocumentRef-doc2:", true, "") - helperForExtractElementID(t, "DocumentRef-doc2:SPDXRef-", true, "") - helperForExtractElementID(t, "DocumentRef-doc2:a", true, "") - helperForExtractElementID(t, "DocumentRef-:", true, "") - helperForExtractElementID(t, "DocumentRef-:SPDXRef-file1", true, "") -} - -func helperForExtractElementID(t *testing.T, tst string, wantErr bool, wantElt string) { - eID, err := extractElementID(tst) - if err != nil && wantErr == false { - t.Errorf("testing %v: expected nil error, got %v", tst, err) - } - if err == nil && wantErr == true { - t.Errorf("testing %v: expected non-nil error, got nil", tst) - } - if eID != spdx.ElementID(wantElt) { - if wantElt == "" { - t.Errorf("testing %v: want emptyString for ElementRefID, got %v", tst, eID) - } else { - t.Errorf("testing %v: want %v for ElementRefID, got %v", tst, wantElt, eID) - } - } -} diff --git a/spdx/package.go b/spdx/package.go index e6c45223..8e9854e2 100644 --- a/spdx/package.go +++ b/spdx/package.go @@ -3,8 +3,11 @@ package spdx import ( + "crypto/sha1" "encoding/json" "fmt" + "github.com/spdx/tools-golang/utils" + "sort" "strings" ) @@ -15,40 +18,63 @@ type Supplier struct { SupplierType string } -// UnmarshalJSON takes a supplier in the typical one-line format and parses it into a Supplier struct. -// This function is also used when unmarshalling YAML -func (s *Supplier) UnmarshalJSON(data []byte) error { - // the value is just a string presented as a slice of bytes - supplierStr := string(data) - supplierStr = strings.Trim(supplierStr, "\"") +// Validate verifies that all the required fields are present. +// Returns an error if the object is invalid. +func (s Supplier) Validate() error { + // SupplierType is allowed to be empty if Supplier is "NOASSERTION" + if s.Supplier == "" || (s.SupplierType == "" && s.Supplier != "NOASSERTION") { + return fmt.Errorf("invalid Supplier, missing fields. %+v", s) + } + + return nil +} - if supplierStr == "NOASSERTION" { - s.Supplier = supplierStr +// FromString parses a string into a Supplier. +// These stings take the form: ": " +func (s *Supplier) FromString(value string) error { + if value == "NOASSERTION" { + s.Supplier = value return nil } - supplierFields := strings.SplitN(supplierStr, ": ", 2) - - if len(supplierFields) != 2 { - return fmt.Errorf("failed to parse Supplier '%s'", supplierStr) + supplierType, supplier, err := utils.ExtractSubs(value) + if err != nil { + return err } - s.SupplierType = supplierFields[0] - s.Supplier = supplierFields[1] + s.SupplierType = supplierType + s.Supplier = supplier return nil } +// String converts the Supplier to a string in the form ": " +func (s Supplier) String() string { + if s.Supplier == "NOASSERTION" { + return s.Supplier + } + + return fmt.Sprintf("%s: %s", s.SupplierType, s.Supplier) +} + +// UnmarshalJSON takes a supplier in the typical one-line format and parses it into a Supplier struct. +// This function is also used when unmarshalling YAML +func (s *Supplier) UnmarshalJSON(data []byte) error { + // the value is just a string presented as a slice of bytes + supplierStr := string(data) + supplierStr = strings.Trim(supplierStr, "\"") + + return s.FromString(supplierStr) +} + // MarshalJSON converts the receiver into a slice of bytes representing a Supplier in string form. // This function is also used when marshalling to YAML func (s Supplier) MarshalJSON() ([]byte, error) { - if s.Supplier == "NOASSERTION" { - return json.Marshal(s.Supplier) - } else if s.SupplierType != "" && s.Supplier != "" { - return json.Marshal(fmt.Sprintf("%s: %s", s.SupplierType, s.Supplier)) + if err := s.Validate(); err != nil { + return nil, err } - return []byte{}, fmt.Errorf("failed to marshal invalid Supplier: %+v", s) + return json.Marshal(s.String()) } type Originator struct { @@ -58,40 +84,63 @@ type Originator struct { OriginatorType string } -// UnmarshalJSON takes an originator in the typical one-line format and parses it into an Originator struct. -// This function is also used when unmarshalling YAML -func (o *Originator) UnmarshalJSON(data []byte) error { - // the value is just a string presented as a slice of bytes - originatorStr := string(data) - originatorStr = strings.Trim(originatorStr, "\"") +// Validate verifies that all the required fields are present. +// Returns an error if the object is invalid. +func (o Originator) Validate() error { + // Originator is allowed to be empty if Originator is "NOASSERTION" + if o.Originator == "" || (o.OriginatorType == "" && o.Originator != "NOASSERTION") { + return fmt.Errorf("invalid Originator, missing fields. %+v", o) + } + + return nil +} - if originatorStr == "NOASSERTION" { - o.Originator = originatorStr +// FromString parses a string into a Originator. +// These stings take the form: ": " +func (o *Originator) FromString(value string) error { + if value == "NOASSERTION" { + o.Originator = value return nil } - originatorFields := strings.SplitN(originatorStr, ": ", 2) - - if len(originatorFields) != 2 { - return fmt.Errorf("failed to parse Originator '%s'", originatorStr) + originatorType, originator, err := utils.ExtractSubs(value) + if err != nil { + return err } - o.OriginatorType = originatorFields[0] - o.Originator = originatorFields[1] + o.OriginatorType = originatorType + o.Originator = originator return nil } +// String converts the Originator to a string in the form ": " +func (o Originator) String() string { + if o.Originator == "NOASSERTION" { + return o.Originator + } + + return fmt.Sprintf("%s: %s", o.OriginatorType, o.Originator) +} + +// UnmarshalJSON takes an originator in the typical one-line format and parses it into an Originator struct. +// This function is also used when unmarshalling YAML +func (o *Originator) UnmarshalJSON(data []byte) error { + // the value is just a string presented as a slice of bytes + originatorStr := string(data) + originatorStr = strings.Trim(originatorStr, "\"") + + return o.FromString(originatorStr) +} + // MarshalJSON converts the receiver into a slice of bytes representing an Originator in string form. // This function is also used when marshalling to YAML func (o Originator) MarshalJSON() ([]byte, error) { - if o.Originator == "NOASSERTION" { - return json.Marshal(o.Originator) - } else if o.Originator != "" { - return json.Marshal(fmt.Sprintf("%s: %s", o.OriginatorType, o.Originator)) + if err := o.Validate(); err != nil { + return nil, err } - return []byte{}, nil + return json.Marshal(o.String()) } type PackageVerificationCode struct { @@ -346,3 +395,81 @@ type PackageExternalReference2_2 struct { // Cardinality: conditional (optional, one) for each External Reference ExternalRefComment string `json:"comment"` } + +// MakePackageVerificationCode2_1 takes a slice of files and an optional filename +// for an "excludes" file, and returns a Package Verification Code calculated +// according to SPDX spec version 2.1, section 3.9.4. +func MakePackageVerificationCode2_1(files []*File2_1, excludeFile string) (PackageVerificationCode, error) { + // create slice of strings - unsorted SHA1s for all files + shas := []string{} + for i, f := range files { + if f == nil { + return PackageVerificationCode{}, fmt.Errorf("got nil file for identifier %v", i) + } + if f.FileName != excludeFile { + // find the SHA1 hash, if present + for _, checksum := range f.Checksums { + if checksum.Algorithm == SHA1 { + shas = append(shas, checksum.Value) + } + } + } + } + + // sort the strings + sort.Strings(shas) + + // concatenate them into one string, with no trailing separators + shasConcat := strings.Join(shas, "") + + // and get its SHA1 value + hsha1 := sha1.New() + hsha1.Write([]byte(shasConcat)) + bs := hsha1.Sum(nil) + + code := PackageVerificationCode{ + Value: fmt.Sprintf("%x", bs), + ExcludedFiles: []string{excludeFile}, + } + + return code, nil +} + +// MakePackageVerificationCode2_2 takes a slice of files and an optional filename +// for an "excludes" file, and returns a Package Verification Code calculated +// according to SPDX spec version 2.2, section 3.9.4. +func MakePackageVerificationCode2_2(files []*File2_2, excludeFile string) (PackageVerificationCode, error) { + // create slice of strings - unsorted SHA1s for all files + shas := []string{} + for i, f := range files { + if f == nil { + return PackageVerificationCode{}, fmt.Errorf("got nil file for identifier %v", i) + } + if f.FileName != excludeFile { + // find the SHA1 hash, if present + for _, checksum := range f.Checksums { + if checksum.Algorithm == SHA1 { + shas = append(shas, checksum.Value) + } + } + } + } + + // sort the strings + sort.Strings(shas) + + // concatenate them into one string, with no trailing separators + shasConcat := strings.Join(shas, "") + + // and get its SHA1 value + hsha1 := sha1.New() + hsha1.Write([]byte(shasConcat)) + bs := hsha1.Sum(nil) + + code := PackageVerificationCode{ + Value: fmt.Sprintf("%x", bs), + ExcludedFiles: []string{excludeFile}, + } + + return code, nil +} diff --git a/utils/verification_test.go b/spdx/package_test.go similarity index 56% rename from utils/verification_test.go rename to spdx/package_test.go index d31614af..fdd01299 100644 --- a/utils/verification_test.go +++ b/spdx/package_test.go @@ -1,47 +1,39 @@ -// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +package spdx -package utils - -import ( - "testing" - - "github.com/spdx/tools-golang/spdx" -) - -// ===== 2.1 Verification code functionality tests ===== +import "testing" func TestPackage2_1CanGetVerificationCode(t *testing.T) { - files := []*spdx.File2_1{ + files := []*File2_1{ { FileName: "file2.txt", FileSPDXIdentifier: "File0", - Checksums: []spdx.Checksum{{Value: "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", Algorithm: spdx.SHA1}}, + Checksums: []Checksum{{Value: "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", Algorithm: SHA1}}, }, { FileName: "file1.txt", FileSPDXIdentifier: "File1", - Checksums: []spdx.Checksum{{Value: "3333333333bbbbbbbbbbccccccccccdddddddddd", Algorithm: spdx.SHA1}}, + Checksums: []Checksum{{Value: "3333333333bbbbbbbbbbccccccccccdddddddddd", Algorithm: SHA1}}, }, { FileName: "file3.txt", FileSPDXIdentifier: "File2", - Checksums: []spdx.Checksum{{Value: "8888888888bbbbbbbbbbccccccccccdddddddddd", Algorithm: spdx.SHA1}}, + Checksums: []Checksum{{Value: "8888888888bbbbbbbbbbccccccccccdddddddddd", Algorithm: SHA1}}, }, { FileName: "file5.txt", FileSPDXIdentifier: "File3", - Checksums: []spdx.Checksum{{Value: "2222222222bbbbbbbbbbccccccccccdddddddddd", Algorithm: spdx.SHA1}}, + Checksums: []Checksum{{Value: "2222222222bbbbbbbbbbccccccccccdddddddddd", Algorithm: SHA1}}, }, { FileName: "file4.txt", FileSPDXIdentifier: "File4", - Checksums: []spdx.Checksum{{Value: "bbbbbbbbbbccccccccccddddddddddaaaaaaaaaa", Algorithm: spdx.SHA1}}, + Checksums: []Checksum{{Value: "bbbbbbbbbbccccccccccddddddddddaaaaaaaaaa", Algorithm: SHA1}}, }, } - wantCode := spdx.PackageVerificationCode{Value: "ac924b375119c81c1f08c3e2722044bfbbdcd3dc"} + wantCode := PackageVerificationCode{Value: "ac924b375119c81c1f08c3e2722044bfbbdcd3dc"} - gotCode, err := GetVerificationCode2_1(files, "") + gotCode, err := MakePackageVerificationCode2_1(files, "") if err != nil { t.Fatalf("expected nil error, got %v", err) } @@ -52,37 +44,37 @@ func TestPackage2_1CanGetVerificationCode(t *testing.T) { } func TestPackage2_1CanGetVerificationCodeIgnoringExcludesFile(t *testing.T) { - files := []*spdx.File2_1{ + files := []*File2_1{ { FileName: "file1.txt", FileSPDXIdentifier: "File0", - Checksums: []spdx.Checksum{{Value: "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", Algorithm: spdx.SHA1}}, + Checksums: []Checksum{{Value: "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", Algorithm: SHA1}}, }, { FileName: "file2.txt", FileSPDXIdentifier: "File1", - Checksums: []spdx.Checksum{{Value: "3333333333bbbbbbbbbbccccccccccdddddddddd", Algorithm: spdx.SHA1}}, + Checksums: []Checksum{{Value: "3333333333bbbbbbbbbbccccccccccdddddddddd", Algorithm: SHA1}}, }, { FileName: "thisfile.spdx", FileSPDXIdentifier: "File2", - Checksums: []spdx.Checksum{{Value: "bbbbbbbbbbccccccccccddddddddddaaaaaaaaaa", Algorithm: spdx.SHA1}}, + Checksums: []Checksum{{Value: "bbbbbbbbbbccccccccccddddddddddaaaaaaaaaa", Algorithm: SHA1}}, }, { FileName: "file3.txt", FileSPDXIdentifier: "File3", - Checksums: []spdx.Checksum{{Value: "8888888888bbbbbbbbbbccccccccccdddddddddd", Algorithm: spdx.SHA1}}, + Checksums: []Checksum{{Value: "8888888888bbbbbbbbbbccccccccccdddddddddd", Algorithm: SHA1}}, }, { FileName: "file4.txt", FileSPDXIdentifier: "File4", - Checksums: []spdx.Checksum{{Value: "2222222222bbbbbbbbbbccccccccccdddddddddd", Algorithm: spdx.SHA1}}, + Checksums: []Checksum{{Value: "2222222222bbbbbbbbbbccccccccccdddddddddd", Algorithm: SHA1}}, }, } - wantCode := spdx.PackageVerificationCode{Value: "17fab1bd18fe5c13b5d3983f1c17e5f88b8ff266"} + wantCode := PackageVerificationCode{Value: "17fab1bd18fe5c13b5d3983f1c17e5f88b8ff266"} - gotCode, err := GetVerificationCode2_1(files, "thisfile.spdx") + gotCode, err := MakePackageVerificationCode2_1(files, "thisfile.spdx") if err != nil { t.Fatalf("expected nil error, got %v", err) } @@ -92,36 +84,34 @@ func TestPackage2_1CanGetVerificationCodeIgnoringExcludesFile(t *testing.T) { } func TestPackage2_1GetVerificationCodeFailsIfNilFileInSlice(t *testing.T) { - files := []*spdx.File2_1{ + files := []*File2_1{ { FileName: "file2.txt", FileSPDXIdentifier: "File0", - Checksums: []spdx.Checksum{{Value: "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", Algorithm: spdx.SHA1}}, + Checksums: []Checksum{{Value: "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", Algorithm: SHA1}}, }, nil, { FileName: "file3.txt", FileSPDXIdentifier: "File2", - Checksums: []spdx.Checksum{{Value: "8888888888bbbbbbbbbbccccccccccdddddddddd", Algorithm: spdx.SHA1}}, + Checksums: []Checksum{{Value: "8888888888bbbbbbbbbbccccccccccdddddddddd", Algorithm: SHA1}}, }, } - _, err := GetVerificationCode2_1(files, "") + _, err := MakePackageVerificationCode2_1(files, "") if err == nil { t.Fatalf("expected non-nil error, got nil") } } -// ===== 2.2 Verification code functionality tests ===== - func TestPackage2_2CanGetVerificationCode(t *testing.T) { - files := []*spdx.File2_2{ + files := []*File2_2{ { FileName: "file2.txt", FileSPDXIdentifier: "File0", - Checksums: []spdx.Checksum{ + Checksums: []Checksum{ { - Algorithm: spdx.SHA1, + Algorithm: SHA1, Value: "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", }, }, @@ -129,9 +119,9 @@ func TestPackage2_2CanGetVerificationCode(t *testing.T) { { FileName: "file1.txt", FileSPDXIdentifier: "File1", - Checksums: []spdx.Checksum{ + Checksums: []Checksum{ { - Algorithm: spdx.SHA1, + Algorithm: SHA1, Value: "3333333333bbbbbbbbbbccccccccccdddddddddd", }, }, @@ -139,9 +129,9 @@ func TestPackage2_2CanGetVerificationCode(t *testing.T) { { FileName: "file3.txt", FileSPDXIdentifier: "File2", - Checksums: []spdx.Checksum{ + Checksums: []Checksum{ { - Algorithm: spdx.SHA1, + Algorithm: SHA1, Value: "8888888888bbbbbbbbbbccccccccccdddddddddd", }, }, @@ -149,9 +139,9 @@ func TestPackage2_2CanGetVerificationCode(t *testing.T) { { FileName: "file5.txt", FileSPDXIdentifier: "File3", - Checksums: []spdx.Checksum{ + Checksums: []Checksum{ { - Algorithm: spdx.SHA1, + Algorithm: SHA1, Value: "2222222222bbbbbbbbbbccccccccccdddddddddd", }, }, @@ -159,18 +149,18 @@ func TestPackage2_2CanGetVerificationCode(t *testing.T) { { FileName: "file4.txt", FileSPDXIdentifier: "File4", - Checksums: []spdx.Checksum{ + Checksums: []Checksum{ { - Algorithm: spdx.SHA1, + Algorithm: SHA1, Value: "bbbbbbbbbbccccccccccddddddddddaaaaaaaaaa", }, }, }, } - wantCode := spdx.PackageVerificationCode{Value: "ac924b375119c81c1f08c3e2722044bfbbdcd3dc"} + wantCode := PackageVerificationCode{Value: "ac924b375119c81c1f08c3e2722044bfbbdcd3dc"} - gotCode, err := GetVerificationCode2_2(files, "") + gotCode, err := MakePackageVerificationCode2_2(files, "") if err != nil { t.Fatalf("expected nil error, got %v", err) } @@ -181,13 +171,13 @@ func TestPackage2_2CanGetVerificationCode(t *testing.T) { } func TestPackage2_2CanGetVerificationCodeIgnoringExcludesFile(t *testing.T) { - files := []*spdx.File2_2{ + files := []*File2_2{ { FileName: "file1.txt", FileSPDXIdentifier: "File0", - Checksums: []spdx.Checksum{ + Checksums: []Checksum{ { - Algorithm: spdx.SHA1, + Algorithm: SHA1, Value: "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", }, }, @@ -195,9 +185,9 @@ func TestPackage2_2CanGetVerificationCodeIgnoringExcludesFile(t *testing.T) { { FileName: "file2.txt", FileSPDXIdentifier: "File1", - Checksums: []spdx.Checksum{ + Checksums: []Checksum{ { - Algorithm: spdx.SHA1, + Algorithm: SHA1, Value: "3333333333bbbbbbbbbbccccccccccdddddddddd", }, }, @@ -205,9 +195,9 @@ func TestPackage2_2CanGetVerificationCodeIgnoringExcludesFile(t *testing.T) { { FileName: "thisfile.spdx", FileSPDXIdentifier: "File2", - Checksums: []spdx.Checksum{ + Checksums: []Checksum{ { - Algorithm: spdx.SHA1, + Algorithm: SHA1, Value: "bbbbbbbbbbccccccccccddddddddddaaaaaaaaaa", }, }, @@ -215,9 +205,9 @@ func TestPackage2_2CanGetVerificationCodeIgnoringExcludesFile(t *testing.T) { { FileName: "file3.txt", FileSPDXIdentifier: "File3", - Checksums: []spdx.Checksum{ + Checksums: []Checksum{ { - Algorithm: spdx.SHA1, + Algorithm: SHA1, Value: "8888888888bbbbbbbbbbccccccccccdddddddddd", }, }, @@ -225,18 +215,18 @@ func TestPackage2_2CanGetVerificationCodeIgnoringExcludesFile(t *testing.T) { { FileName: "file4.txt", FileSPDXIdentifier: "File4", - Checksums: []spdx.Checksum{ + Checksums: []Checksum{ { - Algorithm: spdx.SHA1, + Algorithm: SHA1, Value: "2222222222bbbbbbbbbbccccccccccdddddddddd", }, }, }, } - wantCode := spdx.PackageVerificationCode{Value: "17fab1bd18fe5c13b5d3983f1c17e5f88b8ff266"} + wantCode := PackageVerificationCode{Value: "17fab1bd18fe5c13b5d3983f1c17e5f88b8ff266"} - gotCode, err := GetVerificationCode2_2(files, "thisfile.spdx") + gotCode, err := MakePackageVerificationCode2_2(files, "thisfile.spdx") if err != nil { t.Fatalf("expected nil error, got %v", err) } @@ -246,13 +236,13 @@ func TestPackage2_2CanGetVerificationCodeIgnoringExcludesFile(t *testing.T) { } func TestPackage2_2GetVerificationCodeFailsIfNilFileInSlice(t *testing.T) { - files := []*spdx.File2_2{ + files := []*File2_2{ { FileName: "file2.txt", FileSPDXIdentifier: "File0", - Checksums: []spdx.Checksum{ + Checksums: []Checksum{ { - Algorithm: spdx.SHA1, + Algorithm: SHA1, Value: "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd", }, }, @@ -261,16 +251,16 @@ func TestPackage2_2GetVerificationCodeFailsIfNilFileInSlice(t *testing.T) { { FileName: "file3.txt", FileSPDXIdentifier: "File2", - Checksums: []spdx.Checksum{ + Checksums: []Checksum{ { - Algorithm: spdx.SHA1, + Algorithm: SHA1, Value: "8888888888bbbbbbbbbbccccccccccdddddddddd", }, }, }, } - _, err := GetVerificationCode2_2(files, "") + _, err := MakePackageVerificationCode2_2(files, "") if err == nil { t.Fatalf("expected non-nil error, got nil") } diff --git a/spdx/snippet.go b/spdx/snippet.go index 6bffb8c8..8bd2a4d9 100644 --- a/spdx/snippet.go +++ b/spdx/snippet.go @@ -2,6 +2,13 @@ package spdx +import ( + "errors" + "fmt" + "strconv" + "strings" +) + type SnippetRangePointer struct { // 5.3: Snippet Byte Range: [start byte]:[end byte] // Cardinality: mandatory, one @@ -19,6 +26,50 @@ type SnippetRange struct { EndPointer SnippetRangePointer `json:"endPointer"` } +func (s SnippetRange) Validate() error { + if s.StartPointer.Offset == 0 && s.StartPointer.LineNumber == 0 && + s.EndPointer.Offset == 0 && s.EndPointer.LineNumber == 0 { + return errors.New("no range info present in SnippetRange") + } + + return nil +} + +func (s SnippetRange) String() string { + if s.EndPointer.Offset != 0 { + return fmt.Sprintf("%d:%d", s.StartPointer.Offset, s.EndPointer.Offset) + } + + return fmt.Sprintf("%d:%d", s.StartPointer.LineNumber, s.EndPointer.LineNumber) +} + +func (s *SnippetRange) FromString(value string, isByteRange bool) error { + strValues := strings.Split(value, ":") + if len(strValues) != 2 { + return fmt.Errorf("invalid SnippetRange: %s", value) + } + + values := make([]int, 2) + for ii, value := range strValues { + valueInt, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return fmt.Errorf("couldn't parse integer from SnippetRange value '%s': %v", value, err.Error()) + } + + values[ii] = int(valueInt) + } + + if isByteRange { + s.StartPointer.Offset = values[0] + s.EndPointer.Offset = values[1] + } else { + s.StartPointer.LineNumber = values[0] + s.EndPointer.LineNumber = values[1] + } + + return nil +} + // Snippet2_1 is a Snippet section of an SPDX Document for version 2.1 of the spec. type Snippet2_1 struct { diff --git a/tvloader/parser2v1/parse_annotation.go b/tvloader/parser2v1/parse_annotation.go index ca2e8504..b3df6735 100644 --- a/tvloader/parser2v1/parse_annotation.go +++ b/tvloader/parser2v1/parse_annotation.go @@ -4,6 +4,7 @@ package parser2v1 import ( "fmt" + "github.com/spdx/tools-golang/utils" ) func (parser *tvParser2_1) parsePairForAnnotation2_1(tag string, value string) error { @@ -13,7 +14,7 @@ func (parser *tvParser2_1) parsePairForAnnotation2_1(tag string, value string) e switch tag { case "Annotator": - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } @@ -28,11 +29,10 @@ func (parser *tvParser2_1) parsePairForAnnotation2_1(tag string, value string) e case "AnnotationType": parser.ann.AnnotationType = value case "SPDXREF": - deID, err := extractDocElementID(value) + err := parser.ann.AnnotationSPDXIdentifier.FromString(value) if err != nil { return err } - parser.ann.AnnotationSPDXIdentifier = deID case "AnnotationComment": parser.ann.AnnotationComment = value default: diff --git a/tvloader/parser2v1/parse_creation_info.go b/tvloader/parser2v1/parse_creation_info.go index df16008b..958f8dd1 100644 --- a/tvloader/parser2v1/parse_creation_info.go +++ b/tvloader/parser2v1/parse_creation_info.go @@ -4,9 +4,8 @@ package parser2v1 import ( "fmt" - "strings" - "github.com/spdx/tools-golang/spdx" + "github.com/spdx/tools-golang/utils" ) func (parser *tvParser2_1) parsePairFromCreationInfo2_1(tag string, value string) error { @@ -25,7 +24,7 @@ func (parser *tvParser2_1) parsePairFromCreationInfo2_1(tag string, value string case "LicenseListVersion": ci.LicenseListVersion = value case "Creator": - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } @@ -99,57 +98,3 @@ func (parser *tvParser2_1) parsePairFromCreationInfo2_1(tag string, value string return nil } - -// ===== Helper functions ===== - -func extractExternalDocumentReference(value string) (string, string, string, string, error) { - sp := strings.Split(value, " ") - // remove any that are just whitespace - keepSp := []string{} - for _, s := range sp { - ss := strings.TrimSpace(s) - if ss != "" { - keepSp = append(keepSp, ss) - } - } - - var documentRefID, uri, alg, checksum string - - // now, should have 4 items (or 3, if Alg and Checksum were joined) - // and should be able to map them - if len(keepSp) == 4 { - documentRefID = keepSp[0] - uri = keepSp[1] - alg = keepSp[2] - // check that colon is present for alg, and remove it - if !strings.HasSuffix(alg, ":") { - return "", "", "", "", fmt.Errorf("algorithm does not end with colon") - } - alg = strings.TrimSuffix(alg, ":") - checksum = keepSp[3] - } else if len(keepSp) == 3 { - documentRefID = keepSp[0] - uri = keepSp[1] - // split on colon into alg and checksum - parts := strings.SplitN(keepSp[2], ":", 2) - if len(parts) != 2 { - return "", "", "", "", fmt.Errorf("missing colon separator between algorithm and checksum") - } - alg = parts[0] - checksum = parts[1] - } else { - return "", "", "", "", fmt.Errorf("expected 4 elements, got %d", len(keepSp)) - } - - // additionally, we should be able to parse the first element as a - // DocumentRef- ID string, and we should remove that prefix - if !strings.HasPrefix(documentRefID, "DocumentRef-") { - return "", "", "", "", fmt.Errorf("expected first element to have DocumentRef- prefix") - } - documentRefID = strings.TrimPrefix(documentRefID, "DocumentRef-") - if documentRefID == "" { - return "", "", "", "", fmt.Errorf("document identifier has nothing after prefix") - } - - return documentRefID, uri, alg, checksum, nil -} diff --git a/tvloader/parser2v1/parse_creation_info_test.go b/tvloader/parser2v1/parse_creation_info_test.go index 83058dd8..27b9805d 100644 --- a/tvloader/parser2v1/parse_creation_info_test.go +++ b/tvloader/parser2v1/parse_creation_info_test.go @@ -355,73 +355,3 @@ func TestParser2_1CICreatesAnnotation(t *testing.T) { t.Errorf("pointer to new Annotation doesn't match idx 0 for doc.Annotations[]") } } - -// ===== Helper function tests ===== - -func TestCanExtractExternalDocumentReference(t *testing.T) { - refstring := "DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 SHA1:d6a770ba38583ed4bb4525bd96e50461655d2759" - wantDocumentRefID := "spdx-tool-1.2" - wantURI := "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301" - wantAlg := "SHA1" - wantChecksum := "d6a770ba38583ed4bb4525bd96e50461655d2759" - - gotDocumentRefID, gotURI, gotAlg, gotChecksum, err := extractExternalDocumentReference(refstring) - if err != nil { - t.Errorf("got non-nil error: %v", err) - } - if wantDocumentRefID != gotDocumentRefID { - t.Errorf("wanted document ref ID %s, got %s", wantDocumentRefID, gotDocumentRefID) - } - if wantURI != gotURI { - t.Errorf("wanted URI %s, got %s", wantURI, gotURI) - } - if wantAlg != gotAlg { - t.Errorf("wanted alg %s, got %s", wantAlg, gotAlg) - } - if wantChecksum != gotChecksum { - t.Errorf("wanted checksum %s, got %s", wantChecksum, gotChecksum) - } -} - -func TestCanExtractExternalDocumentReferenceWithExtraWhitespace(t *testing.T) { - refstring := " DocumentRef-spdx-tool-1.2 \t http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 \t SHA1: \t d6a770ba38583ed4bb4525bd96e50461655d2759" - wantDocumentRefID := "spdx-tool-1.2" - wantURI := "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301" - wantAlg := "SHA1" - wantChecksum := "d6a770ba38583ed4bb4525bd96e50461655d2759" - - gotDocumentRefID, gotURI, gotAlg, gotChecksum, err := extractExternalDocumentReference(refstring) - if err != nil { - t.Errorf("got non-nil error: %v", err) - } - if wantDocumentRefID != gotDocumentRefID { - t.Errorf("wanted document ref ID %s, got %s", wantDocumentRefID, gotDocumentRefID) - } - if wantURI != gotURI { - t.Errorf("wanted URI %s, got %s", wantURI, gotURI) - } - if wantAlg != gotAlg { - t.Errorf("wanted alg %s, got %s", wantAlg, gotAlg) - } - if wantChecksum != gotChecksum { - t.Errorf("wanted checksum %s, got %s", wantChecksum, gotChecksum) - } -} - -func TestFailsExternalDocumentReferenceWithInvalidFormats(t *testing.T) { - invalidRefs := []string{ - "whoops", - "DocumentRef-", - "DocumentRef- ", - "DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301", - "DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 d6a770ba38583ed4bb4525bd96e50461655d2759", - "DocumentRef-spdx-tool-1.2", - "spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 SHA1:d6a770ba38583ed4bb4525bd96e50461655d2759", - } - for _, refstring := range invalidRefs { - _, _, _, _, err := extractExternalDocumentReference(refstring) - if err == nil { - t.Errorf("expected non-nil error for %s, got nil", refstring) - } - } -} diff --git a/tvloader/parser2v1/parse_file.go b/tvloader/parser2v1/parse_file.go index 81768bb6..3ae72fb8 100644 --- a/tvloader/parser2v1/parse_file.go +++ b/tvloader/parser2v1/parse_file.go @@ -4,6 +4,7 @@ package parser2v1 import ( "fmt" + "github.com/spdx/tools-golang/utils" "github.com/spdx/tools-golang/spdx" ) @@ -43,11 +44,10 @@ func (parser *tvParser2_1) parsePairFromFile2_1(tag string, value string) error return parser.parsePairFromOtherLicense2_1(tag, value) // tags for file data case "SPDXID": - eID, err := extractElementID(value) + err := parser.file.FileSPDXIdentifier.FromString(value) if err != nil { return err } - parser.file.FileSPDXIdentifier = eID if parser.pkg == nil { if parser.doc.Files == nil { parser.doc.Files = []*spdx.File2_1{} @@ -62,7 +62,7 @@ func (parser *tvParser2_1) parsePairFromFile2_1(tag string, value string) error case "FileType": parser.file.FileTypes = append(parser.file.FileTypes, value) case "FileChecksum": - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } diff --git a/tvloader/parser2v1/parse_package.go b/tvloader/parser2v1/parse_package.go index 22fc1ed2..56a22f57 100644 --- a/tvloader/parser2v1/parse_package.go +++ b/tvloader/parser2v1/parse_package.go @@ -4,6 +4,7 @@ package parser2v1 import ( "fmt" + "github.com/spdx/tools-golang/utils" "strings" "github.com/spdx/tools-golang/spdx" @@ -39,11 +40,10 @@ func (parser *tvParser2_1) parsePairFromPackage2_1(tag string, value string) err parser.st = psOtherLicense2_1 return parser.parsePairFromOtherLicense2_1(tag, value) case "SPDXID": - eID, err := extractElementID(value) + err := parser.pkg.PackageSPDXIdentifier.FromString(value) if err != nil { return err } - parser.pkg.PackageSPDXIdentifier = eID if parser.doc.Packages == nil { parser.doc.Packages = []*spdx.Package2_1{} } @@ -58,7 +58,7 @@ func (parser *tvParser2_1) parsePairFromPackage2_1(tag string, value string) err parser.pkg.PackageSupplier.Supplier = value break } - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } @@ -75,7 +75,7 @@ func (parser *tvParser2_1) parsePairFromPackage2_1(tag string, value string) err parser.pkg.PackageOriginator.Originator = value break } - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } @@ -98,7 +98,7 @@ func (parser *tvParser2_1) parsePairFromPackage2_1(tag string, value string) err case "PackageVerificationCode": parser.pkg.PackageVerificationCode = extractCodeAndExcludes(value) case "PackageChecksum": - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } diff --git a/tvloader/parser2v1/parse_relationship.go b/tvloader/parser2v1/parse_relationship.go index 2b9b8ccb..c561ead2 100644 --- a/tvloader/parser2v1/parse_relationship.go +++ b/tvloader/parser2v1/parse_relationship.go @@ -29,17 +29,15 @@ func (parser *tvParser2_1) parsePairForRelationship2_1(tag string, value string) return fmt.Errorf("invalid relationship format for %s", value) } - aID, err := extractDocElementID(strings.TrimSpace(rp[0])) + err := parser.rln.RefA.FromString(strings.TrimSpace(rp[0])) if err != nil { return err } - parser.rln.RefA = aID parser.rln.Relationship = strings.TrimSpace(rp[1]) - bID, err := extractDocElementID(strings.TrimSpace(rp[2])) + err = parser.rln.RefB.FromString(strings.TrimSpace(rp[2])) if err != nil { return err } - parser.rln.RefB = bID return nil } diff --git a/tvloader/parser2v1/parse_review.go b/tvloader/parser2v1/parse_review.go index 0d102d3b..abdf2d63 100644 --- a/tvloader/parser2v1/parse_review.go +++ b/tvloader/parser2v1/parse_review.go @@ -4,6 +4,7 @@ package parser2v1 import ( "fmt" + "github.com/spdx/tools-golang/utils" "github.com/spdx/tools-golang/spdx" ) @@ -14,7 +15,7 @@ func (parser *tvParser2_1) parsePairFromReview2_1(tag string, value string) erro case "Reviewer": parser.rev = &spdx.Review2_1{} parser.doc.Reviews = append(parser.doc.Reviews, parser.rev) - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } diff --git a/tvloader/parser2v1/parse_snippet.go b/tvloader/parser2v1/parse_snippet.go index 33392d56..cc5ae2a9 100644 --- a/tvloader/parser2v1/parse_snippet.go +++ b/tvloader/parser2v1/parse_snippet.go @@ -4,6 +4,7 @@ package parser2v1 import ( "fmt" + "github.com/spdx/tools-golang/utils" "strconv" "github.com/spdx/tools-golang/spdx" @@ -18,7 +19,7 @@ func (parser *tvParser2_1) parsePairFromSnippet2_1(tag string, value string) err return fmt.Errorf("file with FileName %s does not have SPDX identifier", parser.file.FileName) } parser.snippet = &spdx.Snippet2_1{} - eID, err := extractElementID(value) + err := parser.snippet.SnippetSPDXIdentifier.FromString(value) if err != nil { return err } @@ -27,9 +28,8 @@ func (parser *tvParser2_1) parsePairFromSnippet2_1(tag string, value string) err if parser.file.Snippets == nil { parser.file.Snippets = map[spdx.ElementID]*spdx.Snippet2_1{} } - parser.file.Snippets[eID] = parser.snippet + parser.file.Snippets[parser.snippet.SnippetSPDXIdentifier] = parser.snippet } - parser.snippet.SnippetSPDXIdentifier = eID // tag for creating new file section and going back to parsing File case "FileName": parser.st = psFile2_1 @@ -47,13 +47,14 @@ func (parser *tvParser2_1) parsePairFromSnippet2_1(tag string, value string) err return parser.parsePairFromOtherLicense2_1(tag, value) // tags for snippet data case "SnippetFromFileSPDXID": - deID, err := extractDocElementID(value) + var deID spdx.DocElementID + err := deID.FromString(value) if err != nil { return err } parser.snippet.SnippetFromFileSPDXIdentifier = deID.ElementRefID case "SnippetByteRange": - byteStart, byteEnd, err := extractSubs(value) + byteStart, byteEnd, err := utils.ExtractSubs(value) if err != nil { return err } @@ -72,7 +73,7 @@ func (parser *tvParser2_1) parsePairFromSnippet2_1(tag string, value string) err byteRange := spdx.SnippetRange{StartPointer: spdx.SnippetRangePointer{Offset: bIntStart}, EndPointer: spdx.SnippetRangePointer{Offset: bIntEnd}} parser.snippet.Ranges = append(parser.snippet.Ranges, byteRange) case "SnippetLineRange": - lineStart, lineEnd, err := extractSubs(value) + lineStart, lineEnd, err := utils.ExtractSubs(value) if err != nil { return err } diff --git a/tvloader/parser2v1/parser.go b/tvloader/parser2v1/parser.go index 70f4819c..93c247e4 100644 --- a/tvloader/parser2v1/parser.go +++ b/tvloader/parser2v1/parser.go @@ -5,7 +5,6 @@ package parser2v1 import ( "fmt" - "github.com/spdx/tools-golang/spdx" "github.com/spdx/tools-golang/tvloader/reader" ) @@ -70,25 +69,20 @@ func (parser *tvParser2_1) parsePairFromStart2_1(tag string, value string) error case "DataLicense": parser.doc.DataLicense = value case "SPDXID": - eID, err := extractElementID(value) + err := parser.doc.SPDXIdentifier.FromString(value) if err != nil { return err } - parser.doc.SPDXIdentifier = eID case "DocumentName": parser.doc.DocumentName = value case "DocumentNamespace": parser.doc.DocumentNamespace = value case "ExternalDocumentRef": - documentRefID, uri, alg, checksum, err := extractExternalDocumentReference(value) + var edr spdx.ExternalDocumentRef2_1 + err := edr.FromString(value) if err != nil { return err } - edr := spdx.ExternalDocumentRef2_1{ - DocumentRefID: documentRefID, - URI: uri, - Checksum: spdx.Checksum{Algorithm: spdx.ChecksumAlgorithm(alg), Value: checksum}, - } parser.doc.ExternalDocumentReferences = append(parser.doc.ExternalDocumentReferences, edr) case "DocumentComment": parser.doc.DocumentComment = value diff --git a/tvloader/parser2v1/util.go b/tvloader/parser2v1/util.go deleted file mode 100644 index d2df57b1..00000000 --- a/tvloader/parser2v1/util.go +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - -package parser2v1 - -import ( - "fmt" - "strings" - - "github.com/spdx/tools-golang/spdx" -) - -// used to extract key / value from embedded substrings -// returns subkey, subvalue, nil if no error, or "", "", error otherwise -func extractSubs(value string) (string, string, error) { - // parse the value to see if it's a valid subvalue format - sp := strings.SplitN(value, ":", 2) - if len(sp) == 1 { - return "", "", fmt.Errorf("invalid subvalue format for %s (no colon found)", value) - } - - subkey := strings.TrimSpace(sp[0]) - subvalue := strings.TrimSpace(sp[1]) - - return subkey, subvalue, nil -} - -// used to extract DocumentRef and SPDXRef values from an SPDX Identifier -// which can point either to this document or to a different one -func extractDocElementID(value string) (spdx.DocElementID, error) { - docRefID := "" - idStr := value - - // check prefix to see if it's a DocumentRef ID - if strings.HasPrefix(idStr, "DocumentRef-") { - // extract the part that comes between "DocumentRef-" and ":" - strs := strings.Split(idStr, ":") - // should be exactly two, part before and part after - if len(strs) < 2 { - return spdx.DocElementID{}, fmt.Errorf("no colon found although DocumentRef- prefix present") - } - if len(strs) > 2 { - return spdx.DocElementID{}, fmt.Errorf("more than one colon found") - } - - // trim the prefix and confirm non-empty - docRefID = strings.TrimPrefix(strs[0], "DocumentRef-") - if docRefID == "" { - return spdx.DocElementID{}, fmt.Errorf("document identifier has nothing after prefix") - } - // and use remainder for element ID parsing - idStr = strs[1] - } - - // check prefix to confirm it's got the right prefix for element IDs - if !strings.HasPrefix(idStr, "SPDXRef-") { - return spdx.DocElementID{}, fmt.Errorf("missing SPDXRef- prefix for element identifier") - } - - // make sure no colons are present - if strings.Contains(idStr, ":") { - // we know this means there was no DocumentRef- prefix, because - // we would have handled multiple colons above if it was - return spdx.DocElementID{}, fmt.Errorf("invalid colon in element identifier") - } - - // trim the prefix and confirm non-empty - eltRefID := strings.TrimPrefix(idStr, "SPDXRef-") - if eltRefID == "" { - return spdx.DocElementID{}, fmt.Errorf("element identifier has nothing after prefix") - } - - // we're good - return spdx.DocElementID{DocumentRefID: docRefID, ElementRefID: spdx.ElementID(eltRefID)}, nil -} - -// used to extract SPDXRef values only from an SPDX Identifier which can point -// to this document only. Use extractDocElementID for parsing IDs that can -// refer either to this document or a different one. -func extractElementID(value string) (spdx.ElementID, error) { - // check prefix to confirm it's got the right prefix for element IDs - if !strings.HasPrefix(value, "SPDXRef-") { - return spdx.ElementID(""), fmt.Errorf("missing SPDXRef- prefix for element identifier") - } - - // make sure no colons are present - if strings.Contains(value, ":") { - return spdx.ElementID(""), fmt.Errorf("invalid colon in element identifier") - } - - // trim the prefix and confirm non-empty - eltRefID := strings.TrimPrefix(value, "SPDXRef-") - if eltRefID == "" { - return spdx.ElementID(""), fmt.Errorf("element identifier has nothing after prefix") - } - - // we're good - return spdx.ElementID(eltRefID), nil -} diff --git a/tvloader/parser2v1/util_test.go b/tvloader/parser2v1/util_test.go deleted file mode 100644 index 79afc1e8..00000000 --- a/tvloader/parser2v1/util_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later -package parser2v1 - -import ( - "testing" - - "github.com/spdx/tools-golang/spdx" -) - -// ===== Helper function tests ===== - -func TestCanExtractSubvalues(t *testing.T) { - subkey, subvalue, err := extractSubs("SHA1: abc123") - if err != nil { - t.Errorf("got error when calling extractSubs: %v", err) - } - if subkey != "SHA1" { - t.Errorf("got %v for subkey", subkey) - } - if subvalue != "abc123" { - t.Errorf("got %v for subvalue", subvalue) - } -} - -func TestReturnsErrorForInvalidSubvalueFormat(t *testing.T) { - _, _, err := extractSubs("blah") - if err == nil { - t.Errorf("expected error when calling extractSubs for invalid format (0 colons), got nil") - } -} - -func TestCanExtractDocumentAndElementRefsFromID(t *testing.T) { - // test with valid ID in this document - helperForExtractDocElementID(t, "SPDXRef-file1", false, "", "file1") - // test with valid ID in another document - helperForExtractDocElementID(t, "DocumentRef-doc2:SPDXRef-file2", false, "doc2", "file2") - // test with invalid ID in this document - helperForExtractDocElementID(t, "a:SPDXRef-file1", true, "", "") - helperForExtractDocElementID(t, "file1", true, "", "") - helperForExtractDocElementID(t, "SPDXRef-", true, "", "") - helperForExtractDocElementID(t, "SPDXRef-file1:", true, "", "") - // test with invalid ID in another document - helperForExtractDocElementID(t, "DocumentRef-doc2", true, "", "") - helperForExtractDocElementID(t, "DocumentRef-doc2:", true, "", "") - helperForExtractDocElementID(t, "DocumentRef-doc2:SPDXRef-", true, "", "") - helperForExtractDocElementID(t, "DocumentRef-doc2:a", true, "", "") - helperForExtractDocElementID(t, "DocumentRef-:", true, "", "") - helperForExtractDocElementID(t, "DocumentRef-:SPDXRef-file1", true, "", "") - // test with invalid formats - helperForExtractDocElementID(t, "DocumentRef-doc2:SPDXRef-file1:file2", true, "", "") -} - -func helperForExtractDocElementID(t *testing.T, tst string, wantErr bool, wantDoc string, wantElt string) { - deID, err := extractDocElementID(tst) - if err != nil && wantErr == false { - t.Errorf("testing %v: expected nil error, got %v", tst, err) - } - if err == nil && wantErr == true { - t.Errorf("testing %v: expected non-nil error, got nil", tst) - } - if deID.DocumentRefID != wantDoc { - if wantDoc == "" { - t.Errorf("testing %v: want empty string for DocumentRefID, got %v", tst, deID.DocumentRefID) - } else { - t.Errorf("testing %v: want %v for DocumentRefID, got %v", tst, wantDoc, deID.DocumentRefID) - } - } - if deID.ElementRefID != spdx.ElementID(wantElt) { - if wantElt == "" { - t.Errorf("testing %v: want emptyString for ElementRefID, got %v", tst, deID.ElementRefID) - } else { - t.Errorf("testing %v: want %v for ElementRefID, got %v", tst, wantElt, deID.ElementRefID) - } - } -} - -func TestCanExtractElementRefsOnlyFromID(t *testing.T) { - // test with valid ID in this document - helperForExtractElementID(t, "SPDXRef-file1", false, "file1") - // test with valid ID in another document - helperForExtractElementID(t, "DocumentRef-doc2:SPDXRef-file2", true, "") - // test with invalid ID in this document - helperForExtractElementID(t, "a:SPDXRef-file1", true, "") - helperForExtractElementID(t, "file1", true, "") - helperForExtractElementID(t, "SPDXRef-", true, "") - helperForExtractElementID(t, "SPDXRef-file1:", true, "") - // test with invalid ID in another document - helperForExtractElementID(t, "DocumentRef-doc2", true, "") - helperForExtractElementID(t, "DocumentRef-doc2:", true, "") - helperForExtractElementID(t, "DocumentRef-doc2:SPDXRef-", true, "") - helperForExtractElementID(t, "DocumentRef-doc2:a", true, "") - helperForExtractElementID(t, "DocumentRef-:", true, "") - helperForExtractElementID(t, "DocumentRef-:SPDXRef-file1", true, "") -} - -func helperForExtractElementID(t *testing.T, tst string, wantErr bool, wantElt string) { - eID, err := extractElementID(tst) - if err != nil && wantErr == false { - t.Errorf("testing %v: expected nil error, got %v", tst, err) - } - if err == nil && wantErr == true { - t.Errorf("testing %v: expected non-nil error, got nil", tst) - } - if eID != spdx.ElementID(wantElt) { - if wantElt == "" { - t.Errorf("testing %v: want emptyString for ElementRefID, got %v", tst, eID) - } else { - t.Errorf("testing %v: want %v for ElementRefID, got %v", tst, wantElt, eID) - } - } -} diff --git a/tvloader/parser2v2/parse_annotation.go b/tvloader/parser2v2/parse_annotation.go index 4c5188e9..32d21b6c 100644 --- a/tvloader/parser2v2/parse_annotation.go +++ b/tvloader/parser2v2/parse_annotation.go @@ -4,6 +4,7 @@ package parser2v2 import ( "fmt" + "github.com/spdx/tools-golang/utils" ) func (parser *tvParser2_2) parsePairForAnnotation2_2(tag string, value string) error { @@ -13,7 +14,7 @@ func (parser *tvParser2_2) parsePairForAnnotation2_2(tag string, value string) e switch tag { case "Annotator": - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } @@ -28,11 +29,10 @@ func (parser *tvParser2_2) parsePairForAnnotation2_2(tag string, value string) e case "AnnotationType": parser.ann.AnnotationType = value case "SPDXREF": - deID, err := extractDocElementID(value) + err := parser.ann.AnnotationSPDXIdentifier.FromString(value) if err != nil { return err } - parser.ann.AnnotationSPDXIdentifier = deID case "AnnotationComment": parser.ann.AnnotationComment = value default: diff --git a/tvloader/parser2v2/parse_creation_info.go b/tvloader/parser2v2/parse_creation_info.go index f8406fc5..1506b62b 100644 --- a/tvloader/parser2v2/parse_creation_info.go +++ b/tvloader/parser2v2/parse_creation_info.go @@ -4,9 +4,8 @@ package parser2v2 import ( "fmt" - "strings" - "github.com/spdx/tools-golang/spdx" + "github.com/spdx/tools-golang/utils" ) func (parser *tvParser2_2) parsePairFromCreationInfo2_2(tag string, value string) error { @@ -25,7 +24,7 @@ func (parser *tvParser2_2) parsePairFromCreationInfo2_2(tag string, value string case "LicenseListVersion": ci.LicenseListVersion = value case "Creator": - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } @@ -99,57 +98,3 @@ func (parser *tvParser2_2) parsePairFromCreationInfo2_2(tag string, value string return nil } - -// ===== Helper functions ===== - -func extractExternalDocumentReference(value string) (string, string, string, string, error) { - sp := strings.Split(value, " ") - // remove any that are just whitespace - keepSp := []string{} - for _, s := range sp { - ss := strings.TrimSpace(s) - if ss != "" { - keepSp = append(keepSp, ss) - } - } - - var documentRefID, uri, alg, checksum string - - // now, should have 4 items (or 3, if Alg and Checksum were joined) - // and should be able to map them - if len(keepSp) == 4 { - documentRefID = keepSp[0] - uri = keepSp[1] - alg = keepSp[2] - // check that colon is present for alg, and remove it - if !strings.HasSuffix(alg, ":") { - return "", "", "", "", fmt.Errorf("algorithm does not end with colon") - } - alg = strings.TrimSuffix(alg, ":") - checksum = keepSp[3] - } else if len(keepSp) == 3 { - documentRefID = keepSp[0] - uri = keepSp[1] - // split on colon into alg and checksum - parts := strings.SplitN(keepSp[2], ":", 2) - if len(parts) != 2 { - return "", "", "", "", fmt.Errorf("missing colon separator between algorithm and checksum") - } - alg = parts[0] - checksum = parts[1] - } else { - return "", "", "", "", fmt.Errorf("expected 4 elements, got %d", len(keepSp)) - } - - // additionally, we should be able to parse the first element as a - // DocumentRef- ID string, and we should remove that prefix - if !strings.HasPrefix(documentRefID, "DocumentRef-") { - return "", "", "", "", fmt.Errorf("expected first element to have DocumentRef- prefix") - } - documentRefID = strings.TrimPrefix(documentRefID, "DocumentRef-") - if documentRefID == "" { - return "", "", "", "", fmt.Errorf("document identifier has nothing after prefix") - } - - return documentRefID, uri, alg, checksum, nil -} diff --git a/tvloader/parser2v2/parse_creation_info_test.go b/tvloader/parser2v2/parse_creation_info_test.go index 71213460..54e9f913 100644 --- a/tvloader/parser2v2/parse_creation_info_test.go +++ b/tvloader/parser2v2/parse_creation_info_test.go @@ -355,73 +355,3 @@ func TestParser2_2CICreatesAnnotation(t *testing.T) { t.Errorf("pointer to new Annotation doesn't match idx 0 for doc.Annotations[]") } } - -// ===== Helper function tests ===== - -func TestCanExtractExternalDocumentReference(t *testing.T) { - refstring := "DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 SHA1:d6a770ba38583ed4bb4525bd96e50461655d2759" - wantDocumentRefID := "spdx-tool-1.2" - wantURI := "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301" - wantAlg := "SHA1" - wantChecksum := "d6a770ba38583ed4bb4525bd96e50461655d2759" - - gotDocumentRefID, gotURI, gotAlg, gotChecksum, err := extractExternalDocumentReference(refstring) - if err != nil { - t.Errorf("got non-nil error: %v", err) - } - if wantDocumentRefID != gotDocumentRefID { - t.Errorf("wanted document ref ID %s, got %s", wantDocumentRefID, gotDocumentRefID) - } - if wantURI != gotURI { - t.Errorf("wanted URI %s, got %s", wantURI, gotURI) - } - if wantAlg != gotAlg { - t.Errorf("wanted alg %s, got %s", wantAlg, gotAlg) - } - if wantChecksum != gotChecksum { - t.Errorf("wanted checksum %s, got %s", wantChecksum, gotChecksum) - } -} - -func TestCanExtractExternalDocumentReferenceWithExtraWhitespace(t *testing.T) { - refstring := " DocumentRef-spdx-tool-1.2 \t http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 \t SHA1: \t d6a770ba38583ed4bb4525bd96e50461655d2759" - wantDocumentRefID := "spdx-tool-1.2" - wantURI := "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301" - wantAlg := "SHA1" - wantChecksum := "d6a770ba38583ed4bb4525bd96e50461655d2759" - - gotDocumentRefID, gotURI, gotAlg, gotChecksum, err := extractExternalDocumentReference(refstring) - if err != nil { - t.Errorf("got non-nil error: %v", err) - } - if wantDocumentRefID != gotDocumentRefID { - t.Errorf("wanted document ref ID %s, got %s", wantDocumentRefID, gotDocumentRefID) - } - if wantURI != gotURI { - t.Errorf("wanted URI %s, got %s", wantURI, gotURI) - } - if wantAlg != gotAlg { - t.Errorf("wanted alg %s, got %s", wantAlg, gotAlg) - } - if wantChecksum != gotChecksum { - t.Errorf("wanted checksum %s, got %s", wantChecksum, gotChecksum) - } -} - -func TestFailsExternalDocumentReferenceWithInvalidFormats(t *testing.T) { - invalidRefs := []string{ - "whoops", - "DocumentRef-", - "DocumentRef- ", - "DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301", - "DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 d6a770ba38583ed4bb4525bd96e50461655d2759", - "DocumentRef-spdx-tool-1.2", - "spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 SHA1:d6a770ba38583ed4bb4525bd96e50461655d2759", - } - for _, refstring := range invalidRefs { - _, _, _, _, err := extractExternalDocumentReference(refstring) - if err == nil { - t.Errorf("expected non-nil error for %s, got nil", refstring) - } - } -} diff --git a/tvloader/parser2v2/parse_file.go b/tvloader/parser2v2/parse_file.go index e564147a..9f034b51 100644 --- a/tvloader/parser2v2/parse_file.go +++ b/tvloader/parser2v2/parse_file.go @@ -4,6 +4,7 @@ package parser2v2 import ( "fmt" + "github.com/spdx/tools-golang/utils" "github.com/spdx/tools-golang/spdx" ) @@ -43,11 +44,10 @@ func (parser *tvParser2_2) parsePairFromFile2_2(tag string, value string) error return parser.parsePairFromOtherLicense2_2(tag, value) // tags for file data case "SPDXID": - eID, err := extractElementID(value) + err := parser.file.FileSPDXIdentifier.FromString(value) if err != nil { return err } - parser.file.FileSPDXIdentifier = eID if parser.pkg == nil { if parser.doc.Files == nil { parser.doc.Files = []*spdx.File2_2{} @@ -62,7 +62,7 @@ func (parser *tvParser2_2) parsePairFromFile2_2(tag string, value string) error case "FileType": parser.file.FileTypes = append(parser.file.FileTypes, value) case "FileChecksum": - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } diff --git a/tvloader/parser2v2/parse_package.go b/tvloader/parser2v2/parse_package.go index 4d6caf9d..aa45c6be 100644 --- a/tvloader/parser2v2/parse_package.go +++ b/tvloader/parser2v2/parse_package.go @@ -4,6 +4,7 @@ package parser2v2 import ( "fmt" + "github.com/spdx/tools-golang/utils" "strings" "github.com/spdx/tools-golang/spdx" @@ -39,11 +40,10 @@ func (parser *tvParser2_2) parsePairFromPackage2_2(tag string, value string) err parser.st = psOtherLicense2_2 return parser.parsePairFromOtherLicense2_2(tag, value) case "SPDXID": - eID, err := extractElementID(value) + err := parser.pkg.PackageSPDXIdentifier.FromString(value) if err != nil { return err } - parser.pkg.PackageSPDXIdentifier = eID if parser.doc.Packages == nil { parser.doc.Packages = []*spdx.Package2_2{} } @@ -59,7 +59,7 @@ func (parser *tvParser2_2) parsePairFromPackage2_2(tag string, value string) err break } - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } @@ -78,7 +78,7 @@ func (parser *tvParser2_2) parsePairFromPackage2_2(tag string, value string) err break } - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } @@ -102,7 +102,7 @@ func (parser *tvParser2_2) parsePairFromPackage2_2(tag string, value string) err case "PackageVerificationCode": parser.pkg.PackageVerificationCode = extractCodeAndExcludes(value) case "PackageChecksum": - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } diff --git a/tvloader/parser2v2/parse_relationship.go b/tvloader/parser2v2/parse_relationship.go index 092b554c..88f02e8b 100644 --- a/tvloader/parser2v2/parse_relationship.go +++ b/tvloader/parser2v2/parse_relationship.go @@ -29,19 +29,16 @@ func (parser *tvParser2_2) parsePairForRelationship2_2(tag string, value string) return fmt.Errorf("invalid relationship format for %s", value) } - aID, err := extractDocElementID(strings.TrimSpace(rp[0])) + err := parser.rln.RefA.FromString(strings.TrimSpace(rp[0])) if err != nil { return err } - parser.rln.RefA = aID parser.rln.Relationship = strings.TrimSpace(rp[1]) - // NONE and NOASSERTION are permitted on right side - permittedSpecial := []string{"NONE", "NOASSERTION"} - bID, err := extractDocElementSpecial(strings.TrimSpace(rp[2]), permittedSpecial) + err = parser.rln.RefB.FromString(strings.TrimSpace(rp[2])) if err != nil { return err } - parser.rln.RefB = bID + return nil } diff --git a/tvloader/parser2v2/parse_relationship_test.go b/tvloader/parser2v2/parse_relationship_test.go index 0e6c013e..0301e3e9 100644 --- a/tvloader/parser2v2/parse_relationship_test.go +++ b/tvloader/parser2v2/parse_relationship_test.go @@ -170,17 +170,19 @@ func TestParser2_2SpecialValuesValidForRightSideOfRelationship(t *testing.T) { t.Errorf("expected nil error for CONTAINS NOASSERTION, got %v", err) } - // NONE in left side of relationship should fail - err = parser.parsePair2_2("Relationship", "NONE CONTAINS SPDXRef-a") - if err == nil { - t.Errorf("expected non-nil error for NONE CONTAINS, got nil") - } - - // NOASSERTION in left side of relationship should fail - err = parser.parsePair2_2("Relationship", "NOASSERTION CONTAINS SPDXRef-a") - if err == nil { - t.Errorf("expected non-nil error for NOASSERTION CONTAINS, got nil") - } + // TODO: make invalid special IDs on the left side of a relationship return some kind of warning or error + // during validation. + // // NONE in left side of relationship should fail + // err = parser.parsePair2_2("Relationship", "NONE CONTAINS SPDXRef-a") + // if err == nil { + // t.Errorf("expected non-nil error for NONE CONTAINS, got nil") + // } + // + // // NOASSERTION in left side of relationship should fail + // err = parser.parsePair2_2("Relationship", "NOASSERTION CONTAINS SPDXRef-a") + // if err == nil { + // t.Errorf("expected non-nil error for NOASSERTION CONTAINS, got nil") + // } } func TestParser2_2FailsToParseUnknownTagInRelationshipSection(t *testing.T) { diff --git a/tvloader/parser2v2/parse_review.go b/tvloader/parser2v2/parse_review.go index 065d889d..99a3b973 100644 --- a/tvloader/parser2v2/parse_review.go +++ b/tvloader/parser2v2/parse_review.go @@ -4,6 +4,7 @@ package parser2v2 import ( "fmt" + "github.com/spdx/tools-golang/utils" "github.com/spdx/tools-golang/spdx" ) @@ -14,7 +15,7 @@ func (parser *tvParser2_2) parsePairFromReview2_2(tag string, value string) erro case "Reviewer": parser.rev = &spdx.Review2_2{} parser.doc.Reviews = append(parser.doc.Reviews, parser.rev) - subkey, subvalue, err := extractSubs(value) + subkey, subvalue, err := utils.ExtractSubs(value) if err != nil { return err } diff --git a/tvloader/parser2v2/parse_snippet.go b/tvloader/parser2v2/parse_snippet.go index d3bac476..bf939768 100644 --- a/tvloader/parser2v2/parse_snippet.go +++ b/tvloader/parser2v2/parse_snippet.go @@ -4,6 +4,7 @@ package parser2v2 import ( "fmt" + "github.com/spdx/tools-golang/utils" "strconv" "github.com/spdx/tools-golang/spdx" @@ -18,7 +19,7 @@ func (parser *tvParser2_2) parsePairFromSnippet2_2(tag string, value string) err return fmt.Errorf("file with FileName %s does not have SPDX identifier", parser.file.FileName) } parser.snippet = &spdx.Snippet2_2{} - eID, err := extractElementID(value) + err := parser.snippet.SnippetSPDXIdentifier.FromString(value) if err != nil { return err } @@ -27,9 +28,8 @@ func (parser *tvParser2_2) parsePairFromSnippet2_2(tag string, value string) err if parser.file.Snippets == nil { parser.file.Snippets = map[spdx.ElementID]*spdx.Snippet2_2{} } - parser.file.Snippets[eID] = parser.snippet + parser.file.Snippets[parser.snippet.SnippetSPDXIdentifier] = parser.snippet } - parser.snippet.SnippetSPDXIdentifier = eID // tag for creating new file section and going back to parsing File case "FileName": parser.st = psFile2_2 @@ -47,13 +47,14 @@ func (parser *tvParser2_2) parsePairFromSnippet2_2(tag string, value string) err return parser.parsePairFromOtherLicense2_2(tag, value) // tags for snippet data case "SnippetFromFileSPDXID": - deID, err := extractDocElementID(value) + var deID spdx.DocElementID + err := deID.FromString(value) if err != nil { return err } parser.snippet.SnippetFromFileSPDXIdentifier = deID.ElementRefID case "SnippetByteRange": - byteStart, byteEnd, err := extractSubs(value) + byteStart, byteEnd, err := utils.ExtractSubs(value) if err != nil { return err } @@ -72,7 +73,7 @@ func (parser *tvParser2_2) parsePairFromSnippet2_2(tag string, value string) err byteRange := spdx.SnippetRange{StartPointer: spdx.SnippetRangePointer{Offset: bIntStart}, EndPointer: spdx.SnippetRangePointer{Offset: bIntEnd}} parser.snippet.Ranges = append(parser.snippet.Ranges, byteRange) case "SnippetLineRange": - lineStart, lineEnd, err := extractSubs(value) + lineStart, lineEnd, err := utils.ExtractSubs(value) if err != nil { return err } diff --git a/tvloader/parser2v2/parser.go b/tvloader/parser2v2/parser.go index 1d9f8e9c..eb81fd33 100644 --- a/tvloader/parser2v2/parser.go +++ b/tvloader/parser2v2/parser.go @@ -5,7 +5,6 @@ package parser2v2 import ( "fmt" - "github.com/spdx/tools-golang/spdx" "github.com/spdx/tools-golang/tvloader/reader" ) @@ -69,25 +68,20 @@ func (parser *tvParser2_2) parsePairFromStart2_2(tag string, value string) error case "DataLicense": parser.doc.DataLicense = value case "SPDXID": - eID, err := extractElementID(value) + err := parser.doc.SPDXIdentifier.FromString(value) if err != nil { return err } - parser.doc.SPDXIdentifier = eID case "DocumentName": parser.doc.DocumentName = value case "DocumentNamespace": parser.doc.DocumentNamespace = value case "ExternalDocumentRef": - documentRefID, uri, alg, checksum, err := extractExternalDocumentReference(value) + var edr spdx.ExternalDocumentRef2_2 + err := edr.FromString(value) if err != nil { return err } - edr := spdx.ExternalDocumentRef2_2{ - DocumentRefID: documentRefID, - URI: uri, - Checksum: spdx.Checksum{Algorithm: spdx.ChecksumAlgorithm(alg), Value: checksum}, - } parser.doc.ExternalDocumentReferences = append(parser.doc.ExternalDocumentReferences, edr) default: // move to Creation Info parser state diff --git a/tvloader/parser2v2/util.go b/tvloader/parser2v2/util.go deleted file mode 100644 index 66768469..00000000 --- a/tvloader/parser2v2/util.go +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later - -package parser2v2 - -import ( - "fmt" - "strings" - - "github.com/spdx/tools-golang/spdx" -) - -// used to extract key / value from embedded substrings -// returns subkey, subvalue, nil if no error, or "", "", error otherwise -func extractSubs(value string) (string, string, error) { - // parse the value to see if it's a valid subvalue format - sp := strings.SplitN(value, ":", 2) - if len(sp) == 1 { - return "", "", fmt.Errorf("invalid subvalue format for %s (no colon found)", value) - } - - subkey := strings.TrimSpace(sp[0]) - subvalue := strings.TrimSpace(sp[1]) - - return subkey, subvalue, nil -} - -// used to extract DocumentRef and SPDXRef values from an SPDX Identifier -// which can point either to this document or to a different one -func extractDocElementID(value string) (spdx.DocElementID, error) { - docRefID := "" - idStr := value - - // check prefix to see if it's a DocumentRef ID - if strings.HasPrefix(idStr, "DocumentRef-") { - // extract the part that comes between "DocumentRef-" and ":" - strs := strings.Split(idStr, ":") - // should be exactly two, part before and part after - if len(strs) < 2 { - return spdx.DocElementID{}, fmt.Errorf("no colon found although DocumentRef- prefix present") - } - if len(strs) > 2 { - return spdx.DocElementID{}, fmt.Errorf("more than one colon found") - } - - // trim the prefix and confirm non-empty - docRefID = strings.TrimPrefix(strs[0], "DocumentRef-") - if docRefID == "" { - return spdx.DocElementID{}, fmt.Errorf("document identifier has nothing after prefix") - } - // and use remainder for element ID parsing - idStr = strs[1] - } - - // check prefix to confirm it's got the right prefix for element IDs - if !strings.HasPrefix(idStr, "SPDXRef-") { - return spdx.DocElementID{}, fmt.Errorf("missing SPDXRef- prefix for element identifier") - } - - // make sure no colons are present - if strings.Contains(idStr, ":") { - // we know this means there was no DocumentRef- prefix, because - // we would have handled multiple colons above if it was - return spdx.DocElementID{}, fmt.Errorf("invalid colon in element identifier") - } - - // trim the prefix and confirm non-empty - eltRefID := strings.TrimPrefix(idStr, "SPDXRef-") - if eltRefID == "" { - return spdx.DocElementID{}, fmt.Errorf("element identifier has nothing after prefix") - } - - // we're good - return spdx.DocElementID{DocumentRefID: docRefID, ElementRefID: spdx.ElementID(eltRefID)}, nil -} - -// used to extract SPDXRef values from an SPDX Identifier, OR "special" strings -// from a specified set of permitted values. The primary use case for this is -// the right-hand side of Relationships, where beginning in SPDX 2.2 the values -// "NONE" and "NOASSERTION" are permitted. If the value does not match one of -// the specified permitted values, it will fall back to the ordinary -// DocElementID extractor. -func extractDocElementSpecial(value string, permittedSpecial []string) (spdx.DocElementID, error) { - // check value against special set first - for _, sp := range permittedSpecial { - if sp == value { - return spdx.DocElementID{SpecialID: sp}, nil - } - } - // not found, fall back to regular search - return extractDocElementID(value) -} - -// used to extract SPDXRef values only from an SPDX Identifier which can point -// to this document only. Use extractDocElementID for parsing IDs that can -// refer either to this document or a different one. -func extractElementID(value string) (spdx.ElementID, error) { - // check prefix to confirm it's got the right prefix for element IDs - if !strings.HasPrefix(value, "SPDXRef-") { - return spdx.ElementID(""), fmt.Errorf("missing SPDXRef- prefix for element identifier") - } - - // make sure no colons are present - if strings.Contains(value, ":") { - return spdx.ElementID(""), fmt.Errorf("invalid colon in element identifier") - } - - // trim the prefix and confirm non-empty - eltRefID := strings.TrimPrefix(value, "SPDXRef-") - if eltRefID == "" { - return spdx.ElementID(""), fmt.Errorf("element identifier has nothing after prefix") - } - - // we're good - return spdx.ElementID(eltRefID), nil -} diff --git a/tvsaver/saver2v1/save_annotation.go b/tvsaver/saver2v1/save_annotation.go index f7d79538..36ab47f6 100644 --- a/tvsaver/saver2v1/save_annotation.go +++ b/tvsaver/saver2v1/save_annotation.go @@ -19,7 +19,7 @@ func renderAnnotation2_1(ann *spdx.Annotation2_1, w io.Writer) error { if ann.AnnotationType != "" { fmt.Fprintf(w, "AnnotationType: %s\n", ann.AnnotationType) } - annIDStr := spdx.RenderDocElementID(ann.AnnotationSPDXIdentifier) + annIDStr := ann.AnnotationSPDXIdentifier.String() if annIDStr != "SPDXRef-" { fmt.Fprintf(w, "SPDXREF: %s\n", annIDStr) } diff --git a/tvsaver/saver2v1/save_document.go b/tvsaver/saver2v1/save_document.go index ea17db25..d23aeea2 100644 --- a/tvsaver/saver2v1/save_document.go +++ b/tvsaver/saver2v1/save_document.go @@ -28,7 +28,7 @@ func RenderDocument2_1(doc *spdx.Document2_1, w io.Writer) error { fmt.Fprintf(w, "DataLicense: %s\n", doc.DataLicense) } if doc.SPDXIdentifier != "" { - fmt.Fprintf(w, "SPDXID: %s\n", spdx.RenderElementID(doc.SPDXIdentifier)) + fmt.Fprintf(w, "SPDXID: %s\n", doc.SPDXIdentifier) } if doc.DocumentName != "" { fmt.Fprintf(w, "DocumentName: %s\n", doc.DocumentName) @@ -38,10 +38,10 @@ func RenderDocument2_1(doc *spdx.Document2_1, w io.Writer) error { } // print EDRs in order sorted by identifier sort.Slice(doc.ExternalDocumentReferences, func(i, j int) bool { - return doc.ExternalDocumentReferences[i].DocumentRefID < doc.ExternalDocumentReferences[j].DocumentRefID + return doc.ExternalDocumentReferences[i].DocumentRefID.DocumentRefID < doc.ExternalDocumentReferences[j].DocumentRefID.DocumentRefID }) for _, edr := range doc.ExternalDocumentReferences { - fmt.Fprintf(w, "ExternalDocumentRef: DocumentRef-%s %s %s:%s\n", + fmt.Fprintf(w, "ExternalDocumentRef: %s %s %s:%s\n", edr.DocumentRefID, edr.URI, edr.Checksum.Algorithm, edr.Checksum.Value) } if doc.DocumentComment != "" { diff --git a/tvsaver/saver2v1/save_file.go b/tvsaver/saver2v1/save_file.go index c1311220..7ed9fa44 100644 --- a/tvsaver/saver2v1/save_file.go +++ b/tvsaver/saver2v1/save_file.go @@ -15,7 +15,7 @@ func renderFile2_1(f *spdx.File2_1, w io.Writer) error { fmt.Fprintf(w, "FileName: %s\n", f.FileName) } if f.FileSPDXIdentifier != "" { - fmt.Fprintf(w, "SPDXID: %s\n", spdx.RenderElementID(f.FileSPDXIdentifier)) + fmt.Fprintf(w, "SPDXID: %s\n", f.FileSPDXIdentifier) } for _, s := range f.FileTypes { fmt.Fprintf(w, "FileType: %s\n", s) diff --git a/tvsaver/saver2v1/save_package.go b/tvsaver/saver2v1/save_package.go index 24a468c0..139fd522 100644 --- a/tvsaver/saver2v1/save_package.go +++ b/tvsaver/saver2v1/save_package.go @@ -16,7 +16,7 @@ func renderPackage2_1(pkg *spdx.Package2_1, w io.Writer) error { fmt.Fprintf(w, "PackageName: %s\n", pkg.PackageName) } if pkg.PackageSPDXIdentifier != "" { - fmt.Fprintf(w, "SPDXID: %s\n", spdx.RenderElementID(pkg.PackageSPDXIdentifier)) + fmt.Fprintf(w, "SPDXID: %s\n", pkg.PackageSPDXIdentifier) } if pkg.PackageVersion != "" { fmt.Fprintf(w, "PackageVersion: %s\n", pkg.PackageVersion) diff --git a/tvsaver/saver2v1/save_relationship.go b/tvsaver/saver2v1/save_relationship.go index aea48bc3..356820a0 100644 --- a/tvsaver/saver2v1/save_relationship.go +++ b/tvsaver/saver2v1/save_relationship.go @@ -10,8 +10,8 @@ import ( ) func renderRelationship2_1(rln *spdx.Relationship2_1, w io.Writer) error { - rlnAStr := spdx.RenderDocElementID(rln.RefA) - rlnBStr := spdx.RenderDocElementID(rln.RefB) + rlnAStr := rln.RefA.String() + rlnBStr := rln.RefB.String() if rlnAStr != "SPDXRef-" && rlnBStr != "SPDXRef-" && rln.Relationship != "" { fmt.Fprintf(w, "Relationship: %s %s %s\n", rlnAStr, rln.Relationship, rlnBStr) } diff --git a/tvsaver/saver2v1/save_snippet.go b/tvsaver/saver2v1/save_snippet.go index 13995489..60684797 100644 --- a/tvsaver/saver2v1/save_snippet.go +++ b/tvsaver/saver2v1/save_snippet.go @@ -11,9 +11,9 @@ import ( func renderSnippet2_1(sn *spdx.Snippet2_1, w io.Writer) error { if sn.SnippetSPDXIdentifier != "" { - fmt.Fprintf(w, "SnippetSPDXID: %s\n", spdx.RenderElementID(sn.SnippetSPDXIdentifier)) + fmt.Fprintf(w, "SnippetSPDXID: %s\n", sn.SnippetSPDXIdentifier) } - snFromFileIDStr := spdx.RenderElementID(sn.SnippetFromFileSPDXIdentifier) + snFromFileIDStr := sn.SnippetFromFileSPDXIdentifier.String() if snFromFileIDStr != "" { fmt.Fprintf(w, "SnippetFromFileSPDXID: %s\n", snFromFileIDStr) } diff --git a/tvsaver/saver2v2/save_annotation.go b/tvsaver/saver2v2/save_annotation.go index ddfe483a..281d77f1 100644 --- a/tvsaver/saver2v2/save_annotation.go +++ b/tvsaver/saver2v2/save_annotation.go @@ -19,7 +19,7 @@ func renderAnnotation2_2(ann *spdx.Annotation2_2, w io.Writer) error { if ann.AnnotationType != "" { fmt.Fprintf(w, "AnnotationType: %s\n", ann.AnnotationType) } - annIDStr := spdx.RenderDocElementID(ann.AnnotationSPDXIdentifier) + annIDStr := ann.AnnotationSPDXIdentifier.String() if annIDStr != "SPDXRef-" { fmt.Fprintf(w, "SPDXREF: %s\n", annIDStr) } diff --git a/tvsaver/saver2v2/save_document.go b/tvsaver/saver2v2/save_document.go index 04b482da..2dd55166 100644 --- a/tvsaver/saver2v2/save_document.go +++ b/tvsaver/saver2v2/save_document.go @@ -28,7 +28,7 @@ func RenderDocument2_2(doc *spdx.Document2_2, w io.Writer) error { fmt.Fprintf(w, "DataLicense: %s\n", doc.DataLicense) } if doc.SPDXIdentifier != "" { - fmt.Fprintf(w, "SPDXID: %s\n", spdx.RenderElementID(doc.SPDXIdentifier)) + fmt.Fprintf(w, "SPDXID: %s\n", doc.SPDXIdentifier) } if doc.DocumentName != "" { fmt.Fprintf(w, "DocumentName: %s\n", doc.DocumentName) @@ -38,10 +38,10 @@ func RenderDocument2_2(doc *spdx.Document2_2, w io.Writer) error { } // print EDRs in order sorted by identifier sort.Slice(doc.ExternalDocumentReferences, func(i, j int) bool { - return doc.ExternalDocumentReferences[i].DocumentRefID < doc.ExternalDocumentReferences[j].DocumentRefID + return doc.ExternalDocumentReferences[i].DocumentRefID.DocumentRefID < doc.ExternalDocumentReferences[j].DocumentRefID.DocumentRefID }) for _, edr := range doc.ExternalDocumentReferences { - fmt.Fprintf(w, "ExternalDocumentRef: DocumentRef-%s %s %s:%s\n", + fmt.Fprintf(w, "ExternalDocumentRef: %s %s %s:%s\n", edr.DocumentRefID, edr.URI, edr.Checksum.Algorithm, edr.Checksum.Value) } if doc.DocumentComment != "" { diff --git a/tvsaver/saver2v2/save_file.go b/tvsaver/saver2v2/save_file.go index f1684efb..b3bd8e1f 100644 --- a/tvsaver/saver2v2/save_file.go +++ b/tvsaver/saver2v2/save_file.go @@ -15,7 +15,7 @@ func renderFile2_2(f *spdx.File2_2, w io.Writer) error { fmt.Fprintf(w, "FileName: %s\n", f.FileName) } if f.FileSPDXIdentifier != "" { - fmt.Fprintf(w, "SPDXID: %s\n", spdx.RenderElementID(f.FileSPDXIdentifier)) + fmt.Fprintf(w, "SPDXID: %s\n", f.FileSPDXIdentifier) } for _, s := range f.FileTypes { fmt.Fprintf(w, "FileType: %s\n", s) diff --git a/tvsaver/saver2v2/save_package.go b/tvsaver/saver2v2/save_package.go index 6d21a6d2..23e2bc57 100644 --- a/tvsaver/saver2v2/save_package.go +++ b/tvsaver/saver2v2/save_package.go @@ -16,7 +16,7 @@ func renderPackage2_2(pkg *spdx.Package2_2, w io.Writer) error { fmt.Fprintf(w, "PackageName: %s\n", pkg.PackageName) } if pkg.PackageSPDXIdentifier != "" { - fmt.Fprintf(w, "SPDXID: %s\n", spdx.RenderElementID(pkg.PackageSPDXIdentifier)) + fmt.Fprintf(w, "SPDXID: %s\n", pkg.PackageSPDXIdentifier) } if pkg.PackageVersion != "" { fmt.Fprintf(w, "PackageVersion: %s\n", pkg.PackageVersion) diff --git a/tvsaver/saver2v2/save_relationship.go b/tvsaver/saver2v2/save_relationship.go index 4bd12ddb..26a11ba1 100644 --- a/tvsaver/saver2v2/save_relationship.go +++ b/tvsaver/saver2v2/save_relationship.go @@ -10,8 +10,8 @@ import ( ) func renderRelationship2_2(rln *spdx.Relationship2_2, w io.Writer) error { - rlnAStr := spdx.RenderDocElementID(rln.RefA) - rlnBStr := spdx.RenderDocElementID(rln.RefB) + rlnAStr := rln.RefA.String() + rlnBStr := rln.RefB.String() if rlnAStr != "SPDXRef-" && rlnBStr != "SPDXRef-" && rln.Relationship != "" { fmt.Fprintf(w, "Relationship: %s %s %s\n", rlnAStr, rln.Relationship, rlnBStr) } diff --git a/tvsaver/saver2v2/save_snippet.go b/tvsaver/saver2v2/save_snippet.go index 4f740982..ae26c576 100644 --- a/tvsaver/saver2v2/save_snippet.go +++ b/tvsaver/saver2v2/save_snippet.go @@ -11,9 +11,9 @@ import ( func renderSnippet2_2(sn *spdx.Snippet2_2, w io.Writer) error { if sn.SnippetSPDXIdentifier != "" { - fmt.Fprintf(w, "SnippetSPDXID: %s\n", spdx.RenderElementID(sn.SnippetSPDXIdentifier)) + fmt.Fprintf(w, "SnippetSPDXID: %s\n", sn.SnippetSPDXIdentifier) } - snFromFileIDStr := spdx.RenderElementID(sn.SnippetFromFileSPDXIdentifier) + snFromFileIDStr := sn.SnippetFromFileSPDXIdentifier.String() if snFromFileIDStr != "" { fmt.Fprintf(w, "SnippetFromFileSPDXID: %s\n", snFromFileIDStr) } diff --git a/utils/string_parsers.go b/utils/string_parsers.go new file mode 100644 index 00000000..5756120f --- /dev/null +++ b/utils/string_parsers.go @@ -0,0 +1,27 @@ +package utils + +import ( + "fmt" + "strings" +) + +// ExtractSubsWithSeparator used to extract key / value from embedded substrings that use an arbitrary separator. +// returns subkey, subvalue, nil if no error, or "", "", error otherwise +func ExtractSubsWithSeparator(value string, sep string) (string, string, error) { + // parse the value to see if it's a valid subvalue format + sp := strings.SplitN(value, sep, 2) + if len(sp) == 1 { + return "", "", fmt.Errorf("invalid subvalue format for %s (no %s found)", value, sep) + } + + subkey := strings.TrimSpace(sp[0]) + subvalue := strings.TrimSpace(sp[1]) + + return subkey, subvalue, nil +} + +// ExtractSubs used to extract key / value from embedded substrings that use a colon ":" as the separator. +// returns subkey, subvalue, nil if no error, or "", "", error otherwise +func ExtractSubs(value string) (string, string, error) { + return ExtractSubsWithSeparator(value, ":") +} diff --git a/utils/string_parsers_test.go b/utils/string_parsers_test.go new file mode 100644 index 00000000..8b1c82c6 --- /dev/null +++ b/utils/string_parsers_test.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +package utils + +import ( + "testing" +) + +// ===== Helper function tests ===== + +func TestCanExtractSubvalues(t *testing.T) { + subkey, subvalue, err := ExtractSubs("SHA1: abc123") + if err != nil { + t.Errorf("got error when calling utils.ExtractSubs: %v", err) + } + if subkey != "SHA1" { + t.Errorf("got %v for subkey", subkey) + } + if subvalue != "abc123" { + t.Errorf("got %v for subvalue", subvalue) + } +} + +func TestReturnsErrorForInvalidSubvalueFormat(t *testing.T) { + _, _, err := ExtractSubs("blah") + if err == nil { + t.Errorf("expected error when calling utils.ExtractSubs for invalid format (0 colons), got nil") + } +} diff --git a/utils/verification.go b/utils/verification.go deleted file mode 100644 index 94e8b7ef..00000000 --- a/utils/verification.go +++ /dev/null @@ -1,91 +0,0 @@ -// Package utils contains various utility functions to support the -// main tools-golang packages. -// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later -package utils - -import ( - "crypto/sha1" - "fmt" - "sort" - "strings" - - "github.com/spdx/tools-golang/spdx" -) - -// GetVerificationCode2_1 takes a slice of files and an optional filename -// for an "excludes" file, and returns a Package Verification Code calculated -// according to SPDX spec version 2.1, section 3.9.4. -func GetVerificationCode2_1(files []*spdx.File2_1, excludeFile string) (spdx.PackageVerificationCode, error) { - // create slice of strings - unsorted SHA1s for all files - shas := []string{} - for i, f := range files { - if f == nil { - return spdx.PackageVerificationCode{}, fmt.Errorf("got nil file for identifier %v", i) - } - if f.FileName != excludeFile { - // find the SHA1 hash, if present - for _, checksum := range f.Checksums { - if checksum.Algorithm == spdx.SHA1 { - shas = append(shas, checksum.Value) - } - } - } - } - - // sort the strings - sort.Strings(shas) - - // concatenate them into one string, with no trailing separators - shasConcat := strings.Join(shas, "") - - // and get its SHA1 value - hsha1 := sha1.New() - hsha1.Write([]byte(shasConcat)) - bs := hsha1.Sum(nil) - - code := spdx.PackageVerificationCode{ - Value: fmt.Sprintf("%x", bs), - ExcludedFiles: []string{excludeFile}, - } - - return code, nil -} - -// GetVerificationCode2_2 takes a slice of files and an optional filename -// for an "excludes" file, and returns a Package Verification Code calculated -// according to SPDX spec version 2.2, section 3.9.4. -func GetVerificationCode2_2(files []*spdx.File2_2, excludeFile string) (spdx.PackageVerificationCode, error) { - // create slice of strings - unsorted SHA1s for all files - shas := []string{} - for i, f := range files { - if f == nil { - return spdx.PackageVerificationCode{}, fmt.Errorf("got nil file for identifier %v", i) - } - if f.FileName != excludeFile { - // find the SHA1 hash, if present - for _, checksum := range f.Checksums { - if checksum.Algorithm == spdx.SHA1 { - shas = append(shas, checksum.Value) - } - } - } - } - - // sort the strings - sort.Strings(shas) - - // concatenate them into one string, with no trailing separators - shasConcat := strings.Join(shas, "") - - // and get its SHA1 value - hsha1 := sha1.New() - hsha1.Write([]byte(shasConcat)) - bs := hsha1.Sum(nil) - - code := spdx.PackageVerificationCode{ - Value: fmt.Sprintf("%x", bs), - ExcludedFiles: []string{excludeFile}, - } - - return code, nil -} diff --git a/yaml/yaml_test.go b/yaml/yaml_test.go index 49f8ebfe..c93ce815 100644 --- a/yaml/yaml_test.go +++ b/yaml/yaml_test.go @@ -61,7 +61,7 @@ func TestWrite2_2(t *testing.T) { var want = spdx.Document2_2{ DataLicense: "CC0-1.0", SPDXVersion: "SPDX-2.2", - SPDXIdentifier: "SPDXRef-DOCUMENT", + SPDXIdentifier: "DOCUMENT", DocumentName: "SPDX-Tools-v2.0", DocumentNamespace: "http://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301", CreationInfo: &spdx.CreationInfo2_2{ @@ -77,7 +77,7 @@ var want = spdx.Document2_2{ DocumentComment: "This document was created using SPDX 2.0 using licenses from the web site.", ExternalDocumentReferences: []spdx.ExternalDocumentRef2_2{ { - DocumentRefID: "DocumentRef-spdx-tool-1.2", + DocumentRefID: spdx.MakeDocElementID("spdx-tool-1.2", ""), URI: "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301", Checksum: spdx.Checksum{ Algorithm: spdx.SHA1, @@ -148,7 +148,7 @@ var want = spdx.Document2_2{ Packages: []*spdx.Package2_2{ { PackageName: "glibc", - PackageSPDXIdentifier: "SPDXRef-Package", + PackageSPDXIdentifier: "Package", PackageVersion: "2.11.1", PackageFileName: "glibc-2.11.1.tar.gz", PackageSupplier: &spdx.Supplier{ @@ -223,7 +223,7 @@ var want = spdx.Document2_2{ }, }, { - PackageSPDXIdentifier: "SPDXRef-fromDoap-1", + PackageSPDXIdentifier: "fromDoap-1", PackageCopyrightText: "NOASSERTION", PackageDownloadLocation: "NOASSERTION", FilesAnalyzed: false, @@ -234,7 +234,7 @@ var want = spdx.Document2_2{ }, { PackageName: "Jena", - PackageSPDXIdentifier: "SPDXRef-fromDoap-0", + PackageSPDXIdentifier: "fromDoap-0", PackageCopyrightText: "NOASSERTION", PackageDownloadLocation: "https://search.maven.org/remotecontent?filepath=org/apache/jena/apache-jena/3.12.0/apache-jena-3.12.0.tar.gz", PackageExternalReferences: []*spdx.PackageExternalReference2_2{ @@ -251,7 +251,7 @@ var want = spdx.Document2_2{ PackageVersion: "3.12.0", }, { - PackageSPDXIdentifier: "SPDXRef-Saxon", + PackageSPDXIdentifier: "Saxon", PackageChecksums: []spdx.Checksum{ { Algorithm: "SHA1", @@ -274,7 +274,7 @@ var want = spdx.Document2_2{ Files: []*spdx.File2_2{ { FileName: "./src/org/spdx/parser/DOAPProject.java", - FileSPDXIdentifier: "SPDXRef-DoapSource", + FileSPDXIdentifier: "DoapSource", FileTypes: []string{ "SOURCE", }, @@ -298,7 +298,7 @@ var want = spdx.Document2_2{ }, }, { - FileSPDXIdentifier: "SPDXRef-CommonsLangSrc", + FileSPDXIdentifier: "CommonsLangSrc", Checksums: []spdx.Checksum{ { Algorithm: "SHA1", @@ -315,7 +315,7 @@ var want = spdx.Document2_2{ FileNotice: "Apache Commons Lang\nCopyright 2001-2011 The Apache Software Foundation\n\nThis product includes software developed by\nThe Apache Software Foundation (http://www.apache.org/).\n\nThis product includes software from the Spring Framework,\nunder the Apache License 2.0 (see: StringUtils.containsWhitespace())", }, { - FileSPDXIdentifier: "SPDXRef-JenaLib", + FileSPDXIdentifier: "JenaLib", Checksums: []spdx.Checksum{ { Algorithm: "SHA1", @@ -332,7 +332,7 @@ var want = spdx.Document2_2{ LicenseInfoInFiles: []string{"LicenseRef-1"}, }, { - FileSPDXIdentifier: "SPDXRef-File", + FileSPDXIdentifier: "File", Annotations: []spdx.Annotation2_2{ { Annotator: spdx.Annotator{ @@ -367,27 +367,27 @@ var want = spdx.Document2_2{ }, Snippets: []spdx.Snippet2_2{ { - SnippetSPDXIdentifier: "SPDXRef-Snippet", - SnippetFromFileSPDXIdentifier: "SPDXRef-DoapSource", + SnippetSPDXIdentifier: "Snippet", + SnippetFromFileSPDXIdentifier: "DoapSource", Ranges: []spdx.SnippetRange{ { StartPointer: spdx.SnippetRangePointer{ Offset: 310, - FileSPDXIdentifier: "SPDXRef-DoapSource", + FileSPDXIdentifier: "DoapSource", }, EndPointer: spdx.SnippetRangePointer{ Offset: 420, - FileSPDXIdentifier: "SPDXRef-DoapSource", + FileSPDXIdentifier: "DoapSource", }, }, { StartPointer: spdx.SnippetRangePointer{ LineNumber: 5, - FileSPDXIdentifier: "SPDXRef-DoapSource", + FileSPDXIdentifier: "DoapSource", }, EndPointer: spdx.SnippetRangePointer{ LineNumber: 23, - FileSPDXIdentifier: "SPDXRef-DoapSource", + FileSPDXIdentifier: "DoapSource", }, }, },