1// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 2 3package parser2v3 4 5import ( 6 "fmt" 7 "strings" 8 9 "github.com/spdx/tools-golang/spdx/common" 10 "github.com/spdx/tools-golang/spdx/v2_3" 11) 12 13func (parser *tvParser2_3) parsePairFromCreationInfo2_3(tag string, value string) error { 14 // fail if not in Creation Info parser state 15 if parser.st != psCreationInfo2_3 { 16 return fmt.Errorf("got invalid state %v in parsePairFromCreationInfo2_3", parser.st) 17 } 18 19 // create an SPDX Creation Info data struct if we don't have one already 20 if parser.doc.CreationInfo == nil { 21 parser.doc.CreationInfo = &v2_3.CreationInfo{} 22 } 23 24 ci := parser.doc.CreationInfo 25 switch tag { 26 case "LicenseListVersion": 27 ci.LicenseListVersion = value 28 case "Creator": 29 subkey, subvalue, err := extractSubs(value) 30 if err != nil { 31 return err 32 } 33 34 creator := common.Creator{Creator: subvalue} 35 switch subkey { 36 case "Person", "Organization", "Tool": 37 creator.CreatorType = subkey 38 default: 39 return fmt.Errorf("unrecognized Creator type %v", subkey) 40 } 41 42 ci.Creators = append(ci.Creators, creator) 43 case "Created": 44 ci.Created = value 45 case "CreatorComment": 46 ci.CreatorComment = value 47 48 // tag for going on to package section 49 case "PackageName": 50 // error if last file does not have an identifier 51 // this may be a null case: can we ever have a "last file" in 52 // the "creation info" state? should go on to "file" state 53 // even when parsing unpackaged files. 54 if parser.file != nil && parser.file.FileSPDXIdentifier == nullSpdxElementId2_3 { 55 return fmt.Errorf("file with FileName %s does not have SPDX identifier", parser.file.FileName) 56 } 57 parser.st = psPackage2_3 58 parser.pkg = &v2_3.Package{ 59 FilesAnalyzed: true, 60 IsFilesAnalyzedTagPresent: false, 61 } 62 return parser.parsePairFromPackage2_3(tag, value) 63 // tag for going on to _unpackaged_ file section 64 case "FileName": 65 // leave pkg as nil, so that packages will be placed in Files 66 parser.st = psFile2_3 67 parser.pkg = nil 68 return parser.parsePairFromFile2_3(tag, value) 69 // tag for going on to other license section 70 case "LicenseID": 71 parser.st = psOtherLicense2_3 72 return parser.parsePairFromOtherLicense2_3(tag, value) 73 // tag for going on to review section (DEPRECATED) 74 case "Reviewer": 75 parser.st = psReview2_3 76 return parser.parsePairFromReview2_3(tag, value) 77 // for relationship tags, pass along but don't change state 78 case "Relationship": 79 parser.rln = &v2_3.Relationship{} 80 parser.doc.Relationships = append(parser.doc.Relationships, parser.rln) 81 return parser.parsePairForRelationship2_3(tag, value) 82 case "RelationshipComment": 83 return parser.parsePairForRelationship2_3(tag, value) 84 // for annotation tags, pass along but don't change state 85 case "Annotator": 86 parser.ann = &v2_3.Annotation{} 87 parser.doc.Annotations = append(parser.doc.Annotations, parser.ann) 88 return parser.parsePairForAnnotation2_3(tag, value) 89 case "AnnotationDate": 90 return parser.parsePairForAnnotation2_3(tag, value) 91 case "AnnotationType": 92 return parser.parsePairForAnnotation2_3(tag, value) 93 case "SPDXREF": 94 return parser.parsePairForAnnotation2_3(tag, value) 95 case "AnnotationComment": 96 return parser.parsePairForAnnotation2_3(tag, value) 97 default: 98 return fmt.Errorf("received unknown tag %v in CreationInfo section", tag) 99 } 100 101 return nil 102} 103 104// ===== Helper functions ===== 105 106func extractExternalDocumentReference(value string) (string, string, string, string, error) { 107 sp := strings.Split(value, " ") 108 // remove any that are just whitespace 109 keepSp := []string{} 110 for _, s := range sp { 111 ss := strings.TrimSpace(s) 112 if ss != "" { 113 keepSp = append(keepSp, ss) 114 } 115 } 116 117 var documentRefID, uri, alg, checksum string 118 119 // now, should have 4 items (or 3, if Alg and Checksum were joined) 120 // and should be able to map them 121 if len(keepSp) == 4 { 122 documentRefID = keepSp[0] 123 uri = keepSp[1] 124 alg = keepSp[2] 125 // check that colon is present for alg, and remove it 126 if !strings.HasSuffix(alg, ":") { 127 return "", "", "", "", fmt.Errorf("algorithm does not end with colon") 128 } 129 alg = strings.TrimSuffix(alg, ":") 130 checksum = keepSp[3] 131 } else if len(keepSp) == 3 { 132 documentRefID = keepSp[0] 133 uri = keepSp[1] 134 // split on colon into alg and checksum 135 parts := strings.SplitN(keepSp[2], ":", 2) 136 if len(parts) != 2 { 137 return "", "", "", "", fmt.Errorf("missing colon separator between algorithm and checksum") 138 } 139 alg = parts[0] 140 checksum = parts[1] 141 } else { 142 return "", "", "", "", fmt.Errorf("expected 4 elements, got %d", len(keepSp)) 143 } 144 145 // additionally, we should be able to parse the first element as a 146 // DocumentRef- ID string, and we should remove that prefix 147 if !strings.HasPrefix(documentRefID, "DocumentRef-") { 148 return "", "", "", "", fmt.Errorf("expected first element to have DocumentRef- prefix") 149 } 150 documentRefID = strings.TrimPrefix(documentRefID, "DocumentRef-") 151 if documentRefID == "" { 152 return "", "", "", "", fmt.Errorf("document identifier has nothing after prefix") 153 } 154 155 return documentRefID, uri, alg, checksum, nil 156} 157