diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go index 0a475cd2e2..e546eee6f8 100644 --- a/pkg/fanal/analyzer/analyzer.go +++ b/pkg/fanal/analyzer/analyzer.go @@ -18,6 +18,7 @@ import ( fos "github.com/aquasecurity/trivy/pkg/fanal/analyzer/os" "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/licensing" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/misconf" xio "github.com/aquasecurity/trivy/pkg/x/io" @@ -287,6 +288,7 @@ func (r *AnalysisResult) Merge(newResult *AnalysisResult) { } if len(newResult.Applications) > 0 { + normalizeApplicationsLicenses(newResult.Applications) r.Applications = append(r.Applications, newResult.Applications...) } @@ -335,6 +337,14 @@ func belongToGroup(groupName Group, analyzerType Type, disabledAnalyzers []Type, return true } +func normalizeApplicationsLicenses(applications []types.Application) { + for i, app := range applications { + for j, pkg := range app.Packages { + applications[i].Packages[j].Licenses = licensing.NormalizeLicenses(pkg.Licenses) + } + } +} + const separator = ":" func NewAnalyzerGroup(opts AnalyzerOptions) (AnalyzerGroup, error) { diff --git a/pkg/fanal/analyzer/analyzer_test.go b/pkg/fanal/analyzer/analyzer_test.go index 0183108521..480372bb7a 100644 --- a/pkg/fanal/analyzer/analyzer_test.go +++ b/pkg/fanal/analyzer/analyzer_test.go @@ -276,6 +276,73 @@ func TestAnalysisResult_Merge(t *testing.T) { }, }, }, + { + name: "normalize licenses for PackageInfos and Applications", + args: args{ + new: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: "gomod", + FilePath: "go.mod", + Packages: types.Packages{ + { + Name: "github.com/example/package", + Version: "v1.0.0", + Licenses: []string{ + "", + "BSD", + "GPL-2", + "GPL-2.0", + }, + }, + }, + }, + { + Type: "gomod", + FilePath: "empty-license/go.mod", + Packages: types.Packages{ + { + Name: "github.com/empty/license", + Version: "v1.0.0", + Licenses: []string{ + "", + }, + }, + }, + }, + }, + }, + }, + want: analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: "gomod", + FilePath: "go.mod", + Packages: types.Packages{ + { + Name: "github.com/example/package", + Version: "v1.0.0", + Licenses: []string{ + "BSD-3-Clause", + "GPL-2.0-only", + }, + }, + }, + }, + { + Type: "gomod", + FilePath: "empty-license/go.mod", + Packages: types.Packages{ + { + Name: "github.com/empty/license", + Version: "v1.0.0", + Licenses: nil, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/fanal/analyzer/language/analyze.go b/pkg/fanal/analyzer/language/analyze.go index 6d50a84d1e..c0157e4a61 100644 --- a/pkg/fanal/analyzer/language/analyze.go +++ b/pkg/fanal/analyzer/language/analyze.go @@ -9,7 +9,6 @@ import ( "github.com/aquasecurity/trivy/pkg/digest" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/licensing" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -104,10 +103,6 @@ func toApplication(fileType types.LangType, filePath, libFilePath string, r xio. pkgs[i].DependsOn = deps[pkg.ID] pkgs[i].Digest = d pkgs[i].Indirect = isIndirect(pkg.Relationship) // For backward compatibility - - for j, license := range pkg.Licenses { - pkgs[i].Licenses[j] = licensing.Normalize(license) - } } return &types.Application{ diff --git a/pkg/fanal/analyzer/language/java/pom/pom_test.go b/pkg/fanal/analyzer/language/java/pom/pom_test.go index 3c834f4497..af9888b6a1 100644 --- a/pkg/fanal/analyzer/language/java/pom/pom_test.go +++ b/pkg/fanal/analyzer/language/java/pom/pom_test.go @@ -33,7 +33,7 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { ID: "com.example:example:1.0.0", Name: "com.example:example", Version: "1.0.0", - Licenses: []string{"Apache-2.0"}, + Licenses: []string{"Apache 2.0"}, Relationship: types.RelationshipRoot, DependsOn: []string{ "com.example:example-api:2.0.0", @@ -71,7 +71,7 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { Name: "com.example:example", Version: "1.0.0", Relationship: types.RelationshipRoot, - Licenses: []string{"Apache-2.0"}, + Licenses: []string{"Apache 2.0"}, DependsOn: []string{ "com.example:example-api:2.0.0", }, @@ -106,7 +106,7 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { ID: "com.example:example:1.0.0", Name: "com.example:example", Version: "1.0.0", - Licenses: []string{"Apache-2.0"}, + Licenses: []string{"Apache 2.0"}, Relationship: types.RelationshipRoot, DependsOn: []string{ "com.example:example-api:@example.version@", @@ -144,7 +144,7 @@ func Test_pomAnalyzer_Analyze(t *testing.T) { ID: "com.example:example:2.0.0", Name: "com.example:example", Version: "2.0.0", - Licenses: []string{"Apache-2.0"}, + Licenses: []string{"Apache 2.0"}, Relationship: types.RelationshipRoot, }, { diff --git a/pkg/fanal/analyzer/language/python/packaging/egg_test.go b/pkg/fanal/analyzer/language/python/packaging/egg_test.go index 5fdd6abb69..709705f88e 100644 --- a/pkg/fanal/analyzer/language/python/packaging/egg_test.go +++ b/pkg/fanal/analyzer/language/python/packaging/egg_test.go @@ -32,7 +32,7 @@ func Test_eggAnalyzer_Analyze(t *testing.T) { Name: "kitchen", Version: "1.2.6", Licenses: []string{ - "LGPL-2.1-only", + "GNU Library or Lesser General Public License (LGPL)", }, FilePath: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", }, @@ -55,7 +55,7 @@ func Test_eggAnalyzer_Analyze(t *testing.T) { Name: "kitchen", Version: "1.2.6", Licenses: []string{ - "LGPL-2.1-only", + "GNU Library or Lesser General Public License (LGPL)", }, FilePath: "testdata/egg-zip/kitchen-1.2.6-py2.7.egg", Digest: "sha1:4e13b6e379966771e896ee43cf8e240bf6083dca", diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go index 06f6ed739e..c0623040e5 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging_test.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging_test.go @@ -32,7 +32,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Name: "distlib", Version: "0.3.1", - Licenses: []string{"Python-2.0"}, + Licenses: []string{"Python license"}, FilePath: "distlib-0.3.1.egg-info/PKG-INFO", Digest: "sha1:d9d89d8ed3b2b683767c96814c9c5d3e57ef2e1b", }, @@ -53,7 +53,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Name: "setuptools", Version: "51.3.3", - Licenses: []string{"MIT"}, + Licenses: []string{"MIT License"}, FilePath: "setuptools-51.3.3.egg-info/PKG-INFO", }, }, @@ -73,7 +73,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Name: "setuptools", Version: "51.3.3", - Licenses: []string{"MIT"}, + Licenses: []string{"MIT License"}, FilePath: "setuptools-51.3.3.dist-info/METADATA", }, }, @@ -93,7 +93,7 @@ func Test_packagingAnalyzer_Analyze(t *testing.T) { { Name: "distlib", Version: "0.3.1", - Licenses: []string{"Python-2.0"}, + Licenses: []string{"Python license"}, FilePath: "distlib-0.3.1.dist-info/METADATA", }, }, diff --git a/pkg/fanal/analyzer/pkg/dpkg/copyright.go b/pkg/fanal/analyzer/pkg/dpkg/copyright.go index 4976d45b40..b5e2932ff3 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/copyright.go +++ b/pkg/fanal/analyzer/pkg/dpkg/copyright.go @@ -7,9 +7,9 @@ import ( "os" "path" "regexp" - "slices" "strings" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -88,30 +88,19 @@ func (a *dpkgLicenseAnalyzer) parseCopyright(r xio.ReadSeekerAt) ([]types.Licens l := strings.TrimSpace(line[8:]) l = normalizeLicense(l) - if l != "" { - for _, lic := range licensing.SplitLicenses(l) { - if lic == "" { - continue - } - - lic = licensing.Normalize(lic) - if !slices.Contains(licenses, lic) { - licenses = append(licenses, lic) - } - } - } + licenses = append(licenses, licensing.SplitLicenses(l)...) case strings.Contains(line, "/usr/share/common-licenses/"): // Common license pattern license := commonLicenseReferenceRegexp.FindStringSubmatch(line) if len(license) == 2 { - l := licensing.Normalize(license[1]) - if l != "" && !slices.Contains(licenses, l) { - licenses = append(licenses, l) - } + licenses = append(licenses, license[1]) } } } + licenses = licensing.NormalizeLicenses(licenses) + licenses = lo.Uniq(licenses) + return xslices.Map(licenses, func(license string) types.LicenseFinding { return types.LicenseFinding{Name: license} }), nil diff --git a/pkg/licensing/normalize.go b/pkg/licensing/normalize.go index b39c6de373..a75a8df397 100644 --- a/pkg/licensing/normalize.go +++ b/pkg/licensing/normalize.go @@ -2,8 +2,11 @@ package licensing import ( "regexp" + "slices" "strings" + "github.com/samber/lo/it" + expr "github.com/aquasecurity/trivy/pkg/licensing/expression" ) @@ -671,11 +674,23 @@ func standardizeKeyAndSuffix(name string) expr.SimpleExpr { return expr.SimpleExpr{License: name, HasPlus: hasPlus} } -func Normalize(name string) string { - return NormalizeLicense(expr.SimpleExpr{License: name}).String() +func NormalizeLicenses(licenses []string) []string { + seq := it.UniqMap(slices.Values(licenses), normalizeLicense) + seq = it.Compact(seq) + normalized := slices.Collect(seq) + + if len(normalized) == 0 { + return nil + } + + return normalized } -func NormalizeLicense(exp expr.Expression) expr.Expression { +func normalizeLicense(name string) string { + return NormalizeLicenseExpression(expr.SimpleExpr{License: name}).String() +} + +func NormalizeLicenseExpression(exp expr.Expression) expr.Expression { switch e := exp.(type) { case expr.SimpleExpr: return normalizeSimpleExpr(e) @@ -762,7 +777,7 @@ func LaxSplitLicenses(str string) []string { afterWith = true continue default: - normalizedLicense := Normalize(s) + normalizedLicense := normalizeLicense(s) if afterWith && len(licenses) > 0 { // If we found "WITH" operator, we should not split the license // e.g. "GPL-2 WITH Autoconf exception" => {"GPL-2 WITH Autoconf exception"} diff --git a/pkg/licensing/normalize_private_test.go b/pkg/licensing/normalize_private_test.go index 68d62f2419..01c4409293 100644 --- a/pkg/licensing/normalize_private_test.go +++ b/pkg/licensing/normalize_private_test.go @@ -4,6 +4,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/aquasecurity/trivy/pkg/licensing/expression" ) // All map keys must be standardized to be matched @@ -16,3 +18,234 @@ func TestMap(t *testing.T) { }) } } + +func TestNormalize(t *testing.T) { + tests := []struct { + licenses []expression.Expression + want string + wantLicense expression.Expression + }{ + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: " the apache license "}, + expression.SimpleExpr{License: " the\tapache \r\nlicense \r\n "}, + expression.SimpleExpr{License: " apache "}, + expression.SimpleExpr{License: "ApacheLicence"}, + expression.SimpleExpr{License: "ApacheLicense"}, + expression.SimpleExpr{License: "al-2"}, + expression.SimpleExpr{License: "al-v2"}, + expression.SimpleExpr{License: "al2"}, + expression.SimpleExpr{License: "alv2"}, + expression.SimpleExpr{License: "apache - v 2.0"}, + expression.SimpleExpr{License: "apache - v. 2.0"}, + expression.SimpleExpr{License: "apache - ver 2.0"}, + expression.SimpleExpr{License: "apache - version 2.0"}, + expression.SimpleExpr{License: "apache 2"}, + expression.SimpleExpr{License: "apache 2.0"}, + expression.SimpleExpr{License: "apache license (2.0)"}, + expression.SimpleExpr{License: "apache license (v. 2)"}, + expression.SimpleExpr{License: "apache license (v. 2.0)"}, + expression.SimpleExpr{License: "apache license (v2)"}, + expression.SimpleExpr{License: "apache license (v2.0)"}, + expression.SimpleExpr{License: "apache license (version 2.0)"}, + expression.SimpleExpr{License: "apache license 2"}, + expression.SimpleExpr{License: "apache license 2.0"}, + expression.SimpleExpr{License: "apache license v2"}, + expression.SimpleExpr{License: "apache license v2.0"}, + expression.SimpleExpr{License: "apache license version 2"}, + expression.SimpleExpr{License: "apache license version 2.0"}, + expression.SimpleExpr{License: "apache license"}, + expression.SimpleExpr{License: "apache license, 2.0"}, + expression.SimpleExpr{License: "apache license, asl version 2.0"}, + expression.SimpleExpr{License: "apache license, version 2"}, + expression.SimpleExpr{License: "apache license, version 2.0 (http://www.apache.org/licenses/license-2.0)"}, + expression.SimpleExpr{License: "apache license, version 2.0"}, + expression.SimpleExpr{License: "apache license,version 2.0"}, + expression.SimpleExpr{License: "apache license,version-2.0"}, + expression.SimpleExpr{License: "apache license-2.0"}, + expression.SimpleExpr{License: "apache public 2.0"}, + expression.SimpleExpr{License: "apache public license 2.0"}, + expression.SimpleExpr{License: "apache public license-2.0"}, + expression.SimpleExpr{License: "apache public-2"}, + expression.SimpleExpr{License: "apache public-2.0"}, + expression.SimpleExpr{License: "apache software license (apache-2.0)"}, + expression.SimpleExpr{License: "apache software license - version 2.0"}, + expression.SimpleExpr{License: "apache software license 2.0"}, + expression.SimpleExpr{License: "apache software license, version 2"}, + expression.SimpleExpr{License: "apache software license, version 2.0"}, + expression.SimpleExpr{License: "apache software-2.0"}, + expression.SimpleExpr{License: "apache v 2.0"}, + expression.SimpleExpr{License: "apache v. 2.0"}, + expression.SimpleExpr{License: "apache v2"}, + expression.SimpleExpr{License: "apache v2.0"}, + expression.SimpleExpr{License: "apache ver 2.0"}, + expression.SimpleExpr{License: "apache ver. 2.0"}, + expression.SimpleExpr{License: "apache version 2.0"}, + expression.SimpleExpr{License: "apache version 2.0, january 2004"}, + expression.SimpleExpr{License: "apache version-2"}, + expression.SimpleExpr{License: "apache version-2.0"}, + expression.SimpleExpr{License: "apache"}, + expression.SimpleExpr{License: "apache, 2"}, + expression.SimpleExpr{License: "apache, v2.0"}, + expression.SimpleExpr{License: "apache, version 2"}, + expression.SimpleExpr{License: "apache, version 2.0"}, + expression.SimpleExpr{License: "apache-2"}, + expression.SimpleExpr{License: "apache-2.0"}, + expression.SimpleExpr{License: "apache-licence"}, + expression.SimpleExpr{License: "apache-license"}, + expression.SimpleExpr{License: "apache-licensed"}, + expression.SimpleExpr{License: "apache-licensed"}, + expression.SimpleExpr{License: "asf 2.0"}, + expression.SimpleExpr{License: "asl 2"}, + expression.SimpleExpr{License: "asl, version 2"}, + expression.SimpleExpr{License: "asl2.0"}, + expression.SimpleExpr{License: "the apache license"}, + expression.SimpleExpr{License: "the apache license"}, + }, + want: "Apache-2.0", + wantLicense: expression.SimpleExpr{License: "Apache-2.0"}, + }, + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: "Apache+"}, + }, + want: "Apache-2.0+", + wantLicense: expression.SimpleExpr{License: "Apache-2.0", HasPlus: true}, + }, + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) V1.1"}, + expression.SimpleExpr{License: "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) VERSION 1.1"}, + expression.SimpleExpr{License: "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL), VERSION 1.1"}, + expression.SimpleExpr{License: "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE 1.1 (CDDL-1.1)"}, + }, + want: "CDDL-1.1", + wantLicense: expression.SimpleExpr{License: "CDDL-1.1"}, + }, + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE (EPL) 1.0"}, + expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE (EPL), VERSION 1.0"}, + expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE - V 1.0"}, + expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE - V1.0"}, + expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE - VERSION 1.0"}, + expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE 1.0 (EPL-1.0)"}, + expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE 1.0"}, + expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE V. 1.0"}, + expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE V1.0"}, + expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE VERSION 1.0"}, + expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE, VERSION 1.0"}, + expression.SimpleExpr{License: "ECLIPSE PUBLIC"}, + }, + want: "EPL-1.0", + wantLicense: expression.SimpleExpr{License: "EPL-1.0"}, + }, + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: "EUROPEAN UNION PUBLIC LICENSE (EUPL V.1.1)"}, + expression.SimpleExpr{License: "EUROPEAN UNION PUBLIC LICENSE 1.1 (EUPL 1.1)"}, + expression.SimpleExpr{License: "EUROPEAN UNION PUBLIC LICENSE 1.1"}, + expression.SimpleExpr{License: "EUROPEAN UNION PUBLIC LICENSE, VERSION 1.1"}, + }, + want: "EUPL-1.1", + wantLicense: expression.SimpleExpr{License: "EUPL-1.1"}, + }, + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: "GPL-or-later"}, + expression.SimpleExpr{License: "GPL+"}, + expression.SimpleExpr{License: "GPL-2.0-only+"}, + }, + want: "GPL-2.0-or-later", + wantLicense: expression.SimpleExpr{License: "GPL-2.0", HasPlus: true}, + }, + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: "GPL (≥ 3)"}, + expression.SimpleExpr{License: "GPL3+"}, + expression.SimpleExpr{License: "GPL3-or-later"}, + expression.SimpleExpr{License: "GPL3 or later licence"}, + }, + want: "GPL-3.0-or-later", + wantLicense: expression.SimpleExpr{License: "GPL-3.0", HasPlus: true}, + }, + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: "GNU GENERAL PUBLIC LICENSE 3"}, + expression.SimpleExpr{License: "GNU GENERAL PUBLIC LICENSE (GPL) V. 3"}, + expression.SimpleExpr{License: "GNU GENERAL PUBLIC LICENSE VERSION 3 (GPL V3)"}, + }, + want: "GPL-3.0-only", + wantLicense: expression.SimpleExpr{License: "GPL-3.0"}, + }, + + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: "LGPL LICENSE-3"}, + expression.SimpleExpr{License: "GNU LESSER GENERAL PUBLIC LICENSE V3"}, + expression.SimpleExpr{License: "GNU LESSER GENERAL PUBLIC LICENSE V3.0"}, + expression.SimpleExpr{License: "GNU LESSER GENERAL PUBLIC LICENSE VERSION 3"}, + expression.SimpleExpr{License: "GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0"}, + expression.SimpleExpr{License: "GNU LESSER GENERAL PUBLIC LICENSE, VERSION 3.0"}, + expression.SimpleExpr{License: "GNU LIBRARY OR LESSER GENERAL PUBLIC LICENSE VERSION 3.0 (LGPLV3)"}, + expression.SimpleExpr{License: "GNU GENERAL LESSER PUBLIC LICENSE (LGPL) VERSION 3.0"}, + expression.SimpleExpr{License: "GNU LESSER GENERAL PUBLIC LICENSE (LGPL), VERSION 3"}, + }, + want: "LGPL-3.0-only", + wantLicense: expression.SimpleExpr{License: "LGPL-3.0"}, + }, + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: "The Unlicense"}, + expression.SimpleExpr{License: "Unlicense"}, + expression.SimpleExpr{License: "UNLICENSE"}, + }, + want: "Unlicense", + wantLicense: expression.SimpleExpr{License: "Unlicense"}, + }, + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: "MIT License"}, + expression.SimpleExpr{License: "http://json.codeplex.com/license"}, + }, + want: "MIT", + wantLicense: expression.SimpleExpr{License: "MIT"}, + }, + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: " The unmapped license "}, + }, + want: "The unmapped license", + wantLicense: expression.SimpleExpr{License: "The unmapped license"}, + }, + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: "Universal Permissive License, Version 1.0"}, + }, + want: "UPL-1.0", + wantLicense: expression.SimpleExpr{License: "UPL-1.0"}, + }, + { + licenses: []expression.Expression{ + expression.SimpleExpr{License: "GPLv2 WITH EXCEPTIONS"}, + expression.NewCompoundExpr( // "GPLv2 WITH EXCEPTIONS" + expression.SimpleExpr{License: "GPLv2"}, + expression.TokenWith, + expression.SimpleExpr{License: "EXCEPTIONS"}, + ), + }, + want: "GPL-2.0-with-classpath-exception", + wantLicense: expression.SimpleExpr{License: "GPL-2.0-with-classpath-exception"}, + }, + } + for _, tt := range tests { + t.Run(tt.want, func(t *testing.T) { + for _, ll := range tt.licenses { + got := normalizeLicense(ll.String()) + gotLicense := NormalizeLicenseExpression(ll) + assert.Equal(t, tt.want, got) + assert.Equal(t, tt.wantLicense, gotLicense) + } + }) + } +} diff --git a/pkg/licensing/normalize_test.go b/pkg/licensing/normalize_test.go index 9b0345ce24..5ed1d121cd 100644 --- a/pkg/licensing/normalize_test.go +++ b/pkg/licensing/normalize_test.go @@ -6,240 +6,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/aquasecurity/trivy/pkg/licensing" - "github.com/aquasecurity/trivy/pkg/licensing/expression" ) -func TestNormalize(t *testing.T) { - tests := []struct { - licenses []expression.Expression - want string - wantLicense expression.Expression - }{ - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: " the apache license "}, - expression.SimpleExpr{License: " the\tapache \r\nlicense \r\n "}, - expression.SimpleExpr{License: " apache "}, - expression.SimpleExpr{License: "ApacheLicence"}, - expression.SimpleExpr{License: "ApacheLicense"}, - expression.SimpleExpr{License: "al-2"}, - expression.SimpleExpr{License: "al-v2"}, - expression.SimpleExpr{License: "al2"}, - expression.SimpleExpr{License: "alv2"}, - expression.SimpleExpr{License: "apache - v 2.0"}, - expression.SimpleExpr{License: "apache - v. 2.0"}, - expression.SimpleExpr{License: "apache - ver 2.0"}, - expression.SimpleExpr{License: "apache - version 2.0"}, - expression.SimpleExpr{License: "apache 2"}, - expression.SimpleExpr{License: "apache 2.0"}, - expression.SimpleExpr{License: "apache license (2.0)"}, - expression.SimpleExpr{License: "apache license (v. 2)"}, - expression.SimpleExpr{License: "apache license (v. 2.0)"}, - expression.SimpleExpr{License: "apache license (v2)"}, - expression.SimpleExpr{License: "apache license (v2.0)"}, - expression.SimpleExpr{License: "apache license (version 2.0)"}, - expression.SimpleExpr{License: "apache license 2"}, - expression.SimpleExpr{License: "apache license 2.0"}, - expression.SimpleExpr{License: "apache license v2"}, - expression.SimpleExpr{License: "apache license v2.0"}, - expression.SimpleExpr{License: "apache license version 2"}, - expression.SimpleExpr{License: "apache license version 2.0"}, - expression.SimpleExpr{License: "apache license"}, - expression.SimpleExpr{License: "apache license, 2.0"}, - expression.SimpleExpr{License: "apache license, asl version 2.0"}, - expression.SimpleExpr{License: "apache license, version 2"}, - expression.SimpleExpr{License: "apache license, version 2.0 (http://www.apache.org/licenses/license-2.0)"}, - expression.SimpleExpr{License: "apache license, version 2.0"}, - expression.SimpleExpr{License: "apache license,version 2.0"}, - expression.SimpleExpr{License: "apache license,version-2.0"}, - expression.SimpleExpr{License: "apache license-2.0"}, - expression.SimpleExpr{License: "apache public 2.0"}, - expression.SimpleExpr{License: "apache public license 2.0"}, - expression.SimpleExpr{License: "apache public license-2.0"}, - expression.SimpleExpr{License: "apache public-2"}, - expression.SimpleExpr{License: "apache public-2.0"}, - expression.SimpleExpr{License: "apache software license (apache-2.0)"}, - expression.SimpleExpr{License: "apache software license - version 2.0"}, - expression.SimpleExpr{License: "apache software license 2.0"}, - expression.SimpleExpr{License: "apache software license, version 2"}, - expression.SimpleExpr{License: "apache software license, version 2.0"}, - expression.SimpleExpr{License: "apache software-2.0"}, - expression.SimpleExpr{License: "apache v 2.0"}, - expression.SimpleExpr{License: "apache v. 2.0"}, - expression.SimpleExpr{License: "apache v2"}, - expression.SimpleExpr{License: "apache v2.0"}, - expression.SimpleExpr{License: "apache ver 2.0"}, - expression.SimpleExpr{License: "apache ver. 2.0"}, - expression.SimpleExpr{License: "apache version 2.0"}, - expression.SimpleExpr{License: "apache version 2.0, january 2004"}, - expression.SimpleExpr{License: "apache version-2"}, - expression.SimpleExpr{License: "apache version-2.0"}, - expression.SimpleExpr{License: "apache"}, - expression.SimpleExpr{License: "apache, 2"}, - expression.SimpleExpr{License: "apache, v2.0"}, - expression.SimpleExpr{License: "apache, version 2"}, - expression.SimpleExpr{License: "apache, version 2.0"}, - expression.SimpleExpr{License: "apache-2"}, - expression.SimpleExpr{License: "apache-2.0"}, - expression.SimpleExpr{License: "apache-licence"}, - expression.SimpleExpr{License: "apache-license"}, - expression.SimpleExpr{License: "apache-licensed"}, - expression.SimpleExpr{License: "apache-licensed"}, - expression.SimpleExpr{License: "asf 2.0"}, - expression.SimpleExpr{License: "asl 2"}, - expression.SimpleExpr{License: "asl, version 2"}, - expression.SimpleExpr{License: "asl2.0"}, - expression.SimpleExpr{License: "the apache license"}, - expression.SimpleExpr{License: "the apache license"}, - }, - want: "Apache-2.0", - wantLicense: expression.SimpleExpr{License: "Apache-2.0"}, - }, - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: "Apache+"}, - }, - want: "Apache-2.0+", - wantLicense: expression.SimpleExpr{License: "Apache-2.0", HasPlus: true}, - }, - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) V1.1"}, - expression.SimpleExpr{License: "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) VERSION 1.1"}, - expression.SimpleExpr{License: "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL), VERSION 1.1"}, - expression.SimpleExpr{License: "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE 1.1 (CDDL-1.1)"}, - }, - want: "CDDL-1.1", - wantLicense: expression.SimpleExpr{License: "CDDL-1.1"}, - }, - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE (EPL) 1.0"}, - expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE (EPL), VERSION 1.0"}, - expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE - V 1.0"}, - expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE - V1.0"}, - expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE - VERSION 1.0"}, - expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE 1.0 (EPL-1.0)"}, - expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE 1.0"}, - expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE V. 1.0"}, - expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE V1.0"}, - expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE VERSION 1.0"}, - expression.SimpleExpr{License: "ECLIPSE PUBLIC LICENSE, VERSION 1.0"}, - expression.SimpleExpr{License: "ECLIPSE PUBLIC"}, - }, - want: "EPL-1.0", - wantLicense: expression.SimpleExpr{License: "EPL-1.0"}, - }, - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: "EUROPEAN UNION PUBLIC LICENSE (EUPL V.1.1)"}, - expression.SimpleExpr{License: "EUROPEAN UNION PUBLIC LICENSE 1.1 (EUPL 1.1)"}, - expression.SimpleExpr{License: "EUROPEAN UNION PUBLIC LICENSE 1.1"}, - expression.SimpleExpr{License: "EUROPEAN UNION PUBLIC LICENSE, VERSION 1.1"}, - }, - want: "EUPL-1.1", - wantLicense: expression.SimpleExpr{License: "EUPL-1.1"}, - }, - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: "GPL-or-later"}, - expression.SimpleExpr{License: "GPL+"}, - expression.SimpleExpr{License: "GPL-2.0-only+"}, - }, - want: "GPL-2.0-or-later", - wantLicense: expression.SimpleExpr{License: "GPL-2.0", HasPlus: true}, - }, - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: "GPL (≥ 3)"}, - expression.SimpleExpr{License: "GPL3+"}, - expression.SimpleExpr{License: "GPL3-or-later"}, - expression.SimpleExpr{License: "GPL3 or later licence"}, - }, - want: "GPL-3.0-or-later", - wantLicense: expression.SimpleExpr{License: "GPL-3.0", HasPlus: true}, - }, - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: "GNU GENERAL PUBLIC LICENSE 3"}, - expression.SimpleExpr{License: "GNU GENERAL PUBLIC LICENSE (GPL) V. 3"}, - expression.SimpleExpr{License: "GNU GENERAL PUBLIC LICENSE VERSION 3 (GPL V3)"}, - }, - want: "GPL-3.0-only", - wantLicense: expression.SimpleExpr{License: "GPL-3.0"}, - }, - - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: "LGPL LICENSE-3"}, - expression.SimpleExpr{License: "GNU LESSER GENERAL PUBLIC LICENSE V3"}, - expression.SimpleExpr{License: "GNU LESSER GENERAL PUBLIC LICENSE V3.0"}, - expression.SimpleExpr{License: "GNU LESSER GENERAL PUBLIC LICENSE VERSION 3"}, - expression.SimpleExpr{License: "GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0"}, - expression.SimpleExpr{License: "GNU LESSER GENERAL PUBLIC LICENSE, VERSION 3.0"}, - expression.SimpleExpr{License: "GNU LIBRARY OR LESSER GENERAL PUBLIC LICENSE VERSION 3.0 (LGPLV3)"}, - expression.SimpleExpr{License: "GNU GENERAL LESSER PUBLIC LICENSE (LGPL) VERSION 3.0"}, - expression.SimpleExpr{License: "GNU LESSER GENERAL PUBLIC LICENSE (LGPL), VERSION 3"}, - }, - want: "LGPL-3.0-only", - wantLicense: expression.SimpleExpr{License: "LGPL-3.0"}, - }, - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: "The Unlicense"}, - expression.SimpleExpr{License: "Unlicense"}, - expression.SimpleExpr{License: "UNLICENSE"}, - }, - want: "Unlicense", - wantLicense: expression.SimpleExpr{License: "Unlicense"}, - }, - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: "MIT License"}, - expression.SimpleExpr{License: "http://json.codeplex.com/license"}, - }, - want: "MIT", - wantLicense: expression.SimpleExpr{License: "MIT"}, - }, - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: " The unmapped license "}, - }, - want: "The unmapped license", - wantLicense: expression.SimpleExpr{License: "The unmapped license"}, - }, - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: "Universal Permissive License, Version 1.0"}, - }, - want: "UPL-1.0", - wantLicense: expression.SimpleExpr{License: "UPL-1.0"}, - }, - { - licenses: []expression.Expression{ - expression.SimpleExpr{License: "GPLv2 WITH EXCEPTIONS"}, - expression.NewCompoundExpr( // "GPLv2 WITH EXCEPTIONS" - expression.SimpleExpr{License: "GPLv2"}, - expression.TokenWith, - expression.SimpleExpr{License: "EXCEPTIONS"}, - ), - }, - want: "GPL-2.0-with-classpath-exception", - wantLicense: expression.SimpleExpr{License: "GPL-2.0-with-classpath-exception"}, - }, - } - for _, tt := range tests { - t.Run(tt.want, func(t *testing.T) { - for _, ll := range tt.licenses { - got := licensing.Normalize(ll.String()) - gotLicense := licensing.NormalizeLicense(ll) - assert.Equal(t, tt.want, got) - assert.Equal(t, tt.wantLicense, gotLicense) - } - }) - } -} - func TestSplitLicenses(t *testing.T) { tests := []struct { name string diff --git a/pkg/licensing/scanner.go b/pkg/licensing/scanner.go index daf7133bdb..1831319b97 100644 --- a/pkg/licensing/scanner.go +++ b/pkg/licensing/scanner.go @@ -26,7 +26,7 @@ func NewScanner(categories map[types.LicenseCategory][]string) Scanner { } func (s *Scanner) Scan(licenseName string) (types.LicenseCategory, string) { - expr, err := expression.Normalize(licenseName, NormalizeLicense) + expr, err := expression.Normalize(licenseName, NormalizeLicenseExpression) if err != nil { return types.CategoryUnknown, "" } diff --git a/pkg/result/ignore.go b/pkg/result/ignore.go index 23d599194b..30086b4ea9 100644 --- a/pkg/result/ignore.go +++ b/pkg/result/ignore.go @@ -202,7 +202,7 @@ func (c *IgnoreConfig) MatchLicense(licenseID, filePath string) *IgnoreFinding { return expr } - _, err := expression.Normalize(licenseID, licensing.NormalizeLicense, matchLicenses) + _, err := expression.Normalize(licenseID, licensing.NormalizeLicenseExpression, matchLicenses) if err != nil { log.WithPrefix("ignore").Debug("Unable to normalize license expression", log.String("license", licenseID), log.Err(err)) return nil diff --git a/pkg/sbom/cyclonedx/marshal.go b/pkg/sbom/cyclonedx/marshal.go index 63d81c161e..d8f49039d0 100644 --- a/pkg/sbom/cyclonedx/marshal.go +++ b/pkg/sbom/cyclonedx/marshal.go @@ -348,7 +348,7 @@ func (m *Marshaler) normalizeLicense(license string) expression.Expression { license = strings.ReplaceAll(license, "-with-", " WITH ") license = strings.ReplaceAll(license, "-WITH-", " WITH ") - normalizedLicenses, err := expression.Normalize(license, licensing.NormalizeLicense, expression.NormalizeForSPDX) + normalizedLicenses, err := expression.Normalize(license, licensing.NormalizeLicenseExpression, expression.NormalizeForSPDX) if err != nil { // Not fail on the invalid license m.logger.Warn("Unable to marshal SPDX licenses", log.String("license", license)) diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index 1a8041b137..b4be226f42 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -463,7 +463,7 @@ func (m *Marshaler) normalizeLicenses(licenses []string) (string, []*spdx.OtherL return expression.SimpleExpr{License: l.LicenseIdentifier} } - normalizedLicense, err := expression.Normalize(license, licensing.NormalizeLicense, expression.NormalizeForSPDX, replaceOtherLicenses) + normalizedLicense, err := expression.Normalize(license, licensing.NormalizeLicenseExpression, expression.NormalizeForSPDX, replaceOtherLicenses) if err != nil { // Not fail on the invalid license m.logger.Warn("Unable to marshal SPDX licenses", log.String("license", license))