mirror of
https://github.com/anchore/syft.git
synced 2026-11-05 08:56:41 +01: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:
@@ -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 ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user