Skip to content

Commit a2657d9

Browse files
committed
incorporate bom link; add test files for components merge
Signed-off-by: nscuro <[email protected]>
1 parent 0fb04d7 commit a2657d9

18 files changed

+1174
-18
lines changed

cyclonedx.go

+69
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import (
2323
"errors"
2424
"fmt"
2525
"io"
26+
27+
"github.com/google/uuid"
28+
"github.com/gowebpki/jcs"
2629
)
2730

2831
const (
@@ -164,6 +167,37 @@ type Component struct {
164167
ReleaseNotes *ReleaseNotes `json:"releaseNotes,omitempty" xml:"releaseNotes,omitempty"`
165168
}
166169

170+
func (c Component) bomReference() string {
171+
return c.BOMRef
172+
}
173+
174+
func (c *Component) setBOMReference(ref string) {
175+
c.BOMRef = ref
176+
}
177+
178+
// TODO: Can we solve this more elegantly?
179+
type componentRefSeed Component
180+
181+
func (c componentRefSeed) MarshalJSON() ([]byte, error) {
182+
c.BOMRef = ""
183+
184+
componentJSON, err := json.Marshal(Component(c))
185+
if err != nil {
186+
return nil, err
187+
}
188+
189+
return jcs.Transform(componentJSON)
190+
}
191+
192+
func (c Component) generateBOMReference() (string, error) {
193+
componentJSON, err := json.Marshal(componentRefSeed(c))
194+
if err != nil {
195+
return "", err
196+
}
197+
198+
return uuid.NewSHA1(uuid.MustParse("369fac08-d4a0-452e-b4b1-de87a0f376c6"), componentJSON).String(), nil
199+
}
200+
167201
type Composition struct {
168202
Aggregate CompositionAggregate `json:"aggregate" xml:"aggregate"`
169203
Assemblies *[]BOMReference `json:"assemblies,omitempty" xml:"assemblies>assembly,omitempty"`
@@ -520,6 +554,17 @@ type Property struct {
520554
Value string `json:"value" xml:",innerxml"`
521555
}
522556

557+
// referrer is an internal utility interface that is used
558+
// to address bom elements that have a BOM reference.
559+
type referrer interface {
560+
bomReference() string
561+
setBOMReference(ref string)
562+
563+
// generateBOMReference returns a new value intended to be used as BOM reference.
564+
// Given the same state of the referrer, generateBOMReference must return the same result.
565+
generateBOMReference() (string, error)
566+
}
567+
523568
type ReleaseNotes struct {
524569
Type string `json:"type" xml:"type"`
525570
Title string `json:"title,omitempty" xml:"title,omitempty"`
@@ -570,6 +615,18 @@ type Service struct {
570615
ReleaseNotes *ReleaseNotes `json:"releaseNotes,omitempty" xml:"releaseNotes,omitempty"`
571616
}
572617

618+
func (s Service) bomReference() string {
619+
return s.BOMRef
620+
}
621+
622+
func (s *Service) setBOMReference(ref string) {
623+
s.BOMRef = ref
624+
}
625+
626+
func (s Service) generateBOMReference() (string, error) {
627+
return "", nil
628+
}
629+
573630
type Severity string
574631

575632
const (
@@ -625,6 +682,18 @@ type Vulnerability struct {
625682
Affects *[]Affects `json:"affects,omitempty" xml:"affects>target,omitempty"`
626683
}
627684

685+
func (v Vulnerability) bomReference() string {
686+
return v.BOMRef
687+
}
688+
689+
func (v *Vulnerability) setBOMReference(ref string) {
690+
v.BOMRef = ref
691+
}
692+
693+
func (v Vulnerability) generateBOMReference() (string, error) {
694+
return "", nil
695+
}
696+
628697
type VulnerabilityAnalysis struct {
629698
State ImpactAnalysisState `json:"state,omitempty" xml:"state,omitempty"`
630699
Justification ImpactAnalysisJustification `json:"justification,omitempty" xml:"justification,omitempty"`

cyclonedx_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ package cyclonedx
2020
import (
2121
"encoding/json"
2222
"encoding/xml"
23+
"os"
24+
"path/filepath"
2325
"testing"
2426

2527
"github.com/stretchr/testify/assert"
@@ -217,3 +219,22 @@ func TestLicenses_UnmarshalXML(t *testing.T) {
217219
err = xml.Unmarshal([]byte("<Licenses><somethingElse>expressionValue</somethingElse></Licenses>"), licenses)
218220
assert.Error(t, err)
219221
}
222+
223+
func readTestBOM(t *testing.T, filePath string) *BOM {
224+
format := BOMFileFormatJSON
225+
if filepath.Ext(filePath) == ".xml" {
226+
format = BOMFileFormatXML
227+
}
228+
229+
file, err := os.Open(filePath)
230+
require.NoError(t, err)
231+
defer func() {
232+
_ = file.Close()
233+
}()
234+
235+
var bom BOM
236+
err = NewBOMDecoder(file, format).Decode(&bom)
237+
require.NoError(t, err)
238+
239+
return &bom
240+
}

go.mod

+4
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@ go 1.15
44

55
require (
66
github.com/bradleyjkemp/cupaloy/v2 v2.7.0
7+
github.com/google/go-cmp v0.5.7
8+
github.com/google/uuid v1.3.0
9+
github.com/gowebpki/jcs v1.0.0
10+
github.com/mitchellh/copystructure v1.2.0
711
github.com/stretchr/testify v1.7.0
812
)

go.sum

+12
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,25 @@ github.com/bradleyjkemp/cupaloy/v2 v2.7.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1l
33
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
44
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
55
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6+
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
7+
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
8+
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
9+
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
10+
github.com/gowebpki/jcs v1.0.0 h1:0pZtOgGetfH/L7yXb4KWcJqIyZNA43WXFyMd7ftZACw=
11+
github.com/gowebpki/jcs v1.0.0/go.mod h1:CID1cNZ+sHp1CCpAR8mPf6QRtagFBgPJE0FCUQ6+BrI=
12+
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
13+
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
14+
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
15+
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
616
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
717
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
818
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
919
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
1020
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
1121
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
1222
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
23+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
24+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
1325
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1426
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1527
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=

link.go

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package cyclonedx
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
"regexp"
7+
"strconv"
8+
9+
"github.com/google/uuid"
10+
)
11+
12+
var bomLinkRegex = regexp.MustCompile(`^urn:cdx:(?P<serialNumber>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/(?P<version>[0-9]+)(?:#(?P<bomRef>[0-9a-zA-Z\-._~%!$&'()*+,;=:@\/?]+))?$`)
13+
14+
// IsBOMLink TODO
15+
func IsBOMLink(s string) bool {
16+
return bomLinkRegex.MatchString(s)
17+
}
18+
19+
// BOMLink TODO
20+
type BOMLink struct {
21+
SerialNumber uuid.UUID // Serial number of the linked BOM
22+
Version int // Version of the linked BOM
23+
Reference string // Reference of the linked element
24+
}
25+
26+
// NewBOMLink TODO
27+
func NewBOMLink(bom *BOM, elem referrer) (*BOMLink, error) {
28+
if bom == nil {
29+
return nil, fmt.Errorf("bom is nil")
30+
}
31+
if bom.SerialNumber == "" {
32+
return nil, fmt.Errorf("missing serial number")
33+
}
34+
if bom.Version < 1 {
35+
return nil, fmt.Errorf("versions below 1 are not allowed")
36+
}
37+
38+
serial, err := uuid.Parse(bom.SerialNumber)
39+
if err != nil {
40+
return nil, fmt.Errorf("invalid serial number: %w", err)
41+
}
42+
43+
if elem == nil {
44+
return &BOMLink{
45+
SerialNumber: serial,
46+
Version: bom.Version,
47+
}, nil
48+
}
49+
50+
return &BOMLink{
51+
SerialNumber: serial,
52+
Version: bom.Version,
53+
Reference: elem.bomReference(),
54+
}, nil
55+
}
56+
57+
// String TODO
58+
func (b BOMLink) String() string {
59+
if b.Reference == "" {
60+
return fmt.Sprintf("urn:cdx:%s/%d", b.SerialNumber, b.Version)
61+
}
62+
63+
return fmt.Sprintf("urn:cdx:%s/%d#%s", b.SerialNumber, b.Version, url.QueryEscape(b.Reference))
64+
}
65+
66+
// ParseBOMLink TODO
67+
func ParseBOMLink(s string) (*BOMLink, error) {
68+
matches := bomLinkRegex.FindStringSubmatch(s)
69+
if len(matches) < 3 || len(matches) > 4 {
70+
return nil, fmt.Errorf("")
71+
}
72+
73+
serial, err := uuid.Parse(matches[1])
74+
if err != nil {
75+
return nil, fmt.Errorf("invalid serial number: %w", err)
76+
}
77+
version, err := strconv.Atoi(matches[2])
78+
if err != nil {
79+
return nil, fmt.Errorf("invalid version: %w", err)
80+
}
81+
82+
if len(matches) == 4 {
83+
bomRef, err := url.QueryUnescape(matches[3])
84+
if err != nil {
85+
return nil, fmt.Errorf("invalid reference: %w", err)
86+
}
87+
88+
return &BOMLink{
89+
SerialNumber: serial,
90+
Version: version,
91+
Reference: bomRef,
92+
}, nil
93+
}
94+
95+
return &BOMLink{
96+
SerialNumber: serial,
97+
Version: version,
98+
}, nil
99+
}

link_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package cyclonedx
2+
3+
import "testing"
4+
5+
func TestIsBOMLink(t *testing.T) {
6+
// TODO
7+
}
8+
9+
func TestNewBOMLink(t *testing.T) {
10+
// TODO
11+
}
12+
13+
func TestParseBOMLink(t *testing.T) {
14+
// TODO
15+
}

0 commit comments

Comments
 (0)