1*ba677afaSXin Li// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 2*ba677afaSXin Li 3*ba677afaSXin Lipackage common 4*ba677afaSXin Li 5*ba677afaSXin Liimport ( 6*ba677afaSXin Li "encoding/json" 7*ba677afaSXin Li "fmt" 8*ba677afaSXin Li "strings" 9*ba677afaSXin Li) 10*ba677afaSXin Li 11*ba677afaSXin Liconst ( 12*ba677afaSXin Li spdxRefPrefix = "SPDXRef-" 13*ba677afaSXin Li documentRefPrefix = "DocumentRef-" 14*ba677afaSXin Li) 15*ba677afaSXin Li 16*ba677afaSXin Li// ElementID represents the identifier string portion of an SPDX element 17*ba677afaSXin Li// identifier. DocElementID should be used for any attributes which can 18*ba677afaSXin Li// contain identifiers defined in a different SPDX document. 19*ba677afaSXin Li// ElementIDs should NOT contain the mandatory 'SPDXRef-' portion. 20*ba677afaSXin Litype ElementID string 21*ba677afaSXin Li 22*ba677afaSXin Li// MarshalJSON returns an SPDXRef- prefixed JSON string 23*ba677afaSXin Lifunc (d ElementID) MarshalJSON() ([]byte, error) { 24*ba677afaSXin Li return json.Marshal(prefixElementId(d)) 25*ba677afaSXin Li} 26*ba677afaSXin Li 27*ba677afaSXin Li// UnmarshalJSON validates SPDXRef- prefixes and removes them when processing ElementIDs 28*ba677afaSXin Lifunc (d *ElementID) UnmarshalJSON(data []byte) error { 29*ba677afaSXin Li // SPDX identifier will simply be a string 30*ba677afaSXin Li idStr := string(data) 31*ba677afaSXin Li idStr = strings.Trim(idStr, "\"") 32*ba677afaSXin Li 33*ba677afaSXin Li e, err := trimElementIdPrefix(idStr) 34*ba677afaSXin Li if err != nil { 35*ba677afaSXin Li return err 36*ba677afaSXin Li } 37*ba677afaSXin Li *d = e 38*ba677afaSXin Li return nil 39*ba677afaSXin Li} 40*ba677afaSXin Li 41*ba677afaSXin Li// prefixElementId adds the SPDXRef- prefix to an element ID if it does not have one 42*ba677afaSXin Lifunc prefixElementId(id ElementID) string { 43*ba677afaSXin Li val := string(id) 44*ba677afaSXin Li if !strings.HasPrefix(val, spdxRefPrefix) { 45*ba677afaSXin Li return spdxRefPrefix + val 46*ba677afaSXin Li } 47*ba677afaSXin Li return val 48*ba677afaSXin Li} 49*ba677afaSXin Li 50*ba677afaSXin Li// trimElementIdPrefix removes the SPDXRef- prefix from an element ID string or returns an error if it 51*ba677afaSXin Li// does not start with SPDXRef- 52*ba677afaSXin Lifunc trimElementIdPrefix(id string) (ElementID, error) { 53*ba677afaSXin Li // handle SPDXRef- 54*ba677afaSXin Li idFields := strings.SplitN(id, spdxRefPrefix, 2) 55*ba677afaSXin Li if len(idFields) != 2 { 56*ba677afaSXin Li return "", fmt.Errorf("failed to parse SPDX identifier '%s'", id) 57*ba677afaSXin Li } 58*ba677afaSXin Li 59*ba677afaSXin Li e := ElementID(idFields[1]) 60*ba677afaSXin Li return e, nil 61*ba677afaSXin Li} 62*ba677afaSXin Li 63*ba677afaSXin Li// DocElementID represents an SPDX element identifier that could be defined 64*ba677afaSXin Li// in a different SPDX document, and therefore could have a "DocumentRef-" 65*ba677afaSXin Li// portion, such as Relationships and Annotations. 66*ba677afaSXin Li// ElementID is used for attributes in which a "DocumentRef-" portion cannot 67*ba677afaSXin Li// appear, such as a Package or File definition (since it is necessarily 68*ba677afaSXin Li// being defined in the present document). 69*ba677afaSXin Li// DocumentRefID will be the empty string for elements defined in the 70*ba677afaSXin Li// present document. 71*ba677afaSXin Li// DocElementIDs should NOT contain the mandatory 'DocumentRef-' or 72*ba677afaSXin Li// 'SPDXRef-' portions. 73*ba677afaSXin Li// SpecialID is used ONLY if the DocElementID matches a defined set of 74*ba677afaSXin Li// permitted special values for a particular field, e.g. "NONE" or 75*ba677afaSXin Li// "NOASSERTION" for the right-hand side of Relationships. If SpecialID 76*ba677afaSXin Li// is set, DocumentRefID and ElementRefID should be empty (and vice versa). 77*ba677afaSXin Litype DocElementID struct { 78*ba677afaSXin Li DocumentRefID string 79*ba677afaSXin Li ElementRefID ElementID 80*ba677afaSXin Li SpecialID string 81*ba677afaSXin Li} 82*ba677afaSXin Li 83*ba677afaSXin Li// MarshalJSON converts the receiver into a slice of bytes representing a DocElementID in string form. 84*ba677afaSXin Li// This function is also used when marshalling to YAML 85*ba677afaSXin Lifunc (d DocElementID) MarshalJSON() ([]byte, error) { 86*ba677afaSXin Li if d.DocumentRefID != "" && d.ElementRefID != "" { 87*ba677afaSXin Li idStr := prefixElementId(d.ElementRefID) 88*ba677afaSXin Li return json.Marshal(fmt.Sprintf("%s%s:%s", documentRefPrefix, d.DocumentRefID, idStr)) 89*ba677afaSXin Li } else if d.ElementRefID != "" { 90*ba677afaSXin Li return json.Marshal(prefixElementId(d.ElementRefID)) 91*ba677afaSXin Li } else if d.SpecialID != "" { 92*ba677afaSXin Li return json.Marshal(d.SpecialID) 93*ba677afaSXin Li } 94*ba677afaSXin Li 95*ba677afaSXin Li return []byte{}, fmt.Errorf("failed to marshal empty DocElementID") 96*ba677afaSXin Li} 97*ba677afaSXin Li 98*ba677afaSXin Li// UnmarshalJSON takes a SPDX Identifier string parses it into a DocElementID struct. 99*ba677afaSXin Li// This function is also used when unmarshalling YAML 100*ba677afaSXin Lifunc (d *DocElementID) UnmarshalJSON(data []byte) (err error) { 101*ba677afaSXin Li // SPDX identifier will simply be a string 102*ba677afaSXin Li idStr := string(data) 103*ba677afaSXin Li idStr = strings.Trim(idStr, "\"") 104*ba677afaSXin Li 105*ba677afaSXin Li // handle special cases 106*ba677afaSXin Li if idStr == "NONE" || idStr == "NOASSERTION" { 107*ba677afaSXin Li d.SpecialID = idStr 108*ba677afaSXin Li return nil 109*ba677afaSXin Li } 110*ba677afaSXin Li 111*ba677afaSXin Li var idFields []string 112*ba677afaSXin Li // handle DocumentRef- if present 113*ba677afaSXin Li if strings.HasPrefix(idStr, documentRefPrefix) { 114*ba677afaSXin Li // strip out the "DocumentRef-" so we can get the value 115*ba677afaSXin Li idFields = strings.SplitN(idStr, documentRefPrefix, 2) 116*ba677afaSXin Li idStr = idFields[1] 117*ba677afaSXin Li 118*ba677afaSXin Li // an SPDXRef can appear after a DocumentRef, separated by a colon 119*ba677afaSXin Li idFields = strings.SplitN(idStr, ":", 2) 120*ba677afaSXin Li d.DocumentRefID = idFields[0] 121*ba677afaSXin Li 122*ba677afaSXin Li if len(idFields) == 2 { 123*ba677afaSXin Li idStr = idFields[1] 124*ba677afaSXin Li } else { 125*ba677afaSXin Li return nil 126*ba677afaSXin Li } 127*ba677afaSXin Li } 128*ba677afaSXin Li 129*ba677afaSXin Li d.ElementRefID, err = trimElementIdPrefix(idStr) 130*ba677afaSXin Li return err 131*ba677afaSXin Li} 132*ba677afaSXin Li 133*ba677afaSXin Li// TODO: add equivalents for LicenseRef- identifiers 134*ba677afaSXin Li 135*ba677afaSXin Li// MakeDocElementID takes strings (without prefixes) for the DocumentRef- 136*ba677afaSXin Li// and SPDXRef- identifiers, and returns a DocElementID. An empty string 137*ba677afaSXin Li// should be used for the DocumentRef- portion if it is referring to the 138*ba677afaSXin Li// present document. 139*ba677afaSXin Lifunc MakeDocElementID(docRef string, eltRef string) DocElementID { 140*ba677afaSXin Li return DocElementID{ 141*ba677afaSXin Li DocumentRefID: docRef, 142*ba677afaSXin Li ElementRefID: ElementID(eltRef), 143*ba677afaSXin Li } 144*ba677afaSXin Li} 145*ba677afaSXin Li 146*ba677afaSXin Li// MakeDocElementSpecial takes a "special" string (e.g. "NONE" or 147*ba677afaSXin Li// "NOASSERTION" for the right side of a Relationship), nd returns 148*ba677afaSXin Li// a DocElementID with it in the SpecialID field. Other fields will 149*ba677afaSXin Li// be empty. 150*ba677afaSXin Lifunc MakeDocElementSpecial(specialID string) DocElementID { 151*ba677afaSXin Li return DocElementID{SpecialID: specialID} 152*ba677afaSXin Li} 153*ba677afaSXin Li 154*ba677afaSXin Li// RenderElementID takes an ElementID and returns the string equivalent, 155*ba677afaSXin Li// with the SPDXRef- prefix reinserted. 156*ba677afaSXin Lifunc RenderElementID(eID ElementID) string { 157*ba677afaSXin Li return spdxRefPrefix + string(eID) 158*ba677afaSXin Li} 159*ba677afaSXin Li 160*ba677afaSXin Li// RenderDocElementID takes a DocElementID and returns the string equivalent, 161*ba677afaSXin Li// with the SPDXRef- prefix (and, if applicable, the DocumentRef- prefix) 162*ba677afaSXin Li// reinserted. If a SpecialID is present, it will be rendered verbatim and 163*ba677afaSXin Li// DocumentRefID and ElementRefID will be ignored. 164*ba677afaSXin Lifunc RenderDocElementID(deID DocElementID) string { 165*ba677afaSXin Li if deID.SpecialID != "" { 166*ba677afaSXin Li return deID.SpecialID 167*ba677afaSXin Li } 168*ba677afaSXin Li prefix := "" 169*ba677afaSXin Li if deID.DocumentRefID != "" { 170*ba677afaSXin Li prefix = documentRefPrefix + deID.DocumentRefID + ":" 171*ba677afaSXin Li } 172*ba677afaSXin Li return prefix + spdxRefPrefix + string(deID.ElementRefID) 173*ba677afaSXin Li} 174