fix: add support for licenses not found on list (#1540)

Signed-off-by: Avi Deitcher <avi@deitcher.net>
This commit is contained in:
Avi Deitcher 2023-02-07 18:47:04 +02:00 committed by GitHub
parent deb7052f41
commit 38a090c218
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 113 additions and 18 deletions

View File

@ -12,9 +12,22 @@ import (
// EX: gpl-2.0.0-only ---> GPL-2.0-only // EX: gpl-2.0.0-only ---> GPL-2.0-only
// See the debian link for more details on the spdx license differences // See the debian link for more details on the spdx license differences
const (
LicenseRefPrefix = "LicenseRef-" // prefix for non-standard licenses
)
//go:generate go run ./generate //go:generate go run ./generate
func ID(id string) (string, bool) { func ID(id string) (value, other string, exists bool) {
value, exists := licenseIDs[strings.ToLower(id)] id = strings.TrimSpace(id)
return value, exists // ignore blank strings or the joiner
if id == "" || id == "AND" {
return "", "", false
}
// first look for a canonical license
if value, exists := licenseIDs[strings.ToLower(id)]; exists {
return value, "", exists
}
// we did not find, so treat it as a separate license
return "", id, true
} }

View File

@ -9,52 +9,91 @@ import (
func TestIDParse(t *testing.T) { func TestIDParse(t *testing.T) {
var tests = []struct { var tests = []struct {
shortName string shortName string
spdx string id string
other string
found bool
}{ }{
{ {
"GPL-1-only", "GPL-1-only",
"GPL-1.0-only", "GPL-1.0-only",
"",
true,
}, },
{ {
"GPL-2", "GPL-2",
"GPL-2.0-only", "GPL-2.0-only",
"",
true,
}, },
{ {
"GPL-2+", "GPL-2+",
"GPL-2.0-or-later", "GPL-2.0-or-later",
"",
true,
}, },
{ {
"GPL-3.0.0-or-later", "GPL-3.0.0-or-later",
"GPL-3.0-or-later", "GPL-3.0-or-later",
"",
true,
}, },
{ {
"GPL-3-with-autoconf-exception", "GPL-3-with-autoconf-exception",
"GPL-3.0-with-autoconf-exception", "GPL-3.0-with-autoconf-exception",
"",
true,
}, },
{ {
"CC-by-nc-3-de", "CC-by-nc-3-de",
"CC-BY-NC-3.0-DE", "CC-BY-NC-3.0-DE",
"",
true,
}, },
// the below few cases are NOT expected, however, seem unavoidable given the current approach // the below few cases are NOT expected, however, seem unavoidable given the current approach
{ {
"w3c-20150513.0.0", "w3c-20150513.0.0",
"W3C-20150513", "W3C-20150513",
"",
true,
}, },
{ {
"spencer-86.0.0", "spencer-86.0.0",
"Spencer-86", "Spencer-86",
"",
true,
}, },
{ {
"unicode-dfs-2015.0.0", "unicode-dfs-2015.0.0",
"Unicode-DFS-2015", "Unicode-DFS-2015",
"",
true,
},
{
"Unknown",
"",
"Unknown",
true,
},
{
" ",
"",
"",
false,
},
{
"AND",
"",
"",
false,
}, },
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.shortName, func(t *testing.T) { t.Run(test.shortName, func(t *testing.T) {
got, exists := ID(test.shortName) value, other, exists := ID(test.shortName)
assert.True(t, exists) assert.Equal(t, test.found, exists)
assert.Equal(t, test.spdx, got) assert.Equal(t, test.id, value)
assert.Equal(t, test.other, other)
}) })
} }
} }

View File

@ -10,10 +10,11 @@ import (
func encodeLicenses(p pkg.Package) *cyclonedx.Licenses { func encodeLicenses(p pkg.Package) *cyclonedx.Licenses {
lc := cyclonedx.Licenses{} lc := cyclonedx.Licenses{}
for _, licenseName := range p.Licenses { for _, licenseName := range p.Licenses {
if value, exists := spdxlicense.ID(licenseName); exists { if value, other, exists := spdxlicense.ID(licenseName); exists {
lc = append(lc, cyclonedx.LicenseChoice{ lc = append(lc, cyclonedx.LicenseChoice{
License: &cyclonedx.License{ License: &cyclonedx.License{
ID: value, ID: value,
Name: other,
}, },
}) })
} }
@ -28,7 +29,16 @@ func decodeLicenses(c *cyclonedx.Component) (out []string) {
if c.Licenses != nil { if c.Licenses != nil {
for _, l := range *c.Licenses { for _, l := range *c.Licenses {
if l.License != nil { if l.License != nil {
out = append(out, l.License.ID) 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)
} }
} }
} }

View File

@ -27,7 +27,9 @@ func Test_encodeLicense(t *testing.T) {
"made-up", "made-up",
}, },
}, },
expected: nil, expected: &cyclonedx.Licenses{
{License: &cyclonedx.License{Name: "made-up"}},
},
}, },
{ {
name: "with SPDX license", name: "with SPDX license",

View File

@ -22,12 +22,7 @@ func License(p pkg.Package) string {
} }
// 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/ // 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/
var parsedLicenses []string parsedLicenses := parseLicenses(p.Licenses)
for _, l := range p.Licenses {
if value, exists := spdxlicense.ID(l); exists {
parsedLicenses = append(parsedLicenses, value)
}
}
if len(parsedLicenses) == 0 { if len(parsedLicenses) == 0 {
return NOASSERTION return NOASSERTION
@ -35,3 +30,16 @@ func License(p pkg.Package) string {
return strings.Join(parsedLicenses, " AND ") return strings.Join(parsedLicenses, " AND ")
} }
func parseLicenses(raw []string) (parsedLicenses []string) {
for _, l := range raw {
if value, other, exists := spdxlicense.ID(l); exists {
parsed := value
if other != "" {
parsed = spdxlicense.LicenseRefPrefix + other
}
parsedLicenses = append(parsedLicenses, parsed)
}
}
return
}

View File

@ -26,7 +26,7 @@ func Test_License(t *testing.T) {
"made-up", "made-up",
}, },
}, },
expected: NOASSERTION, expected: "LicenseRef-made-up",
}, },
{ {
name: "with SPDX license", name: "with SPDX license",

View File

@ -124,6 +124,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
Packages: toPackages(s.Artifacts.PackageCatalog, s), Packages: toPackages(s.Artifacts.PackageCatalog, s),
Files: toFiles(s), Files: toFiles(s),
Relationships: relationships, Relationships: relationships,
OtherLicenses: toOtherLicenses(s.Artifacts.PackageCatalog),
} }
} }
@ -511,6 +512,28 @@ func toFileTypes(metadata *source.FileMetadata) (ty []string) {
return ty return ty
} }
func toOtherLicenses(catalog *pkg.Catalog) []*spdx.OtherLicense {
licenses := map[string]bool{}
for _, pkg := range catalog.Sorted() {
for _, license := range parseLicenses(pkg.Licenses) {
if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
licenses[license] = true
}
}
}
var result []*spdx.OtherLicense
for license := range licenses {
// separate the actual ID from the prefix
name := strings.TrimPrefix(license, spdxlicense.LicenseRefPrefix)
result = append(result, &spdx.OtherLicense{
LicenseIdentifier: license,
LicenseName: name,
ExtractedText: NONE, // we probably should have some extracted text here, but this is good enough for now
})
}
return result
}
// TODO: handle SPDX excludes file case // TODO: handle SPDX excludes file case
// f file is an "excludes" file, skip it /* exclude SPDX analysis file(s) */ // f file is an "excludes" file, skip it /* exclude SPDX analysis file(s) */
// see: https://spdx.github.io/spdx-spec/v2.3/package-information/#79-package-verification-code-field // see: https://spdx.github.io/spdx-spec/v2.3/package-information/#79-package-verification-code-field