mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 15:50:15 -08:00
fix: separating multiple licenses from one line in dpkg copyright files (#2508)
Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
1
.github/workflows/semantic-pr.yaml
vendored
1
.github/workflows/semantic-pr.yaml
vendored
@@ -34,6 +34,7 @@ jobs:
|
|||||||
vuln
|
vuln
|
||||||
misconf
|
misconf
|
||||||
secret
|
secret
|
||||||
|
license
|
||||||
|
|
||||||
image
|
image
|
||||||
fs
|
fs
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ checks:
|
|||||||
- vuln
|
- vuln
|
||||||
- misconf
|
- misconf
|
||||||
- secret
|
- secret
|
||||||
|
- license
|
||||||
|
|
||||||
mode:
|
mode:
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package language
|
package language
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
dio "github.com/aquasecurity/go-dep-parser/pkg/io"
|
dio "github.com/aquasecurity/go-dep-parser/pkg/io"
|
||||||
@@ -35,7 +37,10 @@ func ToAnalysisResult(fileType, filePath, libFilePath string, libs []godeptypes.
|
|||||||
for _, lib := range libs {
|
for _, lib := range libs {
|
||||||
var licenses []string
|
var licenses []string
|
||||||
if lib.License != "" {
|
if lib.License != "" {
|
||||||
licenses = []string{licensing.Normalize(lib.License)}
|
licenses = strings.Split(lib.License, ",")
|
||||||
|
for i, license := range licenses {
|
||||||
|
licenses[i] = licensing.Normalize(strings.TrimSpace(license))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pkgs = append(pkgs, types.Package{
|
pkgs = append(pkgs, types.Package{
|
||||||
ID: lib.ID,
|
ID: lib.ID,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func Test_gemspecLibraryAnalyzer_Analyze(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Name: "test-unit",
|
Name: "test-unit",
|
||||||
Version: "3.3.7",
|
Version: "3.3.7",
|
||||||
Licenses: []string{"Ruby, BSDL, PSFL"},
|
Licenses: []string{"Ruby", "BSDL", "PSFL"},
|
||||||
FilePath: "testdata/multiple_licenses.gemspec",
|
FilePath: "testdata/multiple_licenses.gemspec",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ var (
|
|||||||
licenseClassifier *classifier.Classifier
|
licenseClassifier *classifier.Classifier
|
||||||
|
|
||||||
commonLicenseReferenceRegexp = regexp.MustCompile(`/?usr/share/common-licenses/([0-9A-Za-z_.+-]+[0-9A-Za-z+])`)
|
commonLicenseReferenceRegexp = regexp.MustCompile(`/?usr/share/common-licenses/([0-9A-Za-z_.+-]+[0-9A-Za-z+])`)
|
||||||
|
licenseSplitRegexp = regexp.MustCompile("(,?[_ ]+or[_ ]+)|(,?[_ ]+and[_ ])|(,[ ]*)")
|
||||||
)
|
)
|
||||||
|
|
||||||
// dpkgLicenseAnalyzer parses copyright files and detect licenses
|
// dpkgLicenseAnalyzer parses copyright files and detect licenses
|
||||||
@@ -52,7 +53,7 @@ func (a dpkgLicenseAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisI
|
|||||||
}
|
}
|
||||||
|
|
||||||
findings := lo.Map(licenses, func(license string, _ int) types.LicenseFinding {
|
findings := lo.Map(licenses, func(license string, _ int) types.LicenseFinding {
|
||||||
return types.LicenseFinding{Name: licensing.Normalize(license)}
|
return types.LicenseFinding{Name: license}
|
||||||
})
|
})
|
||||||
|
|
||||||
// e.g. "usr/share/doc/zlib1g/copyright" => "zlib1g"
|
// e.g. "usr/share/doc/zlib1g/copyright" => "zlib1g"
|
||||||
@@ -82,14 +83,29 @@ func (a dpkgLicenseAnalyzer) parseCopyright(r dio.ReadSeekerAt) ([]string, error
|
|||||||
// Machine-readable format
|
// Machine-readable format
|
||||||
// cf. https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#:~:text=The%20debian%2Fcopyright%20file%20must,in%20the%20Debian%20Policy%20Manual.
|
// cf. https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/#:~:text=The%20debian%2Fcopyright%20file%20must,in%20the%20Debian%20Policy%20Manual.
|
||||||
l := strings.TrimSpace(line[8:])
|
l := strings.TrimSpace(line[8:])
|
||||||
if len(l) > 0 && !slices.Contains(licenses, l) {
|
if len(l) > 0 {
|
||||||
licenses = append(licenses, l)
|
// Split licenses without considering "and"/"or"
|
||||||
|
// examples:
|
||||||
|
// 'GPL-1+,GPL-2' => {"GPL-1", "GPL-2"}
|
||||||
|
// 'GPL-1+ or Artistic or Artistic-dist' => {"GPL-1", "Artistic", "Artistic-dist"}
|
||||||
|
// 'LGPLv3+_or_GPLv2+' => {"LGPLv3", "GPLv2"}
|
||||||
|
// 'BSD-3-CLAUSE and GPL-2' => {"BSD-3-CLAUSE", "GPL-2"}
|
||||||
|
// 'GPL-1+ or Artistic, and BSD-4-clause-POWERDOG' => {"GPL-1+", "Artistic", "BSD-4-clause-POWERDOG"}
|
||||||
|
for _, lic := range licenseSplitRegexp.Split(l, -1) {
|
||||||
|
lic = licensing.Normalize(lic)
|
||||||
|
if !slices.Contains(licenses, lic) {
|
||||||
|
licenses = append(licenses, lic)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case strings.Contains(line, "/usr/share/common-licenses/"):
|
case strings.Contains(line, "/usr/share/common-licenses/"):
|
||||||
// Common license pattern
|
// Common license pattern
|
||||||
license := commonLicenseReferenceRegexp.FindStringSubmatch(line)
|
license := commonLicenseReferenceRegexp.FindStringSubmatch(line)
|
||||||
if len(license) == 2 && !slices.Contains(licenses, license[1]) {
|
if len(license) == 2 {
|
||||||
licenses = append(licenses, license[1])
|
l := licensing.Normalize(license[1])
|
||||||
|
if !slices.Contains(licenses, l) {
|
||||||
|
licenses = append(licenses, l)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ func Test_dpkgLicenseAnalyzer_Analyze(t *testing.T) {
|
|||||||
Type: types.LicenseTypeDpkg,
|
Type: types.LicenseTypeDpkg,
|
||||||
FilePath: "usr/share/doc/zlib1g/copyright",
|
FilePath: "usr/share/doc/zlib1g/copyright",
|
||||||
Findings: []types.LicenseFinding{
|
Findings: []types.LicenseFinding{
|
||||||
|
{Name: "GPL-1.0"},
|
||||||
|
{Name: "Artistic"},
|
||||||
|
{Name: "BSD-4-clause-POWERDOG"},
|
||||||
{Name: "Zlib"},
|
{Name: "Zlib"},
|
||||||
},
|
},
|
||||||
PkgName: "zlib1g",
|
PkgName: "zlib1g",
|
||||||
@@ -64,7 +67,6 @@ func Test_dpkgLicenseAnalyzer_Analyze(t *testing.T) {
|
|||||||
FilePath: "usr/share/doc/apt/copyright",
|
FilePath: "usr/share/doc/apt/copyright",
|
||||||
Findings: []types.LicenseFinding{
|
Findings: []types.LicenseFinding{
|
||||||
{Name: "GPL-2.0"},
|
{Name: "GPL-2.0"},
|
||||||
{Name: "GPL-2.0"},
|
|
||||||
},
|
},
|
||||||
PkgName: "apt",
|
PkgName: "apt",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ Files-Excluded:
|
|||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: 1995-2013 Jean-loup Gailly and Mark Adler
|
Copyright: 1995-2013 Jean-loup Gailly and Mark Adler
|
||||||
License: Zlib
|
License: GPL-1+ or Artistic, and BSD-4-clause-POWERDOG
|
||||||
|
|
||||||
Files: amiga/Makefile.pup
|
Files: amiga/Makefile.pup
|
||||||
Copyright: 1998 by Andreas R. Kleinert
|
Copyright: 1998 by Andreas R. Kleinert
|
||||||
|
|||||||
@@ -316,7 +316,6 @@ func TestArtifact_Inspect(t *testing.T) {
|
|||||||
Type: types.LicenseTypeDpkg,
|
Type: types.LicenseTypeDpkg,
|
||||||
FilePath: "usr/share/doc/ca-certificates/copyright",
|
FilePath: "usr/share/doc/ca-certificates/copyright",
|
||||||
Findings: []types.LicenseFinding{
|
Findings: []types.LicenseFinding{
|
||||||
{Name: "GPL-2.0"},
|
|
||||||
{Name: "GPL-2.0"},
|
{Name: "GPL-2.0"},
|
||||||
{Name: "MPL-2.0"},
|
{Name: "MPL-2.0"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ var mapping = map[string]string{
|
|||||||
// GPL
|
// GPL
|
||||||
"GPL-1": GPL10,
|
"GPL-1": GPL10,
|
||||||
"GPL-1+": GPL10,
|
"GPL-1+": GPL10,
|
||||||
|
"GPL 1.0": GPL10,
|
||||||
|
"GPL 1": GPL10,
|
||||||
"GPL2": GPL20,
|
"GPL2": GPL20,
|
||||||
|
"GPL 2.0": GPL20,
|
||||||
|
"GPL 2": GPL20,
|
||||||
"GPL-2": GPL20,
|
"GPL-2": GPL20,
|
||||||
"GPL-2.0-ONLY": GPL20,
|
"GPL-2.0-ONLY": GPL20,
|
||||||
"GPL2+": GPL20,
|
"GPL2+": GPL20,
|
||||||
@@ -16,6 +20,8 @@ var mapping = map[string]string{
|
|||||||
"GPL-2.0-OR-LATER": GPL20,
|
"GPL-2.0-OR-LATER": GPL20,
|
||||||
"GPL-2+ WITH AUTOCONF EXCEPTION": GPL20withautoconfexception,
|
"GPL-2+ WITH AUTOCONF EXCEPTION": GPL20withautoconfexception,
|
||||||
"GPL3": GPL30,
|
"GPL3": GPL30,
|
||||||
|
"GPL 3.0": GPL30,
|
||||||
|
"GPL 3": GPL30,
|
||||||
"GPLV3+": GPL30,
|
"GPLV3+": GPL30,
|
||||||
"GPL-3": GPL30,
|
"GPL-3": GPL30,
|
||||||
"GPL-3.0-ONLY": GPL30,
|
"GPL-3.0-ONLY": GPL30,
|
||||||
@@ -27,22 +33,30 @@ var mapping = map[string]string{
|
|||||||
|
|
||||||
// LGPL
|
// LGPL
|
||||||
"LGPL2": LGPL20,
|
"LGPL2": LGPL20,
|
||||||
|
"LGPL 2": LGPL20,
|
||||||
|
"LGPL 2.0": LGPL20,
|
||||||
"LGPL-2": LGPL20,
|
"LGPL-2": LGPL20,
|
||||||
"LGPL2+": LGPL20,
|
"LGPL2+": LGPL20,
|
||||||
"LGPL-2+": LGPL20,
|
"LGPL-2+": LGPL20,
|
||||||
"LGPL-2.0+": LGPL20,
|
"LGPL-2.0+": LGPL20,
|
||||||
"LGPL-2.1": LGPL21,
|
"LGPL-2.1": LGPL21,
|
||||||
|
"LGPL 2.1": LGPL21,
|
||||||
"LGPL-2.1+": LGPL21,
|
"LGPL-2.1+": LGPL21,
|
||||||
"LGPLV2.1+": LGPL21,
|
"LGPLV2.1+": LGPL21,
|
||||||
"LGPL-3": LGPL30,
|
"LGPL-3": LGPL30,
|
||||||
|
"LGPL 3": LGPL30,
|
||||||
"LGPL-3+": LGPL30,
|
"LGPL-3+": LGPL30,
|
||||||
"LGPL": LGPL30, // 2? 3?
|
"LGPL": LGPL30, // 2? 3?
|
||||||
|
|
||||||
// MPL
|
// MPL
|
||||||
"MPL1.0": MPL10,
|
"MPL1.0": MPL10,
|
||||||
"MPL1": MPL10,
|
"MPL1": MPL10,
|
||||||
"MPL2.0": MPL20,
|
"MPL 1.0": MPL10,
|
||||||
"MPL2": MPL20,
|
"MPL 1": MPL10,
|
||||||
|
"MPL2.0": MPL20,
|
||||||
|
"MPL 2.0": MPL20,
|
||||||
|
"MPL2": MPL20,
|
||||||
|
"MPL 2": MPL20,
|
||||||
|
|
||||||
// BSD
|
// BSD
|
||||||
"BSD": BSD3Clause, // 2? 3?
|
"BSD": BSD3Clause, // 2? 3?
|
||||||
@@ -50,9 +64,10 @@ var mapping = map[string]string{
|
|||||||
"BSD-3-CLAUSE": BSD3Clause,
|
"BSD-3-CLAUSE": BSD3Clause,
|
||||||
"BSD-4-CLAUSE": BSD4Clause,
|
"BSD-4-CLAUSE": BSD4Clause,
|
||||||
|
|
||||||
"APACHE": Apache20, // 1? 2?
|
"APACHE": Apache20, // 1? 2?
|
||||||
"ZLIB": Zlib,
|
"APACHE 2.0": Apache20,
|
||||||
"RUBY": Ruby,
|
"RUBY": Ruby,
|
||||||
|
"ZLIB": Zlib,
|
||||||
}
|
}
|
||||||
|
|
||||||
func Normalize(name string) string {
|
func Normalize(name string) string {
|
||||||
|
|||||||
Reference in New Issue
Block a user