mirror of
https://github.com/anchore/syft.git
synced 2026-11-04 08:26:38 +01:00
feat: update licenses to including license content when SPDX expressions are unable to be determined (#3366)
--------- Signed-off-by: HeyeOpenSource <opensource@heye-international.com> Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com> Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
This commit is contained in:
co-authored by
Christopher Phillips
parent
58dc43de86
commit
f7e767fc25
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
internallicenses "github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/mimetype"
|
||||
"github.com/anchore/syft/internal/relationship"
|
||||
@@ -762,10 +763,16 @@ func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
|
||||
if license.Value == "" {
|
||||
value, _ = strings.CutPrefix(license.ID, "LicenseRef-")
|
||||
}
|
||||
result = append(result, &spdx.OtherLicense{
|
||||
other := &spdx.OtherLicense{
|
||||
LicenseIdentifier: license.ID,
|
||||
ExtractedText: value,
|
||||
})
|
||||
}
|
||||
customPrefix := spdxlicense.LicenseRefPrefix + helpers.SanitizeElementID(internallicenses.UnknownLicensePrefix)
|
||||
if strings.HasPrefix(license.ID, customPrefix) {
|
||||
other.LicenseName = strings.TrimPrefix(license.ID, customPrefix)
|
||||
other.LicenseComment = strings.Trim(internallicenses.UnknownLicensePrefix, "-_")
|
||||
}
|
||||
result = append(result, other)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
@@ -110,7 +112,7 @@ func separateLicenses(p pkg.Package) (spdx, other cyclonedx.Licenses, expression
|
||||
continue
|
||||
}
|
||||
|
||||
if l.SPDXExpression != "" {
|
||||
if l.SPDXExpression != "" && !strings.HasPrefix(l.SPDXExpression, licenses.UnknownLicensePrefix) {
|
||||
// COMPLEX EXPRESSION CASE
|
||||
ex = append(ex, l.SPDXExpression)
|
||||
continue
|
||||
@@ -118,17 +120,43 @@ func separateLicenses(p pkg.Package) (spdx, other cyclonedx.Licenses, expression
|
||||
|
||||
// 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 len(l.URLs) > 0 {
|
||||
if len(l.URLs) > 0 && !strings.HasPrefix(l.SPDXExpression, licenses.UnknownLicensePrefix) {
|
||||
processLicenseURLs(l, "", &otherc)
|
||||
continue
|
||||
}
|
||||
otherc = append(otherc, cyclonedx.LicenseChoice{
|
||||
|
||||
otherc = append(otherc, processCustomLicense(l)...)
|
||||
}
|
||||
return spdxc, otherc, ex
|
||||
}
|
||||
|
||||
func processCustomLicense(l pkg.License) cyclonedx.Licenses {
|
||||
result := cyclonedx.Licenses{}
|
||||
if strings.HasPrefix(l.SPDXExpression, licenses.UnknownLicensePrefix) {
|
||||
cyclonedxLicense := &cyclonedx.License{
|
||||
Name: l.SPDXExpression,
|
||||
}
|
||||
if len(l.URLs) > 0 {
|
||||
cyclonedxLicense.URL = l.URLs[0]
|
||||
}
|
||||
if len(l.Contents) > 0 {
|
||||
cyclonedxLicense.Text = &cyclonedx.AttachedText{
|
||||
Content: base64.StdEncoding.EncodeToString([]byte(l.Contents)),
|
||||
}
|
||||
cyclonedxLicense.Text.ContentType = "text/plain"
|
||||
cyclonedxLicense.Text.Encoding = "base64"
|
||||
}
|
||||
result = append(result, cyclonedx.LicenseChoice{
|
||||
License: cyclonedxLicense,
|
||||
})
|
||||
} else {
|
||||
result = append(result, cyclonedx.LicenseChoice{
|
||||
License: &cyclonedx.License{
|
||||
Name: l.Value,
|
||||
},
|
||||
})
|
||||
}
|
||||
return spdxc, otherc, ex
|
||||
return result
|
||||
}
|
||||
|
||||
func processLicenseURLs(l pkg.License, spdxID string, populate *cyclonedx.Licenses) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -69,19 +70,26 @@ func ParseLicenses(raw []pkg.License) (concluded, declared []SPDXLicense) {
|
||||
}
|
||||
|
||||
candidate := SPDXLicense{}
|
||||
if l.SPDXExpression != "" {
|
||||
if l.SPDXExpression != "" && !strings.HasPrefix(l.SPDXExpression, licenses.UnknownLicensePrefix) {
|
||||
candidate.ID = l.SPDXExpression
|
||||
} else {
|
||||
// we did not find a valid SPDX license ID so treat as separate license
|
||||
if len(l.Value) <= 64 {
|
||||
// if the license text is less than the size of the hash,
|
||||
// just use it directly so the id is more readable
|
||||
candidate.ID = spdxlicense.LicenseRefPrefix + SanitizeElementID(l.Value)
|
||||
} else {
|
||||
hash := sha256.Sum256([]byte(l.Value))
|
||||
candidate.ID = fmt.Sprintf("%s%x", spdxlicense.LicenseRefPrefix, hash)
|
||||
}
|
||||
candidate.Value = l.Value
|
||||
// we did not find a valid SPDX license ID so treat as separate license
|
||||
if strings.HasPrefix(l.SPDXExpression, licenses.UnknownLicensePrefix) {
|
||||
candidate.ID = spdxlicense.LicenseRefPrefix + SanitizeElementID(l.SPDXExpression)
|
||||
if len(l.Contents) > 0 {
|
||||
candidate.Value = l.Contents
|
||||
}
|
||||
} else {
|
||||
if len(l.Value) <= 64 {
|
||||
// if the license text is less than the size of the hash,
|
||||
// just use it directly so the id is more readable
|
||||
candidate.ID = spdxlicense.LicenseRefPrefix + SanitizeElementID(l.Value)
|
||||
} else {
|
||||
hash := sha256.Sum256([]byte(l.Value))
|
||||
candidate.ID = fmt.Sprintf("%s%x", spdxlicense.LicenseRefPrefix, hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch l.Type {
|
||||
|
||||
@@ -51,6 +51,7 @@ type License struct {
|
||||
Type license.Type `json:"type"`
|
||||
URLs []string `json:"urls"`
|
||||
Locations []file.Location `json:"locations"`
|
||||
Contents string `json:"contents,omitempty"`
|
||||
}
|
||||
|
||||
func newModelLicensesFromValues(licenses []string) (ml []License) {
|
||||
|
||||
@@ -234,6 +234,7 @@ func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
|
||||
Type: l.Type,
|
||||
URLs: urls,
|
||||
Locations: locations,
|
||||
Contents: l.Contents,
|
||||
})
|
||||
}
|
||||
return
|
||||
|
||||
@@ -164,6 +164,7 @@ func toSyftLicenses(m []model.License) (p []pkg.License) {
|
||||
Type: l.Type,
|
||||
URLs: l.URLs,
|
||||
Locations: file.NewLocationSet(l.Locations...),
|
||||
Contents: l.Contents,
|
||||
})
|
||||
}
|
||||
return
|
||||
|
||||
@@ -36,6 +36,7 @@ type goLicense struct {
|
||||
Type license.Type `json:"type,omitempty"`
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
Locations []string `json:"locations,omitempty"`
|
||||
Contents string `json:"contents,omitempty"`
|
||||
}
|
||||
|
||||
type goLicenseResolver struct {
|
||||
@@ -449,6 +450,7 @@ func toPkgLicenses(goLicenses []goLicense) []pkg.License {
|
||||
Type: l.Type,
|
||||
URLs: l.URLs,
|
||||
Locations: toPkgLocations(l.Locations),
|
||||
Contents: l.Contents,
|
||||
})
|
||||
}
|
||||
return requireCollection(out)
|
||||
@@ -471,6 +473,7 @@ func toGoLicenses(pkgLicenses []pkg.License) []goLicense {
|
||||
Type: l.Type,
|
||||
URLs: l.URLs,
|
||||
Locations: toGoLocations(l.Locations),
|
||||
Contents: l.Contents,
|
||||
})
|
||||
}
|
||||
return out
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/licensecheck"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
@@ -70,7 +71,7 @@ func Test_LicenseSearch(t *testing.T) {
|
||||
|
||||
localVendorDir := filepath.Join(wd, "test-fixtures", "licenses-vendor")
|
||||
|
||||
licenseScanner := licenses.TestingOnlyScanner()
|
||||
licenseScanner := licenses.NewScanner(licensecheck.Scan, float64(75))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -295,7 +296,7 @@ func Test_findVersionPath(t *testing.T) {
|
||||
|
||||
func Test_walkDirErrors(t *testing.T) {
|
||||
resolver := newGoLicenseResolver("", CatalogerConfig{})
|
||||
_, err := resolver.findLicensesInFS(context.Background(), licenses.TestingOnlyScanner(), "somewhere", badFS{})
|
||||
_, err := resolver.findLicensesInFS(context.Background(), licenses.NewScanner(licensecheck.Scan, float64(75)), "somewhere", badFS{})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -313,8 +314,7 @@ func Test_noLocalGoModDir(t *testing.T) {
|
||||
validTmp := t.TempDir()
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(validTmp, "mod@ver"), 0700|os.ModeDir))
|
||||
|
||||
licenseScanner := licenses.TestingOnlyScanner()
|
||||
|
||||
licenseScanner := licenses.NewScanner(licensecheck.Scan, float64(75))
|
||||
tests := []struct {
|
||||
name string
|
||||
dir string
|
||||
@@ -353,3 +353,30 @@ func Test_noLocalGoModDir(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLicenseConversion(t *testing.T) {
|
||||
inputLicenses := []pkg.License{
|
||||
{
|
||||
Value: "Apache-2.0",
|
||||
SPDXExpression: "Apache-2.0",
|
||||
Type: "concluded",
|
||||
URLs: nil,
|
||||
Locations: file.NewLocationSet(file.NewLocation("LICENSE")),
|
||||
Contents: "",
|
||||
},
|
||||
{
|
||||
Value: "UNKNOWN",
|
||||
SPDXExpression: "UNKNOWN_4d1cffe420916f2b706300ab63fcafaf35226a0ad3725cb9f95b26036cefae32",
|
||||
Type: "declared",
|
||||
URLs: nil,
|
||||
Locations: file.NewLocationSet(file.NewLocation("LICENSE2")),
|
||||
Contents: "NVIDIA Software License Agreement and CUDA Supplement to Software License Agreement",
|
||||
},
|
||||
}
|
||||
|
||||
goLicenses := toGoLicenses(inputLicenses)
|
||||
|
||||
result := toPkgLicenses(goLicenses)
|
||||
|
||||
require.Equal(t, inputLicenses, result)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/google/licensecheck"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -169,7 +170,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
licenseScanner := licenses.TestingOnlyScanner()
|
||||
licenseScanner := licenses.NewScanner(licensecheck.Scan, float64(75))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/google/licensecheck"
|
||||
"github.com/gookit/color"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -32,7 +33,7 @@ import (
|
||||
func TestSearchMavenForLicenses(t *testing.T) {
|
||||
url := maventest.MockRepo(t, "internal/maven/test-fixtures/maven-repo")
|
||||
|
||||
ctx := licenses.SetContextLicenseScanner(context.Background(), licenses.TestingOnlyScanner())
|
||||
ctx := licenses.SetContextLicenseScanner(context.Background(), licenses.NewScanner(licensecheck.Scan, float64(75)))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -91,7 +92,7 @@ func TestSearchMavenForLicenses(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseJar(t *testing.T) {
|
||||
ctx := licenses.SetContextLicenseScanner(context.Background(), licenses.TestingOnlyScanner())
|
||||
ctx := licenses.SetContextLicenseScanner(context.Background(), licenses.NewScanner(licensecheck.Scan, float64(75)))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -1374,7 +1375,7 @@ func Test_parseJavaArchive_regressions(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_deterministicMatchingPomProperties(t *testing.T) {
|
||||
ctx := licenses.SetContextLicenseScanner(context.Background(), licenses.TestingOnlyScanner())
|
||||
ctx := licenses.SetContextLicenseScanner(context.Background(), licenses.NewScanner(licensecheck.Scan, float64(75)))
|
||||
|
||||
tests := []struct {
|
||||
fixture string
|
||||
|
||||
@@ -31,6 +31,7 @@ type License struct {
|
||||
Type license.Type
|
||||
URLs []string `hash:"ignore"`
|
||||
Locations file.LocationSet `hash:"ignore"`
|
||||
Contents string `hash:"ignore"` // The optional binary contents of the license file
|
||||
}
|
||||
|
||||
type Licenses []License
|
||||
|
||||
Reference in New Issue
Block a user