mirror of
https://github.com/anchore/syft
synced 2024-11-10 06:14:16 +00:00
feat: update syft license concept to complex struct (#1743)
this PR makes the following changes to update the underlying license model to have more expressive capabilities it also provides some guarantee's surrounding the license values themselves - Licenses are updated from string -> pkg.LicenseSet which contain pkg.License with the following fields: - original `Value` read by syft - If it's possible to construct licenses will always have a valid SPDX expression for downstream consumption - the above is run against a generated list of SPDX license ID to try and find the correct ID - SPDX concluded vs declared is added to the new struct - URL source for license is added to the new struct - Location source is added to the new struct to show where the expression was pulled from
This commit is contained in:
parent
8046f09562
commit
42fa9e4965
126 changed files with 5165 additions and 1503 deletions
1
go.mod
1
go.mod
|
@ -56,6 +56,7 @@ require (
|
|||
github.com/anchore/stereoscope v0.0.0-20230412183729-8602f1afc574
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/docker/docker v23.0.6+incompatible
|
||||
github.com/github/go-spdx/v2 v2.1.2
|
||||
github.com/go-git/go-billy/v5 v5.4.1
|
||||
github.com/go-git/go-git/v5 v5.6.1
|
||||
github.com/google/go-containerregistry v0.15.1
|
||||
|
|
2
go.sum
2
go.sum
|
@ -207,6 +207,8 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS
|
|||
github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/github/go-spdx/v2 v2.1.2 h1:p+Tv0yMgcuO0/vnMe9Qh4tmUgYhI6AsLVlakZ/Sx+DM=
|
||||
github.com/github/go-spdx/v2 v2.1.2/go.mod h1:hMCrsFgT0QnCwn7G8gxy/MxMpy67WgZrwFeISTn0o6w=
|
||||
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||
|
|
|
@ -6,5 +6,5 @@ const (
|
|||
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "7.1.6"
|
||||
JSONSchemaVersion = "8.0.0"
|
||||
)
|
||||
|
|
|
@ -4,7 +4,10 @@ import (
|
|||
"io"
|
||||
|
||||
"github.com/google/licensecheck"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -13,21 +16,24 @@ const (
|
|||
)
|
||||
|
||||
// Parse scans the contents of a license file to attempt to determine the type of license it is
|
||||
func Parse(reader io.Reader) (licenses []string, err error) {
|
||||
func Parse(reader io.Reader, l source.Location) (licenses []pkg.License, err error) {
|
||||
licenses = make([]pkg.License, 0)
|
||||
contents, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cov := licensecheck.Scan(contents)
|
||||
if cov.Percent < coverageThreshold {
|
||||
// unknown or no licenses here?
|
||||
return licenses, nil
|
||||
}
|
||||
|
||||
if cov.Percent < float64(coverageThreshold) {
|
||||
licenses = append(licenses, unknownLicenseType)
|
||||
}
|
||||
for _, m := range cov.Match {
|
||||
if slices.Contains(licenses, m.ID) {
|
||||
continue
|
||||
}
|
||||
licenses = append(licenses, m.ID)
|
||||
lic := pkg.NewLicenseFromLocations(m.ID, l)
|
||||
lic.Type = license.Concluded
|
||||
|
||||
licenses = append(licenses, lic)
|
||||
}
|
||||
return
|
||||
|
||||
return licenses, nil
|
||||
}
|
||||
|
|
|
@ -15,8 +15,10 @@ func NewStringSet(start ...string) StringSet {
|
|||
}
|
||||
|
||||
// Add a string to the set.
|
||||
func (s StringSet) Add(i string) {
|
||||
s[i] = struct{}{}
|
||||
func (s StringSet) Add(i ...string) {
|
||||
for _, str := range i {
|
||||
s[str] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove a string from the set.
|
||||
|
@ -41,3 +43,19 @@ func (s StringSet) ToSlice() []string {
|
|||
sort.Strings(ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s StringSet) Equals(o StringSet) bool {
|
||||
if len(s) != len(o) {
|
||||
return false
|
||||
}
|
||||
for k := range s {
|
||||
if !o.Contains(k) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s StringSet) Empty() bool {
|
||||
return len(s) < 1
|
||||
}
|
||||
|
|
|
@ -78,7 +78,6 @@ func build() *jsonschema.Schema {
|
|||
}
|
||||
documentSchema := reflector.ReflectFromType(reflect.TypeOf(&syftjsonModel.Document{}))
|
||||
metadataSchema := reflector.ReflectFromType(reflect.TypeOf(&artifactMetadataContainer{}))
|
||||
|
||||
// TODO: inject source definitions
|
||||
|
||||
// inject the definitions of all metadatas into the schema definitions
|
||||
|
|
1870
schema/json/schema-8.0.0.json
Normal file
1870
schema/json/schema-8.0.0.json
Normal file
File diff suppressed because it is too large
Load diff
32
syft/file/license.go
Normal file
32
syft/file/license.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package file
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
)
|
||||
|
||||
type License struct {
|
||||
Value string
|
||||
SPDXExpression string
|
||||
Type license.Type
|
||||
LicenseEvidence *LicenseEvidence // evidence from license classifier
|
||||
}
|
||||
|
||||
type LicenseEvidence struct {
|
||||
Confidence int
|
||||
Offset int
|
||||
Extent int
|
||||
}
|
||||
|
||||
func NewLicense(value string) License {
|
||||
spdxExpression, err := license.ParseExpression(value)
|
||||
if err != nil {
|
||||
log.Trace("unable to parse license expression: %s, %w", value, err)
|
||||
}
|
||||
|
||||
return License{
|
||||
Value: value,
|
||||
SPDXExpression: spdxExpression,
|
||||
Type: license.Concluded,
|
||||
}
|
||||
}
|
|
@ -78,7 +78,7 @@ func decodeComponent(c *cyclonedx.Component) *pkg.Package {
|
|||
Name: c.Name,
|
||||
Version: c.Version,
|
||||
Locations: decodeLocations(values),
|
||||
Licenses: decodeLicenses(c),
|
||||
Licenses: pkg.NewLicenseSet(decodeLicenses(c)...),
|
||||
CPEs: decodeCPEs(c),
|
||||
PURL: c.PackageURL,
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ func Test_encodeComponentProperties(t *testing.T) {
|
|||
OriginPackage: "libc-dev",
|
||||
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
|
||||
Version: "0.7.2-r0",
|
||||
License: "BSD",
|
||||
Architecture: "x86_64",
|
||||
URL: "http://alpinelinux.org",
|
||||
Description: "Meta package to pull in correct libc",
|
||||
|
@ -140,7 +139,6 @@ func Test_encodeComponentProperties(t *testing.T) {
|
|||
Version: "0.9.2",
|
||||
SourceRpm: "dive-0.9.2-1.src.rpm",
|
||||
Size: 12406784,
|
||||
License: "MIT",
|
||||
Vendor: "",
|
||||
Files: []pkg.RpmdbFileRecord{},
|
||||
},
|
||||
|
|
|
@ -322,8 +322,7 @@ func Test_missingDataDecode(t *testing.T) {
|
|||
},
|
||||
},
|
||||
})
|
||||
|
||||
assert.Len(t, pkg.Licenses, 0)
|
||||
assert.Equal(t, pkg.Licenses.Empty(), true)
|
||||
}
|
||||
|
||||
func Test_missingComponentsDecode(t *testing.T) {
|
||||
|
|
|
@ -50,7 +50,7 @@ func Test_encodeExternalReferences(t *testing.T) {
|
|||
Language: pkg.Rust,
|
||||
Type: pkg.RustPkg,
|
||||
MetadataType: pkg.RustCargoPackageMetadataType,
|
||||
Licenses: nil,
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Metadata: pkg.CargoPackageMetadata{
|
||||
Name: "ansi_term",
|
||||
Version: "0.12.1",
|
||||
|
|
|
@ -1,53 +1,205 @@
|
|||
package cyclonedxhelpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// This should be a function that just surfaces licenses already validated in the package struct
|
||||
func encodeLicenses(p pkg.Package) *cyclonedx.Licenses {
|
||||
lc := cyclonedx.Licenses{}
|
||||
for _, licenseName := range p.Licenses {
|
||||
if value, exists := spdxlicense.ID(licenseName); exists {
|
||||
lc = append(lc, cyclonedx.LicenseChoice{
|
||||
spdxc, otherc, ex := separateLicenses(p)
|
||||
if len(otherc) > 0 {
|
||||
// found non spdx related licenses
|
||||
// build individual license choices for each
|
||||
// complex expressions are not combined and set as NAME fields
|
||||
for _, e := range ex {
|
||||
otherc = append(otherc, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
Name: e,
|
||||
},
|
||||
})
|
||||
}
|
||||
otherc = append(otherc, spdxc...)
|
||||
return &otherc
|
||||
}
|
||||
|
||||
if len(spdxc) > 0 {
|
||||
for _, l := range ex {
|
||||
spdxc = append(spdxc, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
Name: l,
|
||||
},
|
||||
})
|
||||
}
|
||||
return &spdxc
|
||||
}
|
||||
|
||||
if len(ex) > 0 {
|
||||
// only expressions found
|
||||
var expressions cyclonedx.Licenses
|
||||
expressions = append(expressions, cyclonedx.LicenseChoice{
|
||||
Expression: mergeSPDX(ex),
|
||||
})
|
||||
return &expressions
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeLicenses(c *cyclonedx.Component) []pkg.License {
|
||||
licenses := make([]pkg.License, 0)
|
||||
if c == nil || c.Licenses == nil {
|
||||
return licenses
|
||||
}
|
||||
|
||||
for _, l := range *c.Licenses {
|
||||
if l.License == nil {
|
||||
continue
|
||||
}
|
||||
// these fields are mutually exclusive in the spec
|
||||
switch {
|
||||
case l.License.ID != "":
|
||||
licenses = append(licenses, pkg.NewLicenseFromURLs(l.License.ID, l.License.URL))
|
||||
case l.License.Name != "":
|
||||
licenses = append(licenses, pkg.NewLicenseFromURLs(l.License.Name, l.License.URL))
|
||||
case l.Expression != "":
|
||||
licenses = append(licenses, pkg.NewLicenseFromURLs(l.Expression, l.License.URL))
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return licenses
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
func separateLicenses(p pkg.Package) (spdx, other cyclonedx.Licenses, expressions []string) {
|
||||
ex := make([]string, 0)
|
||||
spdxc := cyclonedx.Licenses{}
|
||||
otherc := cyclonedx.Licenses{}
|
||||
/*
|
||||
pkg.License can be a couple of things: see above declarations
|
||||
- Complex SPDX expression
|
||||
- Some other Valid license ID
|
||||
- Some non-standard non spdx license
|
||||
|
||||
To determine if an expression is a singular ID we first run it against the SPDX license list.
|
||||
|
||||
The weird case we run into is if there is a package with a license that is not a valid SPDX expression
|
||||
and a license that is a valid complex expression. In this case we will surface the valid complex expression
|
||||
as a license choice and the invalid expression as a license string.
|
||||
|
||||
*/
|
||||
seen := make(map[string]bool)
|
||||
for _, l := range p.Licenses.ToSlice() {
|
||||
// singular expression case
|
||||
// only ID field here since we guarantee that the license is valid
|
||||
if value, exists := spdxlicense.ID(l.SPDXExpression); exists {
|
||||
if !l.URL.Empty() {
|
||||
processLicenseURLs(l, value, &spdxc)
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := seen[value]; exists {
|
||||
continue
|
||||
}
|
||||
// try making set of license choices to avoid duplicates
|
||||
// only update if the license has more information
|
||||
spdxc = append(spdxc, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
ID: value,
|
||||
},
|
||||
})
|
||||
seen[value] = true
|
||||
// we have added the license to the SPDX license list check next license
|
||||
continue
|
||||
}
|
||||
|
||||
// not found so append the licenseName as is
|
||||
lc = append(lc, cyclonedx.LicenseChoice{
|
||||
if l.SPDXExpression != "" {
|
||||
// COMPLEX EXPRESSION CASE
|
||||
ex = append(ex, l.SPDXExpression)
|
||||
continue
|
||||
}
|
||||
|
||||
// license string that are not valid spdx expressions or ids
|
||||
// we only use license Name here since we cannot guarantee that the license is a valid SPDX expression
|
||||
if !l.URL.Empty() {
|
||||
processLicenseURLs(l, "", &otherc)
|
||||
continue
|
||||
}
|
||||
otherc = append(otherc, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
Name: licenseName,
|
||||
Name: l.Value,
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(lc) > 0 {
|
||||
return &lc
|
||||
}
|
||||
return nil
|
||||
return spdxc, otherc, ex
|
||||
}
|
||||
|
||||
func decodeLicenses(c *cyclonedx.Component) (out []string) {
|
||||
if c.Licenses != nil {
|
||||
for _, l := range *c.Licenses {
|
||||
if l.License != nil {
|
||||
var lic string
|
||||
switch {
|
||||
case l.License.ID != "":
|
||||
lic = l.License.ID
|
||||
case l.License.Name != "":
|
||||
lic = l.License.Name
|
||||
default:
|
||||
continue
|
||||
}
|
||||
out = append(out, lic)
|
||||
}
|
||||
func processLicenseURLs(l pkg.License, spdxID string, populate *cyclonedx.Licenses) {
|
||||
for _, url := range l.URL.ToSlice() {
|
||||
if spdxID == "" {
|
||||
*populate = append(*populate, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
URL: url,
|
||||
Name: l.Value,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
*populate = append(*populate, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
ID: spdxID,
|
||||
URL: url,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func mergeSPDX(ex []string) string {
|
||||
var candidate []string
|
||||
for _, e := range ex {
|
||||
// if the expression does not have balanced parens add them
|
||||
if !strings.HasPrefix(e, "(") && !strings.HasSuffix(e, ")") {
|
||||
e = "(" + e + ")"
|
||||
candidate = append(candidate, e)
|
||||
}
|
||||
}
|
||||
|
||||
if len(candidate) == 1 {
|
||||
return reduceOuter(strings.Join(candidate, " AND "))
|
||||
}
|
||||
|
||||
return strings.Join(candidate, " AND ")
|
||||
}
|
||||
|
||||
func reduceOuter(expression string) string {
|
||||
var (
|
||||
sb strings.Builder
|
||||
openCount int
|
||||
)
|
||||
|
||||
for _, c := range expression {
|
||||
if string(c) == "(" && openCount > 0 {
|
||||
fmt.Fprintf(&sb, "%c", c)
|
||||
}
|
||||
if string(c) == "(" {
|
||||
openCount++
|
||||
continue
|
||||
}
|
||||
if string(c) == ")" && openCount > 1 {
|
||||
fmt.Fprintf(&sb, "%c", c)
|
||||
}
|
||||
if string(c) == ")" {
|
||||
openCount--
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&sb, "%c", c)
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
|
@ -23,60 +25,170 @@ func Test_encodeLicense(t *testing.T) {
|
|||
{
|
||||
name: "no SPDX licenses",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"made-up",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("RandomLicense"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{License: &cyclonedx.License{Name: "made-up"}},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "RandomLicense",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with SPDX license",
|
||||
name: "single SPDX ID and Non SPDX ID",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("mit"),
|
||||
pkg.NewLicense("FOOBAR"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{License: &cyclonedx.License{ID: "MIT"}},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "FOOBAR",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with SPDX license expression",
|
||||
name: "with complex SPDX license expression",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
"GPL-3.0",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT AND GPL-3.0-only"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{License: &cyclonedx.License{ID: "MIT"}},
|
||||
{License: &cyclonedx.License{ID: "GPL-3.0-only"}},
|
||||
{
|
||||
Expression: "MIT AND GPL-3.0-only",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "cap insensitive",
|
||||
name: "with multiple complex SPDX license expression",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"gpl-3.0",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT AND GPL-3.0-only"),
|
||||
pkg.NewLicense("MIT AND GPL-3.0-only WITH Classpath-exception-2.0"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{License: &cyclonedx.License{ID: "GPL-3.0-only"}},
|
||||
{
|
||||
Expression: "(MIT AND GPL-3.0-only) AND (MIT AND GPL-3.0-only WITH Classpath-exception-2.0)",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "debian to spdx conversion",
|
||||
name: "with multiple URLs and expressions",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"GPL-2",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromURLs("MIT", "https://opensource.org/licenses/MIT", "https://spdx.org/licenses/MIT.html"),
|
||||
pkg.NewLicense("MIT AND GPL-3.0-only"),
|
||||
pkg.NewLicenseFromURLs("FakeLicense", "htts://someurl.com"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{License: &cyclonedx.License{ID: "GPL-2.0-only"}},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "FakeLicense",
|
||||
URL: "htts://someurl.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "MIT AND GPL-3.0-only",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
URL: "https://opensource.org/licenses/MIT",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
URL: "https://spdx.org/licenses/MIT.html",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with multiple values licenses are deduplicated",
|
||||
input: pkg.Package{
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("Apache-2"),
|
||||
pkg.NewLicense("Apache-2.0"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "Apache-2.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with multiple URLs and single with no URL",
|
||||
input: pkg.Package{
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
pkg.NewLicenseFromURLs("MIT", "https://opensource.org/licenses/MIT", "https://spdx.org/licenses/MIT.html"),
|
||||
pkg.NewLicense("MIT AND GPL-3.0-only"),
|
||||
),
|
||||
},
|
||||
expected: &cyclonedx.Licenses{
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
URL: "https://opensource.org/licenses/MIT",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
URL: "https://spdx.org/licenses/MIT.html",
|
||||
},
|
||||
},
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "MIT AND GPL-3.0-only",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// TODO: do we drop the non SPDX ID license and do a single expression
|
||||
// OR do we keep the non SPDX ID license and do multiple licenses where the complex
|
||||
// expressions are set as the NAME field?
|
||||
//{
|
||||
// name: "with multiple complex SPDX license expression and a non spdx id",
|
||||
// input: pkg.Package{
|
||||
// Licenses: []pkg.License{
|
||||
// {
|
||||
// SPDXExpression: "MIT AND GPL-3.0-only",
|
||||
// },
|
||||
// {
|
||||
// SPDXExpression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
|
||||
// },
|
||||
// {
|
||||
// Value: "FOOBAR",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// expected: &cyclonedx.Licenses{
|
||||
// {
|
||||
// Expression: "(MIT AND GPL-3.0-only) AND (MIT AND GPL-3.0-only WITH Classpath-exception-2.0)",
|
||||
// },
|
||||
// },
|
||||
//},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
@ -84,3 +196,81 @@ func Test_encodeLicense(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeLicenses(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input *cyclonedx.Component
|
||||
expected []pkg.License
|
||||
}{
|
||||
{
|
||||
name: "no licenses",
|
||||
input: &cyclonedx.Component{},
|
||||
expected: []pkg.License{},
|
||||
},
|
||||
{
|
||||
name: "no SPDX license ID or expression",
|
||||
input: &cyclonedx.Component{
|
||||
Licenses: &cyclonedx.Licenses{
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
Name: "RandomLicense",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []pkg.License{
|
||||
{
|
||||
Value: "RandomLicense",
|
||||
// CycloneDX specification doesn't give a field for determining the license type
|
||||
Type: license.Declared,
|
||||
URL: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with SPDX license ID",
|
||||
input: &cyclonedx.Component{
|
||||
Licenses: &cyclonedx.Licenses{
|
||||
{
|
||||
License: &cyclonedx.License{
|
||||
ID: "MIT",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []pkg.License{
|
||||
{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
URL: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with complex SPDX license expression",
|
||||
input: &cyclonedx.Component{
|
||||
Licenses: &cyclonedx.Licenses{
|
||||
{
|
||||
License: &cyclonedx.License{},
|
||||
Expression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []pkg.License{
|
||||
{
|
||||
Value: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
|
||||
SPDXExpression: "MIT AND GPL-3.0-only WITH Classpath-exception-2.0",
|
||||
Type: license.Declared,
|
||||
URL: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, decodeLicenses(test.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,11 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func License(p pkg.Package) string {
|
||||
func License(p pkg.Package) (concluded, declared string) {
|
||||
// source: https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license
|
||||
// The options to populate this field are limited to:
|
||||
// A valid SPDX License Expression as defined in Appendix IV;
|
||||
|
@ -17,35 +18,70 @@ func License(p pkg.Package) string {
|
|||
// (ii) the SPDX file creator has made no attempt to determine this field; or
|
||||
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
|
||||
if len(p.Licenses) == 0 {
|
||||
return NONE
|
||||
if p.Licenses.Empty() {
|
||||
return NOASSERTION, NOASSERTION
|
||||
}
|
||||
|
||||
// take all licenses and assume an AND expression; for information about license expressions see https://spdx.github.io/spdx-spec/appendix-IV-SPDX-license-expressions/
|
||||
parsedLicenses := parseLicenses(p.Licenses)
|
||||
// take all licenses and assume an AND expression;
|
||||
// for information about license expressions see:
|
||||
// https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/
|
||||
pc, pd := parseLicenses(p.Licenses.ToSlice())
|
||||
|
||||
for i, v := range parsedLicenses {
|
||||
for i, v := range pc {
|
||||
if strings.HasPrefix(v, spdxlicense.LicenseRefPrefix) {
|
||||
parsedLicenses[i] = SanitizeElementID(v)
|
||||
pc[i] = SanitizeElementID(v)
|
||||
}
|
||||
}
|
||||
|
||||
if len(parsedLicenses) == 0 {
|
||||
for i, v := range pd {
|
||||
if strings.HasPrefix(v, spdxlicense.LicenseRefPrefix) {
|
||||
pd[i] = SanitizeElementID(v)
|
||||
}
|
||||
}
|
||||
|
||||
return joinLicenses(pc), joinLicenses(pd)
|
||||
}
|
||||
|
||||
func joinLicenses(licenses []string) string {
|
||||
if len(licenses) == 0 {
|
||||
return NOASSERTION
|
||||
}
|
||||
|
||||
return strings.Join(parsedLicenses, " AND ")
|
||||
var newLicenses []string
|
||||
|
||||
for _, v := range licenses {
|
||||
// check if license does not start or end with parens
|
||||
if !strings.HasPrefix(v, "(") && !strings.HasSuffix(v, ")") {
|
||||
// if license contains AND, OR, or WITH, then wrap in parens
|
||||
if strings.Contains(v, " AND ") ||
|
||||
strings.Contains(v, " OR ") ||
|
||||
strings.Contains(v, " WITH ") {
|
||||
newLicenses = append(newLicenses, "("+v+")")
|
||||
continue
|
||||
}
|
||||
}
|
||||
newLicenses = append(newLicenses, v)
|
||||
}
|
||||
|
||||
return strings.Join(newLicenses, " AND ")
|
||||
}
|
||||
|
||||
func parseLicenses(raw []string) (parsedLicenses []string) {
|
||||
func parseLicenses(raw []pkg.License) (concluded, declared []string) {
|
||||
for _, l := range raw {
|
||||
if value, exists := spdxlicense.ID(l); exists {
|
||||
parsedLicenses = append(parsedLicenses, value)
|
||||
var candidate string
|
||||
if l.SPDXExpression != "" {
|
||||
candidate = l.SPDXExpression
|
||||
} else {
|
||||
// we did not find a valid SPDX license ID so treat as separate license
|
||||
otherLicense := spdxlicense.LicenseRefPrefix + l
|
||||
parsedLicenses = append(parsedLicenses, otherLicense)
|
||||
candidate = spdxlicense.LicenseRefPrefix + l.Value
|
||||
}
|
||||
|
||||
switch l.Type {
|
||||
case license.Concluded:
|
||||
concluded = append(concluded, candidate)
|
||||
case license.Declared:
|
||||
declared = append(declared, candidate)
|
||||
}
|
||||
}
|
||||
return
|
||||
return concluded, declared
|
||||
}
|
||||
|
|
|
@ -9,77 +9,120 @@ import (
|
|||
)
|
||||
|
||||
func Test_License(t *testing.T) {
|
||||
type expected struct {
|
||||
concluded string
|
||||
declared string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
name: "no licenses",
|
||||
input: pkg.Package{},
|
||||
expected: NONE,
|
||||
name: "no licenses",
|
||||
input: pkg.Package{},
|
||||
expected: expected{
|
||||
concluded: "NOASSERTION",
|
||||
declared: "NOASSERTION",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no SPDX licenses",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"made-up",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicense("made-up")),
|
||||
},
|
||||
expected: expected{
|
||||
concluded: "NOASSERTION",
|
||||
declared: "LicenseRef-made-up",
|
||||
},
|
||||
expected: "LicenseRef-made-up",
|
||||
},
|
||||
{
|
||||
name: "with SPDX license",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")),
|
||||
},
|
||||
expected: struct {
|
||||
concluded string
|
||||
declared string
|
||||
}{
|
||||
concluded: "NOASSERTION",
|
||||
declared: "MIT",
|
||||
},
|
||||
expected: "MIT",
|
||||
},
|
||||
{
|
||||
name: "with SPDX license expression",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
"GPL-3.0",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
pkg.NewLicense("GPL-3.0-only"),
|
||||
),
|
||||
},
|
||||
expected: "MIT AND GPL-3.0-only",
|
||||
},
|
||||
{
|
||||
name: "cap insensitive",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"gpl-3.0",
|
||||
},
|
||||
expected: expected{
|
||||
concluded: "NOASSERTION",
|
||||
// because we sort licenses alphabetically GPL ends up at the start
|
||||
declared: "GPL-3.0-only AND MIT",
|
||||
},
|
||||
expected: "GPL-3.0-only",
|
||||
},
|
||||
{
|
||||
name: "debian to spdx conversion",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"GPL-2",
|
||||
},
|
||||
},
|
||||
expected: "GPL-2.0-only",
|
||||
},
|
||||
{
|
||||
name: "includes valid LicenseRef-",
|
||||
input: pkg.Package{
|
||||
Licenses: []string{
|
||||
"one thing first",
|
||||
"two things/#$^second",
|
||||
"MIT",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("one thing first"),
|
||||
pkg.NewLicense("two things/#$^second"),
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
},
|
||||
expected: expected{
|
||||
concluded: "NOASSERTION",
|
||||
// because we separate licenses between valid SPDX and non valid, valid ID always end at the front
|
||||
declared: "MIT AND LicenseRef-one-thing-first AND LicenseRef-two-things----second",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "join parentheses correctly",
|
||||
input: pkg.Package{
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("one thing first"),
|
||||
pkg.NewLicense("MIT AND GPL-3.0-only"),
|
||||
pkg.NewLicense("MIT OR APACHE-2.0"),
|
||||
),
|
||||
},
|
||||
expected: expected{
|
||||
concluded: "NOASSERTION",
|
||||
// because we separate licenses between valid SPDX and non valid, valid ID always end at the front
|
||||
declared: "(MIT AND GPL-3.0-only) AND (MIT OR APACHE-2.0) AND LicenseRef-one-thing-first",
|
||||
},
|
||||
expected: "LicenseRef-one-thing-first AND LicenseRef-two-things----second AND MIT",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, License(test.input))
|
||||
c, d := License(test.input)
|
||||
assert.Equal(t, test.expected.concluded, c)
|
||||
assert.Equal(t, test.expected.declared, d)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_joinLicenses(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "multiple licenses",
|
||||
args: []string{"MIT", "GPL-3.0-only"},
|
||||
want: "MIT AND GPL-3.0-only",
|
||||
},
|
||||
{
|
||||
name: "multiple licenses with complex expressions",
|
||||
args: []string{"MIT AND Apache", "GPL-3.0-only"},
|
||||
want: "(MIT AND Apache) AND GPL-3.0-only",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, joinLicenses(tt.args), "joinLicenses(%v)", tt.args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,7 +170,8 @@ func toPackages(catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Packag
|
|||
// If the Concluded License is not the same as the Declared License, a written explanation should be provided
|
||||
// in the Comments on License field (section 7.16). With respect to NOASSERTION, a written explanation in
|
||||
// the Comments on License field (section 7.16) is preferred.
|
||||
license := License(p)
|
||||
// extract these correctly to the spdx license format
|
||||
concluded, declared := License(p)
|
||||
|
||||
// two ways to get filesAnalyzed == true:
|
||||
// 1. syft has generated a sha1 digest for the package itself - usually in the java cataloger
|
||||
|
@ -274,7 +275,7 @@ func toPackages(catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Packag
|
|||
// Cardinality: mandatory, one
|
||||
// Purpose: Contain the license the SPDX file creator has concluded as governing the
|
||||
// package or alternative values, if the governing license cannot be determined.
|
||||
PackageLicenseConcluded: license,
|
||||
PackageLicenseConcluded: concluded,
|
||||
|
||||
// 7.14: All Licenses Info from Files: SPDX License Expression, "NONE" or "NOASSERTION"
|
||||
// Cardinality: mandatory, one or many if filesAnalyzed is true / omitted;
|
||||
|
@ -286,7 +287,7 @@ func toPackages(catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Packag
|
|||
// Purpose: List the licenses that have been declared by the authors of the package.
|
||||
// Any license information that does not originate from the package authors, e.g. license
|
||||
// information from a third party repository, should not be included in this field.
|
||||
PackageLicenseDeclared: license,
|
||||
PackageLicenseDeclared: declared,
|
||||
|
||||
// 7.16: Comments on License
|
||||
// Cardinality: optional, one
|
||||
|
@ -534,10 +535,18 @@ func toFileTypes(metadata *source.FileMetadata) (ty []string) {
|
|||
return ty
|
||||
}
|
||||
|
||||
// other licenses are for licenses from the pkg.Package that do not have an SPDXExpression
|
||||
// field. The spdxexpression field is only filled given a validated Value field.
|
||||
func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
|
||||
licenses := map[string]bool{}
|
||||
for _, p := range catalog.Sorted() {
|
||||
for _, license := range parseLicenses(p.Licenses) {
|
||||
declaredLicenses, concludedLicenses := parseLicenses(p.Licenses.ToSlice())
|
||||
for _, license := range declaredLicenses {
|
||||
if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
|
||||
licenses[license] = true
|
||||
}
|
||||
}
|
||||
for _, license := range concludedLicenses {
|
||||
if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
|
||||
licenses[license] = true
|
||||
}
|
||||
|
@ -549,12 +558,12 @@ func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
|
|||
sorted := maps.Keys(licenses)
|
||||
slices.Sort(sorted)
|
||||
for _, license := range sorted {
|
||||
// separate the actual ID from the prefix
|
||||
// separate the found value from the prefix
|
||||
// this only contains licenses that are not found on the SPDX License List
|
||||
name := strings.TrimPrefix(license, spdxlicense.LicenseRefPrefix)
|
||||
result = append(result, &spdx.OtherLicense{
|
||||
LicenseIdentifier: SanitizeElementID(license),
|
||||
LicenseName: name,
|
||||
ExtractedText: NONE, // we probably should have some extracted text here, but this is good enough for now
|
||||
ExtractedText: name,
|
||||
})
|
||||
}
|
||||
return result
|
||||
|
|
|
@ -448,46 +448,40 @@ func Test_OtherLicenses(t *testing.T) {
|
|||
{
|
||||
name: "no licenseRef",
|
||||
pkg: pkg.Package{
|
||||
Licenses: []string{
|
||||
"MIT",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "single licenseRef",
|
||||
pkg: pkg.Package{
|
||||
Licenses: []string{
|
||||
"un known",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("foobar"),
|
||||
),
|
||||
},
|
||||
expected: []*spdx.OtherLicense{
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-un-known",
|
||||
LicenseName: "un known",
|
||||
ExtractedText: NONE,
|
||||
LicenseIdentifier: "LicenseRef-foobar",
|
||||
ExtractedText: "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple licenseRef",
|
||||
pkg: pkg.Package{
|
||||
Licenses: []string{
|
||||
"un known",
|
||||
"not known %s",
|
||||
"MIT",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("internal made up license name"),
|
||||
pkg.NewLicense("new apple license 2.0"),
|
||||
),
|
||||
},
|
||||
expected: []*spdx.OtherLicense{
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-not-known--s",
|
||||
LicenseName: "not known %s",
|
||||
ExtractedText: NONE,
|
||||
LicenseIdentifier: "LicenseRef-internal-made-up-license-name",
|
||||
ExtractedText: "internal made up license name",
|
||||
},
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-un-known",
|
||||
LicenseName: "un known",
|
||||
ExtractedText: NONE,
|
||||
LicenseIdentifier: "LicenseRef-new-apple-license-2.0",
|
||||
ExtractedText: "new apple license 2.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/formats/common/util"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
|
@ -279,7 +280,7 @@ func toSyftPackage(p *spdx.Package) *pkg.Package {
|
|||
Type: info.typ,
|
||||
Name: p.PackageName,
|
||||
Version: p.PackageVersion,
|
||||
Licenses: parseLicense(p.PackageLicenseDeclared),
|
||||
Licenses: pkg.NewLicenseSet(parseSPDXLicenses(p)...),
|
||||
CPEs: extractCPEs(p),
|
||||
PURL: info.purl.String(),
|
||||
Language: info.lang,
|
||||
|
@ -292,6 +293,33 @@ func toSyftPackage(p *spdx.Package) *pkg.Package {
|
|||
return &sP
|
||||
}
|
||||
|
||||
func parseSPDXLicenses(p *spdx.Package) []pkg.License {
|
||||
licenses := make([]pkg.License, 0)
|
||||
|
||||
// concluded
|
||||
if p.PackageLicenseConcluded != NOASSERTION && p.PackageLicenseConcluded != NONE && p.PackageLicenseConcluded != "" {
|
||||
l := pkg.NewLicense(cleanSPDXID(p.PackageLicenseConcluded))
|
||||
l.Type = license.Concluded
|
||||
licenses = append(licenses, l)
|
||||
}
|
||||
|
||||
// declared
|
||||
if p.PackageLicenseDeclared != NOASSERTION && p.PackageLicenseDeclared != NONE && p.PackageLicenseDeclared != "" {
|
||||
l := pkg.NewLicense(cleanSPDXID(p.PackageLicenseDeclared))
|
||||
l.Type = license.Declared
|
||||
licenses = append(licenses, l)
|
||||
}
|
||||
|
||||
return licenses
|
||||
}
|
||||
|
||||
func cleanSPDXID(id string) string {
|
||||
if strings.HasPrefix(id, "LicenseRef-") {
|
||||
return strings.TrimPrefix(id, "LicenseRef-")
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface{}) {
|
||||
arch := info.qualifierValue(pkg.PURLQualifierArch)
|
||||
|
@ -317,7 +345,6 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
|
|||
OriginPackage: upstreamName,
|
||||
Maintainer: supplier,
|
||||
Version: p.PackageVersion,
|
||||
License: p.PackageLicenseDeclared,
|
||||
Architecture: arch,
|
||||
URL: p.PackageHomePage,
|
||||
Description: p.PackageDescription,
|
||||
|
@ -330,17 +357,12 @@ func extractMetadata(p *spdx.Package, info pkgInfo) (pkg.MetadataType, interface
|
|||
} else {
|
||||
epoch = &converted
|
||||
}
|
||||
license := p.PackageLicenseDeclared
|
||||
if license == "" {
|
||||
license = p.PackageLicenseConcluded
|
||||
}
|
||||
return pkg.RpmMetadataType, pkg.RpmMetadata{
|
||||
Name: p.PackageName,
|
||||
Version: p.PackageVersion,
|
||||
Epoch: epoch,
|
||||
Arch: arch,
|
||||
SourceRpm: upstreamValue,
|
||||
License: license,
|
||||
Vendor: originator,
|
||||
}
|
||||
case pkg.DebPkg:
|
||||
|
@ -400,10 +422,3 @@ func extractCPEs(p *spdx.Package) (cpes []cpe.CPE) {
|
|||
}
|
||||
return cpes
|
||||
}
|
||||
|
||||
func parseLicense(l string) []string {
|
||||
if l == NOASSERTION || l == NONE {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(l, " AND ")
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
"$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json",
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:redacted",
|
||||
"serialNumber": "urn:uuid:1b71a5b4-4bc5-4548-a51a-212e631976cd",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "timestamp:redacted",
|
||||
"timestamp": "2023-05-08T14:40:32-04:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
|
@ -14,14 +14,14 @@
|
|||
}
|
||||
],
|
||||
"component": {
|
||||
"bom-ref": "redacted",
|
||||
"bom-ref": "163686ac6e30c752",
|
||||
"type": "file",
|
||||
"name": "/some/path"
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "redacted",
|
||||
"bom-ref": "8c7e1242588c971a",
|
||||
"type": "library",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
|
@ -58,7 +58,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"bom-ref": "redacted",
|
||||
"bom-ref": "pkg:deb/debian/package-2@2.0.1?package-id=db4abfe497c180d3",
|
||||
"type": "library",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
"$schema": "http://cyclonedx.org/schema/bom-1.4.schema.json",
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:redacted",
|
||||
"serialNumber": "urn:uuid:1695d6ae-0ddf-4e77-9c9d-74df1bdd8d5b",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "timestamp:redacted",
|
||||
"timestamp": "2023-05-08T14:40:32-04:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "anchore",
|
||||
|
@ -14,15 +14,15 @@
|
|||
}
|
||||
],
|
||||
"component": {
|
||||
"bom-ref": "redacted",
|
||||
"bom-ref": "38160ebc2a6876e8",
|
||||
"type": "container",
|
||||
"name": "user-image-input",
|
||||
"version": "sha256:redacted"
|
||||
"version": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368"
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "redacted",
|
||||
"bom-ref": "ec2e0c93617507ef",
|
||||
"type": "library",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
|
@ -54,7 +54,7 @@
|
|||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:redacted"
|
||||
"value": "sha256:ab62016f9bec7286af65604081564cadeeb364a48faca2346c3f5a5a1f5ef777"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
|
@ -63,7 +63,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"bom-ref": "redacted",
|
||||
"bom-ref": "pkg:deb/debian/package-2@2.0.1?package-id=958443e2d9304af4",
|
||||
"type": "library",
|
||||
"name": "package-2",
|
||||
"version": "2.0.1",
|
||||
|
@ -84,7 +84,7 @@
|
|||
},
|
||||
{
|
||||
"name": "syft:location:0:layerID",
|
||||
"value": "sha256:redacted"
|
||||
"value": "sha256:f1803845b6747d94d6e4ecce2331457e5f1c4fb97de5216f392a76f4582f63b2"
|
||||
},
|
||||
{
|
||||
"name": "syft:location:0:path",
|
||||
|
|
Binary file not shown.
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_decodeXML(t *testing.T) {
|
||||
|
@ -34,7 +35,7 @@ func Test_decodeXML(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
t.Run(test.file, func(t *testing.T) {
|
||||
reader, err := os.Open("test-fixtures/" + test.file)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
if test.err {
|
||||
err = Format().Validate(reader)
|
||||
|
@ -44,7 +45,7 @@ func Test_decodeXML(t *testing.T) {
|
|||
|
||||
bom, err := Format().Decode(reader)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
split := strings.SplitN(test.distro, ":", 2)
|
||||
name := split[0]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:2939b822-b9cb-489d-8a8b-4431b755031d" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:60f4e726-e884-4ae3-9b0e-18a918fbb02e" version="1">
|
||||
<metadata>
|
||||
<timestamp>2022-11-07T09:11:06-05:00</timestamp>
|
||||
<timestamp>2023-05-08T14:40:52-04:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
|
@ -14,7 +14,7 @@
|
|||
</component>
|
||||
</metadata>
|
||||
<components>
|
||||
<component bom-ref="1b1d0be59ac59d2c" type="library">
|
||||
<component bom-ref="8c7e1242588c971a" type="library">
|
||||
<name>package-1</name>
|
||||
<version>1.0.1</version>
|
||||
<licenses>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:2896b5ce-2016-49e8-a422-239d662846c7" version="1">
|
||||
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" serialNumber="urn:uuid:c8894728-c156-4fc5-8f5d-3e397eede5a7" version="1">
|
||||
<metadata>
|
||||
<timestamp>2022-11-07T09:11:06-05:00</timestamp>
|
||||
<timestamp>2023-05-08T14:40:52-04:00</timestamp>
|
||||
<tools>
|
||||
<tool>
|
||||
<vendor>anchore</vendor>
|
||||
|
@ -9,13 +9,13 @@
|
|||
<version>v0.42.0-bogus</version>
|
||||
</tool>
|
||||
</tools>
|
||||
<component bom-ref="522dc6b135a55bb4" type="container">
|
||||
<component bom-ref="38160ebc2a6876e8" type="container">
|
||||
<name>user-image-input</name>
|
||||
<version>sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368</version>
|
||||
</component>
|
||||
</metadata>
|
||||
<components>
|
||||
<component bom-ref="66ba429119b8bec6" type="library">
|
||||
<component bom-ref="ec2e0c93617507ef" type="library">
|
||||
<name>package-1</name>
|
||||
<version>1.0.1</version>
|
||||
<licenses>
|
||||
|
@ -30,7 +30,7 @@
|
|||
<property name="syft:package:language">python</property>
|
||||
<property name="syft:package:metadataType">PythonPackageMetadata</property>
|
||||
<property name="syft:package:type">python</property>
|
||||
<property name="syft:location:0:layerID">sha256:fb6beecb75b39f4bb813dbf177e501edd5ddb3e69bb45cedeb78c676ee1b7a59</property>
|
||||
<property name="syft:location:0:layerID">sha256:ab62016f9bec7286af65604081564cadeeb364a48faca2346c3f5a5a1f5ef777</property>
|
||||
<property name="syft:location:0:path">/somefile-1.txt</property>
|
||||
</properties>
|
||||
</component>
|
||||
|
@ -43,7 +43,7 @@
|
|||
<property name="syft:package:foundBy">the-cataloger-2</property>
|
||||
<property name="syft:package:metadataType">DpkgMetadata</property>
|
||||
<property name="syft:package:type">deb</property>
|
||||
<property name="syft:location:0:layerID">sha256:319b588ce64253a87b533c8ed01cf0025e0eac98e7b516e12532957e1244fdec</property>
|
||||
<property name="syft:location:0:layerID">sha256:f1803845b6747d94d6e4ecce2331457e5f1c4fb97de5216f392a76f4582f63b2</property>
|
||||
<property name="syft:location:0:path">/somefile-2.txt</property>
|
||||
<property name="syft:metadata:installedSize">0</property>
|
||||
</properties>
|
||||
|
|
Binary file not shown.
|
@ -162,7 +162,9 @@ func populateImageCatalog(catalog *pkg.Collection, img *image.Image) {
|
|||
FoundBy: "the-cataloger-1",
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
|
@ -268,7 +270,9 @@ func newDirectoryCatalog() *pkg.Collection {
|
|||
),
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
|
@ -319,7 +323,9 @@ func newDirectoryCatalogWithAuthorField() *pkg.Collection {
|
|||
),
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
|
|
|
@ -3,23 +3,23 @@
|
|||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "/some/path",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-4029b5ec-6d70-4c0c-aedf-b61c8f5ea93c",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-5ea40e59-d91a-4682-a016-da45ddd540e4",
|
||||
"creationInfo": {
|
||||
"licenseListVersion": "3.20",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"created": "2023-05-02T18:24:17Z"
|
||||
"created": "2023-05-09T17:11:26Z"
|
||||
},
|
||||
"packages": [
|
||||
{
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-1b1d0be59ac59d2c",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-9265397e5e15168a",
|
||||
"versionInfo": "1.0.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
|
||||
"licenseConcluded": "MIT",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "MIT",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
|
@ -41,8 +41,8 @@
|
|||
"versionInfo": "2.0.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
|
||||
"licenseConcluded": "NONE",
|
||||
"licenseDeclared": "NONE",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
|
|
|
@ -3,23 +3,23 @@
|
|||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "user-image-input",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-6b0c6ff8-0f5f-4d95-8c1b-eb966d400804",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-2cc737fb-af51-4e4b-9395-cceabcc305eb",
|
||||
"creationInfo": {
|
||||
"licenseListVersion": "3.20",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"created": "2023-05-02T18:24:18Z"
|
||||
"created": "2023-05-09T17:11:26Z"
|
||||
},
|
||||
"packages": [
|
||||
{
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"versionInfo": "1.0.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||
"licenseConcluded": "MIT",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "MIT",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
|
@ -41,8 +41,8 @@
|
|||
"versionInfo": "2.0.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||
"licenseConcluded": "NONE",
|
||||
"licenseDeclared": "NONE",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
|
|
|
@ -3,23 +3,23 @@
|
|||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "user-image-input",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-ec2f9b25-22ca-46b8-b7f4-484994fe126c",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-1de3ac0e-5829-4294-9198-8d8fcdb5dd51",
|
||||
"creationInfo": {
|
||||
"licenseListVersion": "3.20",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"created": "2023-05-02T18:24:18Z"
|
||||
"created": "2023-05-09T17:11:26Z"
|
||||
},
|
||||
"packages": [
|
||||
{
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"versionInfo": "1.0.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||
"licenseConcluded": "MIT",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "MIT",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
|
@ -41,8 +41,8 @@
|
|||
"versionInfo": "2.0.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||
"licenseConcluded": "NONE",
|
||||
"licenseDeclared": "NONE",
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
"licenseDeclared": "NOASSERTION",
|
||||
"copyrightText": "NOASSERTION",
|
||||
"externalRefs": [
|
||||
{
|
||||
|
@ -152,32 +152,32 @@
|
|||
],
|
||||
"relationships": [
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-File-f1-5265a4dde3edbf7c",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-File-z1-f5-839d99ee67d9d174",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-File-a1-f6-9c2f7510199b17f6",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-File-d2-f4-c641caa71518099f",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-File-d1-f3-c6f5b29dca12661f",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
{
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-66ba429119b8bec6",
|
||||
"spdxElementId": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"relatedSpdxElement": "SPDXRef-File-f2-f9e49132a4b96ccd",
|
||||
"relationshipType": "CONTAINS"
|
||||
},
|
||||
|
|
Binary file not shown.
|
@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
|
|||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: foobar/baz
|
||||
DocumentNamespace: https://anchore.com/syft/dir/foobar/baz-9c1f31fb-7c72-40a6-8c81-3a08590000a2
|
||||
DocumentNamespace: https://anchore.com/syft/dir/foobar/baz-1813dede-1ac5-4c44-a640-4c56e213d575
|
||||
LicenseListVersion: 3.20
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2023-05-02T18:24:33Z
|
||||
Created: 2023-05-09T17:11:49Z
|
||||
|
||||
##### Package: @at-sign
|
||||
|
||||
|
@ -15,8 +15,8 @@ SPDXID: SPDXRef-Package---at-sign-3732f7a5679bdec4
|
|||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from the following paths:
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
|
||||
##### Package: some/slashes
|
||||
|
@ -26,8 +26,8 @@ SPDXID: SPDXRef-Package--some-slashes-1345166d4801153b
|
|||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from the following paths:
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
|
||||
##### Package: under_scores
|
||||
|
@ -37,8 +37,8 @@ SPDXID: SPDXRef-Package--under-scores-290d5c77210978c1
|
|||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from the following paths:
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
|
||||
##### Relationships
|
||||
|
|
|
@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
|
|||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: user-image-input
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-5be37b11-b99a-47ff-8725-3984e323d129
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-96ea886a-3297-4847-b211-6da405ff1f8f
|
||||
LicenseListVersion: 3.20
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2023-05-02T18:24:33Z
|
||||
Created: 2023-05-09T17:11:49Z
|
||||
|
||||
##### Unpackaged files
|
||||
|
||||
|
@ -54,8 +54,8 @@ PackageVersion: 2.0.1
|
|||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
|
@ -63,12 +63,12 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
|||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-66ba429119b8bec6
|
||||
SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7
|
||||
PackageVersion: 1.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt
|
||||
PackageLicenseConcluded: MIT
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: MIT
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:*
|
||||
|
@ -76,11 +76,11 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
|
|||
|
||||
##### Relationships
|
||||
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
|
||||
Relationship: SPDXRef-Package-python-package-1-66ba429119b8bec6 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
|
||||
Relationship: SPDXRef-Package-python-package-1-125840abc1c66dd7 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
|
||||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DOCUMENT
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
|
|||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: /some/path
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-0f346656-6d10-4dec-b549-a256468cbd35
|
||||
DocumentNamespace: https://anchore.com/syft/dir/some/path-f7bdb1ee-7fef-48e7-a386-6ee3836d4a28
|
||||
LicenseListVersion: 3.20
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2023-05-02T18:24:33Z
|
||||
Created: 2023-05-09T17:11:49Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
|
@ -16,8 +16,8 @@ PackageVersion: 2.0.1
|
|||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from DPKG DB: /some/path/pkg1
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
|
@ -25,12 +25,12 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
|||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-1b1d0be59ac59d2c
|
||||
SPDXID: SPDXRef-Package-python-package-1-9265397e5e15168a
|
||||
PackageVersion: 1.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from installed python package manifest file: /some/path/pkg1
|
||||
PackageLicenseConcluded: MIT
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: MIT
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
|
|
|
@ -2,11 +2,11 @@ SPDXVersion: SPDX-2.3
|
|||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: user-image-input
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-4ce1e7c7-642f-4428-bb44-1b48b8edf74d
|
||||
DocumentNamespace: https://anchore.com/syft/image/user-image-input-44d44a85-2207-4b51-bd73-d0c7b080f6d3
|
||||
LicenseListVersion: 3.20
|
||||
Creator: Organization: Anchore, Inc
|
||||
Creator: Tool: syft-v0.42.0-bogus
|
||||
Created: 2023-05-02T18:24:33Z
|
||||
Created: 2023-05-09T17:11:49Z
|
||||
|
||||
##### Package: package-2
|
||||
|
||||
|
@ -16,8 +16,8 @@ PackageVersion: 2.0.1
|
|||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt
|
||||
PackageLicenseConcluded: NONE
|
||||
PackageLicenseDeclared: NONE
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:2:*:*:*:*:*:*:*
|
||||
ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
|
@ -25,12 +25,12 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
|||
##### Package: package-1
|
||||
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-66ba429119b8bec6
|
||||
SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7
|
||||
PackageVersion: 1.0.1
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt
|
||||
PackageLicenseConcluded: MIT
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: MIT
|
||||
PackageCopyrightText: NOASSERTION
|
||||
ExternalRef: SECURITY cpe23Type cpe:2.3:*:some:package:1:*:*:*:*:*:*:*
|
||||
|
|
Binary file not shown.
|
@ -61,7 +61,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
|||
FoundBy: "the-cataloger-1",
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Licenses: []string{"MIT"},
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")),
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"reflect"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
@ -27,12 +28,50 @@ type PackageBasicData struct {
|
|||
Type pkg.Type `json:"type"`
|
||||
FoundBy string `json:"foundBy"`
|
||||
Locations []source.Location `json:"locations"`
|
||||
Licenses []string `json:"licenses"`
|
||||
Licenses licenses `json:"licenses"`
|
||||
Language pkg.Language `json:"language"`
|
||||
CPEs []string `json:"cpes"`
|
||||
PURL string `json:"purl"`
|
||||
}
|
||||
|
||||
type licenses []License
|
||||
|
||||
type License struct {
|
||||
Value string `json:"value"`
|
||||
SPDXExpression string `json:"spdxExpression"`
|
||||
Type license.Type `json:"type"`
|
||||
URL []string `json:"url"`
|
||||
Location []source.Location `json:"locations"`
|
||||
}
|
||||
|
||||
func newModelLicensesFromValues(licenses []string) (ml []License) {
|
||||
for _, v := range licenses {
|
||||
expression, err := license.ParseExpression(v)
|
||||
if err != nil {
|
||||
log.Trace("could not find valid spdx expression for %s: %w", v, err)
|
||||
}
|
||||
ml = append(ml, License{
|
||||
Value: v,
|
||||
SPDXExpression: expression,
|
||||
Type: license.Declared,
|
||||
})
|
||||
}
|
||||
return ml
|
||||
}
|
||||
|
||||
func (f *licenses) UnmarshalJSON(b []byte) error {
|
||||
var licenses []License
|
||||
if err := json.Unmarshal(b, &licenses); err != nil {
|
||||
var simpleLicense []string
|
||||
if err := json.Unmarshal(b, &simpleLicense); err != nil {
|
||||
return fmt.Errorf("unable to unmarshal license: %w", err)
|
||||
}
|
||||
licenses = newModelLicensesFromValues(simpleLicense)
|
||||
}
|
||||
*f = licenses
|
||||
return nil
|
||||
}
|
||||
|
||||
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
|
||||
type PackageCustomData struct {
|
||||
MetadataType pkg.MetadataType `json:"metadataType,omitempty"`
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
|
@ -30,7 +31,14 @@ func TestUnmarshalPackageGolang(t *testing.T) {
|
|||
"path": "/Users/hal/go/bin/syft"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"licenses": [
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"url": []
|
||||
}
|
||||
],
|
||||
"language": "go",
|
||||
"cpes": [],
|
||||
"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0",
|
||||
|
@ -61,7 +69,20 @@ func TestUnmarshalPackageGolang(t *testing.T) {
|
|||
"path": "/Users/hal/go/bin/syft"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"licenses": [
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"url": ["https://www.github.com"]
|
||||
},
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"locations": [{"path": "/Users/hal/go/bin/syft"}]
|
||||
}
|
||||
],
|
||||
"language": "go",
|
||||
"cpes": [],
|
||||
"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0"
|
||||
|
@ -71,6 +92,83 @@ func TestUnmarshalPackageGolang(t *testing.T) {
|
|||
assert.Empty(t, p.Metadata)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can handle package with []string licenses",
|
||||
packageData: []byte(`{
|
||||
"id": "8b594519bc23da50",
|
||||
"name": "gopkg.in/square/go-jose.v2",
|
||||
"version": "v2.6.0",
|
||||
"type": "go-module",
|
||||
"foundBy": "go-mod-cataloger",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/Users/hal/go/bin/syft"
|
||||
}
|
||||
],
|
||||
"licenses": ["MIT", "Apache-2.0"],
|
||||
"language": "go",
|
||||
"cpes": [],
|
||||
"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0"
|
||||
}`),
|
||||
assert: func(p *Package) {
|
||||
assert.Equal(t, licenses{
|
||||
{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
},
|
||||
{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Declared,
|
||||
},
|
||||
}, p.Licenses)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can handle package with []pkg.License licenses",
|
||||
packageData: []byte(`{
|
||||
"id": "8b594519bc23da50",
|
||||
"name": "gopkg.in/square/go-jose.v2",
|
||||
"version": "v2.6.0",
|
||||
"type": "go-module",
|
||||
"foundBy": "go-mod-cataloger",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/Users/hal/go/bin/syft"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared"
|
||||
},
|
||||
{
|
||||
"value": "Apache-2.0",
|
||||
"spdxExpression": "Apache-2.0",
|
||||
"type": "declared"
|
||||
}
|
||||
],
|
||||
"language": "go",
|
||||
"cpes": [],
|
||||
"purl": "pkg:golang/gopkg.in/square/go-jose.v2@v2.6.0"
|
||||
}`),
|
||||
assert: func(p *Package) {
|
||||
assert.Equal(t, licenses{
|
||||
{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Declared,
|
||||
},
|
||||
{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Declared,
|
||||
},
|
||||
}, p.Licenses)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
@ -152,9 +250,6 @@ func Test_unpackMetadata(t *testing.T) {
|
|||
"layerID": "sha256:74ddd0ec08fa43d09f32636ba91a0a3053b02cb4627c35051aff89f853606b59"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
"GPLv2+"
|
||||
],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:a:centos:acl:2.2.53-1.el8:*:*:*:*:*:*:*",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "1b1d0be59ac59d2c",
|
||||
"id": "9265397e5e15168a",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
|
@ -12,7 +12,13 @@
|
|||
}
|
||||
],
|
||||
"licenses": [
|
||||
"MIT"
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"url": [],
|
||||
"locations": []
|
||||
}
|
||||
],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
|
@ -23,7 +29,6 @@
|
|||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"license": "",
|
||||
"author": "",
|
||||
"authorEmail": "",
|
||||
"platform": "",
|
||||
|
@ -87,5 +92,9 @@
|
|||
"configuration": {
|
||||
"config-key": "config-value"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "8.0.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-8.0.0.json"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "304a5a8e5958a49d",
|
||||
"id": "271e49ba46e0b601",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
|
@ -12,7 +12,13 @@
|
|||
}
|
||||
],
|
||||
"licenses": [
|
||||
"MIT"
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"url": [],
|
||||
"locations": []
|
||||
}
|
||||
],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
|
@ -23,7 +29,6 @@
|
|||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"license": "",
|
||||
"author": "",
|
||||
"authorEmail": "",
|
||||
"platform": "",
|
||||
|
@ -187,5 +192,9 @@
|
|||
"configuration": {
|
||||
"config-key": "config-value"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "8.0.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-8.0.0.json"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"artifacts": [
|
||||
{
|
||||
"id": "66ba429119b8bec6",
|
||||
"id": "125840abc1c66dd7",
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"type": "python",
|
||||
|
@ -9,11 +9,17 @@
|
|||
"locations": [
|
||||
{
|
||||
"path": "/somefile-1.txt",
|
||||
"layerID": "sha256:7e139310bd6ce0956d65a70d26a6d31b240a4f47094a831638f05d381b6c424a"
|
||||
"layerID": "sha256:ab62016f9bec7286af65604081564cadeeb364a48faca2346c3f5a5a1f5ef777"
|
||||
}
|
||||
],
|
||||
"licenses": [
|
||||
"MIT"
|
||||
{
|
||||
"value": "MIT",
|
||||
"spdxExpression": "MIT",
|
||||
"type": "declared",
|
||||
"url": [],
|
||||
"locations": []
|
||||
}
|
||||
],
|
||||
"language": "python",
|
||||
"cpes": [
|
||||
|
@ -24,7 +30,6 @@
|
|||
"metadata": {
|
||||
"name": "package-1",
|
||||
"version": "1.0.1",
|
||||
"license": "",
|
||||
"author": "",
|
||||
"authorEmail": "",
|
||||
"platform": "",
|
||||
|
@ -40,7 +45,7 @@
|
|||
"locations": [
|
||||
{
|
||||
"path": "/somefile-2.txt",
|
||||
"layerID": "sha256:cc833bf31a480c064d65ca67ee37f77f0d0c8ab98eedde7b286ad1ef6f5bdcac"
|
||||
"layerID": "sha256:f1803845b6747d94d6e4ecce2331457e5f1c4fb97de5216f392a76f4582f63b2"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
|
@ -64,11 +69,11 @@
|
|||
],
|
||||
"artifactRelationships": [],
|
||||
"source": {
|
||||
"id": "0af8fa79f5497297e4e32f3e03de14ac20ad695159df0ac8373e6543614b9a50",
|
||||
"id": "c8ac88bbaf3d1c036f6a1d601c3d52bafbf05571c97d68322e7cb3a7ecaa304f",
|
||||
"type": "image",
|
||||
"target": {
|
||||
"userInput": "user-image-input",
|
||||
"imageID": "sha256:0cb4395791986bda17562bd6f76811bb6f163f686e198397197ef8241bed58df",
|
||||
"imageID": "sha256:a3c61dc134d2f31b415c50324e75842d7f91622f39a89468e51938330b3fd3af",
|
||||
"manifestDigest": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [
|
||||
|
@ -78,17 +83,17 @@
|
|||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:7e139310bd6ce0956d65a70d26a6d31b240a4f47094a831638f05d381b6c424a",
|
||||
"digest": "sha256:ab62016f9bec7286af65604081564cadeeb364a48faca2346c3f5a5a1f5ef777",
|
||||
"size": 22
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:cc833bf31a480c064d65ca67ee37f77f0d0c8ab98eedde7b286ad1ef6f5bdcac",
|
||||
"digest": "sha256:f1803845b6747d94d6e4ecce2331457e5f1c4fb97de5216f392a76f4582f63b2",
|
||||
"size": 16
|
||||
}
|
||||
],
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzEsImRpZ2VzdCI6InNoYTI1NjowY2I0Mzk1NzkxOTg2YmRhMTc1NjJiZDZmNzY4MTFiYjZmMTYzZjY4NmUxOTgzOTcxOTdlZjgyNDFiZWQ1OGRmIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1Njo3ZTEzOTMxMGJkNmNlMDk1NmQ2NWE3MGQyNmE2ZDMxYjI0MGE0ZjQ3MDk0YTgzMTYzOGYwNWQzODFiNmM0MjRhIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OmNjODMzYmYzMWE0ODBjMDY0ZDY1Y2E2N2VlMzdmNzdmMGQwYzhhYjk4ZWVkZGU3YjI4NmFkMWVmNmY1YmRjYWMifV19",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhcm02NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjMtMDQtMThUMTQ6MDk6NDIuMzAxMDI2MzhaIiwiaGlzdG9yeSI6W3siY3JlYXRlZCI6IjIwMjMtMDQtMThUMTQ6MDk6NDIuMjg3OTQyNzEzWiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0xLnR4dCAvc29tZWZpbGUtMS50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMy0wNC0xOFQxNDowOTo0Mi4zMDEwMjYzOFoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMi50eHQgL3NvbWVmaWxlLTIudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9XSwib3MiOiJsaW51eCIsInJvb3RmcyI6eyJ0eXBlIjoibGF5ZXJzIiwiZGlmZl9pZHMiOlsic2hhMjU2OjdlMTM5MzEwYmQ2Y2UwOTU2ZDY1YTcwZDI2YTZkMzFiMjQwYTRmNDcwOTRhODMxNjM4ZjA1ZDM4MWI2YzQyNGEiLCJzaGEyNTY6Y2M4MzNiZjMxYTQ4MGMwNjRkNjVjYTY3ZWUzN2Y3N2YwZDBjOGFiOThlZWRkZTdiMjg2YWQxZWY2ZjViZGNhYyJdfX0=",
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjo2NzMsImRpZ2VzdCI6InNoYTI1NjphM2M2MWRjMTM0ZDJmMzFiNDE1YzUwMzI0ZTc1ODQyZDdmOTE2MjJmMzlhODk0NjhlNTE5MzgzMzBiM2ZkM2FmIn0sImxheWVycyI6W3sibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjIwNDgsImRpZ2VzdCI6InNoYTI1NjphYjYyMDE2ZjliZWM3Mjg2YWY2NTYwNDA4MTU2NGNhZGVlYjM2NGE0OGZhY2EyMzQ2YzNmNWE1YTFmNWVmNzc3In0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6MjA0OCwiZGlnZXN0Ijoic2hhMjU2OmYxODAzODQ1YjY3NDdkOTRkNmU0ZWNjZTIzMzE0NTdlNWYxYzRmYjk3ZGU1MjE2ZjM5MmE3NmY0NTgyZjYzYjIifV19",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iXSwiV29ya2luZ0RpciI6Ii8iLCJPbkJ1aWxkIjpudWxsfSwiY3JlYXRlZCI6IjIwMjMtMDQtMjFUMTk6MTA6MzcuNjUxODMxMjM0WiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDIzLTA0LTIxVDE5OjEwOjM3LjYwNzYxMzU1NVoiLCJjcmVhdGVkX2J5IjoiQUREIGZpbGUtMS50eHQgL3NvbWVmaWxlLTEudHh0ICMgYnVpbGRraXQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9LHsiY3JlYXRlZCI6IjIwMjMtMDQtMjFUMTk6MTA6MzcuNjUxODMxMjM0WiIsImNyZWF0ZWRfYnkiOiJBREQgZmlsZS0yLnR4dCAvc29tZWZpbGUtMi50eHQgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6YWI2MjAxNmY5YmVjNzI4NmFmNjU2MDQwODE1NjRjYWRlZWIzNjRhNDhmYWNhMjM0NmMzZjVhNWExZjVlZjc3NyIsInNoYTI1NjpmMTgwMzg0NWI2NzQ3ZDk0ZDZlNGVjY2UyMzMxNDU3ZTVmMWM0ZmI5N2RlNTIxNmYzOTJhNzZmNDU4MmY2M2IyIl19fQ==",
|
||||
"repoDigests": [],
|
||||
"architecture": "",
|
||||
"os": ""
|
||||
|
@ -110,5 +115,9 @@
|
|||
"configuration": {
|
||||
"config-key": "config-value"
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "8.0.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-8.0.0.json"
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -184,6 +184,24 @@ func toPackageModels(catalog *pkg.Collection) []model.Package {
|
|||
return artifacts
|
||||
}
|
||||
|
||||
func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
|
||||
for _, l := range pkgLicenses {
|
||||
// guarantee collection
|
||||
locations := make([]source.Location, 0)
|
||||
if v := l.Location.ToSlice(); v != nil {
|
||||
locations = v
|
||||
}
|
||||
modelLicenses = append(modelLicenses, model.License{
|
||||
Value: l.Value,
|
||||
SPDXExpression: l.SPDXExpression,
|
||||
Type: l.Type,
|
||||
URL: l.URL.ToSlice(),
|
||||
Location: locations,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// toPackageModel crates a new Package from the given pkg.Package.
|
||||
func toPackageModel(p pkg.Package) model.Package {
|
||||
var cpes = make([]string, len(p.CPEs))
|
||||
|
@ -191,9 +209,11 @@ func toPackageModel(p pkg.Package) model.Package {
|
|||
cpes[i] = cpe.String(c)
|
||||
}
|
||||
|
||||
var licenses = make([]string, 0)
|
||||
if p.Licenses != nil {
|
||||
licenses = p.Licenses
|
||||
// we want to make sure all catalogers are
|
||||
// initializing the array; this is a good choke point for this check
|
||||
var licenses = make([]model.License, 0)
|
||||
if !p.Licenses.Empty() {
|
||||
licenses = toLicenseModel(p.Licenses.ToSlice())
|
||||
}
|
||||
|
||||
return model.Package{
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
|
@ -101,6 +102,19 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
|||
return ret
|
||||
}
|
||||
|
||||
func toSyftLicenses(m []model.License) (p []pkg.License) {
|
||||
for _, l := range m {
|
||||
p = append(p, pkg.License{
|
||||
Value: l.Value,
|
||||
SPDXExpression: l.SPDXExpression,
|
||||
Type: l.Type,
|
||||
URL: internal.NewStringSet(l.URL...),
|
||||
Location: source.NewLocationSet(l.Location...),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func toSyftFileType(ty string) stereoscopeFile.Type {
|
||||
switch ty {
|
||||
case "SymbolicLink":
|
||||
|
@ -304,7 +318,7 @@ func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package {
|
|||
Version: p.Version,
|
||||
FoundBy: p.FoundBy,
|
||||
Locations: source.NewLocationSet(p.Locations...),
|
||||
Licenses: p.Licenses,
|
||||
Licenses: pkg.NewLicenseSet(toSyftLicenses(p.Licenses)...),
|
||||
Language: p.Language,
|
||||
Type: p.Type,
|
||||
CPEs: cpes,
|
||||
|
|
35
syft/license/license.go
Normal file
35
syft/license/license.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
// package license provides common methods for working with SPDX license data
|
||||
package license
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/github/go-spdx/v2/spdxexp"
|
||||
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
)
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
Declared Type = "declared"
|
||||
Concluded Type = "concluded"
|
||||
)
|
||||
|
||||
func ParseExpression(expression string) (string, error) {
|
||||
licenseID, exists := spdxlicense.ID(expression)
|
||||
if exists {
|
||||
return licenseID, nil
|
||||
}
|
||||
|
||||
// If it doesn't exist initially in the SPDX list it might be a more complex expression
|
||||
// ignored variable is any invalid expressions
|
||||
// TODO: contribute to spdxexp to expose deprecated license IDs
|
||||
// https://github.com/anchore/syft/issues/1814
|
||||
valid, _ := spdxexp.ValidateLicenses([]string{expression})
|
||||
if !valid {
|
||||
return "", fmt.Errorf("failed to validate spdx expression: %s", expression)
|
||||
}
|
||||
|
||||
return expression, nil
|
||||
}
|
42
syft/license/license_test.go
Normal file
42
syft/license/license_test.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package license
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseExpression(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expression string
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid single ID expression returns SPDX ID",
|
||||
expression: "mit",
|
||||
want: "MIT",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid SPDX expression returns SPDX expression",
|
||||
expression: "MIT OR Apache-2.0",
|
||||
want: "MIT OR Apache-2.0",
|
||||
},
|
||||
{
|
||||
name: "Invalid SPDX expression returns error",
|
||||
expression: "MIT OR Apache-2.0 OR invalid",
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseExpression(tt.expression)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseExpression() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ParseExpression() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -20,13 +20,12 @@ type AlpmMetadata struct {
|
|||
Description string `mapstructure:"desc" json:"description" cyclonedx:"description"`
|
||||
Architecture string `mapstructure:"arch" json:"architecture" cyclonedx:"architecture"`
|
||||
Size int `mapstructure:"size" json:"size" cyclonedx:"size"`
|
||||
Packager string `mapstructure:"packager" json:"packager" cyclonedx:"packager"`
|
||||
License string `mapstructure:"license" json:"license" cyclonedx:"license"`
|
||||
URL string `mapstructure:"url" json:"url" cyclonedx:"url"`
|
||||
Validation string `mapstructure:"validation" json:"validation" cyclonedx:"validation"`
|
||||
Reason int `mapstructure:"reason" json:"reason" cyclonedx:"reason"`
|
||||
Files []AlpmFileRecord `mapstructure:"files" json:"files" cyclonedx:"files"`
|
||||
Backup []AlpmFileRecord `mapstructure:"backup" json:"backup" cyclonedx:"backup"`
|
||||
Packager string `mapstructure:"packager" json:"packager"`
|
||||
URL string `mapstructure:"url" json:"url"`
|
||||
Validation string `mapstructure:"validation" json:"validation"`
|
||||
Reason int `mapstructure:"reason" json:"reason"`
|
||||
Files []AlpmFileRecord `mapstructure:"files" json:"files"`
|
||||
Backup []AlpmFileRecord `mapstructure:"backup" json:"backup"`
|
||||
}
|
||||
|
||||
type AlpmFileRecord struct {
|
||||
|
|
|
@ -27,7 +27,6 @@ type ApkMetadata struct {
|
|||
OriginPackage string `mapstructure:"o" json:"originPackage" cyclonedx:"originPackage"`
|
||||
Maintainer string `mapstructure:"m" json:"maintainer"`
|
||||
Version string `mapstructure:"V" json:"version"`
|
||||
License string `mapstructure:"L" json:"license"`
|
||||
Architecture string `mapstructure:"A" json:"architecture"`
|
||||
URL string `mapstructure:"U" json:"url"`
|
||||
Description string `mapstructure:"T" json:"description"`
|
||||
|
|
|
@ -47,7 +47,6 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
|
|||
OriginPackage: "pax-utils",
|
||||
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
|
||||
Version: "1.3.4-r0",
|
||||
License: "GPL-2.0-only",
|
||||
Architecture: "x86_64",
|
||||
URL: "https://wiki.gentoo.org/wiki/Hardened/PaX_Utilities",
|
||||
Description: "Scan ELF binaries for stuff",
|
||||
|
@ -86,7 +85,6 @@ func TestApkMetadata_UnmarshalJSON(t *testing.T) {
|
|||
OriginPackage: "pax-utils",
|
||||
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
|
||||
Version: "1.3.4-r0",
|
||||
License: "GPL-2.0-only",
|
||||
Architecture: "x86_64",
|
||||
URL: "https://wiki.gentoo.org/wiki/Hardened/PaX_Utilities",
|
||||
Description: "Scan ELF binaries for stuff",
|
||||
|
|
|
@ -17,6 +17,49 @@ type expectedIndexes struct {
|
|||
byPath map[string]*strset.Set
|
||||
}
|
||||
|
||||
func TestCatalogMergePackageLicenses(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pkgs []Package
|
||||
expectedPkgs []Package
|
||||
}{
|
||||
{
|
||||
name: "merges licenses of packages with equal ID",
|
||||
pkgs: []Package{
|
||||
{
|
||||
id: "equal",
|
||||
Licenses: NewLicenseSet(
|
||||
NewLicensesFromValues("foo", "baq", "quz")...,
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "equal",
|
||||
Licenses: NewLicenseSet(
|
||||
NewLicensesFromValues("bar", "baz", "foo", "qux")...,
|
||||
),
|
||||
},
|
||||
},
|
||||
expectedPkgs: []Package{
|
||||
{
|
||||
id: "equal",
|
||||
Licenses: NewLicenseSet(
|
||||
NewLicensesFromValues("foo", "baq", "quz", "qux", "bar", "baz")...,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
collection := NewCollection(test.pkgs...)
|
||||
for i, p := range collection.Sorted() {
|
||||
assert.Equal(t, test.expectedPkgs[i].Licenses, p.Licenses)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCatalogDeleteRemovesPackages(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
@ -13,15 +13,18 @@ import (
|
|||
)
|
||||
|
||||
func TestAlpmCataloger(t *testing.T) {
|
||||
|
||||
dbLocation := source.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "gmp",
|
||||
Version: "6.2.1-2",
|
||||
Type: pkg.AlpmPkg,
|
||||
FoundBy: "alpmdb-cataloger",
|
||||
Licenses: []string{"LGPL3", "GPL"},
|
||||
Locations: source.NewLocationSet(source.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")),
|
||||
Name: "gmp",
|
||||
Version: "6.2.1-2",
|
||||
Type: pkg.AlpmPkg,
|
||||
FoundBy: "alpmdb-cataloger",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("LGPL3", dbLocation),
|
||||
pkg.NewLicenseFromLocations("GPL", dbLocation),
|
||||
),
|
||||
Locations: source.NewLocationSet(dbLocation),
|
||||
CPEs: nil,
|
||||
PURL: "",
|
||||
MetadataType: "AlpmMetadata",
|
||||
|
@ -33,7 +36,6 @@ func TestAlpmCataloger(t *testing.T) {
|
|||
Architecture: "x86_64",
|
||||
Size: 1044438,
|
||||
Packager: "Antonio Rojas <arojas@archlinux.org>",
|
||||
License: "LGPL3\nGPL",
|
||||
URL: "https://gmplib.org/",
|
||||
Validation: "pgp",
|
||||
Reason: 1,
|
||||
|
|
|
@ -1,29 +1,33 @@
|
|||
package alpm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newPackage(m pkg.AlpmMetadata, release *linux.Release, locations ...source.Location) pkg.Package {
|
||||
func newPackage(m *parsedData, release *linux.Release, dbLocation source.Location) pkg.Package {
|
||||
licenseCandidates := strings.Split(m.Licenses, "\n")
|
||||
|
||||
p := pkg.Package{
|
||||
Name: m.Package,
|
||||
Version: m.Version,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Locations: source.NewLocationSet(dbLocation),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation.WithoutAnnotations(), licenseCandidates...)...),
|
||||
Type: pkg.AlpmPkg,
|
||||
Licenses: internal.SplitAny(m.License, " \n"),
|
||||
PURL: packageURL(m, release),
|
||||
MetadataType: pkg.AlpmMetadataType,
|
||||
Metadata: m,
|
||||
Metadata: m.AlpmMetadata,
|
||||
}
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func packageURL(m pkg.AlpmMetadata, distro *linux.Release) string {
|
||||
func packageURL(m *parsedData, distro *linux.Release) string {
|
||||
if distro == nil || distro.ID != "arch" {
|
||||
// note: there is no namespace variation (like with debian ID_LIKE for ubuntu ID, for example)
|
||||
return ""
|
||||
|
|
|
@ -13,16 +13,19 @@ import (
|
|||
func Test_PackageURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata pkg.AlpmMetadata
|
||||
metadata *parsedData
|
||||
distro linux.Release
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "bad distro id",
|
||||
metadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "something-else",
|
||||
|
@ -32,10 +35,13 @@ func Test_PackageURL(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "gocase",
|
||||
metadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
|
@ -45,9 +51,12 @@ func Test_PackageURL(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "missing architecture",
|
||||
metadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
|
@ -55,10 +64,13 @@ func Test_PackageURL(t *testing.T) {
|
|||
expected: "pkg:alpm/arch/p@v?distro=arch",
|
||||
},
|
||||
{
|
||||
metadata: pkg.AlpmMetadata{
|
||||
Package: "python",
|
||||
Version: "3.10.0",
|
||||
Architecture: "any",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
Package: "python",
|
||||
Version: "3.10.0",
|
||||
Architecture: "any",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
|
@ -67,10 +79,13 @@ func Test_PackageURL(t *testing.T) {
|
|||
expected: "pkg:alpm/arch/python@3.10.0?arch=any&distro=arch-rolling",
|
||||
},
|
||||
{
|
||||
metadata: pkg.AlpmMetadata{
|
||||
Package: "g plus plus",
|
||||
Version: "v84",
|
||||
Architecture: "x86_64",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
Package: "g plus plus",
|
||||
Version: "v84",
|
||||
Architecture: "x86_64",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
|
@ -80,11 +95,14 @@ func Test_PackageURL(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "add source information as qualifier",
|
||||
metadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
BasePackage: "origin",
|
||||
metadata: &parsedData{
|
||||
Licenses: "",
|
||||
AlpmMetadata: pkg.AlpmMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
BasePackage: "origin",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "arch",
|
||||
|
|
|
@ -31,8 +31,13 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
type parsedData struct {
|
||||
Licenses string `mapstructure:"license"`
|
||||
pkg.AlpmMetadata `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func parseAlpmDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
metadata, err := parseAlpmDBEntry(reader)
|
||||
data, err := parseAlpmDBEntry(reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -48,9 +53,10 @@ func parseAlpmDB(resolver source.FileResolver, env *generic.Environment, reader
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
// The replace the files found the the pacman database with the files from the mtree These contain more metadata and
|
||||
// replace the files found the pacman database with the files from the mtree These contain more metadata and
|
||||
// thus more useful.
|
||||
metadata.Files = pkgFiles
|
||||
// TODO: probably want to use MTREE and PKGINFO here
|
||||
data.Files = pkgFiles
|
||||
|
||||
// We only really do this to get any backup database entries from the files database
|
||||
files := filepath.Join(base, "files")
|
||||
|
@ -62,23 +68,23 @@ func parseAlpmDB(resolver source.FileResolver, env *generic.Environment, reader
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if filesMetadata != nil {
|
||||
metadata.Backup = filesMetadata.Backup
|
||||
data.Backup = filesMetadata.Backup
|
||||
}
|
||||
|
||||
if metadata.Package == "" {
|
||||
if data.Package == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
return []pkg.Package{
|
||||
newPackage(
|
||||
*metadata,
|
||||
data,
|
||||
env.LinuxRelease,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func parseAlpmDBEntry(reader io.Reader) (*pkg.AlpmMetadata, error) {
|
||||
func parseAlpmDBEntry(reader io.Reader) (*parsedData, error) {
|
||||
scanner := newScanner(reader)
|
||||
metadata, err := parseDatabase(scanner)
|
||||
if err != nil {
|
||||
|
@ -128,8 +134,7 @@ func getFileReader(path string, resolver source.FileResolver) (io.Reader, error)
|
|||
return dbContentReader, nil
|
||||
}
|
||||
|
||||
func parseDatabase(b *bufio.Scanner) (*pkg.AlpmMetadata, error) {
|
||||
var entry pkg.AlpmMetadata
|
||||
func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
|
||||
var err error
|
||||
pkgFields := make(map[string]interface{})
|
||||
for b.Scan() {
|
||||
|
@ -181,16 +186,23 @@ func parseDatabase(b *bufio.Scanner) (*pkg.AlpmMetadata, error) {
|
|||
pkgFields[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return parsePkgFiles(pkgFields)
|
||||
}
|
||||
|
||||
func parsePkgFiles(pkgFields map[string]interface{}) (*parsedData, error) {
|
||||
var entry parsedData
|
||||
if err := mapstructure.Decode(pkgFields, &entry); err != nil {
|
||||
return nil, fmt.Errorf("unable to parse ALPM metadata: %w", err)
|
||||
}
|
||||
if entry.Package == "" && len(entry.Files) == 0 && len(entry.Backup) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if entry.Backup == nil {
|
||||
entry.Backup = make([]pkg.AlpmFileRecord, 0)
|
||||
}
|
||||
|
||||
if entry.Package == "" && len(entry.Files) == 0 && len(entry.Backup) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &entry, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -9,16 +9,18 @@ import (
|
|||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newPackage(d pkg.ApkMetadata, release *linux.Release, locations ...source.Location) pkg.Package {
|
||||
func newPackage(d parsedData, release *linux.Release, dbLocation source.Location) pkg.Package {
|
||||
licenseStrings := strings.Split(d.License, " ")
|
||||
|
||||
p := pkg.Package{
|
||||
Name: d.Package,
|
||||
Version: d.Version,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Licenses: strings.Split(d.License, " "),
|
||||
PURL: packageURL(d, release),
|
||||
Locations: source.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation, licenseStrings...)...),
|
||||
PURL: packageURL(d.ApkMetadata, release),
|
||||
Type: pkg.ApkPkg,
|
||||
MetadataType: pkg.ApkMetadataType,
|
||||
Metadata: d,
|
||||
Metadata: d.ApkMetadata,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
|
|
@ -15,16 +15,19 @@ import (
|
|||
func Test_PackageURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata pkg.ApkMetadata
|
||||
metadata parsedData
|
||||
distro linux.Release
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "non-alpine distro",
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "something else",
|
||||
|
@ -34,10 +37,13 @@ func Test_PackageURL(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "gocase",
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "alpine",
|
||||
|
@ -47,9 +53,12 @@ func Test_PackageURL(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "missing architecture",
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "alpine",
|
||||
|
@ -59,10 +68,13 @@ func Test_PackageURL(t *testing.T) {
|
|||
},
|
||||
// verify #351
|
||||
{
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "g++",
|
||||
Version: "v84",
|
||||
Architecture: "am86",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "g++",
|
||||
Version: "v84",
|
||||
Architecture: "am86",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "alpine",
|
||||
|
@ -71,10 +83,13 @@ func Test_PackageURL(t *testing.T) {
|
|||
expected: "pkg:apk/alpine/g++@v84?arch=am86&distro=alpine-3.4.6",
|
||||
},
|
||||
{
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "g plus plus",
|
||||
Version: "v84",
|
||||
Architecture: "am86",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "g plus plus",
|
||||
Version: "v84",
|
||||
Architecture: "am86",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "alpine",
|
||||
|
@ -84,11 +99,14 @@ func Test_PackageURL(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "add source information as qualifier",
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
OriginPackage: "origin",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
OriginPackage: "origin",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "alpine",
|
||||
|
@ -98,10 +116,13 @@ func Test_PackageURL(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "wolfi distro",
|
||||
metadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
metadata: parsedData{
|
||||
License: "",
|
||||
ApkMetadata: pkg.ApkMetadata{
|
||||
Package: "p",
|
||||
Version: "v",
|
||||
Architecture: "a",
|
||||
},
|
||||
},
|
||||
distro: linux.Release{
|
||||
ID: "wolfi",
|
||||
|
@ -113,7 +134,7 @@ func Test_PackageURL(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actual := packageURL(test.metadata, &test.distro)
|
||||
actual := packageURL(test.metadata.ApkMetadata, &test.distro)
|
||||
if actual != test.expected {
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(test.expected, actual, true)
|
||||
|
|
|
@ -26,6 +26,11 @@ var (
|
|||
repoRegex = regexp.MustCompile(`(?m)^https://.*\.alpinelinux\.org/alpine/v([^/]+)/([a-zA-Z0-9_]+)$`)
|
||||
)
|
||||
|
||||
type parsedData struct {
|
||||
License string `mapstructure:"L" json:"license"`
|
||||
pkg.ApkMetadata
|
||||
}
|
||||
|
||||
// parseApkDB parses packages from a given APK installed DB file. For more
|
||||
// information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec.
|
||||
//
|
||||
|
@ -33,15 +38,15 @@ var (
|
|||
func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
var apks []pkg.ApkMetadata
|
||||
var currentEntry pkg.ApkMetadata
|
||||
var apks []parsedData
|
||||
var currentEntry parsedData
|
||||
entryParsingInProgress := false
|
||||
fileParsingCtx := newApkFileParsingContext()
|
||||
|
||||
// creating a dedicated append-like function here instead of using `append(...)`
|
||||
// below since there is nontrivial logic to be performed for each finalized apk
|
||||
// entry.
|
||||
appendApk := func(p pkg.ApkMetadata) {
|
||||
appendApk := func(p parsedData) {
|
||||
if files := fileParsingCtx.files; len(files) >= 1 {
|
||||
// attached accumulated files to current package
|
||||
p.Files = files
|
||||
|
@ -68,7 +73,7 @@ func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader s
|
|||
entryParsingInProgress = false
|
||||
|
||||
// zero-out currentEntry for use by any future entry
|
||||
currentEntry = pkg.ApkMetadata{}
|
||||
currentEntry = parsedData{}
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -123,7 +128,7 @@ func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader s
|
|||
|
||||
pkgs := make([]pkg.Package, 0, len(apks))
|
||||
for _, apk := range apks {
|
||||
pkgs = append(pkgs, newPackage(apk, r, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)))
|
||||
pkgs = append(pkgs, newPackage(apk, r, reader.Location))
|
||||
}
|
||||
|
||||
return pkgs, discoverPackageDependencies(pkgs), nil
|
||||
|
@ -201,7 +206,7 @@ type apkField struct {
|
|||
}
|
||||
|
||||
//nolint:funlen
|
||||
func (f apkField) apply(p *pkg.ApkMetadata, ctx *apkFileParsingContext) {
|
||||
func (f apkField) apply(p *parsedData, ctx *apkFileParsingContext) {
|
||||
switch f.name {
|
||||
// APKINDEX field parsing
|
||||
|
||||
|
@ -347,7 +352,7 @@ func parseListValue(value string) []string {
|
|||
return nil
|
||||
}
|
||||
|
||||
func nilFieldsToEmptySlice(p *pkg.ApkMetadata) {
|
||||
func nilFieldsToEmptySlice(p *parsedData) {
|
||||
if p.Dependencies == nil {
|
||||
p.Dependencies = []string{}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,12 +10,17 @@ import (
|
|||
)
|
||||
|
||||
func TestDpkgCataloger(t *testing.T) {
|
||||
licenseLocation := source.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")
|
||||
expected := []pkg.Package{
|
||||
{
|
||||
Name: "libpam-runtime",
|
||||
Version: "1.1.8-3.6",
|
||||
FoundBy: "dpkgdb-cataloger",
|
||||
Licenses: []string{"GPL-1", "GPL-2", "LGPL-2.1"},
|
||||
Name: "libpam-runtime",
|
||||
Version: "1.1.8-3.6",
|
||||
FoundBy: "dpkgdb-cataloger",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("GPL-1", licenseLocation),
|
||||
pkg.NewLicenseFromLocations("GPL-2", licenseLocation),
|
||||
pkg.NewLicenseFromLocations("LGPL-2.1", licenseLocation),
|
||||
),
|
||||
Locations: source.NewLocationSet(
|
||||
source.NewVirtualLocation("/var/lib/dpkg/status", "/var/lib/dpkg/status"),
|
||||
source.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.md5sums", "/var/lib/dpkg/info/libpam-runtime.md5sums"),
|
||||
|
|
|
@ -22,9 +22,12 @@ const (
|
|||
)
|
||||
|
||||
func newDpkgPackage(d pkg.DpkgMetadata, dbLocation source.Location, resolver source.FileResolver, release *linux.Release) pkg.Package {
|
||||
// TODO: separate pr to license refactor, but explore extracting dpkg-specific license parsing into a separate function
|
||||
licenses := make([]pkg.License, 0)
|
||||
p := pkg.Package{
|
||||
Name: d.Package,
|
||||
Version: d.Version,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Locations: source.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(d, release),
|
||||
Type: pkg.DebPkg,
|
||||
|
@ -93,8 +96,10 @@ func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pk
|
|||
if copyrightReader != nil && copyrightLocation != nil {
|
||||
defer internal.CloseAndLogError(copyrightReader, copyrightLocation.VirtualPath)
|
||||
// attach the licenses
|
||||
p.Licenses = parseLicensesFromCopyright(copyrightReader)
|
||||
|
||||
licenseStrs := parseLicensesFromCopyright(copyrightReader)
|
||||
for _, licenseStr := range licenseStrs {
|
||||
p.Licenses.Add(pkg.NewLicenseFromLocations(licenseStr, copyrightLocation.WithoutAnnotations()))
|
||||
}
|
||||
// keep a record of the file where this was discovered
|
||||
p.Locations.Add(*copyrightLocation)
|
||||
}
|
||||
|
|
|
@ -307,6 +307,7 @@ Installed-Size: 10kib
|
|||
Name: "apt",
|
||||
Type: "deb",
|
||||
PURL: "pkg:deb/debian/apt?distro=debian-10",
|
||||
Licenses: pkg.NewLicenseSet(),
|
||||
Locations: source.NewLocationSet(source.NewLocation("place")),
|
||||
MetadataType: "DpkgMetadata",
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
|
@ -74,7 +75,7 @@ func modCacheResolver(modCacheDir string) source.WritableFileResolver {
|
|||
return r
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicenses(resolver source.FileResolver, moduleName, moduleVersion string) (licenses []string, err error) {
|
||||
func (c *goLicenses) getLicenses(resolver source.FileResolver, moduleName, moduleVersion string) (licenses []pkg.License, err error) {
|
||||
licenses, err = findLicenses(resolver,
|
||||
fmt.Sprintf(`**/go/pkg/mod/%s@%s/*`, processCaps(moduleName), moduleVersion),
|
||||
)
|
||||
|
@ -93,7 +94,7 @@ func (c *goLicenses) getLicenses(resolver source.FileResolver, moduleName, modul
|
|||
return requireCollection(licenses), err
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]string, error) {
|
||||
func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]pkg.License, error) {
|
||||
if !c.opts.searchLocalModCacheLicenses {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -103,7 +104,7 @@ func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]s
|
|||
return findLicenses(c.localModCacheResolver, moduleSearchGlob(moduleName, moduleVersion))
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicensesFromRemote(moduleName, moduleVersion string) ([]string, error) {
|
||||
func (c *goLicenses) getLicensesFromRemote(moduleName, moduleVersion string) ([]pkg.License, error) {
|
||||
if !c.opts.searchRemoteLicenses {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -148,14 +149,15 @@ func moduleSearchGlob(moduleName, moduleVersion string) string {
|
|||
return fmt.Sprintf("%s/*", moduleDir(moduleName, moduleVersion))
|
||||
}
|
||||
|
||||
func requireCollection(licenses []string) []string {
|
||||
func requireCollection(licenses []pkg.License) []pkg.License {
|
||||
if licenses == nil {
|
||||
return []string{}
|
||||
return make([]pkg.License, 0)
|
||||
}
|
||||
return licenses
|
||||
}
|
||||
|
||||
func findLicenses(resolver source.FileResolver, globMatch string) (out []string, err error) {
|
||||
func findLicenses(resolver source.FileResolver, globMatch string) (out []pkg.License, err error) {
|
||||
out = make([]pkg.License, 0)
|
||||
if resolver == nil {
|
||||
return
|
||||
}
|
||||
|
@ -172,7 +174,7 @@ func findLicenses(resolver source.FileResolver, globMatch string) (out []string,
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsed, err := licenses.Parse(contents)
|
||||
parsed, err := licenses.Parse(contents, l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -13,24 +13,42 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_LocalLicenseSearch(t *testing.T) {
|
||||
loc1 := source.NewLocation("github.com/someorg/somename@v0.3.2/LICENSE")
|
||||
loc2 := source.NewLocation("github.com/!cap!o!r!g/!cap!project@v4.111.5/LICENSE.txt")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
expected string
|
||||
expected pkg.License
|
||||
}{
|
||||
{
|
||||
name: "github.com/someorg/somename",
|
||||
version: "v0.3.2",
|
||||
expected: "Apache-2.0",
|
||||
name: "github.com/someorg/somename",
|
||||
version: "v0.3.2",
|
||||
expected: pkg.License{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
Location: source.NewLocationSet(loc1),
|
||||
URL: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "github.com/CapORG/CapProject",
|
||||
version: "v4.111.5",
|
||||
expected: "MIT",
|
||||
name: "github.com/CapORG/CapProject",
|
||||
version: "v4.111.5",
|
||||
expected: pkg.License{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Concluded,
|
||||
Location: source.NewLocationSet(loc2),
|
||||
URL: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -39,10 +57,12 @@ func Test_LocalLicenseSearch(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
l := newGoLicenses(GoCatalogerOpts{
|
||||
searchLocalModCacheLicenses: true,
|
||||
localModCacheDir: path.Join(wd, "test-fixtures", "licenses", "pkg", "mod"),
|
||||
})
|
||||
l := newGoLicenses(
|
||||
GoCatalogerOpts{
|
||||
searchLocalModCacheLicenses: true,
|
||||
localModCacheDir: path.Join(wd, "test-fixtures", "licenses", "pkg", "mod"),
|
||||
},
|
||||
)
|
||||
licenses, err := l.getLicenses(source.EmptyResolver{}, test.name, test.version)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -54,6 +74,9 @@ func Test_LocalLicenseSearch(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_RemoteProxyLicenseSearch(t *testing.T) {
|
||||
loc1 := source.NewLocation("github.com/someorg/somename@v0.3.2/LICENSE")
|
||||
loc2 := source.NewLocation("github.com/!cap!o!r!g/!cap!project@v4.111.5/LICENSE.txt")
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf := &bytes.Buffer{}
|
||||
uri := strings.TrimPrefix(strings.TrimSuffix(r.RequestURI, ".zip"), "/")
|
||||
|
@ -94,17 +117,29 @@ func Test_RemoteProxyLicenseSearch(t *testing.T) {
|
|||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
expected string
|
||||
expected pkg.License
|
||||
}{
|
||||
{
|
||||
name: "github.com/someorg/somename",
|
||||
version: "v0.3.2",
|
||||
expected: "Apache-2.0",
|
||||
name: "github.com/someorg/somename",
|
||||
version: "v0.3.2",
|
||||
expected: pkg.License{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: license.Concluded,
|
||||
Location: source.NewLocationSet(loc1),
|
||||
URL: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "github.com/CapORG/CapProject",
|
||||
version: "v4.111.5",
|
||||
expected: "MIT",
|
||||
name: "github.com/CapORG/CapProject",
|
||||
version: "v4.111.5",
|
||||
expected: pkg.License{
|
||||
Value: "MIT",
|
||||
SPDXExpression: "MIT",
|
||||
Type: license.Concluded,
|
||||
Location: source.NewLocationSet(loc2),
|
||||
URL: internal.NewStringSet(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ func (c *goBinaryCataloger) newGoBinaryPackage(resolver source.FileResolver, dep
|
|||
p := pkg.Package{
|
||||
Name: dep.Path,
|
||||
Version: dep.Version,
|
||||
Licenses: licenses,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
PURL: packageURL(dep.Path, dep.Version),
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
|
|
|
@ -497,9 +497,6 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
|||
t.Run(test.name, func(t *testing.T) {
|
||||
for i := range test.expected {
|
||||
p := &test.expected[i]
|
||||
if p.Licenses == nil {
|
||||
p.Licenses = []string{}
|
||||
}
|
||||
p.SetID()
|
||||
}
|
||||
location := source.NewLocationFromCoordinates(
|
||||
|
|
|
@ -50,7 +50,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic
|
|||
packages[m.Mod.Path] = pkg.Package{
|
||||
Name: m.Mod.Path,
|
||||
Version: m.Mod.Version,
|
||||
Licenses: licenses,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Locations: source.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(m.Mod.Path, m.Mod.Version),
|
||||
Language: pkg.Go,
|
||||
|
@ -72,7 +72,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic
|
|||
packages[m.New.Path] = pkg.Package{
|
||||
Name: m.New.Path,
|
||||
Version: m.New.Version,
|
||||
Licenses: licenses,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Locations: source.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(m.New.Path, m.New.Version),
|
||||
Language: pkg.Go,
|
||||
|
|
|
@ -88,12 +88,6 @@ func TestParseGoMod(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
for i := range test.expected {
|
||||
p := &test.expected[i]
|
||||
if p.Licenses == nil {
|
||||
p.Licenses = []string{}
|
||||
}
|
||||
}
|
||||
c := goModCataloger{}
|
||||
pkgtest.NewCatalogTester().
|
||||
FromFile(t, test.fixture).
|
||||
|
@ -154,12 +148,6 @@ func Test_GoSumHashes(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
for i := range test.expected {
|
||||
p := &test.expected[i]
|
||||
if p.Licenses == nil {
|
||||
p.Licenses = []string{}
|
||||
}
|
||||
}
|
||||
pkgtest.NewCatalogTester().
|
||||
FromDirectory(t, test.fixture).
|
||||
Expects(test.expected, nil).
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
)
|
||||
|
||||
type locationComparer func(x, y source.Location) bool
|
||||
type licenseComparer func(x, y pkg.License) bool
|
||||
|
||||
type CatalogTester struct {
|
||||
expectedPkgs []pkg.Package
|
||||
|
@ -36,12 +37,14 @@ type CatalogTester struct {
|
|||
wantErr require.ErrorAssertionFunc
|
||||
compareOptions []cmp.Option
|
||||
locationComparer locationComparer
|
||||
licenseComparer licenseComparer
|
||||
}
|
||||
|
||||
func NewCatalogTester() *CatalogTester {
|
||||
return &CatalogTester{
|
||||
wantErr: require.NoError,
|
||||
locationComparer: DefaultLocationComparer,
|
||||
licenseComparer: DefaultLicenseComparer,
|
||||
ignoreUnfulfilledPathResponses: map[string][]string{
|
||||
"FilesByPath": {
|
||||
// most catalogers search for a linux release, which will not be fulfilled in testing
|
||||
|
@ -59,6 +62,25 @@ func DefaultLocationComparer(x, y source.Location) bool {
|
|||
return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath)
|
||||
}
|
||||
|
||||
func DefaultLicenseComparer(x, y pkg.License) bool {
|
||||
return cmp.Equal(x, y, cmp.Comparer(DefaultLocationComparer), cmp.Comparer(
|
||||
func(x, y source.LocationSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !DefaultLocationComparer(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
func (p *CatalogTester) FromDirectory(t *testing.T, path string) *CatalogTester {
|
||||
t.Helper()
|
||||
|
||||
|
@ -139,6 +161,26 @@ func (p *CatalogTester) IgnoreLocationLayer() *CatalogTester {
|
|||
p.locationComparer = func(x, y source.Location) bool {
|
||||
return cmp.Equal(x.Coordinates.RealPath, y.Coordinates.RealPath) && cmp.Equal(x.VirtualPath, y.VirtualPath)
|
||||
}
|
||||
|
||||
// we need to update the license comparer to use the ignored location layer
|
||||
p.licenseComparer = func(x, y pkg.License) bool {
|
||||
return cmp.Equal(x, y, cmp.Comparer(p.locationComparer), cmp.Comparer(
|
||||
func(x, y source.LocationSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !p.locationComparer(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}))
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
|
@ -209,6 +251,7 @@ func (p *CatalogTester) TestCataloger(t *testing.T, cataloger pkg.Cataloger) {
|
|||
}
|
||||
}
|
||||
|
||||
// nolint:funlen
|
||||
func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
||||
t.Helper()
|
||||
|
||||
|
@ -233,6 +276,30 @@ func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationshi
|
|||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
func(x, y pkg.LicenseSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !p.licenseComparer(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
p.locationComparer,
|
||||
),
|
||||
cmp.Comparer(
|
||||
p.licenseComparer,
|
||||
),
|
||||
)
|
||||
|
||||
{
|
||||
|
@ -295,6 +362,30 @@ func AssertPackagesEqual(t *testing.T, a, b pkg.Package) {
|
|||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
func(x, y pkg.LicenseSet) bool {
|
||||
xs := x.ToSlice()
|
||||
ys := y.ToSlice()
|
||||
|
||||
if len(xs) != len(ys) {
|
||||
return false
|
||||
}
|
||||
for i, xe := range xs {
|
||||
ye := ys[i]
|
||||
if !DefaultLicenseComparer(xe, ye) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
DefaultLocationComparer,
|
||||
),
|
||||
cmp.Comparer(
|
||||
DefaultLicenseComparer,
|
||||
),
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(a, b, opts...); diff != "" {
|
||||
|
|
|
@ -185,11 +185,13 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
|||
log.Warnf("failed to create digest for file=%q: %+v", j.archivePath, err)
|
||||
}
|
||||
|
||||
// we use j.location because we want to associate the license declaration with where we discovered the contents in the manifest
|
||||
licenses := pkg.NewLicensesFromLocation(j.location, selectLicenses(manifest)...)
|
||||
return &pkg.Package{
|
||||
Name: selectName(manifest, j.fileInfo),
|
||||
Version: selectVersion(manifest, j.fileInfo),
|
||||
Licenses: selectLicense(manifest),
|
||||
Language: pkg.Java,
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Locations: source.NewLocationSet(
|
||||
j.location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
|
|
|
@ -96,10 +96,12 @@ func TestParseJar(t *testing.T) {
|
|||
},
|
||||
expected: map[string]pkg.Package{
|
||||
"example-jenkins-plugin": {
|
||||
Name: "example-jenkins-plugin",
|
||||
Version: "1.0-SNAPSHOT",
|
||||
PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT",
|
||||
Licenses: []string{"MIT License"},
|
||||
Name: "example-jenkins-plugin",
|
||||
Version: "1.0-SNAPSHOT",
|
||||
PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT License", source.NewLocation("test-fixtures/java-builds/packages/example-jenkins-plugin.hpi")),
|
||||
),
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
|
@ -150,7 +152,6 @@ func TestParseJar(t *testing.T) {
|
|||
Name: "example-java-app-gradle",
|
||||
Version: "0.1.0",
|
||||
PURL: "pkg:maven/example-java-app-gradle/example-java-app-gradle@0.1.0",
|
||||
Licenses: []string{},
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
|
@ -205,7 +206,6 @@ func TestParseJar(t *testing.T) {
|
|||
Name: "example-java-app-maven",
|
||||
Version: "0.1.0",
|
||||
PURL: "pkg:maven/org.anchore/example-java-app-maven@0.1.0",
|
||||
Licenses: []string{},
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
|
|
|
@ -157,7 +157,7 @@ func selectVersion(manifest *pkg.JavaManifest, filenameObj archiveFilename) stri
|
|||
return ""
|
||||
}
|
||||
|
||||
func selectLicense(manifest *pkg.JavaManifest) []string {
|
||||
func selectLicenses(manifest *pkg.JavaManifest) []string {
|
||||
result := []string{}
|
||||
if manifest == nil {
|
||||
return result
|
||||
|
|
|
@ -12,14 +12,16 @@ func Test_JavascriptCataloger(t *testing.T) {
|
|||
locationSet := source.NewLocationSet(source.NewLocation("package-lock.json"))
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "@actions/core",
|
||||
Version: "1.6.0",
|
||||
FoundBy: "javascript-lock-cataloger",
|
||||
PURL: "pkg:npm/%40actions/core@1.6.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "@actions/core",
|
||||
Version: "1.6.0",
|
||||
FoundBy: "javascript-lock-cataloger",
|
||||
PURL: "pkg:npm/%40actions/core@1.6.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation("package-lock.json")),
|
||||
),
|
||||
MetadataType: pkg.NpmPackageLockJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz", Integrity: "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw=="},
|
||||
},
|
||||
|
@ -35,14 +37,16 @@ func Test_JavascriptCataloger(t *testing.T) {
|
|||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", Integrity: "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="},
|
||||
},
|
||||
{
|
||||
Name: "cowsay",
|
||||
Version: "1.4.0",
|
||||
FoundBy: "javascript-lock-cataloger",
|
||||
PURL: "pkg:npm/cowsay@1.4.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "cowsay",
|
||||
Version: "1.4.0",
|
||||
FoundBy: "javascript-lock-cataloger",
|
||||
PURL: "pkg:npm/cowsay@1.4.0",
|
||||
Locations: locationSet,
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation("package-lock.json")),
|
||||
),
|
||||
MetadataType: pkg.NpmPackageLockJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/cowsay/-/cowsay-1.4.0.tgz", Integrity: "sha512-rdg5k5PsHFVJheO/pmE3aDg2rUDDTfPJau6yYkZYlHFktUz+UxbE+IgnUAEyyCyv4noL5ltxXD0gZzmHPCy/9g=="},
|
||||
},
|
||||
|
|
|
@ -12,30 +12,30 @@ import (
|
|||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newPackageJSONPackage(u packageJSON, locations ...source.Location) pkg.Package {
|
||||
licenses, err := u.licensesFromJSON()
|
||||
func newPackageJSONPackage(u packageJSON, indexLocation source.Location) pkg.Package {
|
||||
licenseCandidates, err := u.licensesFromJSON()
|
||||
if err != nil {
|
||||
log.Warnf("unable to extract licenses from javascript package.json: %+v", err)
|
||||
}
|
||||
|
||||
license := pkg.NewLicensesFromLocation(indexLocation, licenseCandidates...)
|
||||
p := pkg.Package{
|
||||
Name: u.Name,
|
||||
Version: u.Version,
|
||||
Licenses: licenses,
|
||||
PURL: packageURL(u.Name, u.Version),
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Locations: source.NewLocationSet(indexLocation),
|
||||
Language: pkg.JavaScript,
|
||||
Licenses: pkg.NewLicenseSet(license...),
|
||||
Type: pkg.NpmPkg,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: u.Name,
|
||||
Version: u.Version,
|
||||
Description: u.Description,
|
||||
Author: u.Author.AuthorString(),
|
||||
Homepage: u.Homepage,
|
||||
URL: u.Repository.URL,
|
||||
Licenses: licenses,
|
||||
Private: u.Private,
|
||||
Description: u.Description,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -77,12 +77,6 @@ func newPackageLockV1Package(resolver source.FileResolver, location source.Locat
|
|||
}
|
||||
|
||||
func newPackageLockV2Package(resolver source.FileResolver, location source.Location, name string, u lockPackage) pkg.Package {
|
||||
var licenses []string
|
||||
|
||||
if u.License != nil {
|
||||
licenses = u.License
|
||||
}
|
||||
|
||||
return finalizeLockPkg(
|
||||
resolver,
|
||||
location,
|
||||
|
@ -90,10 +84,10 @@ func newPackageLockV2Package(resolver source.FileResolver, location source.Locat
|
|||
Name: name,
|
||||
Version: u.Version,
|
||||
Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(location, u.License...)...),
|
||||
PURL: packageURL(name, u.Version),
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: licenses,
|
||||
MetadataType: pkg.NpmPackageLockJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: u.Resolved, Integrity: u.Integrity},
|
||||
},
|
||||
|
@ -131,7 +125,8 @@ func newYarnLockPackage(resolver source.FileResolver, location source.Location,
|
|||
}
|
||||
|
||||
func finalizeLockPkg(resolver source.FileResolver, location source.Location, p pkg.Package) pkg.Package {
|
||||
p.Licenses = append(p.Licenses, addLicenses(p.Name, resolver, location)...)
|
||||
licenseCandidate := addLicenses(p.Name, resolver, location)
|
||||
p.Licenses.Add(pkg.NewLicensesFromLocation(location, licenseCandidate...)...)
|
||||
p.SetID()
|
||||
return p
|
||||
}
|
||||
|
@ -140,13 +135,13 @@ func addLicenses(name string, resolver source.FileResolver, location source.Loca
|
|||
if resolver == nil {
|
||||
return allLicenses
|
||||
}
|
||||
|
||||
dir := path.Dir(location.RealPath)
|
||||
pkgPath := []string{dir, "node_modules"}
|
||||
pkgPath = append(pkgPath, strings.Split(name, "/")...)
|
||||
pkgPath = append(pkgPath, "package.json")
|
||||
pkgFile := path.Join(pkgPath...)
|
||||
locations, err := resolver.FilesByPath(pkgFile)
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("an error occurred attempting to read: %s - %+v", pkgFile, err)
|
||||
return allLicenses
|
||||
|
|
|
@ -140,7 +140,7 @@ func (r *repository) UnmarshalJSON(b []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type license struct {
|
||||
type npmPackageLicense struct {
|
||||
Type string `json:"type"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ func licenseFromJSON(b []byte) (string, error) {
|
|||
}
|
||||
|
||||
// then try as object (this format is deprecated)
|
||||
var licenseObject license
|
||||
var licenseObject npmPackageLicense
|
||||
err = json.Unmarshal(b, &licenseObject)
|
||||
if err == nil {
|
||||
return licenseObject.Type, nil
|
||||
|
@ -178,7 +178,7 @@ func (p packageJSON) licensesFromJSON() ([]string, error) {
|
|||
|
||||
// The "licenses" field is deprecated. It should be inspected as a last resort.
|
||||
if multiLicense != nil && err == nil {
|
||||
mapLicenses := func(licenses []license) []string {
|
||||
mapLicenses := func(licenses []npmPackageLicense) []string {
|
||||
mappedLicenses := make([]string, len(licenses))
|
||||
for i, l := range licenses {
|
||||
mappedLicenses[i] = l.Type
|
||||
|
@ -192,8 +192,8 @@ func (p packageJSON) licensesFromJSON() ([]string, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
func licensesFromJSON(b []byte) ([]license, error) {
|
||||
var licenseObject []license
|
||||
func licensesFromJSON(b []byte) ([]npmPackageLicense, error) {
|
||||
var licenseObject []npmPackageLicense
|
||||
err := json.Unmarshal(b, &licenseObject)
|
||||
if err == nil {
|
||||
return licenseObject, nil
|
||||
|
|
|
@ -18,12 +18,14 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
{
|
||||
Fixture: "test-fixtures/pkg-json/package.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Language: pkg.JavaScript,
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Language: pkg.JavaScript,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("Artistic-2.0", source.NewLocation("test-fixtures/pkg-json/package.json")),
|
||||
),
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
|
@ -31,7 +33,6 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
|
@ -39,12 +40,14 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-license-object.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"ISC"},
|
||||
Language: pkg.JavaScript,
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Language: pkg.JavaScript,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("ISC", source.NewLocation("test-fixtures/pkg-json/package-license-object.json")),
|
||||
),
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
Name: "npm",
|
||||
|
@ -52,7 +55,6 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"ISC"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
|
@ -60,11 +62,14 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-license-objects.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT", "Apache-2.0"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation("test-fixtures/pkg-json/package-license-objects.json")),
|
||||
pkg.NewLicenseFromLocations("Apache-2.0", source.NewLocation("test-fixtures/pkg-json/package-license-objects.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
|
@ -73,7 +78,6 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"MIT", "Apache-2.0"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
|
@ -85,7 +89,6 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: nil,
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
|
@ -94,7 +97,6 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: nil,
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
|
@ -106,7 +108,6 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{},
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
|
@ -115,7 +116,6 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
|
@ -123,11 +123,13 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-nested-author.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("Artistic-2.0", source.NewLocation("test-fixtures/pkg-json/package-nested-author.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
|
@ -136,7 +138,6 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
},
|
||||
|
@ -144,11 +145,13 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-repo-string.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "function-bind",
|
||||
Version: "1.1.1",
|
||||
PURL: "pkg:npm/function-bind@1.1.1",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "function-bind",
|
||||
Version: "1.1.1",
|
||||
PURL: "pkg:npm/function-bind@1.1.1",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation("test-fixtures/pkg-json/package-repo-string.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
|
@ -157,7 +160,6 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
Author: "Raynos <raynos2@gmail.com>",
|
||||
Homepage: "https://github.com/Raynos/function-bind",
|
||||
URL: "git://github.com/Raynos/function-bind.git",
|
||||
Licenses: []string{"MIT"},
|
||||
Description: "Implementation of Function.prototype.bind",
|
||||
},
|
||||
},
|
||||
|
@ -165,11 +167,13 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
{
|
||||
Fixture: "test-fixtures/pkg-json/package-private.json",
|
||||
ExpectedPkg: pkg.Package{
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Name: "npm",
|
||||
Version: "6.14.6",
|
||||
PURL: "pkg:npm/npm@6.14.6",
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("Artistic-2.0", source.NewLocation("test-fixtures/pkg-json/package-private.json")),
|
||||
),
|
||||
Language: pkg.JavaScript,
|
||||
MetadataType: pkg.NpmPackageJSONMetadataType,
|
||||
Metadata: pkg.NpmPackageJSONMetadata{
|
||||
|
@ -178,7 +182,6 @@ func TestParsePackageJSON(t *testing.T) {
|
|||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
Homepage: "https://docs.npmjs.com/",
|
||||
URL: "https://github.com/npm/cli",
|
||||
Licenses: []string{"Artistic-2.0"},
|
||||
Private: true,
|
||||
Description: "a package manager for JavaScript",
|
||||
},
|
||||
|
|
|
@ -25,39 +25,6 @@ type packageLock struct {
|
|||
Packages map[string]lockPackage
|
||||
}
|
||||
|
||||
// packageLockLicense
|
||||
type packageLockLicense []string
|
||||
|
||||
func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
|
||||
// The license field could be either a string or an array.
|
||||
|
||||
// 1. An array
|
||||
var arr []string
|
||||
if err := json.Unmarshal(data, &arr); err == nil {
|
||||
*licenses = arr
|
||||
return nil
|
||||
}
|
||||
|
||||
// 2. A string
|
||||
var str string
|
||||
if err = json.Unmarshal(data, &str); err == nil {
|
||||
*licenses = make([]string, 1)
|
||||
(*licenses)[0] = str
|
||||
return nil
|
||||
}
|
||||
|
||||
// debug the content we did not expect
|
||||
if len(data) > 0 {
|
||||
log.WithFields("license", string(data)).Debug("Unable to parse the following `license` value in package-lock.json")
|
||||
}
|
||||
|
||||
// 3. Unexpected
|
||||
// In case we are unable to parse the license field,
|
||||
// i.e if we have not covered the full specification,
|
||||
// we do not want to throw an error, instead assign nil.
|
||||
return nil
|
||||
}
|
||||
|
||||
// lockDependency represents a single package dependency listed in the package.lock json file
|
||||
type lockDependency struct {
|
||||
Version string `json:"version"`
|
||||
|
@ -73,6 +40,9 @@ type lockPackage struct {
|
|||
License packageLockLicense `json:"license"`
|
||||
}
|
||||
|
||||
// packageLockLicense
|
||||
type packageLockLicense []string
|
||||
|
||||
// parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages.
|
||||
func parsePackageLock(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
// in the case we find package-lock.json files in the node_modules directories, skip those
|
||||
|
@ -125,6 +95,36 @@ func parsePackageLock(resolver source.FileResolver, _ *generic.Environment, read
|
|||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
|
||||
// The license field could be either a string or an array.
|
||||
|
||||
// 1. An array
|
||||
var arr []string
|
||||
if err := json.Unmarshal(data, &arr); err == nil {
|
||||
*licenses = arr
|
||||
return nil
|
||||
}
|
||||
|
||||
// 2. A string
|
||||
var str string
|
||||
if err = json.Unmarshal(data, &str); err == nil {
|
||||
*licenses = make([]string, 1)
|
||||
(*licenses)[0] = str
|
||||
return nil
|
||||
}
|
||||
|
||||
// debug the content we did not expect
|
||||
if len(data) > 0 {
|
||||
log.WithFields("license", string(data)).Debug("Unable to parse the following `license` value in package-lock.json")
|
||||
}
|
||||
|
||||
// 3. Unexpected
|
||||
// In case we are unable to parse the license field,
|
||||
// i.e if we have not covered the full specification,
|
||||
// we do not want to throw an error, instead assign nil.
|
||||
return nil
|
||||
}
|
||||
|
||||
func getNameFromPath(path string) string {
|
||||
parts := strings.Split(path, "node_modules/")
|
||||
return parts[len(parts)-1]
|
||||
|
|
|
@ -134,42 +134,50 @@ func TestParsePackageLockV2(t *testing.T) {
|
|||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
},
|
||||
{
|
||||
Name: "@types/prop-types",
|
||||
Version: "15.7.5",
|
||||
PURL: "pkg:npm/%40types/prop-types@15.7.5",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "@types/prop-types",
|
||||
Version: "15.7.5",
|
||||
PURL: "pkg:npm/%40types/prop-types@15.7.5",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
),
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", Integrity: "sha1-XxnSuFqY6VWANvajysyIGUIPBc8="},
|
||||
},
|
||||
{
|
||||
Name: "@types/react",
|
||||
Version: "18.0.17",
|
||||
PURL: "pkg:npm/%40types/react@18.0.17",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "@types/react",
|
||||
Version: "18.0.17",
|
||||
PURL: "pkg:npm/%40types/react@18.0.17",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
),
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", Integrity: "sha1-RYPZwyLWfv5LOak10iPtzHBQzPQ="},
|
||||
},
|
||||
{
|
||||
Name: "@types/scheduler",
|
||||
Version: "0.16.2",
|
||||
PURL: "pkg:npm/%40types/scheduler@0.16.2",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "@types/scheduler",
|
||||
Version: "0.16.2",
|
||||
PURL: "pkg:npm/%40types/scheduler@0.16.2",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
),
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", Integrity: "sha1-GmL4lSVyPd4kuhsBsJK/XfitTTk="},
|
||||
},
|
||||
{
|
||||
Name: "csstype",
|
||||
Version: "3.1.0",
|
||||
PURL: "pkg:npm/csstype@3.1.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"MIT"},
|
||||
Name: "csstype",
|
||||
Version: "3.1.0",
|
||||
PURL: "pkg:npm/csstype@3.1.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
),
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", Integrity: "sha1-TdysNxjXh8+d8NG30VAzklyPKfI="},
|
||||
},
|
||||
|
@ -268,33 +276,35 @@ func TestParsePackageLockAlias(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
v2Pkg := pkg.Package{
|
||||
Name: "alias-check",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:npm/alias-check@1.0.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: []string{"ISC"},
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
}
|
||||
|
||||
packageLockV1 := "test-fixtures/pkg-lock/alias-package-lock-1.json"
|
||||
packageLockV2 := "test-fixtures/pkg-lock/alias-package-lock-2.json"
|
||||
packageLocks := []string{packageLockV1, packageLockV2}
|
||||
|
||||
for _, packageLock := range packageLocks {
|
||||
v2Pkg := pkg.Package{
|
||||
Name: "alias-check",
|
||||
Version: "1.0.0",
|
||||
PURL: "pkg:npm/alias-check@1.0.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("ISC", source.NewLocation(packageLockV2)),
|
||||
),
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
}
|
||||
|
||||
for _, pl := range packageLocks {
|
||||
expected := make([]pkg.Package, len(commonPkgs))
|
||||
copy(expected, commonPkgs)
|
||||
|
||||
if packageLock == packageLockV2 {
|
||||
if pl == packageLockV2 {
|
||||
expected = append(expected, v2Pkg)
|
||||
}
|
||||
|
||||
for i := range expected {
|
||||
expected[i].Locations.Add(source.NewLocation(packageLock))
|
||||
expected[i].Locations.Add(source.NewLocation(pl))
|
||||
}
|
||||
pkgtest.TestFileParser(t, packageLock, parsePackageLock, expected, expectedRelationships)
|
||||
pkgtest.TestFileParser(t, pl, parsePackageLock, expected, expectedRelationships)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -303,31 +313,39 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) {
|
|||
var expectedRelationships []artifact.Relationship
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "tmp",
|
||||
Version: "1.0.0",
|
||||
Licenses: []string{"ISC"},
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Name: "tmp",
|
||||
Version: "1.0.0",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("ISC", source.NewLocation(fixture)),
|
||||
),
|
||||
PURL: "pkg:npm/tmp@1.0.0",
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
},
|
||||
{
|
||||
Name: "pause-stream",
|
||||
Version: "0.0.11",
|
||||
Licenses: []string{"MIT", "Apache2"},
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Name: "pause-stream",
|
||||
Version: "0.0.11",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
pkg.NewLicenseFromLocations("Apache2", source.NewLocation(fixture)),
|
||||
),
|
||||
PURL: "pkg:npm/pause-stream@0.0.11",
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
},
|
||||
{
|
||||
Name: "through",
|
||||
Version: "2.3.8",
|
||||
Licenses: []string{"MIT"},
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Name: "through",
|
||||
Version: "2.3.8",
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
),
|
||||
PURL: "pkg:npm/through@2.3.8",
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
|
|
|
@ -47,9 +47,14 @@ func Test_KernelCataloger(t *testing.T) {
|
|||
"/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko",
|
||||
),
|
||||
),
|
||||
Licenses: []string{
|
||||
"GPL v2",
|
||||
},
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("GPL v2",
|
||||
source.NewVirtualLocation(
|
||||
"/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko",
|
||||
"/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko",
|
||||
),
|
||||
),
|
||||
),
|
||||
Type: pkg.LinuxKernelModulePkg,
|
||||
PURL: "pkg:generic/ttynull",
|
||||
MetadataType: pkg.LinuxKernelModuleMetadataType,
|
||||
|
|
|
@ -10,11 +10,11 @@ import (
|
|||
|
||||
const linuxKernelPackageName = "linux-kernel"
|
||||
|
||||
func newLinuxKernelPackage(metadata pkg.LinuxKernelMetadata, locations ...source.Location) pkg.Package {
|
||||
func newLinuxKernelPackage(metadata pkg.LinuxKernelMetadata, archiveLocation source.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: linuxKernelPackageName,
|
||||
Version: metadata.Version,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Locations: source.NewLocationSet(archiveLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(linuxKernelPackageName, metadata.Version),
|
||||
Type: pkg.LinuxKernelPkg,
|
||||
MetadataType: pkg.LinuxKernelMetadataType,
|
||||
|
@ -26,19 +26,12 @@ func newLinuxKernelPackage(metadata pkg.LinuxKernelMetadata, locations ...source
|
|||
return p
|
||||
}
|
||||
|
||||
func newLinuxKernelModulePackage(metadata pkg.LinuxKernelModuleMetadata, locations ...source.Location) pkg.Package {
|
||||
var licenses []string
|
||||
if metadata.License != "" {
|
||||
licenses = []string{metadata.License}
|
||||
} else {
|
||||
licenses = []string{}
|
||||
}
|
||||
|
||||
func newLinuxKernelModulePackage(metadata pkg.LinuxKernelModuleMetadata, kmLocation source.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: metadata.Name,
|
||||
Version: metadata.Version,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Licenses: licenses,
|
||||
Locations: source.NewLocationSet(kmLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(kmLocation, metadata.License)...),
|
||||
PURL: packageURL(metadata.Name, metadata.Version),
|
||||
Type: pkg.LinuxKernelModulePkg,
|
||||
MetadataType: pkg.LinuxKernelModuleMetadataType,
|
||||
|
|
|
@ -37,7 +37,7 @@ func parseLinuxKernelFile(_ source.FileResolver, _ *generic.Environment, reader
|
|||
return []pkg.Package{
|
||||
newLinuxKernelPackage(
|
||||
metadata,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
reader.Location,
|
||||
),
|
||||
}, nil, nil
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ func parseLinuxKernelModuleFile(_ source.FileResolver, _ *generic.Environment, r
|
|||
return []pkg.Package{
|
||||
newLinuxKernelModulePackage(
|
||||
*metadata,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
reader.Location,
|
||||
),
|
||||
}, nil, nil
|
||||
}
|
||||
|
|
|
@ -8,23 +8,24 @@ import (
|
|||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newComposerLockPackage(m pkg.PhpComposerJSONMetadata, location ...source.Location) pkg.Package {
|
||||
func newComposerLockPackage(m parsedData, indexLocation source.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: m.Name,
|
||||
Version: m.Version,
|
||||
Locations: source.NewLocationSet(location...),
|
||||
Locations: source.NewLocationSet(indexLocation),
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(indexLocation, m.License...)...),
|
||||
PURL: packageURL(m),
|
||||
Language: pkg.PHP,
|
||||
Type: pkg.PhpComposerPkg,
|
||||
MetadataType: pkg.PhpComposerJSONMetadataType,
|
||||
Metadata: m,
|
||||
Metadata: m.PhpComposerJSONMetadata,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
return p
|
||||
}
|
||||
|
||||
func packageURL(m pkg.PhpComposerJSONMetadata) string {
|
||||
func packageURL(m parsedData) string {
|
||||
var name, vendor string
|
||||
fields := strings.Split(m.Name, "/")
|
||||
switch len(fields) {
|
||||
|
|
|
@ -11,30 +11,39 @@ import (
|
|||
func Test_packageURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata pkg.PhpComposerJSONMetadata
|
||||
metadata parsedData
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "with extractable vendor",
|
||||
metadata: pkg.PhpComposerJSONMetadata{
|
||||
Name: "ven/name",
|
||||
Version: "1.0.1",
|
||||
metadata: parsedData{
|
||||
[]string{},
|
||||
pkg.PhpComposerJSONMetadata{
|
||||
Version: "1.0.1",
|
||||
Name: "ven/name",
|
||||
},
|
||||
},
|
||||
expected: "pkg:composer/ven/name@1.0.1",
|
||||
},
|
||||
{
|
||||
name: "name with slashes (invalid)",
|
||||
metadata: pkg.PhpComposerJSONMetadata{
|
||||
Name: "ven/name/component",
|
||||
Version: "1.0.1",
|
||||
metadata: parsedData{
|
||||
[]string{},
|
||||
pkg.PhpComposerJSONMetadata{
|
||||
Name: "ven/name/component",
|
||||
Version: "1.0.1",
|
||||
},
|
||||
},
|
||||
expected: "pkg:composer/ven/name-component@1.0.1",
|
||||
},
|
||||
{
|
||||
name: "unknown vendor",
|
||||
metadata: pkg.PhpComposerJSONMetadata{
|
||||
Name: "name",
|
||||
Version: "1.0.1",
|
||||
metadata: parsedData{
|
||||
[]string{},
|
||||
pkg.PhpComposerJSONMetadata{
|
||||
Name: "name",
|
||||
Version: "1.0.1",
|
||||
},
|
||||
},
|
||||
expected: "pkg:composer/name@1.0.1",
|
||||
},
|
||||
|
|
|
@ -14,9 +14,14 @@ import (
|
|||
|
||||
var _ generic.Parser = parseComposerLock
|
||||
|
||||
type parsedData struct {
|
||||
License []string `json:"license"`
|
||||
pkg.PhpComposerJSONMetadata
|
||||
}
|
||||
|
||||
type composerLock struct {
|
||||
Packages []pkg.PhpComposerJSONMetadata `json:"packages"`
|
||||
PackageDev []pkg.PhpComposerJSONMetadata `json:"packages-dev"`
|
||||
Packages []parsedData `json:"packages"`
|
||||
PackageDev []parsedData `json:"packages-dev"`
|
||||
}
|
||||
|
||||
// parseComposerLock is a parser function for Composer.lock contents, returning "Default" php packages discovered.
|
||||
|
@ -40,6 +45,11 @@ func parseComposerLock(_ source.FileResolver, _ *generic.Environment, reader sou
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: did we omit this on purpose?
|
||||
// for _, m := range lock.PackageDev {
|
||||
// pkgs = append(pkgs, newComposerLockPackage(m, reader.Location))
|
||||
//}
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
|
|
|
@ -15,10 +15,13 @@ func TestParseComposerFileLock(t *testing.T) {
|
|||
locations := source.NewLocationSet(source.NewLocation(fixture))
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "adoy/fastcgi-client",
|
||||
Version: "1.0.2",
|
||||
PURL: "pkg:composer/adoy/fastcgi-client@1.0.2",
|
||||
Locations: locations,
|
||||
Name: "adoy/fastcgi-client",
|
||||
Version: "1.0.2",
|
||||
PURL: "pkg:composer/adoy/fastcgi-client@1.0.2",
|
||||
Locations: locations,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
),
|
||||
Language: pkg.PHP,
|
||||
Type: pkg.PhpComposerPkg,
|
||||
MetadataType: pkg.PhpComposerJSONMetadataType,
|
||||
|
@ -37,9 +40,6 @@ func TestParseComposerFileLock(t *testing.T) {
|
|||
},
|
||||
Type: "library",
|
||||
NotificationURL: "https://packagist.org/downloads/",
|
||||
License: []string{
|
||||
"MIT",
|
||||
},
|
||||
Authors: []pkg.PhpComposerAuthors{
|
||||
{
|
||||
Name: "Pierrick Charron",
|
||||
|
@ -55,11 +55,14 @@ func TestParseComposerFileLock(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
Name: "alcaeus/mongo-php-adapter",
|
||||
Version: "1.1.11",
|
||||
Locations: locations,
|
||||
PURL: "pkg:composer/alcaeus/mongo-php-adapter@1.1.11",
|
||||
Language: pkg.PHP,
|
||||
Name: "alcaeus/mongo-php-adapter",
|
||||
Version: "1.1.11",
|
||||
Locations: locations,
|
||||
PURL: "pkg:composer/alcaeus/mongo-php-adapter@1.1.11",
|
||||
Language: pkg.PHP,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)),
|
||||
),
|
||||
Type: pkg.PhpComposerPkg,
|
||||
MetadataType: pkg.PhpComposerJSONMetadataType,
|
||||
Metadata: pkg.PhpComposerJSONMetadata{
|
||||
|
@ -91,9 +94,6 @@ func TestParseComposerFileLock(t *testing.T) {
|
|||
},
|
||||
Type: "library",
|
||||
NotificationURL: "https://packagist.org/downloads/",
|
||||
License: []string{
|
||||
"MIT",
|
||||
},
|
||||
Authors: []pkg.PhpComposerAuthors{
|
||||
{
|
||||
Name: "alcaeus",
|
||||
|
|
|
@ -16,19 +16,19 @@ var _ generic.Parser = parseComposerLock
|
|||
|
||||
// Note: composer version 2 introduced a new structure for the installed.json file, so we support both
|
||||
type installedJSONComposerV2 struct {
|
||||
Packages []pkg.PhpComposerJSONMetadata `json:"packages"`
|
||||
Packages []parsedData `json:"packages"`
|
||||
}
|
||||
|
||||
func (w *installedJSONComposerV2) UnmarshalJSON(data []byte) error {
|
||||
type compv2 struct {
|
||||
Packages []pkg.PhpComposerJSONMetadata `json:"packages"`
|
||||
Packages []parsedData `json:"packages"`
|
||||
}
|
||||
compv2er := new(compv2)
|
||||
err := json.Unmarshal(data, &compv2er)
|
||||
if err != nil {
|
||||
// If we had an err or, we may be dealing with a composer v.1 installed.json
|
||||
// which should be all arrays
|
||||
var packages []pkg.PhpComposerJSONMetadata
|
||||
var packages []parsedData
|
||||
err := json.Unmarshal(data, &packages)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -24,6 +24,9 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
|
|||
Language: pkg.PHP,
|
||||
Type: pkg.PhpComposerPkg,
|
||||
MetadataType: pkg.PhpComposerJSONMetadataType,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
Metadata: pkg.PhpComposerJSONMetadata{
|
||||
Name: "asm89/stack-cors",
|
||||
Version: "1.3.0",
|
||||
|
@ -49,9 +52,6 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
|
|||
Time: "2019-12-24T22:41:47+00:00",
|
||||
Type: "library",
|
||||
NotificationURL: "https://packagist.org/downloads/",
|
||||
License: []string{
|
||||
"MIT",
|
||||
},
|
||||
Authors: []pkg.PhpComposerAuthors{
|
||||
{
|
||||
Name: "Alexander",
|
||||
|
@ -68,11 +68,14 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
Name: "behat/mink",
|
||||
Version: "v1.8.1",
|
||||
PURL: "pkg:composer/behat/mink@v1.8.1",
|
||||
Language: pkg.PHP,
|
||||
Type: pkg.PhpComposerPkg,
|
||||
Name: "behat/mink",
|
||||
Version: "v1.8.1",
|
||||
PURL: "pkg:composer/behat/mink@v1.8.1",
|
||||
Language: pkg.PHP,
|
||||
Type: pkg.PhpComposerPkg,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
MetadataType: pkg.PhpComposerJSONMetadataType,
|
||||
Metadata: pkg.PhpComposerJSONMetadata{
|
||||
Name: "behat/mink",
|
||||
|
@ -106,9 +109,6 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
|
|||
Time: "2020-03-11T15:45:53+00:00",
|
||||
Type: "library",
|
||||
NotificationURL: "https://packagist.org/downloads/",
|
||||
License: []string{
|
||||
"MIT",
|
||||
},
|
||||
Authors: []pkg.PhpComposerAuthors{
|
||||
{
|
||||
Name: "Konstantin Kudryashov",
|
||||
|
@ -133,9 +133,14 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
|
|||
locations := source.NewLocationSet(source.NewLocation(fixture))
|
||||
for i := range expectedPkgs {
|
||||
expectedPkgs[i].Locations = locations
|
||||
locationLicenses := pkg.NewLicenseSet()
|
||||
for _, license := range expectedPkgs[i].Licenses.ToSlice() {
|
||||
license.Location = locations
|
||||
locationLicenses.Add(license)
|
||||
}
|
||||
expectedPkgs[i].Licenses = locationLicenses
|
||||
}
|
||||
pkgtest.TestFileParser(t, fixture, parseInstalledJSON, expectedPkgs, expectedRelationships)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
)
|
||||
|
||||
func TestPortageCataloger(t *testing.T) {
|
||||
|
||||
expectedLicenseLocation := source.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/LICENSE")
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "app-containers/skopeo",
|
||||
|
@ -20,10 +20,10 @@ func TestPortageCataloger(t *testing.T) {
|
|||
PURL: "pkg:ebuild/app-containers/skopeo@1.5.1",
|
||||
Locations: source.NewLocationSet(
|
||||
source.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/CONTENTS"),
|
||||
source.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/LICENSE"),
|
||||
source.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/SIZE"),
|
||||
expectedLicenseLocation,
|
||||
),
|
||||
Licenses: []string{"Apache-2.0", "BSD", "BSD-2", "CC-BY-SA-4.0", "ISC", "MIT"},
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(expectedLicenseLocation, "Apache-2.0", "BSD", "BSD-2", "CC-BY-SA-4.0", "ISC", "MIT")...),
|
||||
Type: pkg.PortagePkg,
|
||||
MetadataType: pkg.PortageMetadataType,
|
||||
Metadata: pkg.PortageMetadata{
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -116,9 +115,9 @@ func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pk
|
|||
findings.Add(token)
|
||||
}
|
||||
}
|
||||
licenses := findings.ToSlice()
|
||||
sort.Strings(licenses)
|
||||
p.Licenses = licenses
|
||||
|
||||
licenseCandidates := findings.ToSlice()
|
||||
p.Licenses = pkg.NewLicenseSet(pkg.NewLicensesFromLocation(*location, licenseCandidates...)...)
|
||||
p.Locations.Add(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
}
|
||||
|
||||
|
|
|
@ -40,18 +40,19 @@ func Test_PackageCataloger(t *testing.T) {
|
|||
"test-fixtures/egg-info/top_level.txt",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "requests",
|
||||
Version: "2.22.0",
|
||||
PURL: "pkg:pypi/requests@2.22.0",
|
||||
Type: pkg.PythonPkg,
|
||||
Language: pkg.Python,
|
||||
Licenses: []string{"Apache 2.0"},
|
||||
Name: "requests",
|
||||
Version: "2.22.0",
|
||||
PURL: "pkg:pypi/requests@2.22.0",
|
||||
Type: pkg.PythonPkg,
|
||||
Language: pkg.Python,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("Apache 2.0", source.NewLocation("test-fixtures/egg-info/PKG-INFO")),
|
||||
),
|
||||
FoundBy: "python-package-cataloger",
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "requests",
|
||||
Version: "2.22.0",
|
||||
License: "Apache 2.0",
|
||||
Platform: "UNKNOWN",
|
||||
Author: "Kenneth Reitz",
|
||||
AuthorEmail: "me@kennethreitz.org",
|
||||
|
@ -77,18 +78,19 @@ func Test_PackageCataloger(t *testing.T) {
|
|||
"test-fixtures/dist-info/direct_url.json",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
PURL: "pkg:pypi/Pygments@2.6.1?vcs_url=git+https://github.com/python-test/test.git%40aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
Type: pkg.PythonPkg,
|
||||
Language: pkg.Python,
|
||||
Licenses: []string{"BSD License"},
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
PURL: "pkg:pypi/Pygments@2.6.1?vcs_url=git+https://github.com/python-test/test.git%40aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
Type: pkg.PythonPkg,
|
||||
Language: pkg.Python,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("BSD License", source.NewLocation("test-fixtures/dist-info/METADATA")),
|
||||
),
|
||||
FoundBy: "python-package-cataloger",
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
License: "BSD License",
|
||||
Platform: "any",
|
||||
Author: "Georg Brandl",
|
||||
AuthorEmail: "georg@python.org",
|
||||
|
@ -114,18 +116,19 @@ func Test_PackageCataloger(t *testing.T) {
|
|||
"test-fixtures/malformed-record/dist-info/RECORD",
|
||||
},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
PURL: "pkg:pypi/Pygments@2.6.1",
|
||||
Type: pkg.PythonPkg,
|
||||
Language: pkg.Python,
|
||||
Licenses: []string{"BSD License"},
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
PURL: "pkg:pypi/Pygments@2.6.1",
|
||||
Type: pkg.PythonPkg,
|
||||
Language: pkg.Python,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("BSD License", source.NewLocation("test-fixtures/malformed-record/dist-info/METADATA")),
|
||||
),
|
||||
FoundBy: "python-package-cataloger",
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
License: "BSD License",
|
||||
Platform: "any",
|
||||
Author: "Georg Brandl",
|
||||
AuthorEmail: "georg@python.org",
|
||||
|
@ -145,18 +148,19 @@ func Test_PackageCataloger(t *testing.T) {
|
|||
name: "partial dist-info directory",
|
||||
fixtures: []string{"test-fixtures/partial.dist-info/METADATA"},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
PURL: "pkg:pypi/Pygments@2.6.1",
|
||||
Type: pkg.PythonPkg,
|
||||
Language: pkg.Python,
|
||||
Licenses: []string{"BSD License"},
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
PURL: "pkg:pypi/Pygments@2.6.1",
|
||||
Type: pkg.PythonPkg,
|
||||
Language: pkg.Python,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("BSD License", source.NewLocation("test-fixtures/partial.dist-info/METADATA")),
|
||||
),
|
||||
FoundBy: "python-package-cataloger",
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
License: "BSD License",
|
||||
Platform: "any",
|
||||
Author: "Georg Brandl",
|
||||
AuthorEmail: "georg@python.org",
|
||||
|
@ -168,18 +172,19 @@ func Test_PackageCataloger(t *testing.T) {
|
|||
name: "egg-info regular file",
|
||||
fixtures: []string{"test-fixtures/test.egg-info"},
|
||||
expectedPackage: pkg.Package{
|
||||
Name: "requests",
|
||||
Version: "2.22.0",
|
||||
PURL: "pkg:pypi/requests@2.22.0",
|
||||
Type: pkg.PythonPkg,
|
||||
Language: pkg.Python,
|
||||
Licenses: []string{"Apache 2.0"},
|
||||
Name: "requests",
|
||||
Version: "2.22.0",
|
||||
PURL: "pkg:pypi/requests@2.22.0",
|
||||
Type: pkg.PythonPkg,
|
||||
Language: pkg.Python,
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicenseFromLocations("Apache 2.0", source.NewLocation("test-fixtures/test.egg-info")),
|
||||
),
|
||||
FoundBy: "python-package-cataloger",
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Metadata: pkg.PythonPackageMetadata{
|
||||
Name: "requests",
|
||||
Version: "2.22.0",
|
||||
License: "Apache 2.0",
|
||||
Platform: "UNKNOWN",
|
||||
Author: "Kenneth Reitz",
|
||||
AuthorEmail: "me@kennethreitz.org",
|
||||
|
|
|
@ -57,25 +57,21 @@ func newPackageForRequirementsWithMetadata(name, version string, metadata pkg.Py
|
|||
return p
|
||||
}
|
||||
|
||||
func newPackageForPackage(m pkg.PythonPackageMetadata, sources ...source.Location) pkg.Package {
|
||||
var licenses []string
|
||||
if m.License != "" {
|
||||
licenses = []string{m.License}
|
||||
}
|
||||
|
||||
func newPackageForPackage(m parsedData, sources ...source.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: m.Name,
|
||||
Version: m.Version,
|
||||
PURL: packageURL(m.Name, m.Version, &m),
|
||||
PURL: packageURL(m.Name, m.Version, &m.PythonPackageMetadata),
|
||||
Locations: source.NewLocationSet(sources...),
|
||||
Licenses: licenses,
|
||||
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(m.LicenseLocation, m.Licenses)...),
|
||||
Language: pkg.Python,
|
||||
Type: pkg.PythonPkg,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
Metadata: m,
|
||||
Metadata: m.PythonPackageMetadata,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
|
|
|
@ -17,21 +17,21 @@ import (
|
|||
|
||||
// parseWheelOrEgg takes the primary metadata file reference and returns the python package it represents.
|
||||
func parseWheelOrEgg(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
metadata, sources, err := assembleEggOrWheelMetadata(resolver, reader.Location)
|
||||
pd, sources, err := assembleEggOrWheelMetadata(resolver, reader.Location)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if metadata == nil {
|
||||
if pd == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// This can happen for Python 2.7 where it is reported from an egg-info, but Python is
|
||||
// the actual runtime, it isn't a "package". The special-casing here allows to skip it
|
||||
if metadata.Name == "Python" {
|
||||
if pd.Name == "Python" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
pkgs := []pkg.Package{newPackageForPackage(*metadata, sources...)}
|
||||
pkgs := []pkg.Package{newPackageForPackage(*pd, sources...)}
|
||||
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ func fetchDirectURLData(resolver source.FileResolver, metadataLocation source.Lo
|
|||
}
|
||||
|
||||
// assembleEggOrWheelMetadata discovers and accumulates python package metadata from multiple file sources and returns a single metadata object as well as a list of files where the metadata was derived from.
|
||||
func assembleEggOrWheelMetadata(resolver source.FileResolver, metadataLocation source.Location) (*pkg.PythonPackageMetadata, []source.Location, error) {
|
||||
func assembleEggOrWheelMetadata(resolver source.FileResolver, metadataLocation source.Location) (*parsedData, []source.Location, error) {
|
||||
var sources = []source.Location{
|
||||
metadataLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
}
|
||||
|
@ -171,12 +171,12 @@ func assembleEggOrWheelMetadata(resolver source.FileResolver, metadataLocation s
|
|||
}
|
||||
defer internal.CloseAndLogError(metadataContents, metadataLocation.VirtualPath)
|
||||
|
||||
metadata, err := parseWheelOrEggMetadata(metadataLocation.RealPath, metadataContents)
|
||||
pd, err := parseWheelOrEggMetadata(metadataLocation.RealPath, metadataContents)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if metadata.Name == "" {
|
||||
if pd.Name == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
|
@ -186,14 +186,14 @@ func assembleEggOrWheelMetadata(resolver source.FileResolver, metadataLocation s
|
|||
return nil, nil, err
|
||||
}
|
||||
if len(r) == 0 {
|
||||
r, s, err = fetchInstalledFiles(resolver, metadataLocation, metadata.SitePackagesRootPath)
|
||||
r, s, err = fetchInstalledFiles(resolver, metadataLocation, pd.SitePackagesRootPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
sources = append(sources, s...)
|
||||
metadata.Files = r
|
||||
pd.Files = r
|
||||
|
||||
// attach any top-level package names found for the given wheel/egg installation
|
||||
p, s, err := fetchTopLevelPackages(resolver, metadataLocation)
|
||||
|
@ -201,15 +201,15 @@ func assembleEggOrWheelMetadata(resolver source.FileResolver, metadataLocation s
|
|||
return nil, nil, err
|
||||
}
|
||||
sources = append(sources, s...)
|
||||
metadata.TopLevelPackages = p
|
||||
pd.TopLevelPackages = p
|
||||
|
||||
// attach any direct-url package data found for the given wheel/egg installation
|
||||
d, s, err := fetchDirectURLData(resolver, metadataLocation)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sources = append(sources, s...)
|
||||
metadata.DirectURLOrigin = d
|
||||
|
||||
return &metadata, sources, nil
|
||||
sources = append(sources, s...)
|
||||
pd.DirectURLOrigin = d
|
||||
return &pd, sources, nil
|
||||
}
|
||||
|
|
|
@ -12,11 +12,18 @@ import (
|
|||
"github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type parsedData struct {
|
||||
Licenses string `mapstructure:"License"`
|
||||
LicenseLocation source.Location
|
||||
pkg.PythonPackageMetadata `mapstructure:",squash"`
|
||||
}
|
||||
|
||||
// parseWheelOrEggMetadata takes a Python Egg or Wheel (which share the same format and values for our purposes),
|
||||
// returning all Python packages listed.
|
||||
func parseWheelOrEggMetadata(path string, reader io.Reader) (pkg.PythonPackageMetadata, error) {
|
||||
func parseWheelOrEggMetadata(path string, reader io.Reader) (parsedData, error) {
|
||||
fields := make(map[string]string)
|
||||
var key string
|
||||
|
||||
|
@ -43,7 +50,7 @@ func parseWheelOrEggMetadata(path string, reader io.Reader) (pkg.PythonPackageMe
|
|||
// a field-body continuation
|
||||
updatedValue, err := handleFieldBodyContinuation(key, line, fields)
|
||||
if err != nil {
|
||||
return pkg.PythonPackageMetadata{}, err
|
||||
return parsedData{}, err
|
||||
}
|
||||
|
||||
fields[key] = updatedValue
|
||||
|
@ -62,19 +69,22 @@ func parseWheelOrEggMetadata(path string, reader io.Reader) (pkg.PythonPackageMe
|
|||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return pkg.PythonPackageMetadata{}, fmt.Errorf("failed to parse python wheel/egg: %w", err)
|
||||
return parsedData{}, fmt.Errorf("failed to parse python wheel/egg: %w", err)
|
||||
}
|
||||
|
||||
var metadata pkg.PythonPackageMetadata
|
||||
if err := mapstructure.Decode(fields, &metadata); err != nil {
|
||||
return pkg.PythonPackageMetadata{}, fmt.Errorf("unable to parse APK metadata: %w", err)
|
||||
var pd parsedData
|
||||
if err := mapstructure.Decode(fields, &pd); err != nil {
|
||||
return pd, fmt.Errorf("unable to parse APK metadata: %w", err)
|
||||
}
|
||||
|
||||
// add additional metadata not stored in the egg/wheel metadata file
|
||||
|
||||
metadata.SitePackagesRootPath = determineSitePackagesRootPath(path)
|
||||
pd.SitePackagesRootPath = determineSitePackagesRootPath(path)
|
||||
if pd.Licenses != "" {
|
||||
pd.LicenseLocation = source.NewLocation(path)
|
||||
}
|
||||
|
||||
return metadata, nil
|
||||
return pd, nil
|
||||
}
|
||||
|
||||
// isEggRegularFile determines if the specified path is the regular file variant
|
||||
|
|
|
@ -7,35 +7,42 @@ import (
|
|||
"github.com/go-test/deep"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestParseWheelEggMetadata(t *testing.T) {
|
||||
tests := []struct {
|
||||
Fixture string
|
||||
ExpectedMetadata pkg.PythonPackageMetadata
|
||||
ExpectedMetadata parsedData
|
||||
}{
|
||||
{
|
||||
Fixture: "test-fixtures/egg-info/PKG-INFO",
|
||||
ExpectedMetadata: pkg.PythonPackageMetadata{
|
||||
Name: "requests",
|
||||
Version: "2.22.0",
|
||||
License: "Apache 2.0",
|
||||
Platform: "UNKNOWN",
|
||||
Author: "Kenneth Reitz",
|
||||
AuthorEmail: "me@kennethreitz.org",
|
||||
SitePackagesRootPath: "test-fixtures",
|
||||
ExpectedMetadata: parsedData{
|
||||
"Apache 2.0",
|
||||
source.NewLocation("test-fixtures/egg-info/PKG-INFO"),
|
||||
pkg.PythonPackageMetadata{
|
||||
Name: "requests",
|
||||
Version: "2.22.0",
|
||||
Platform: "UNKNOWN",
|
||||
Author: "Kenneth Reitz",
|
||||
AuthorEmail: "me@kennethreitz.org",
|
||||
SitePackagesRootPath: "test-fixtures",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Fixture: "test-fixtures/dist-info/METADATA",
|
||||
ExpectedMetadata: pkg.PythonPackageMetadata{
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
License: "BSD License",
|
||||
Platform: "any",
|
||||
Author: "Georg Brandl",
|
||||
AuthorEmail: "georg@python.org",
|
||||
SitePackagesRootPath: "test-fixtures",
|
||||
ExpectedMetadata: parsedData{
|
||||
"BSD License",
|
||||
source.NewLocation("test-fixtures/dist-info/METADATA"),
|
||||
pkg.PythonPackageMetadata{
|
||||
Name: "Pygments",
|
||||
Version: "2.6.1",
|
||||
Platform: "any",
|
||||
Author: "Georg Brandl",
|
||||
AuthorEmail: "georg@python.org",
|
||||
SitePackagesRootPath: "test-fixtures",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -122,14 +129,18 @@ func TestDetermineSitePackagesRootPath(t *testing.T) {
|
|||
func TestParseWheelEggMetadataInvalid(t *testing.T) {
|
||||
tests := []struct {
|
||||
Fixture string
|
||||
ExpectedMetadata pkg.PythonPackageMetadata
|
||||
ExpectedMetadata parsedData
|
||||
}{
|
||||
{
|
||||
Fixture: "test-fixtures/egg-info/PKG-INFO-INVALID",
|
||||
ExpectedMetadata: pkg.PythonPackageMetadata{
|
||||
Name: "mxnet",
|
||||
Version: "1.8.0",
|
||||
SitePackagesRootPath: "test-fixtures",
|
||||
ExpectedMetadata: parsedData{
|
||||
"",
|
||||
source.Location{},
|
||||
pkg.PythonPackageMetadata{
|
||||
Name: "mxnet",
|
||||
Version: "1.8.0",
|
||||
SitePackagesRootPath: "test-fixtures",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ func TestRPackageCataloger(t *testing.T) {
|
|||
Version: "4.3.0",
|
||||
FoundBy: "r-package-cataloger",
|
||||
Locations: source.NewLocationSet(source.NewLocation("base/DESCRIPTION")),
|
||||
Licenses: []string{"Part of R 4.3.0"},
|
||||
Licenses: pkg.NewLicenseSet([]pkg.License{pkg.NewLicense("Part of R 4.3.0")}...),
|
||||
Language: pkg.R,
|
||||
Type: pkg.Rpkg,
|
||||
PURL: "pkg:cran/base@4.3.0",
|
||||
|
@ -35,7 +35,7 @@ func TestRPackageCataloger(t *testing.T) {
|
|||
Version: "1.5.0.9000",
|
||||
FoundBy: "r-package-cataloger",
|
||||
Locations: source.NewLocationSet(source.NewLocation("stringr/DESCRIPTION")),
|
||||
Licenses: []string{"MIT + file LICENSE"},
|
||||
Licenses: pkg.NewLicenseSet([]pkg.License{pkg.NewLicense("MIT")}...),
|
||||
Language: pkg.R,
|
||||
Type: pkg.Rpkg,
|
||||
PURL: "pkg:cran/stringr@1.5.0.9000",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package r
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
|
@ -11,11 +13,14 @@ func newPackage(pd parseData, locations ...source.Location) pkg.Package {
|
|||
for _, loc := range locations {
|
||||
locationSet.Add(loc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
||||
}
|
||||
|
||||
licenses := parseLicenseData(pd.License)
|
||||
|
||||
result := pkg.Package{
|
||||
Name: pd.Package,
|
||||
Version: pd.Version,
|
||||
Locations: locationSet,
|
||||
Licenses: []string{pd.License},
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Language: pkg.R,
|
||||
Type: pkg.Rpkg,
|
||||
PURL: packageURL(pd),
|
||||
|
@ -30,3 +35,95 @@ func newPackage(pd parseData, locations ...source.Location) pkg.Package {
|
|||
func packageURL(m parseData) string {
|
||||
return packageurl.NewPackageURL("cran", "", m.Package, m.Version, nil, "").ToString()
|
||||
}
|
||||
|
||||
// https://r-pkgs.org/description.html#the-license-field
|
||||
// four forms:
|
||||
// 1. "GPL (>= 2)"
|
||||
// 2. "GPL-2"
|
||||
// 3. "MIT + file LICENSE"
|
||||
// 4. "pointer to the full text of the license; file LICENSE"
|
||||
// Multiple licences can be specified separated by ‘|’
|
||||
// (surrounded by spaces) in which case the user can choose any of the above cases.
|
||||
// https://cran.rstudio.com/doc/manuals/r-devel/R-exts.html#Licensing
|
||||
func parseLicenseData(license string, locations ...source.Location) []pkg.License {
|
||||
licenses := make([]pkg.License, 0)
|
||||
|
||||
// check if multiple licenses are separated by |
|
||||
splitField := strings.Split(license, "|")
|
||||
for _, l := range splitField {
|
||||
// check case 1 for surrounding parens
|
||||
l = strings.TrimSpace(l)
|
||||
if strings.Contains(l, "(") && strings.Contains(l, ")") {
|
||||
licenseVersion := strings.SplitN(l, " ", 2)
|
||||
if len(licenseVersion) == 2 {
|
||||
l = strings.Join([]string{licenseVersion[0], parseVersion(licenseVersion[1])}, "")
|
||||
licenses = append(licenses, pkg.NewLicenseFromLocations(l, locations...))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// case 3
|
||||
if strings.Contains(l, "+") && strings.Contains(l, "LICENSE") {
|
||||
splitField := strings.Split(l, " ")
|
||||
if len(splitField) > 0 {
|
||||
licenses = append(licenses, pkg.NewLicenseFromLocations(splitField[0], locations...))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: case 4 if we are able to read the location data and find the adjacent file?
|
||||
if l == "file LICENSE" {
|
||||
continue
|
||||
}
|
||||
|
||||
// no specific case found for the above so assume case 2
|
||||
// check if the common name in case 2 is valid SDPX otherwise value will be populated
|
||||
licenses = append(licenses, pkg.NewLicenseFromLocations(l, locations...))
|
||||
continue
|
||||
}
|
||||
return licenses
|
||||
}
|
||||
|
||||
// attempt to make best guess at SPDX license ID from version operator in case 2
|
||||
/*
|
||||
‘<’, ‘<=’, ‘>’, ‘>=’, ‘==’, or ‘!=’
|
||||
cant be (>= 2.0) OR (>= 2.0, < 3)
|
||||
since there is no way in SPDX licenses to express < some other version
|
||||
we attempt to check the constraint to see if this should be + or not
|
||||
*/
|
||||
func parseVersion(version string) string {
|
||||
version = strings.ReplaceAll(version, "(", "")
|
||||
version = strings.ReplaceAll(version, ")", "")
|
||||
|
||||
// multiple constraints
|
||||
if strings.Contains(version, ",") {
|
||||
multipleConstraints := strings.Split(version, ",")
|
||||
// SPDX does not support considering multiple constraints
|
||||
// so we will just take the first one and attempt to form the best SPDX ID we can
|
||||
for _, v := range multipleConstraints {
|
||||
constraintVersion := strings.SplitN(v, " ", 2)
|
||||
if len(constraintVersion) == 2 {
|
||||
// switch on the operator and return the version with + or without
|
||||
switch constraintVersion[0] {
|
||||
case ">", ">=":
|
||||
return constraintVersion[1] + "+"
|
||||
default:
|
||||
return constraintVersion[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// single constraint
|
||||
singleContraint := strings.Split(version, " ")
|
||||
if len(singleContraint) == 2 {
|
||||
switch singleContraint[0] {
|
||||
case ">", ">=":
|
||||
return singleContraint[1] + "+"
|
||||
default:
|
||||
return singleContraint[1]
|
||||
}
|
||||
}
|
||||
|
||||
// could not parse version constraint so return ""
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -1,14 +1,106 @@
|
|||
package r
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
func Test_newPackage(t *testing.T) {
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_NewPackageLicenses(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
}{}
|
||||
pd parseData
|
||||
want []pkg.License
|
||||
}{
|
||||
{
|
||||
"License field with single valid spdx",
|
||||
parseData{
|
||||
Package: "Foo",
|
||||
Version: "1",
|
||||
License: "MIT",
|
||||
},
|
||||
[]pkg.License{
|
||||
pkg.NewLicense("MIT"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"License field with single version separator no +",
|
||||
parseData{
|
||||
Package: "Bar",
|
||||
Version: "2",
|
||||
License: "LGPL (== 2.0)",
|
||||
},
|
||||
[]pkg.License{
|
||||
pkg.NewLicense("LGPL2.0"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"License field with multiple version separator",
|
||||
parseData{
|
||||
Package: "Bar",
|
||||
Version: "2",
|
||||
License: "LGPL (>= 2.0, < 3)",
|
||||
},
|
||||
[]pkg.License{
|
||||
pkg.NewLicense("LGPL2.0+"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"License field with file reference",
|
||||
parseData{
|
||||
Package: "Baz",
|
||||
Version: "3",
|
||||
License: "GPL-2 + file LICENSE",
|
||||
},
|
||||
[]pkg.License{
|
||||
pkg.NewLicense("GPL-2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"License field which covers no case",
|
||||
parseData{
|
||||
Package: "Baz",
|
||||
Version: "3",
|
||||
License: "Mozilla Public License",
|
||||
},
|
||||
[]pkg.License{
|
||||
pkg.NewLicense("Mozilla Public License"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"License field with multiple cases",
|
||||
parseData{
|
||||
Package: "Baz",
|
||||
Version: "3",
|
||||
License: "GPL-2 | file LICENSE | LGPL (>= 2.0)",
|
||||
},
|
||||
[]pkg.License{
|
||||
pkg.NewLicense("GPL-2"),
|
||||
pkg.NewLicense("LGPL2.0+"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := parseLicenseData(tt.pd.License)
|
||||
if len(got) != len(tt.want) {
|
||||
t.Errorf("unexpected number of licenses: got=%d, want=%d", len(got), len(tt.want))
|
||||
}
|
||||
|
||||
for _, wantLicense := range tt.want {
|
||||
found := false
|
||||
for _, gotLicense := range got {
|
||||
if wantLicense.Type == gotLicense.Type &&
|
||||
wantLicense.SPDXExpression == gotLicense.SPDXExpression &&
|
||||
wantLicense.Value == gotLicense.Value {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("could not find expected license: %+v; got: %+v", wantLicense, got)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,42 +13,46 @@ import (
|
|||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newPackage(location source.Location, metadata pkg.RpmMetadata, distro *linux.Release) pkg.Package {
|
||||
func newPackage(dbOrRpmLocation source.Location, pd parsedData, distro *linux.Release) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: metadata.Name,
|
||||
Version: toELVersion(metadata),
|
||||
PURL: packageURL(metadata, distro),
|
||||
Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Name: pd.Name,
|
||||
Version: toELVersion(pd.RpmMetadata),
|
||||
Licenses: pkg.NewLicenseSet(pd.Licenses...),
|
||||
PURL: packageURL(pd.RpmMetadata, distro),
|
||||
Locations: source.NewLocationSet(dbOrRpmLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
if metadata.License != "" {
|
||||
p.Licenses = append(p.Licenses, metadata.License)
|
||||
Metadata: pd.RpmMetadata,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
return p
|
||||
}
|
||||
|
||||
func newMetadataFromEntry(entry rpmdb.PackageInfo, files []pkg.RpmdbFileRecord) pkg.RpmMetadata {
|
||||
return pkg.RpmMetadata{
|
||||
Name: entry.Name,
|
||||
Version: entry.Version,
|
||||
Epoch: entry.Epoch,
|
||||
Arch: entry.Arch,
|
||||
Release: entry.Release,
|
||||
SourceRpm: entry.SourceRpm,
|
||||
Vendor: entry.Vendor,
|
||||
License: entry.License,
|
||||
Size: entry.Size,
|
||||
ModularityLabel: entry.Modularitylabel,
|
||||
Files: files,
|
||||
type parsedData struct {
|
||||
Licenses []pkg.License
|
||||
pkg.RpmMetadata
|
||||
}
|
||||
|
||||
func newParsedDataFromEntry(licenseLocation source.Location, entry rpmdb.PackageInfo, files []pkg.RpmdbFileRecord) parsedData {
|
||||
return parsedData{
|
||||
Licenses: pkg.NewLicensesFromLocation(licenseLocation, entry.License),
|
||||
RpmMetadata: pkg.RpmMetadata{
|
||||
Name: entry.Name,
|
||||
Version: entry.Version,
|
||||
Epoch: entry.Epoch,
|
||||
Arch: entry.Arch,
|
||||
Release: entry.Release,
|
||||
SourceRpm: entry.SourceRpm,
|
||||
Vendor: entry.Vendor,
|
||||
Size: entry.Size,
|
||||
ModularityLabel: entry.Modularitylabel,
|
||||
Files: files,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newMetadataFromManifestLine(entry string) (*pkg.RpmMetadata, error) {
|
||||
func newMetadataFromManifestLine(entry string) (*parsedData, error) {
|
||||
parts := strings.Split(entry, "\t")
|
||||
if len(parts) < 10 {
|
||||
return nil, fmt.Errorf("unexpected number of fields in line: %s", entry)
|
||||
|
@ -74,16 +78,17 @@ func newMetadataFromManifestLine(entry string) (*pkg.RpmMetadata, error) {
|
|||
if err == nil {
|
||||
size = converted
|
||||
}
|
||||
|
||||
return &pkg.RpmMetadata{
|
||||
Name: parts[0],
|
||||
Version: version,
|
||||
Epoch: epoch,
|
||||
Arch: parts[7],
|
||||
Release: release,
|
||||
SourceRpm: parts[9],
|
||||
Vendor: parts[4],
|
||||
Size: size,
|
||||
return &parsedData{
|
||||
RpmMetadata: pkg.RpmMetadata{
|
||||
Name: parts[0],
|
||||
Version: version,
|
||||
Epoch: epoch,
|
||||
Arch: parts[7],
|
||||
Release: release,
|
||||
SourceRpm: parts[9],
|
||||
Vendor: parts[4],
|
||||
Size: size,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ package rpm
|
|||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
rpmdb "github.com/knqyf263/go-rpmdb/pkg"
|
||||
"github.com/sassoftware/go-rpmutils"
|
||||
|
@ -34,20 +33,22 @@ func parseRpm(_ source.FileResolver, _ *generic.Environment, reader source.Locat
|
|||
size, _ := rpm.Header.InstalledSize()
|
||||
files, _ := rpm.Header.GetFiles()
|
||||
|
||||
metadata := pkg.RpmMetadata{
|
||||
Name: nevra.Name,
|
||||
Version: nevra.Version,
|
||||
Epoch: parseEpoch(nevra.Epoch),
|
||||
Arch: nevra.Arch,
|
||||
Release: nevra.Release,
|
||||
SourceRpm: sourceRpm,
|
||||
Vendor: vendor,
|
||||
License: strings.Join(licenses, " AND "), // TODO: AND conjunction is not necessarily correct, but we don't have a way to represent multiple licenses yet
|
||||
Size: int(size),
|
||||
Files: mapFiles(files, digestAlgorithm),
|
||||
pd := parsedData{
|
||||
Licenses: pkg.NewLicensesFromLocation(reader.Location, licenses...),
|
||||
RpmMetadata: pkg.RpmMetadata{
|
||||
Name: nevra.Name,
|
||||
Version: nevra.Version,
|
||||
Epoch: parseEpoch(nevra.Epoch),
|
||||
Arch: nevra.Arch,
|
||||
Release: nevra.Release,
|
||||
SourceRpm: sourceRpm,
|
||||
Vendor: vendor,
|
||||
Size: int(size),
|
||||
Files: mapFiles(files, digestAlgorithm),
|
||||
},
|
||||
}
|
||||
|
||||
return []pkg.Package{newPackage(reader.Location, metadata, nil)}, nil, nil
|
||||
return []pkg.Package{newPackage(reader.Location, pd, nil)}, nil, nil
|
||||
}
|
||||
|
||||
func getDigestAlgorithm(header *rpmutils.RpmHeader) string {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue