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