mirror of
https://github.com/anchore/syft.git
synced 2026-11-05 00:46:38 +01:00
feat: detect when full license text has been provided and preserve as separate field (#3450)
* feat: add full text field to syft license struct --------- Signed-off-by: Christopher Angelo Phillips <32073428+spiffcs@users.noreply.github.com>
This commit is contained in:
+30
-4
@@ -25,9 +25,15 @@ var _ sort.Interface = (*Licenses)(nil)
|
||||
// in order to distinguish if packages should be kept separate
|
||||
// this is different for licenses since we're only looking for evidence
|
||||
// of where a license was declared/concluded for a given package
|
||||
// If a license is given as it's full text in the metadata rather than it's value or SPDX expression
|
||||
|
||||
// The FullText field is used to represent this data
|
||||
// A Concluded License type is the license the SBOM creator believes governs the package (human crafted or altered SBOM)
|
||||
// The Declared License is what the authors of a project believe govern the package. This is the default type syft declares.
|
||||
type License struct {
|
||||
Value string
|
||||
SPDXExpression string
|
||||
Value string
|
||||
FullText string
|
||||
Type license.Type
|
||||
URLs []string `hash:"ignore"`
|
||||
Locations file.LocationSet `hash:"ignore"`
|
||||
@@ -68,8 +74,16 @@ func NewLicense(value string) License {
|
||||
}
|
||||
|
||||
func NewLicenseFromType(value string, t license.Type) License {
|
||||
var spdxExpression string
|
||||
if value != "" {
|
||||
var (
|
||||
spdxExpression string
|
||||
fullText string
|
||||
)
|
||||
// Check parsed value for newline character to see if it's the full license text
|
||||
// License: <HERE IS THE FULL TEXT> <Expressions>
|
||||
// DO we want to also submit file name when determining fulltext
|
||||
if strings.Contains(strings.TrimSpace(value), "\n") {
|
||||
fullText = value
|
||||
} else {
|
||||
var err error
|
||||
spdxExpression, err = license.ParseExpression(value)
|
||||
if err != nil {
|
||||
@@ -77,6 +91,14 @@ func NewLicenseFromType(value string, t license.Type) License {
|
||||
}
|
||||
}
|
||||
|
||||
if fullText != "" {
|
||||
return License{
|
||||
FullText: fullText,
|
||||
Type: t,
|
||||
Locations: file.NewLocationSet(),
|
||||
}
|
||||
}
|
||||
|
||||
return License{
|
||||
Value: value,
|
||||
SPDXExpression: spdxExpression,
|
||||
@@ -99,7 +121,7 @@ func NewLicensesFromLocation(location file.Location, values ...string) (licenses
|
||||
}
|
||||
licenses = append(licenses, NewLicenseFromLocations(v, location))
|
||||
}
|
||||
return
|
||||
return licenses
|
||||
}
|
||||
|
||||
func NewLicenseFromLocations(value string, locations ...file.Location) License {
|
||||
@@ -157,6 +179,10 @@ func NewLicenseFromFields(value, url string, location *file.Location) License {
|
||||
return l
|
||||
}
|
||||
|
||||
func (s License) Empty() bool {
|
||||
return s.Value == "" && s.SPDXExpression == "" && s.FullText == ""
|
||||
}
|
||||
|
||||
// Merge two licenses into a new license object. If the merge is not possible due to unmergeable fields
|
||||
// (e.g. different values for Value, SPDXExpression, Type, or any non-collection type) an error is returned.
|
||||
// TODO: this is a bit of a hack to not infinitely recurse when hashing a license
|
||||
|
||||
@@ -50,15 +50,16 @@ func (s *LicenseSet) Add(licenses ...License) {
|
||||
s.set = make(map[artifact.ID]License)
|
||||
}
|
||||
for _, l := range licenses {
|
||||
// we only want to add licenses that have a value
|
||||
// we only want to add licenses that are not empty
|
||||
if l.Empty() {
|
||||
continue
|
||||
}
|
||||
// note, this check should be moved to the license constructor in the future
|
||||
if l.Value != "" {
|
||||
if id, merged, err := s.addToExisting(l); err == nil && !merged {
|
||||
// doesn't exist, add it
|
||||
s.set[id] = l
|
||||
} else if err != nil {
|
||||
log.Trace("license set failed to add license %#v: %+v", l, err)
|
||||
}
|
||||
if id, merged, err := s.addToExisting(l); err == nil && !merged {
|
||||
// doesn't exist, add it
|
||||
s.set[id] = l
|
||||
} else if err != nil {
|
||||
log.WithFields("error", err, "license", l).Trace("failed to add license to license set")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
)
|
||||
|
||||
func Test_Hash(t *testing.T) {
|
||||
|
||||
loc1 := file.NewLocation("place!")
|
||||
loc1.FileSystemID = "fs1"
|
||||
loc2 := file.NewLocation("place!")
|
||||
@@ -227,6 +226,33 @@ func TestLicense_Merge(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFullText(t *testing.T) {
|
||||
fullText := `I am a license with full text
|
||||
my authors put new line characters in metadata for labeling a license`
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
want License
|
||||
}{
|
||||
{
|
||||
name: "Full Text field is populated with the correct full text",
|
||||
value: fullText,
|
||||
want: License{
|
||||
Value: "",
|
||||
Type: license.Declared,
|
||||
FullText: fullText,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := NewLicense(tt.value)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLicenseConstructors(t *testing.T) {
|
||||
type input struct {
|
||||
value string
|
||||
@@ -244,7 +270,6 @@ func TestLicenseConstructors(t *testing.T) {
|
||||
urls: []string{
|
||||
`
|
||||
http://user-agent-utils.googlecode.com/svn/trunk/UserAgentUtils/LICENSE.txt
|
||||
|
||||
`},
|
||||
},
|
||||
expected: License{
|
||||
|
||||
Reference in New Issue
Block a user