detect license ID from full text when incidentally provided as a value (#3876)

---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Christopher Angelo Phillips 2025-05-13 16:37:18 -04:00 committed by GitHub
parent b4d717fb30
commit f77d503892
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
117 changed files with 2083 additions and 1328 deletions

View File

@ -158,8 +158,8 @@ func (cfg Catalog) ToFilesConfig() filecataloging.Config {
func (cfg Catalog) ToLicenseConfig() cataloging.LicenseConfig {
return cataloging.LicenseConfig{
IncludeUnkownLicenseContent: cfg.License.IncludeUnknownLicenseContent,
Coverage: cfg.License.LicenseCoverage,
IncludeContent: cfg.License.Content,
Coverage: cfg.License.Coverage,
}
}

View File

@ -1,12 +1,22 @@
package options
import (
"fmt"
"github.com/anchore/clio"
"github.com/anchore/syft/syft/cataloging"
)
type licenseConfig struct {
IncludeUnknownLicenseContent bool `yaml:"include-unknown-license-content" json:"include-unknown-license-content" mapstructure:"include-unknown-license-content"`
LicenseCoverage float64 `yaml:"license-coverage" json:"license-coverage" mapstructure:"license-coverage"`
Content cataloging.LicenseContent `yaml:"content" json:"content" mapstructure:"content"`
// Deprecated: please use include-license-content instead
IncludeUnknownLicenseContent *bool `yaml:"-" json:"-" mapstructure:"include-unknown-license-content"`
Coverage float64 `yaml:"coverage" json:"coverage" mapstructure:"coverage"`
// Deprecated: please use coverage instead
LicenseCoverage *float64 `yaml:"license-coverage" json:"license-coverage" mapstructure:"license-coverage"`
AvailableLicenseContent []cataloging.LicenseContent `yaml:"-" json:"-" mapstructure:"-"`
}
var _ interface {
@ -14,15 +24,56 @@ var _ interface {
} = (*licenseConfig)(nil)
func (o *licenseConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.IncludeUnknownLicenseContent, `include the content of a license in the SBOM when syft
cannot determine a valid SPDX ID for the given license`)
descriptions.Add(&o.LicenseCoverage, `adjust the percent as a fraction of the total text, in normalized words, that
descriptions.Add(&o.Content, fmt.Sprintf("include the content of licenses in the SBOM for a given syft scan; valid values are: %s", o.AvailableLicenseContent))
descriptions.Add(&o.IncludeUnknownLicenseContent, `deprecated: please use 'license-content' instead`)
descriptions.Add(&o.Coverage, `adjust the percent as a fraction of the total text, in normalized words, that
matches any valid license for the given inputs, expressed as a percentage across all of the licenses matched.`)
descriptions.Add(&o.LicenseCoverage, `deprecated: please use 'coverage' instead`)
}
func (o *licenseConfig) PostLoad() error {
cfg := cataloging.DefaultLicenseConfig()
defaultContent := cfg.IncludeContent
defaultCoverage := cfg.Coverage
// if both legacy and new fields are specified, error out
if o.IncludeUnknownLicenseContent != nil && o.Content != defaultContent {
return fmt.Errorf("both 'include-unknown-license-content' and 'content' are set, please use only 'content'")
}
if o.LicenseCoverage != nil && o.Coverage != defaultCoverage {
return fmt.Errorf("both 'license-coverage' and 'coverage' are set, please use only 'coverage'")
}
// finalize the license content value
if o.IncludeUnknownLicenseContent != nil {
// convert 'include-unknown-license-content' -> 'license-content'
v := cataloging.LicenseContentExcludeAll
if *o.IncludeUnknownLicenseContent {
v = cataloging.LicenseContentIncludeUnknown
}
o.Content = v
}
// finalize the coverage value
if o.LicenseCoverage != nil {
// convert 'license-coverage' -> 'coverage'
o.Coverage = *o.LicenseCoverage
}
return nil
}
func defaultLicenseConfig() licenseConfig {
cfg := cataloging.DefaultLicenseConfig()
return licenseConfig{
IncludeUnknownLicenseContent: false,
LicenseCoverage: 75,
Content: cfg.IncludeContent,
Coverage: cfg.Coverage,
AvailableLicenseContent: []cataloging.LicenseContent{
cataloging.LicenseContentIncludeAll,
cataloging.LicenseContentIncludeUnknown,
cataloging.LicenseContentExcludeAll,
},
}
}

View File

@ -8,7 +8,7 @@ import (
)
func TestSetContextLicenseScanner(t *testing.T) {
scanner := testScanner(true)
scanner := testScanner()
ctx := context.Background()
ctx = SetContextLicenseScanner(ctx, scanner)
@ -20,7 +20,7 @@ func TestSetContextLicenseScanner(t *testing.T) {
}
func TestIsContextLicenseScannerSet(t *testing.T) {
scanner := testScanner(true)
scanner := testScanner()
ctx := context.Background()
require.False(t, IsContextLicenseScannerSet(ctx))
@ -30,7 +30,7 @@ func TestIsContextLicenseScannerSet(t *testing.T) {
func TestContextLicenseScanner(t *testing.T) {
t.Run("with scanner", func(t *testing.T) {
scanner := testScanner(true)
scanner := testScanner()
ctx := SetContextLicenseScanner(context.Background(), scanner)
s, err := ContextLicenseScanner(ctx)
if err != nil || s != scanner {

View File

@ -0,0 +1,36 @@
package licenses
import (
"context"
"io"
)
func (s *scanner) FindEvidence(_ context.Context, reader io.Reader) (evidence []Evidence, content []byte, err error) {
if s.scanner == nil {
return nil, nil, nil
}
content, err = io.ReadAll(reader)
if err != nil {
return nil, nil, err
}
cov := s.scanner(content)
if cov.Percent < s.coverageThreshold {
// unknown or no licenses here
// => check return content to Search to process
return nil, content, nil
}
evidence = make([]Evidence, 0)
for _, m := range cov.Match {
evidence = append(evidence, Evidence{
ID: m.ID,
Type: m.Type,
Start: m.Start,
End: m.End,
IsURL: m.IsURL,
})
}
return evidence, content, nil
}

View File

@ -0,0 +1,81 @@
package licenses
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/google/licensecheck"
"github.com/stretchr/testify/require"
)
func TestDefaultScanner_FindEvidence(t *testing.T) {
testCases := []struct {
name string
fixture string
wantIDs []string // expected license IDs
minMatch int // minimum # of matches required
}{
{
name: "Single licenses are able to be recognized and returned Apache 2.0",
fixture: "test-fixtures/apache-license-2.0",
wantIDs: []string{"Apache-2.0"},
minMatch: 1,
},
{
name: "Multiple Licenses are returned as evidence with duplicates at different offset",
fixture: "test-fixtures/multi-license",
wantIDs: []string{
"MIT",
"MIT",
"NCSA",
"Apache-2.0",
"Zlib",
"Unlicense",
"BSD-2-Clause",
"BSD-2-Clause",
"BSD-3-Clause",
},
minMatch: 2,
},
}
scanner := testScanner()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
filePath := filepath.Clean(tc.fixture)
f, err := os.Open(filePath)
require.NoError(t, err)
defer f.Close()
evidence, content, err := scanner.FindEvidence(context.Background(), f)
require.NoError(t, err)
require.NotEmpty(t, content)
require.GreaterOrEqual(t, len(evidence), tc.minMatch, "expected at least %d matches", tc.minMatch)
var foundIDs []string
for _, ev := range evidence {
foundIDs = append(foundIDs, ev.ID)
}
require.ElementsMatch(t, tc.wantIDs, foundIDs, "expected license IDs %v, but got %v", tc.wantIDs, foundIDs)
})
}
}
func testScanner() Scanner {
return &scanner{
coverageThreshold: DefaultCoverageThreshold,
scanner: licensecheck.Scan,
}
}
func mustOpen(fixture string) []byte {
content, err := os.ReadFile(fixture)
if err != nil {
panic(err)
}
return content
}

View File

@ -8,33 +8,37 @@ import (
"github.com/google/licensecheck"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
const (
DefaultCoverageThreshold = 75 // determined by experimentation
DefaultIncludeLicenseContent = false
UnknownLicensePrefix = unknownLicenseType + "_"
DefaultCoverageThreshold = 75 // determined by experimentation
unknownLicenseType = "UNKNOWN"
)
type Evidence struct {
ID string // License identifier. (See licenses/README.md.)
Type licensecheck.Type // The type of the license: BSD, MIT, etc.
Start int // Start offset of match in text; match is at text[Start:End].
End int // End offset of match in text.
IsURL bool // Whether match is a URL.
}
type Scanner interface {
IdentifyLicenseIDs(context.Context, io.Reader) ([]string, []byte, error)
FileSearch(context.Context, file.LocationReadCloser) ([]file.License, error)
PkgSearch(context.Context, file.LocationReadCloser) ([]pkg.License, error)
FindEvidence(context.Context, io.Reader) ([]Evidence, []byte, error)
}
var _ Scanner = (*scanner)(nil)
type scanner struct {
coverageThreshold float64 // between 0 and 100
includeLicenseContent bool
scanner func([]byte) licensecheck.Coverage
coverageThreshold float64 // between 0 and 100
scanner func([]byte) licensecheck.Coverage
}
type ScannerConfig struct {
CoverageThreshold float64
IncludeLicenseContent bool
Scanner func([]byte) licensecheck.Coverage
CoverageThreshold float64
Scanner func([]byte) licensecheck.Coverage
}
type Option func(*scanner)
@ -45,12 +49,6 @@ func WithCoverage(coverage float64) Option {
}
}
func WithIncludeLicenseContent(includeLicenseContent bool) Option {
return func(s *scanner) {
s.includeLicenseContent = includeLicenseContent
}
}
// NewDefaultScanner returns a scanner that uses a new instance of the default licensecheck package scanner.
func NewDefaultScanner(o ...Option) (Scanner, error) {
s, err := licensecheck.NewScanner(licensecheck.BuiltinLicenses())
@ -58,10 +56,10 @@ func NewDefaultScanner(o ...Option) (Scanner, error) {
log.WithFields("error", err).Trace("unable to create default license scanner")
return nil, fmt.Errorf("unable to create default license scanner: %w", err)
}
newScanner := &scanner{
coverageThreshold: DefaultCoverageThreshold,
includeLicenseContent: DefaultIncludeLicenseContent,
scanner: s.Scan,
coverageThreshold: DefaultCoverageThreshold,
scanner: s.Scan,
}
for _, opt := range o {
@ -78,8 +76,7 @@ func NewScanner(c *ScannerConfig) (Scanner, error) {
}
return &scanner{
coverageThreshold: c.CoverageThreshold,
includeLicenseContent: c.IncludeLicenseContent,
scanner: c.Scanner,
coverageThreshold: c.CoverageThreshold,
scanner: c.Scanner,
}, nil
}

View File

@ -1,84 +0,0 @@
package licenses
import (
"bytes"
"context"
"os"
"testing"
"github.com/google/licensecheck"
"github.com/stretchr/testify/require"
)
func TestIdentifyLicenseIDs(t *testing.T) {
type expectation struct {
yieldError bool
ids []string
content []byte
}
tests := []struct {
name string
in string
expected expectation
}{
{
name: "apache license 2.0",
in: `test-fixtures/apache-license-2.0`,
expected: expectation{
yieldError: false,
ids: []string{"Apache-2.0"},
content: nil,
},
},
{
name: "custom license includes content for IdentifyLicenseIDs",
in: "test-fixtures/nvidia-software-and-cuda-supplement",
expected: expectation{
yieldError: false,
ids: []string{},
content: mustOpen("test-fixtures/nvidia-software-and-cuda-supplement"),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
content, err := os.ReadFile(test.in)
require.NoError(t, err)
ids, content, err := testScanner(false).IdentifyLicenseIDs(context.TODO(), bytes.NewReader(content))
if test.expected.yieldError {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Len(t, ids, len(test.expected.ids))
require.Len(t, content, len(test.expected.content))
if len(test.expected.ids) > 0 {
require.Equal(t, ids, test.expected.ids)
}
if len(test.expected.content) > 0 {
require.Equal(t, content, test.expected.content)
}
}
})
}
}
func testScanner(includeLicenseContent bool) Scanner {
return &scanner{
coverageThreshold: DefaultCoverageThreshold,
includeLicenseContent: includeLicenseContent,
scanner: licensecheck.Scan,
}
}
func mustOpen(fixture string) []byte {
content, err := os.ReadFile(fixture)
if err != nil {
panic(err)
}
return content
}

View File

@ -1,123 +0,0 @@
package licenses
import (
"context"
"crypto/sha256"
"fmt"
"io"
"strings"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/license"
"github.com/anchore/syft/syft/pkg"
)
const (
unknownLicenseType = "UNKNOWN"
UnknownLicensePrefix = unknownLicenseType + "_"
)
func getCustomLicenseContentHash(contents []byte) string {
hash := sha256.Sum256(contents)
return fmt.Sprintf("%x", hash[:])
}
func (s *scanner) IdentifyLicenseIDs(_ context.Context, reader io.Reader) ([]string, []byte, error) {
if s.scanner == nil {
return nil, nil, nil
}
content, err := io.ReadAll(reader)
if err != nil {
return nil, nil, err
}
cov := s.scanner(content)
if cov.Percent < s.coverageThreshold {
// unknown or no licenses here
// => check return content to Search to process
return nil, content, nil
}
var ids []string
for _, m := range cov.Match {
ids = append(ids, m.ID)
}
return ids, nil, nil
}
// PkgSearch scans the contents of a license file to attempt to determine the type of license it is
func (s *scanner) PkgSearch(ctx context.Context, reader file.LocationReadCloser) (licenses []pkg.License, err error) {
licenses = make([]pkg.License, 0)
ids, content, err := s.IdentifyLicenseIDs(ctx, reader)
if err != nil {
return nil, err
}
// IdentifyLicenseIDs can only return a list of ID or content
// These return values are mutually exclusive.
// If the scanner threshold for matching scores < 75% then we return the license full content
if len(ids) > 0 {
for _, id := range ids {
lic := pkg.NewLicenseFromLocations(id, reader.Location)
lic.Type = license.Concluded
licenses = append(licenses, lic)
}
} else if len(content) > 0 {
// harmonize line endings to unix compatible first:
// 1. \r\n => \n (Windows => UNIX)
// 2. \r => \n (Macintosh => UNIX)
content = []byte(strings.ReplaceAll(strings.ReplaceAll(string(content), "\r\n", "\n"), "\r", "\n"))
lic := pkg.NewLicenseFromLocations(unknownLicenseType, reader.Location)
lic.SPDXExpression = UnknownLicensePrefix + getCustomLicenseContentHash(content)
if s.includeLicenseContent {
lic.Contents = string(content)
}
lic.Type = license.Declared
licenses = append(licenses, lic)
}
return licenses, nil
}
// FileSearch scans the contents of a license file to attempt to determine the type of license it is
func (s *scanner) FileSearch(ctx context.Context, reader file.LocationReadCloser) (licenses []file.License, err error) {
licenses = make([]file.License, 0)
ids, content, err := s.IdentifyLicenseIDs(ctx, reader)
if err != nil {
return nil, err
}
// IdentifyLicenseIDs can only return a list of ID or content
// These return values are mutually exclusive.
// If the scanner threshold for matching scores < 75% then we return the license full content
if len(ids) > 0 {
for _, id := range ids {
lic := file.NewLicense(id)
lic.Type = license.Concluded
licenses = append(licenses, lic)
}
} else if len(content) > 0 {
// harmonize line endings to unix compatible first:
// 1. \r\n => \n (Windows => UNIX)
// 2. \r => \n (Macintosh => UNIX)
content = []byte(strings.ReplaceAll(strings.ReplaceAll(string(content), "\r\n", "\n"), "\r", "\n"))
lic := file.NewLicense(unknownLicenseType)
lic.SPDXExpression = UnknownLicensePrefix + getCustomLicenseContentHash(content)
if s.includeLicenseContent {
lic.Contents = string(content)
}
lic.Type = license.Declared
licenses = append(licenses, lic)
}
return licenses, nil
}

View File

@ -1,166 +0,0 @@
package licenses
import (
"bytes"
"context"
"io"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
type bytesReadCloser struct {
bytes.Buffer
}
func (brc *bytesReadCloser) Close() error {
return nil
}
func newBytesReadCloser(data []byte) *bytesReadCloser {
return &bytesReadCloser{
Buffer: *bytes.NewBuffer(data),
}
}
func TestSearchFileLicenses(t *testing.T) {
type expectation struct {
yieldError bool
licenses []file.License
}
tests := []struct {
name string
in string
includeUnkownLicenseContent bool
expected expectation
}{
{
name: "apache license 2.0",
in: "test-fixtures/apache-license-2.0",
expected: expectation{
yieldError: false,
licenses: []file.License{
{
Value: "Apache-2.0",
SPDXExpression: "Apache-2.0",
Type: "concluded",
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := context.TODO()
content, err := os.ReadFile(test.in)
require.NoError(t, err)
s := testScanner(false)
result, err := s.FileSearch(ctx, file.NewLocationReadCloser(file.NewLocation("LICENSE"), io.NopCloser(bytes.NewReader(content))))
if test.expected.yieldError {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Len(t, result, len(test.expected.licenses))
if len(test.expected.licenses) > 0 {
require.Equal(t, test.expected.licenses, result)
}
}
})
}
}
func TestSearchPkgLicenses(t *testing.T) {
type expectation struct {
wantErr require.ErrorAssertionFunc
licenses []pkg.License
}
testLocation := file.NewLocation("LICENSE")
tests := []struct {
name string
in string
includeUnkownLicenseContent bool
expected expectation
}{
{
name: "apache license 2.0",
in: "test-fixtures/apache-license-2.0",
expected: expectation{
licenses: []pkg.License{
{
Value: "Apache-2.0",
SPDXExpression: "Apache-2.0",
Type: "concluded",
URLs: nil,
Locations: file.NewLocationSet(testLocation),
Contents: "",
},
},
wantErr: nil,
},
},
{
name: "custom license no content by default",
in: "test-fixtures/nvidia-software-and-cuda-supplement",
expected: expectation{
licenses: []pkg.License{
{
Value: "UNKNOWN",
SPDXExpression: "UNKNOWN_eebcea3ab1d1a28e671de90119ffcfb35fe86951e4af1b17af52b7a82fcf7d0a",
Type: "declared",
URLs: nil,
Locations: file.NewLocationSet(testLocation),
Contents: "",
},
},
wantErr: nil,
},
},
{
name: "custom license with content when scanner has content config",
in: "test-fixtures/nvidia-software-and-cuda-supplement",
includeUnkownLicenseContent: true,
expected: expectation{
licenses: []pkg.License{
{
Value: "UNKNOWN",
SPDXExpression: "UNKNOWN_eebcea3ab1d1a28e671de90119ffcfb35fe86951e4af1b17af52b7a82fcf7d0a",
Type: "declared",
URLs: nil,
Locations: file.NewLocationSet(testLocation),
Contents: string(mustOpen("test-fixtures/nvidia-software-and-cuda-supplement")),
},
},
wantErr: nil,
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := context.TODO()
content, err := os.ReadFile(test.in)
require.NoError(t, err)
s := testScanner(test.includeUnkownLicenseContent)
result, err := s.PkgSearch(ctx, file.NewLocationReadCloser(file.NewLocation("LICENSE"), io.NopCloser(bytes.NewReader(content))))
if test.expected.wantErr != nil {
test.expected.wantErr(t, err)
}
require.NoError(t, err)
require.Len(t, result, len(test.expected.licenses))
if len(test.expected.licenses) > 0 {
require.Equal(t, test.expected.licenses, result)
}
})
}
}

View File

@ -0,0 +1,445 @@
Emscripten is available under 2 licenses, the MIT license and the
University of Illinois/NCSA Open Source License.
Both are permissive open source licenses, with little if any
practical difference between them.
The reason for offering both is that (1) the MIT license is
well-known, while (2) the University of Illinois/NCSA Open Source
License allows Emscripten's code to be integrated upstream into
LLVM, which uses that license, should the opportunity arise.
Additionally, the binaryen project is available under the Apache License
Version 2.0.
The full text of all three licenses follows.
==============================================================================
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
==============================================================================
Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file.
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal with the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimers.
Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimers
in the documentation and/or other materials provided with the
distribution.
Neither the names of Mozilla,
nor the names of its contributors may be used to endorse
or promote products derived from this Software without specific prior
written permission.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
==============================================================================
This program uses portions of Node.js source code located in src/library_path.js,
in accordance with the terms of the MIT license. Node's license follows:
"""
Copyright Joyent, Inc. and other Node contributors. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
"""
==============================================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================
Simple DirectMedia Layer
Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
==============================================================================
Files: tools/filelock.py
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>
==============================================================================
Files: tools/eliminator/node_modules/uglify-js/... tools/node_modules/terser/...
Distributed under the BSD license:
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the following
disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
==============================================================================
Files: system/include/webgpu/webgpu.h
BSD 3-Clause License
Copyright (c) 2019, "WebGPU native" developers
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==============================================================================
Copyright (c) 2005-2011 David Schultz <das@FreeBSD.ORG>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

View File

@ -107,6 +107,11 @@ func finalizePkgCatalogerResults(cfg CatalogingFactoryConfig, resolver file.Path
}
}
// we want to know if the user wants to preserve license content or not in the final SBOM
// note: this looks incorrect, but pkg.License.Content is NOT used to compute the Package ID
// this does NOT change the reproducibility of the Package ID
applyLicenseContentRules(&p, cfg.LicenseConfig)
pkgs[i] = p
}
return pkgs, relationships
@ -262,3 +267,29 @@ func packageFileOwnershipRelationships(p pkg.Package, resolver file.PathResolver
}
return relationships, nil
}
func applyLicenseContentRules(p *pkg.Package, cfg cataloging.LicenseConfig) {
if p.Licenses.Empty() {
return
}
licenses := p.Licenses.ToSlice()
for i := range licenses {
l := &licenses[i]
switch cfg.IncludeContent {
case cataloging.LicenseContentIncludeUnknown:
// we don't have an SPDX expression, which means we didn't find an SPDX license
// include the unknown licenses content in the final SBOM
if l.SPDXExpression != "" {
licenses[i].Contents = ""
}
case cataloging.LicenseContentExcludeAll:
// clear it all out
licenses[i].Contents = ""
case cataloging.LicenseContentIncludeAll:
// always include the content
}
}
p.Licenses = pkg.NewLicenseSet(licenses...)
}

View File

@ -117,6 +117,114 @@ func TestFilterNonCompliantPackages(t *testing.T) {
assert.Equal(t, p2, droppedPkgs[0])
}
func TestApplyLicenseContentRules(t *testing.T) {
licenseWithSPDX := pkg.License{
SPDXExpression: "MIT",
Contents: "MIT license content",
}
licenseWithoutSPDX := pkg.License{
Value: "License-Not-A-SPDX-Expression",
Contents: "Non-SPDX license content",
}
tests := []struct {
name string
inputLicenses []pkg.License
cfg cataloging.LicenseConfig
expectedLicenses []pkg.License
}{
{
name: "LicenseContentIncludeUnknown",
inputLicenses: []pkg.License{
licenseWithSPDX,
licenseWithoutSPDX,
},
cfg: cataloging.LicenseConfig{
IncludeContent: cataloging.LicenseContentIncludeUnknown,
},
expectedLicenses: []pkg.License{
{
SPDXExpression: "MIT",
Contents: "", // content cleared for SPDX license
},
{
Value: "License-Not-A-SPDX-Expression",
Contents: "Non-SPDX license content", // content preserved for non-SPDX
},
},
},
{
name: "LicenseContentExcludeAll",
inputLicenses: []pkg.License{
licenseWithSPDX,
licenseWithoutSPDX,
},
cfg: cataloging.LicenseConfig{
IncludeContent: cataloging.LicenseContentExcludeAll,
},
expectedLicenses: []pkg.License{
{
SPDXExpression: "MIT",
Contents: "", // content cleared
},
{
Value: "License-Not-A-SPDX-Expression",
Contents: "", // content cleared
},
},
},
{
name: "IncludeLicenseContentDefault",
inputLicenses: []pkg.License{
licenseWithSPDX,
licenseWithoutSPDX,
},
cfg: cataloging.LicenseConfig{
IncludeContent: cataloging.LicenseContentIncludeAll,
},
expectedLicenses: []pkg.License{
{
SPDXExpression: "MIT",
Contents: "MIT license content", // content preserved
},
{
Value: "License-Not-A-SPDX-Expression",
Contents: "Non-SPDX license content", // content preserved
},
},
},
{
name: "Empty licenses",
inputLicenses: []pkg.License{},
cfg: cataloging.LicenseConfig{
IncludeContent: cataloging.LicenseContentIncludeAll,
},
expectedLicenses: []pkg.License{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
inputPkg := &pkg.Package{
Licenses: pkg.NewLicenseSet(tt.inputLicenses...),
}
inputPkg.SetID()
originalID := inputPkg.ID()
applyLicenseContentRules(inputPkg, tt.cfg)
assert.Equal(t, originalID, inputPkg.ID(), "package ID changed unexpectedly")
actualLicenses := inputPkg.Licenses.ToSlice()
expectedLicenses := pkg.NewLicenseSet(tt.expectedLicenses...).ToSlice()
assert.Equal(t, expectedLicenses, actualLicenses, "license contents do not match expected values")
})
}
}
func TestApplyComplianceRules_DropAndStub(t *testing.T) {
p := pkg.Package{Name: "", Version: ""}
p.SetID()

View File

@ -1,15 +1,33 @@
package cataloging
import "github.com/anchore/syft/internal/licenses"
import (
"github.com/anchore/syft/internal/licenses"
)
// LicenseContent controls when license content should be included in the SBOM.
type LicenseContent string
const (
LicenseContentIncludeAll LicenseContent = "all"
LicenseContentIncludeUnknown LicenseContent = "unknown"
LicenseContentExcludeAll LicenseContent = "none"
)
type LicenseConfig struct {
IncludeUnkownLicenseContent bool `json:"include-unknown-license-content" yaml:"include-unknown-license-content" mapstructure:"include-unknown-license-content"`
Coverage float64 `json:"coverage" yaml:"coverage" mapstructure:"coverage"`
// IncludeUnknownLicenseContent controls whether the content of a license should be included in the SBOM when the license ID cannot be determined.
// Deprecated: use IncludeContent instead
IncludeUnknownLicenseContent bool `json:"-" yaml:"-" mapstructure:"-"`
// IncludeContent controls whether license copy discovered should be included in the SBOM.
IncludeContent LicenseContent `json:"include-content" yaml:"include-content" mapstructure:"include-content"`
// Coverage is the percentage of text that must match a license for it to be considered a match.
Coverage float64 `json:"coverage" yaml:"coverage" mapstructure:"coverage"`
}
func DefaultLicenseConfig() LicenseConfig {
return LicenseConfig{
IncludeUnkownLicenseContent: licenses.DefaultIncludeLicenseContent,
Coverage: licenses.DefaultCoverageThreshold,
IncludeContent: LicenseContentExcludeAll,
Coverage: licenses.DefaultCoverageThreshold,
}
}

View File

@ -109,7 +109,6 @@ func setupContext(ctx context.Context, cfg *CreateSBOMConfig) (context.Context,
func SetContextLicenseScanner(ctx context.Context, cfg cataloging.LicenseConfig) (context.Context, error) {
// inject a single license scanner and content config for all package cataloging tasks into context
licenseScanner, err := licenses.NewDefaultScanner(
licenses.WithIncludeLicenseContent(cfg.IncludeUnkownLicenseContent),
licenses.WithCoverage(cfg.Coverage),
)
if err != nil {

View File

@ -5,7 +5,6 @@ import (
"crypto/sha1"
"fmt"
"path"
"regexp"
"slices"
"sort"
"strings"
@ -15,7 +14,6 @@ import (
"github.com/spdx/tools-golang/spdx"
"github.com/anchore/packageurl-go"
internallicenses "github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/mimetype"
"github.com/anchore/syft/internal/relationship"
@ -50,7 +48,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
name, namespace := helpers.DocumentNameAndNamespace(s.Source, s.Descriptor)
rels := relationship.NewIndex(s.Relationships...)
packages := toPackages(rels, s.Artifacts.Packages, s)
packages, otherLicenses := toPackages(rels, s.Artifacts.Packages, s)
allRelationships := toRelationships(rels.All())
@ -156,7 +154,7 @@ func ToFormatModel(s sbom.SBOM) *spdx.Document {
Packages: packages,
Files: toFiles(s),
Relationships: allRelationships,
OtherLicenses: toOtherLicenses(s.Artifacts.Packages),
OtherLicenses: convertOtherLicense(otherLicenses),
}
}
@ -319,16 +317,18 @@ func toSPDXID(identifiable artifact.Identifiable) spdx.ElementID {
// packages populates all Package Information from the package Collection (see https://spdx.github.io/spdx-spec/3-package-information/)
//
//nolint:funlen
func toPackages(rels *relationship.Index, catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Package) {
func toPackages(rels *relationship.Index, catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Package, otherLicenses []spdx.OtherLicense) {
otherLicenseSet := helpers.NewSPDXOtherLicenseSet()
for _, p := range catalog.Sorted() {
// name should be guaranteed to be unique, but semantically useful and stable
// name should be guaranteed to be unique but semantically useful and stable
id := toSPDXID(p)
// If the Concluded License is not the same as the Declared License, a written explanation should be provided
// in the Comments on License field (section 7.16). With respect to NOASSERTION, a written explanation in
// the Comments on License field (section 7.16) is preferred.
// extract these correctly to the spdx license format
concluded, declared := helpers.License(p)
concluded, declared, ol := helpers.License(p)
otherLicenseSet.Add(ol...)
// two ways to get filesAnalyzed == true:
// 1. syft has generated a sha1 digest for the package itself - usually in the java cataloger
@ -487,7 +487,7 @@ func toPackages(rels *relationship.Index, catalog *pkg.Collection, sbom sbom.SBO
PackageAttributionTexts: nil,
})
}
return results
return results, otherLicenseSet.ToSlice()
}
func toPackageChecksums(p pkg.Package) ([]spdx.Checksum, bool) {
@ -740,81 +740,14 @@ func toFileTypes(metadata *file.Metadata) (ty []string) {
return ty
}
// other licenses are for licenses from the pkg.Package that do not have a valid SPDX Expression
// OR are an expression that is a single `License-Ref-*`
func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
licenses := map[string]helpers.SPDXLicense{}
for p := range catalog.Enumerate() {
declaredLicenses, concludedLicenses := helpers.ParseLicenses(p.Licenses.ToSlice())
for _, l := range declaredLicenses {
if l.Value != "" {
licenses[l.ID] = l
}
if l.ID != "" && isLicenseRef(l.ID) {
licenses[l.ID] = l
}
}
for _, l := range concludedLicenses {
if l.Value != "" {
licenses[l.ID] = l
}
if l.ID != "" && isLicenseRef(l.ID) {
licenses[l.ID] = l
}
}
}
var result []*spdx.OtherLicense
var ids []string
for licenseID := range licenses {
ids = append(ids, licenseID)
}
slices.Sort(ids)
for _, id := range ids {
license := licenses[id]
value := license.Value
fullText := license.FullText
// handle cases where LicenseRef needs to be included in hasExtractedLicensingInfos
if license.Value == "" {
value, _ = strings.CutPrefix(license.ID, "LicenseRef-")
}
other := &spdx.OtherLicense{
LicenseIdentifier: license.ID,
}
if fullText != "" {
other.ExtractedText = fullText
} else {
other.ExtractedText = value
}
customPrefix := spdxlicense.LicenseRefPrefix + helpers.SanitizeElementID(internallicenses.UnknownLicensePrefix)
if strings.HasPrefix(license.ID, customPrefix) {
other.LicenseName = strings.TrimPrefix(license.ID, customPrefix)
other.LicenseComment = strings.Trim(internallicenses.UnknownLicensePrefix, "-_")
}
result = append(result, other)
}
return result
}
var licenseRefRegEx = regexp.MustCompile(`^LicenseRef-[A-Za-z0-9_-]+$`)
// isSingularLicenseRef checks if the string is a singular LicenseRef-* identifier
func isLicenseRef(s string) bool {
// Match the input string against the regex
return licenseRefRegEx.MatchString(s)
}
// TODO: handle SPDX excludes file case
// 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
// the above link contains the SPDX algorithm for a package verification code
func newPackageVerificationCode(rels *relationship.Index, p pkg.Package, sbom sbom.SBOM) *spdx.PackageVerificationCode {
// key off of the contains relationship;
// spdx validator will fail if a package claims to contain a file but no sha1 provided
// if a sha1 for a file is provided then the validator will fail if the package does not have
// key off of the spdx contains relationship;
// spdx validator will fail if a package claims to contain a file, but no sha1 provided
// if a sha1 for a file is provided, then the validator will fail if the package does not have
// a package verification code
coordinates := rels.Coordinates(p, artifact.ContainsRelationship)
var digests []file.Digest
@ -887,3 +820,15 @@ func convertAbsoluteToRelative(absPath string) (string, error) {
return relPath, nil
}
func convertOtherLicense(otherLicenses []spdx.OtherLicense) []*spdx.OtherLicense {
if len(otherLicenses) == 0 {
return nil
}
result := make([]*spdx.OtherLicense, 0, len(otherLicenses))
for i := range otherLicenses {
result = append(result, &otherLicenses[i])
}
return result
}

View File

@ -1,8 +1,10 @@
package spdxhelpers
import (
"context"
"fmt"
"regexp"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
@ -736,7 +738,7 @@ func Test_H1Digest(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
catalog := pkg.NewCollection(test.pkg)
pkgs := toPackages(relationship.NewIndex(), catalog, s)
pkgs, _ := toPackages(relationship.NewIndex(), catalog, s)
require.Len(t, pkgs, 1)
for _, p := range pkgs {
if test.expectedDigest == "" {
@ -753,29 +755,31 @@ func Test_H1Digest(t *testing.T) {
}
func Test_OtherLicenses(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
pkg pkg.Package
expected []*spdx.OtherLicense
expected []spdx.OtherLicense
}{
{
name: "no licenseRef",
pkg: pkg.Package{
Licenses: pkg.NewLicenseSet(),
},
expected: nil,
expected: []spdx.OtherLicense{},
},
{
name: "single licenseRef",
pkg: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("foobar"),
pkg.NewLicenseWithContext(ctx, "foobar"),
),
},
expected: []*spdx.OtherLicense{
expected: []spdx.OtherLicense{
{
LicenseIdentifier: "LicenseRef-foobar",
ExtractedText: "foobar",
LicenseName: "foobar",
ExtractedText: "NOASSERTION",
},
},
},
@ -783,18 +787,20 @@ func Test_OtherLicenses(t *testing.T) {
name: "multiple licenseRef",
pkg: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("internal made up license name"),
pkg.NewLicense("new apple license 2.0"),
pkg.NewLicenseWithContext(ctx, "internal made up license name"),
pkg.NewLicenseWithContext(ctx, "new apple license 2.0"),
),
},
expected: []*spdx.OtherLicense{
expected: []spdx.OtherLicense{
{
LicenseIdentifier: "LicenseRef-internal-made-up-license-name",
ExtractedText: "internal made up license name",
ExtractedText: "NOASSERTION",
LicenseName: "internal made up license name",
},
{
LicenseIdentifier: "LicenseRef-new-apple-license-2.0",
ExtractedText: "new apple license 2.0",
ExtractedText: "NOASSERTION",
LicenseName: "new apple license 2.0",
},
},
},
@ -802,31 +808,27 @@ func Test_OtherLicenses(t *testing.T) {
name: "LicenseRef as a valid spdx expression",
pkg: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("LicenseRef-Fedora-Public-Domain"),
pkg.NewLicenseWithContext(ctx, "LicenseRef-Fedora-Public-Domain"),
),
},
expected: []*spdx.OtherLicense{
{
LicenseIdentifier: "LicenseRef-Fedora-Public-Domain",
ExtractedText: "Fedora-Public-Domain",
},
},
expected: []spdx.OtherLicense{},
},
{
name: "LicenseRef as a valid spdx expression does not otherize compound spdx expressions",
pkg: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("(MIT AND LicenseRef-Fedora-Public-Domain)"),
pkg.NewLicenseWithContext(ctx, "(MIT AND LicenseRef-Fedora-Public-Domain)"),
),
},
expected: nil,
expected: []spdx.OtherLicense{},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
catalog := pkg.NewCollection(test.pkg)
otherLicenses := toOtherLicenses(catalog)
rels := relationship.NewIndex()
_, otherLicenses := toPackages(rels, catalog, sbom.SBOM{})
require.Len(t, otherLicenses, len(test.expected))
require.Equal(t, test.expected, otherLicenses)
})
@ -902,18 +904,19 @@ func Test_toSPDXID(t *testing.T) {
}
func Test_otherLicenses(t *testing.T) {
ctx := context.TODO()
pkg1 := pkg.Package{
Name: "first-pkg",
Version: "1.1",
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
pkg.NewLicenseWithContext(ctx, "MIT"),
),
}
pkg2 := pkg.Package{
Name: "second-pkg",
Version: "2.2",
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("non spdx license"),
pkg.NewLicenseWithContext(ctx, "non spdx license"),
),
}
bigText := `
@ -923,7 +926,7 @@ func Test_otherLicenses(t *testing.T) {
Name: "third-pkg",
Version: "3.3",
Licenses: pkg.NewLicenseSet(
pkg.NewLicense(bigText),
pkg.NewLicenseWithContext(ctx, bigText),
),
}
@ -938,22 +941,25 @@ func Test_otherLicenses(t *testing.T) {
expected: nil,
},
{
name: "other licenses includes original text",
name: "other licenses must include some original text",
packages: []pkg.Package{pkg2},
expected: []*spdx.OtherLicense{
{
LicenseIdentifier: "LicenseRef-non-spdx-license",
ExtractedText: "non spdx license",
LicenseName: "non spdx license",
ExtractedText: "NOASSERTION",
},
},
},
{
name: "big licenses get hashed",
name: "big licenses get hashed and space is trimmed",
packages: []pkg.Package{pkg3},
expected: []*spdx.OtherLicense{
{
LicenseIdentifier: "LicenseRef-e9a1e42833d3e456f147052f4d312101bd171a0798893169fe596ca6b55c049e",
ExtractedText: bigText,
LicenseIdentifier: "LicenseRef-3f17782eef51ae86f18fdd6832f5918e2b40f688b52c9adc07ba6ec1024ef408",
// Carries through the syft-json license value when we shasum large texts
LicenseName: "LicenseRef-sha256:3f17782eef51ae86f18fdd6832f5918e2b40f688b52c9adc07ba6ec1024ef408",
ExtractedText: strings.TrimSpace(bigText),
},
},
},

View File

@ -1,6 +1,7 @@
package spdxhelpers
import (
"context"
"errors"
"fmt"
"net/url"
@ -535,14 +536,14 @@ func parseSPDXLicenses(p *spdx.Package) []pkg.License {
// concluded
if p.PackageLicenseConcluded != helpers.NOASSERTION && p.PackageLicenseConcluded != helpers.NONE && p.PackageLicenseConcluded != "" {
l := pkg.NewLicense(cleanSPDXID(p.PackageLicenseConcluded))
l := pkg.NewLicenseWithContext(context.TODO(), cleanSPDXID(p.PackageLicenseConcluded))
l.Type = license.Concluded
licenses = append(licenses, l)
}
// declared
if p.PackageLicenseDeclared != helpers.NOASSERTION && p.PackageLicenseDeclared != helpers.NONE && p.PackageLicenseDeclared != "" {
l := pkg.NewLicense(cleanSPDXID(p.PackageLicenseDeclared))
l := pkg.NewLicenseWithContext(context.TODO(), cleanSPDXID(p.PackageLicenseDeclared))
l.Type = license.Declared
licenses = append(licenses, l)
}

View File

@ -24,7 +24,7 @@
},
"components": [
{
"bom-ref": "4dd25c6ee16b729a",
"bom-ref": "f04d218ff5ff50db",
"type": "library",
"name": "package-1",
"version": "1.0.1",

View File

@ -25,7 +25,7 @@
},
"components": [
{
"bom-ref": "72567175418f73f8",
"bom-ref": "2f52f617f1548337",
"type": "library",
"name": "package-1",
"version": "1.0.1",

View File

@ -16,7 +16,7 @@
</component>
</metadata>
<components>
<component bom-ref="4dd25c6ee16b729a" type="library">
<component bom-ref="f04d218ff5ff50db" type="library">
<name>package-1</name>
<version>1.0.1</version>
<licenses>

View File

@ -17,7 +17,7 @@
</component>
</metadata>
<components>
<component bom-ref="72567175418f73f8" type="library">
<component bom-ref="2f52f617f1548337" type="library">
<name>package-1</name>
<version>1.0.1</version>
<licenses>

View File

@ -1,6 +1,7 @@
package helpers
import (
"context"
"encoding/base64"
"strings"
@ -58,11 +59,11 @@ func decodeLicenses(c *cyclonedx.Component) []pkg.License {
// these fields are mutually exclusive in the spec
switch {
case l.License != nil && l.License.ID != "":
licenses = append(licenses, pkg.NewLicenseFromURLs(l.License.ID, l.License.URL))
licenses = append(licenses, pkg.NewLicenseFromURLsWithContext(context.TODO(), l.License.ID, l.License.URL))
case l.License != nil && l.License.Name != "":
licenses = append(licenses, pkg.NewLicenseFromURLs(l.License.Name, l.License.URL))
licenses = append(licenses, pkg.NewLicenseFromURLsWithContext(context.TODO(), l.License.Name, l.License.URL))
case l.Expression != "":
licenses = append(licenses, pkg.NewLicense(l.Expression))
licenses = append(licenses, pkg.NewLicenseWithContext(context.TODO(), l.Expression))
default:
}
}

View File

@ -1,6 +1,7 @@
package helpers
import (
"context"
"testing"
"github.com/CycloneDX/cyclonedx-go"
@ -12,6 +13,7 @@ import (
)
func Test_encodeLicense(t *testing.T) {
ctx := context.TODO()
tests := []struct {
name string
input pkg.Package
@ -25,7 +27,7 @@ func Test_encodeLicense(t *testing.T) {
name: "no SPDX licenses",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("RandomLicense"),
pkg.NewLicenseWithContext(ctx, "RandomLicense"),
),
},
expected: &cyclonedx.Licenses{
@ -40,8 +42,8 @@ func Test_encodeLicense(t *testing.T) {
name: "single SPDX ID and Non SPDX ID",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("mit"),
pkg.NewLicense("FOOBAR"),
pkg.NewLicenseWithContext(ctx, "mit"),
pkg.NewLicenseWithContext(ctx, "FOOBAR"),
),
},
expected: &cyclonedx.Licenses{
@ -61,7 +63,7 @@ func Test_encodeLicense(t *testing.T) {
name: "with complex SPDX license expression",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT AND GPL-3.0-only"),
pkg.NewLicenseWithContext(ctx, "MIT AND GPL-3.0-only"),
),
},
expected: &cyclonedx.Licenses{
@ -74,8 +76,8 @@ func Test_encodeLicense(t *testing.T) {
name: "with multiple complex SPDX license expression",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT AND GPL-3.0-only"),
pkg.NewLicense("MIT AND GPL-3.0-only WITH Classpath-exception-2.0"),
pkg.NewLicenseWithContext(ctx, "MIT AND GPL-3.0-only"),
pkg.NewLicenseWithContext(ctx, "MIT AND GPL-3.0-only WITH Classpath-exception-2.0"),
),
},
expected: &cyclonedx.Licenses{
@ -88,9 +90,9 @@ func Test_encodeLicense(t *testing.T) {
name: "with multiple URLs and expressions",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromURLs("MIT", "https://opensource.org/licenses/MIT", "https://spdx.org/licenses/MIT.html"),
pkg.NewLicense("MIT AND GPL-3.0-only"),
pkg.NewLicenseFromURLs("FakeLicense", "htts://someurl.com"),
pkg.NewLicenseFromURLsWithContext(ctx, "MIT", "https://opensource.org/licenses/MIT", "https://spdx.org/licenses/MIT.html"),
pkg.NewLicenseWithContext(ctx, "MIT AND GPL-3.0-only"),
pkg.NewLicenseFromURLsWithContext(ctx, "FakeLicense", "htts://someurl.com"),
),
},
expected: &cyclonedx.Licenses{
@ -123,8 +125,8 @@ func Test_encodeLicense(t *testing.T) {
name: "with multiple values licenses are deduplicated",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("Apache-2"),
pkg.NewLicense("Apache-2.0"),
pkg.NewLicenseWithContext(ctx, "Apache-2"),
pkg.NewLicenseWithContext(ctx, "Apache-2.0"),
),
},
expected: &cyclonedx.Licenses{
@ -139,9 +141,9 @@ func Test_encodeLicense(t *testing.T) {
name: "with multiple URLs and single with no URLs",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
pkg.NewLicenseFromURLs("MIT", "https://opensource.org/licenses/MIT", "https://spdx.org/licenses/MIT.html"),
pkg.NewLicense("MIT AND GPL-3.0-only"),
pkg.NewLicenseWithContext(ctx, "MIT"),
pkg.NewLicenseFromURLsWithContext(ctx, "MIT", "https://opensource.org/licenses/MIT", "https://spdx.org/licenses/MIT.html"),
pkg.NewLicenseWithContext(ctx, "MIT AND GPL-3.0-only"),
),
},
expected: &cyclonedx.Licenses{
@ -167,7 +169,7 @@ func Test_encodeLicense(t *testing.T) {
{
name: "single parenthesized SPDX expression",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValues("(MIT OR Apache-2.0)")...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValuesWithContext(ctx, "(MIT OR Apache-2.0)")...),
},
expected: &cyclonedx.Licenses{
{
@ -179,7 +181,7 @@ func Test_encodeLicense(t *testing.T) {
name: "single license AND to parenthesized SPDX expression",
// (LGPL-3.0-or-later OR GPL-2.0-or-later OR (LGPL-3.0-or-later AND GPL-2.0-or-later)) AND GFDL-1.3-invariants-or-later
input: pkg.Package{
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValues("(LGPL-3.0-or-later OR GPL-2.0-or-later OR (LGPL-3.0-or-later AND GPL-2.0-or-later)) AND GFDL-1.3-invariants-or-later")...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValuesWithContext(ctx, "(LGPL-3.0-or-later OR GPL-2.0-or-later OR (LGPL-3.0-or-later AND GPL-2.0-or-later)) AND GFDL-1.3-invariants-or-later")...),
},
expected: &cyclonedx.Licenses{
{
@ -248,7 +250,6 @@ func TestDecodeLicenses(t *testing.T) {
Value: "RandomLicense",
// CycloneDX specification doesn't give a field for determining the license type
Type: license.Declared,
URLs: []string{},
},
},
},
@ -268,7 +269,6 @@ func TestDecodeLicenses(t *testing.T) {
Value: "MIT",
SPDXExpression: "MIT",
Type: license.Declared,
URLs: []string{},
},
},
},

View File

@ -1,16 +1,17 @@
package helpers
import (
"crypto/sha256"
"fmt"
"sort"
"strings"
"github.com/spdx/tools-golang/spdx"
"github.com/anchore/syft/internal/spdxlicense"
"github.com/anchore/syft/syft/license"
"github.com/anchore/syft/syft/pkg"
)
func License(p pkg.Package) (concluded, declared string) {
func License(p pkg.Package) (concluded, declared string, otherLicenses []spdx.OtherLicense) {
// source: https://spdx.github.io/spdx-spec/v2.3/package-information/#713-concluded-license-field
// The options to populate this field are limited to:
// A valid SPDX License Expression as defined in Annex D;
@ -21,15 +22,15 @@ func License(p pkg.Package) (concluded, declared string) {
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
if p.Licenses.Empty() {
return NOASSERTION, NOASSERTION
return NOASSERTION, NOASSERTION, nil
}
// take all licenses and assume an AND expression;
// for information about license expressions see:
// https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/
pc, pd := ParseLicenses(p.Licenses.ToSlice())
pc, pd, ol := ParseLicenses(p.Licenses.ToSlice())
return joinLicenses(pc), joinLicenses(pd)
return joinLicenses(pc), joinLicenses(pd), ol
}
func joinLicenses(licenses []SPDXLicense) string {
@ -58,14 +59,32 @@ func joinLicenses(licenses []SPDXLicense) string {
}
type SPDXLicense struct {
ID string
Value string
FullText string
// Valid SPDX ID OR License Value (should have LicenseRef- prefix and be sanitized)
// OR combination of the above as a valid SPDX License Expression as defined in Annex D.
// https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/
ID string
// If the SPDX license is not on the SPDX License List
LicenseName string
FullText string // 0..1 (Mandatory, one) if there is a License Identifier assigned (LicenseRef).
URLs []string
}
func ParseLicenses(raw []pkg.License) (concluded, declared []SPDXLicense) {
func ParseLicenses(raw []pkg.License) (concluded, declared []SPDXLicense, otherLicenses []spdx.OtherLicense) {
for _, l := range raw {
candidate := createSPDXLicense(l)
// isCustomLicense determines if the candidate falls under https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#
// of the SPDX spec, where:
// - we should not have a complex SPDX expression
// - if a single license, it should not be a known license (on the SPDX license list)
if l.SPDXExpression == "" && strings.Contains(candidate.ID, spdxlicense.LicenseRefPrefix) {
otherLicenses = append(otherLicenses, spdx.OtherLicense{
LicenseIdentifier: candidate.ID,
ExtractedText: candidate.FullText,
LicenseName: candidate.LicenseName,
LicenseCrossReferences: candidate.URLs,
})
}
switch l.Type {
case license.Concluded:
concluded = append(concluded, candidate)
@ -74,35 +93,70 @@ func ParseLicenses(raw []pkg.License) (concluded, declared []SPDXLicense) {
}
}
return concluded, declared
return concluded, declared, otherLicenses
}
func createSPDXLicense(l pkg.License) SPDXLicense {
candidate := SPDXLicense{
ID: generateLicenseID(l),
FullText: l.Contents,
// source: https://spdx.github.io/spdx-spec/v2.3/other-licensing-information-detected/#102-extracted-text-field
// we need to populate this field in the spdx document if we have a license ref
// 0..1 (Mandatory, one) if there is a License Identifier assigned (LicenseRef).
ft := NOASSERTION
if l.Contents != "" {
ft = l.Contents
}
if l.SPDXExpression == "" {
candidate.Value = l.Value
return SPDXLicense{
ID: generateLicenseID(l),
LicenseName: l.Value,
FullText: ft,
URLs: l.URLs,
}
return candidate
}
// generateLicenseID generates a license ID for the given license, which is either the license value or the SPDX expression.
func generateLicenseID(l pkg.License) string {
if l.SPDXExpression != "" {
return l.SPDXExpression
}
if l.Value != "" {
return spdxlicense.LicenseRefPrefix + SanitizeElementID(l.Value)
// syft format includes the algo for the sha in the values
// we can strip this and just make LicenseRef-<sum> for spdx consumption
id := strings.ReplaceAll(l.Value, "sha256:", "")
if !strings.HasPrefix(id, "LicenseRef-") {
id = "LicenseRef-" + id
}
return licenseSum(l.Contents)
return SanitizeElementID(id)
}
func licenseSum(s string) string {
if len(s) <= 64 {
return spdxlicense.LicenseRefPrefix + SanitizeElementID(s)
}
hash := sha256.Sum256([]byte(s))
return fmt.Sprintf("%s%x", spdxlicense.LicenseRefPrefix, hash)
type SPDXOtherLicenseSet struct {
set map[string]spdx.OtherLicense
}
func NewSPDXOtherLicenseSet() *SPDXOtherLicenseSet {
return &SPDXOtherLicenseSet{
set: make(map[string]spdx.OtherLicense),
}
}
func (s *SPDXOtherLicenseSet) Add(licenses ...spdx.OtherLicense) {
for _, l := range licenses {
s.set[l.LicenseIdentifier] = l
}
}
type ByLicenseIdentifier []spdx.OtherLicense
func (o ByLicenseIdentifier) Len() int { return len(o) }
func (o ByLicenseIdentifier) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
func (o ByLicenseIdentifier) Less(i, j int) bool {
return o[i].LicenseIdentifier < o[j].LicenseIdentifier
}
func (s *SPDXOtherLicenseSet) ToSlice() []spdx.OtherLicense {
values := make([]spdx.OtherLicense, 0, len(s.set))
for _, v := range s.set {
values = append(values, v)
}
sort.Sort(ByLicenseIdentifier(values))
return values
}

View File

@ -1,9 +1,10 @@
package helpers
import (
"strings"
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/internal/spdxlicense"
@ -11,6 +12,7 @@ import (
)
func Test_License(t *testing.T) {
ctx := context.TODO()
type expected struct {
concluded string
declared string
@ -31,7 +33,7 @@ func Test_License(t *testing.T) {
{
name: "no SPDX licenses",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(pkg.NewLicense("made-up")),
Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, "made-up")),
},
expected: expected{
concluded: "NOASSERTION",
@ -41,7 +43,7 @@ func Test_License(t *testing.T) {
{
name: "with SPDX license",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")),
Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, "MIT")),
},
expected: struct {
concluded string
@ -55,8 +57,8 @@ func Test_License(t *testing.T) {
name: "with SPDX license expression",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
pkg.NewLicense("GPL-3.0-only"),
pkg.NewLicenseWithContext(ctx, "MIT"),
pkg.NewLicenseWithContext(ctx, "GPL-3.0-only"),
),
},
expected: expected{
@ -69,9 +71,9 @@ func Test_License(t *testing.T) {
name: "includes valid LicenseRef-",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("one thing first"),
pkg.NewLicense("two things/#$^second"),
pkg.NewLicense("MIT"),
pkg.NewLicenseWithContext(ctx, "one thing first"),
pkg.NewLicenseWithContext(ctx, "two things/#$^second"),
pkg.NewLicenseWithContext(ctx, "MIT"),
),
},
expected: expected{
@ -84,9 +86,9 @@ func Test_License(t *testing.T) {
name: "join parentheses correctly",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("one thing first"),
pkg.NewLicense("MIT AND GPL-3.0-only"),
pkg.NewLicense("MIT OR APACHE-2.0"),
pkg.NewLicenseWithContext(ctx, "one thing first"),
pkg.NewLicenseWithContext(ctx, "MIT AND GPL-3.0-only"),
pkg.NewLicenseWithContext(ctx, "MIT OR APACHE-2.0"),
),
},
expected: expected{
@ -98,7 +100,7 @@ func Test_License(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c, d := License(test.input)
c, d, _ := License(test.input)
assert.Equal(t, test.expected.concluded, c)
assert.Equal(t, test.expected.declared, d)
})
@ -123,11 +125,13 @@ func TestGenerateLicenseID(t *testing.T) {
{
name: "Uses value if no SPDX expression",
license: pkg.License{
Value: "MIT",
Value: "my-sweet-custom-license",
},
expected: spdxlicense.LicenseRefPrefix + "MIT",
expected: spdxlicense.LicenseRefPrefix + "my-sweet-custom-license",
},
{
// note: this is an oversight of the SPDX spec. It does NOT allow "+" in the ID even though they are
// significant to the licenses in the expressions below
name: "Long value is sanitized correctly",
license: pkg.License{
Value: "LGPLv2+ and LGPLv2+ with exceptions and GPLv2+ and GPLv2+ with exceptions and BSD and Inner-Net and ISC and Public Domain and GFDL",
@ -135,13 +139,6 @@ func TestGenerateLicenseID(t *testing.T) {
expected: spdxlicense.LicenseRefPrefix +
"LGPLv2--and-LGPLv2--with-exceptions-and-GPLv2--and-GPLv2--with-exceptions-and-BSD-and-Inner-Net-and-ISC-and-Public-Domain-and-GFDL",
},
{
name: "Uses hash of contents when nothing else is provided",
license: pkg.License{
Contents: "This is a very long custom license text that should be hashed because it's more than 64 characters long.",
},
expected: "", // We'll verify it starts with the correct prefix
},
}
for _, tt := range tests {
@ -160,34 +157,92 @@ func TestGenerateLicenseID(t *testing.T) {
func Test_joinLicenses(t *testing.T) {
tests := []struct {
name string
args []string
args []SPDXLicense
want string
}{
{
name: "multiple licenses",
args: []string{"MIT", "GPL-3.0-only"},
args: []SPDXLicense{{ID: "MIT"}, {ID: "GPL-3.0-only"}},
want: "MIT AND GPL-3.0-only",
},
{
name: "multiple licenses with complex expressions",
args: []string{"MIT AND Apache", "GPL-3.0-only"},
args: []SPDXLicense{{ID: "MIT AND Apache"}, {ID: "GPL-3.0-only"}},
want: "(MIT AND Apache) AND GPL-3.0-only",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, joinLicenses(toSpdxLicenses(tt.args)), "joinLicenses(%v)", tt.args)
assert.Equalf(t, tt.want, joinLicenses(tt.args), "joinLicenses(%v)", tt.args)
})
}
}
func toSpdxLicenses(ids []string) (licenses []SPDXLicense) {
for _, l := range ids {
license := SPDXLicense{ID: l}
if strings.HasPrefix(l, spdxlicense.LicenseRefPrefix) {
license.Value = l
}
licenses = append(licenses, license)
func TestCreateSPDXLicenseAndGenerateLicenseID(t *testing.T) {
tests := []struct {
name string
input pkg.License
expected SPDXLicense
}{
{
name: "SPDX expression used as ID",
input: pkg.License{
SPDXExpression: "MIT",
Value: "MIT",
Contents: "",
},
expected: SPDXLicense{
ID: "MIT",
LicenseName: "MIT",
FullText: "NOASSERTION",
},
},
{
name: "LicenseRef with contents",
input: pkg.License{
Value: "sha256:123abc",
Contents: "license contents here",
},
expected: SPDXLicense{
ID: "LicenseRef-123abc",
LicenseName: "sha256:123abc",
FullText: "license contents here",
},
},
{
name: "LicenseRef without contents",
input: pkg.License{
Value: "custom-license",
Contents: "",
},
expected: SPDXLicense{
ID: "LicenseRef-custom-license",
LicenseName: "custom-license",
FullText: "NOASSERTION",
},
},
{
name: "URL is passed through",
input: pkg.License{
SPDXExpression: "MIT",
URLs: []string{
"https://example.com/license",
},
},
expected: SPDXLicense{
ID: "MIT",
FullText: "NOASSERTION",
URLs: []string{"https://example.com/license"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
license := createSPDXLicense(tt.input)
if d := cmp.Diff(tt.expected, license); d != "" {
t.Errorf("createSPDXLicense() mismatch (-want +got):\n%s", d)
}
})
}
return licenses
}

View File

@ -1,6 +1,7 @@
package testutil
import (
"context"
"os"
"path/filepath"
"testing"
@ -98,7 +99,7 @@ func DirectoryInputWithAuthorField(t testing.TB) sbom.SBOM {
func newDirectoryCatalog() *pkg.Collection {
catalog := pkg.NewCollection()
ctx := context.TODO()
// populate catalog with test data
catalog.Add(pkg.Package{
Name: "package-1",
@ -110,7 +111,7 @@ func newDirectoryCatalog() *pkg.Collection {
),
Language: pkg.Python,
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
pkg.NewLicenseWithContext(ctx, "MIT"),
),
Metadata: pkg.PythonPackage{
Name: "package-1",
@ -149,7 +150,7 @@ func newDirectoryCatalog() *pkg.Collection {
func newDirectoryCatalogWithAuthorField() *pkg.Collection {
catalog := pkg.NewCollection()
ctx := context.TODO()
// populate catalog with test data
catalog.Add(pkg.Package{
Name: "package-1",
@ -161,7 +162,7 @@ func newDirectoryCatalogWithAuthorField() *pkg.Collection {
),
Language: pkg.Python,
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
pkg.NewLicenseWithContext(ctx, "MIT"),
),
Metadata: pkg.PythonPackage{
Name: "package-1",

View File

@ -1,6 +1,7 @@
package testutil
import (
"context"
"os"
"path/filepath"
"testing"
@ -98,7 +99,7 @@ func populateImageCatalog(catalog *pkg.Collection, img *image.Image) {
// TODO: this helper function is coupled to the image-simple fixture, which seems like a bad idea
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
ctx := context.TODO()
// populate catalog with test data
if ref1 != nil {
catalog.Add(pkg.Package{
@ -111,7 +112,7 @@ func populateImageCatalog(catalog *pkg.Collection, img *image.Image) {
FoundBy: "the-cataloger-1",
Language: pkg.Python,
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
pkg.NewLicenseWithContext(ctx, "MIT"),
),
Metadata: pkg.PythonPackage{
Name: "package-1",

View File

@ -15,7 +15,7 @@
"packages": [
{
"name": "package-1",
"SPDXID": "SPDXRef-Package-python-package-1-4dd25c6ee16b729a",
"SPDXID": "SPDXRef-Package-python-package-1-f04d218ff5ff50db",
"versionInfo": "1.0.1",
"supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION",
@ -76,7 +76,7 @@
"relationships": [
{
"spdxElementId": "SPDXRef-DocumentRoot-Directory-some-path",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-4dd25c6ee16b729a",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-f04d218ff5ff50db",
"relationshipType": "CONTAINS"
},
{

View File

@ -15,7 +15,7 @@
"packages": [
{
"name": "package-1",
"SPDXID": "SPDXRef-Package-python-package-1-72567175418f73f8",
"SPDXID": "SPDXRef-Package-python-package-1-2f52f617f1548337",
"versionInfo": "1.0.1",
"supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION",
@ -90,7 +90,7 @@
"relationships": [
{
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-72567175418f73f8",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-2f52f617f1548337",
"relationshipType": "CONTAINS"
},
{

View File

@ -15,7 +15,7 @@
"packages": [
{
"name": "package-1",
"SPDXID": "SPDXRef-Package-python-package-1-72567175418f73f8",
"SPDXID": "SPDXRef-Package-python-package-1-2f52f617f1548337",
"versionInfo": "1.0.1",
"supplier": "NOASSERTION",
"downloadLocation": "NOASSERTION",
@ -199,38 +199,38 @@
],
"relationships": [
{
"spdxElementId": "SPDXRef-Package-python-package-1-72567175418f73f8",
"spdxElementId": "SPDXRef-Package-python-package-1-2f52f617f1548337",
"relatedSpdxElement": "SPDXRef-File-f1-5265a4dde3edbf7c",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-72567175418f73f8",
"spdxElementId": "SPDXRef-Package-python-package-1-2f52f617f1548337",
"relatedSpdxElement": "SPDXRef-File-z1-f5-839d99ee67d9d174",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-72567175418f73f8",
"spdxElementId": "SPDXRef-Package-python-package-1-2f52f617f1548337",
"relatedSpdxElement": "SPDXRef-File-a1-f6-9c2f7510199b17f6",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-72567175418f73f8",
"spdxElementId": "SPDXRef-Package-python-package-1-2f52f617f1548337",
"relatedSpdxElement": "SPDXRef-File-d2-f4-c641caa71518099f",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-72567175418f73f8",
"spdxElementId": "SPDXRef-Package-python-package-1-2f52f617f1548337",
"relatedSpdxElement": "SPDXRef-File-d1-f3-c6f5b29dca12661f",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Package-python-package-1-72567175418f73f8",
"spdxElementId": "SPDXRef-Package-python-package-1-2f52f617f1548337",
"relatedSpdxElement": "SPDXRef-File-f2-f9e49132a4b96ccd",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-DocumentRoot-Image-user-image-input",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-72567175418f73f8",
"relatedSpdxElement": "SPDXRef-Package-python-package-1-2f52f617f1548337",
"relationshipType": "CONTAINS"
},
{

View File

@ -91,7 +91,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1
PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-72567175418f73f8
SPDXID: SPDXRef-Package-python-package-1-2f52f617f1548337
PackageVersion: 1.0.1
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
@ -105,13 +105,13 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
##### Relationships
Relationship: SPDXRef-Package-python-package-1-72567175418f73f8 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
Relationship: SPDXRef-Package-python-package-1-72567175418f73f8 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
Relationship: SPDXRef-Package-python-package-1-72567175418f73f8 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
Relationship: SPDXRef-Package-python-package-1-72567175418f73f8 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
Relationship: SPDXRef-Package-python-package-1-72567175418f73f8 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
Relationship: SPDXRef-Package-python-package-1-72567175418f73f8 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-72567175418f73f8
Relationship: SPDXRef-Package-python-package-1-2f52f617f1548337 CONTAINS SPDXRef-File-f1-5265a4dde3edbf7c
Relationship: SPDXRef-Package-python-package-1-2f52f617f1548337 CONTAINS SPDXRef-File-z1-f5-839d99ee67d9d174
Relationship: SPDXRef-Package-python-package-1-2f52f617f1548337 CONTAINS SPDXRef-File-a1-f6-9c2f7510199b17f6
Relationship: SPDXRef-Package-python-package-1-2f52f617f1548337 CONTAINS SPDXRef-File-d2-f4-c641caa71518099f
Relationship: SPDXRef-Package-python-package-1-2f52f617f1548337 CONTAINS SPDXRef-File-d1-f3-c6f5b29dca12661f
Relationship: SPDXRef-Package-python-package-1-2f52f617f1548337 CONTAINS SPDXRef-File-f2-f9e49132a4b96ccd
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-2f52f617f1548337
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input

View File

@ -38,7 +38,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1
PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-4dd25c6ee16b729a
SPDXID: SPDXRef-Package-python-package-1-f04d218ff5ff50db
PackageVersion: 1.0.1
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
@ -52,7 +52,7 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-2
##### Relationships
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-python-package-1-4dd25c6ee16b729a
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-python-package-1-f04d218ff5ff50db
Relationship: SPDXRef-DocumentRoot-Directory-some-path CONTAINS SPDXRef-Package-deb-package-2-39392bb5e270f669
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Directory-some-path

View File

@ -41,7 +41,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
##### Package: package-1
PackageName: package-1
SPDXID: SPDXRef-Package-python-package-1-72567175418f73f8
SPDXID: SPDXRef-Package-python-package-1-2f52f617f1548337
PackageVersion: 1.0.1
PackageSupplier: NOASSERTION
PackageDownloadLocation: NOASSERTION
@ -55,7 +55,7 @@ ExternalRef: PACKAGE-MANAGER purl a-purl-1
##### Relationships
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-72567175418f73f8
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-python-package-1-2f52f617f1548337
Relationship: SPDXRef-DocumentRoot-Image-user-image-input CONTAINS SPDXRef-Package-deb-package-2-4b756c6f6fb127a3
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-DocumentRoot-Image-user-image-input

View File

@ -2,6 +2,7 @@ package syftjson
import (
"bytes"
"context"
"flag"
"strings"
"testing"
@ -132,7 +133,7 @@ func TestImageEncoder(t *testing.T) {
func TestEncodeFullJSONDocument(t *testing.T) {
catalog := pkg.NewCollection()
ctx := context.TODO()
p1 := pkg.Package{
Name: "package-1",
Version: "1.0.1",
@ -144,7 +145,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1",
Language: pkg.Python,
Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")),
Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, "MIT")),
Metadata: pkg.PythonPackage{
Name: "package-1",
Version: "1.0.1",

View File

@ -1,7 +1,7 @@
{
"artifacts": [
{
"id": "4dd25c6ee16b729a",
"id": "f04d218ff5ff50db",
"name": "package-1",
"version": "1.0.1",
"type": "python",

View File

@ -1,7 +1,7 @@
{
"artifacts": [
{
"id": "fba4ca04d4906f25",
"id": "951845d9a8d6b5b2",
"name": "package-1",
"version": "1.0.1",
"type": "python",

View File

@ -1,7 +1,7 @@
{
"artifacts": [
{
"id": "72567175418f73f8",
"id": "2f52f617f1548337",
"name": "package-1",
"version": "1.0.1",
"type": "python",

View File

@ -1,6 +1,7 @@
package alpine
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
@ -14,14 +15,14 @@ import (
func TestApkDBCataloger(t *testing.T) {
dbLocation := file.NewLocation("lib/apk/db/installed")
ctx := context.TODO()
bashPkg := pkg.Package{
Name: "bash",
Version: "5.2.21-r0",
Type: pkg.ApkPkg,
FoundBy: "apk-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("GPL-3.0-or-later", dbLocation),
pkg.NewLicenseFromLocationsWithContext(ctx, "GPL-3.0-or-later", dbLocation),
),
Locations: file.NewLocationSet(dbLocation),
Metadata: pkg.ApkDBEntry{
@ -50,7 +51,7 @@ func TestApkDBCataloger(t *testing.T) {
Type: pkg.ApkPkg,
FoundBy: "apk-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("GPL-2.0-only", dbLocation),
pkg.NewLicenseFromLocationsWithContext(ctx, "GPL-2.0-only", dbLocation),
),
Locations: file.NewLocationSet(dbLocation),
Metadata: pkg.ApkDBEntry{
@ -79,7 +80,7 @@ func TestApkDBCataloger(t *testing.T) {
Type: pkg.ApkPkg,
FoundBy: "apk-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", dbLocation),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", dbLocation),
),
Locations: file.NewLocationSet(dbLocation),
Metadata: pkg.ApkDBEntry{
@ -106,7 +107,7 @@ func TestApkDBCataloger(t *testing.T) {
Type: pkg.ApkPkg,
FoundBy: "apk-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("GPL-2.0-or-later", dbLocation),
pkg.NewLicenseFromLocationsWithContext(ctx, "GPL-2.0-or-later", dbLocation),
),
Locations: file.NewLocationSet(dbLocation),
Metadata: pkg.ApkDBEntry{

View File

@ -1,6 +1,7 @@
package alpine
import (
"context"
"strings"
"github.com/anchore/packageurl-go"
@ -10,7 +11,7 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func newPackage(d parsedData, release *linux.Release, dbLocation file.Location) pkg.Package {
func newPackage(ctx context.Context, d parsedData, release *linux.Release, dbLocation file.Location) pkg.Package {
// check if license is a valid spdx expression before splitting
licenseStrings := []string{d.License}
_, err := license.ParseExpression(d.License)
@ -23,7 +24,7 @@ func newPackage(d parsedData, release *linux.Release, dbLocation file.Location)
Name: d.Package,
Version: d.Version,
Locations: file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation, licenseStrings...)...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocationWithContext(ctx, dbLocation, licenseStrings...)...),
PURL: packageURL(d.ApkDBEntry, release),
Type: pkg.ApkPkg,
Metadata: d.ApkDBEntry,

View File

@ -36,7 +36,7 @@ type parsedData struct {
// information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec.
//
//nolint:funlen
func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parseApkDB(ctx context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
scanner := bufio.NewScanner(reader)
var errs error
@ -132,7 +132,7 @@ func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environm
pkgs := make([]pkg.Package, 0, len(apks))
for _, apk := range apks {
pkgs = append(pkgs, newPackage(apk, r, reader.Location))
pkgs = append(pkgs, newPackage(ctx, apk, r, reader.Location))
}
return pkgs, nil, errs

View File

@ -76,6 +76,7 @@ func TestExtraFileAttributes(t *testing.T) {
}
func TestSinglePackageDetails(t *testing.T) {
ctx := context.TODO()
tests := []struct {
fixture string
expected pkg.Package
@ -86,9 +87,9 @@ func TestSinglePackageDetails(t *testing.T) {
Name: "musl-utils",
Version: "1.1.24-r2",
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
pkg.NewLicense("BSD"),
pkg.NewLicense("GPL2+"),
pkg.NewLicenseWithContext(ctx, "MIT"),
pkg.NewLicenseWithContext(ctx, "BSD"),
pkg.NewLicenseWithContext(ctx, "GPL2+"),
),
Type: pkg.ApkPkg,
Metadata: pkg.ApkDBEntry{
@ -175,7 +176,7 @@ func TestSinglePackageDetails(t *testing.T) {
Name: "alpine-baselayout-data",
Version: "3.4.0-r0",
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("GPL-2.0-only"),
pkg.NewLicenseWithContext(ctx, "GPL-2.0-only"),
),
Type: pkg.ApkPkg,
Metadata: pkg.ApkDBEntry{
@ -219,7 +220,7 @@ func TestSinglePackageDetails(t *testing.T) {
Name: "alpine-baselayout",
Version: "3.2.0-r6",
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("GPL-2.0-only"),
pkg.NewLicenseWithContext(ctx, "GPL-2.0-only"),
),
Type: pkg.ApkPkg,
PURL: "",

View File

@ -1,6 +1,7 @@
package arch
import (
"context"
"testing"
"github.com/google/go-cmp/cmp/cmpopts"
@ -25,6 +26,7 @@ func TestAlpmCataloger(t *testing.T) {
emacsDbLocation := file.NewLocation("var/lib/pacman/local/emacs-29.3-3/desc")
fuzzyDbLocation := file.NewLocation("var/lib/pacman/local/fuzzy-1.2-3/desc")
madeupDbLocation := file.NewLocation("var/lib/pacman/local/madeup-20.30-4/desc")
ctx := context.TODO()
treeSitterPkg := pkg.Package{
Name: "tree-sitter",
@ -32,7 +34,7 @@ func TestAlpmCataloger(t *testing.T) {
Type: pkg.AlpmPkg,
FoundBy: "alpm-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", treeSitterDbLocation),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", treeSitterDbLocation),
),
Locations: file.NewLocationSet(treeSitterDbLocation),
Metadata: pkg.AlpmDBEntry{
@ -58,7 +60,7 @@ func TestAlpmCataloger(t *testing.T) {
Type: pkg.AlpmPkg,
FoundBy: "alpm-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("GPL3", emacsDbLocation),
pkg.NewLicenseFromLocationsWithContext(ctx, "GPL3", emacsDbLocation),
),
Locations: file.NewLocationSet(emacsDbLocation),
Metadata: pkg.AlpmDBEntry{
@ -123,8 +125,8 @@ func TestAlpmCataloger(t *testing.T) {
Type: pkg.AlpmPkg,
FoundBy: "alpm-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("LGPL3", gmpDbLocation),
pkg.NewLicenseFromLocations("GPL", gmpDbLocation),
pkg.NewLicenseFromLocationsWithContext(ctx, "LGPL3", gmpDbLocation),
pkg.NewLicenseFromLocationsWithContext(ctx, "GPL", gmpDbLocation),
),
Locations: file.NewLocationSet(
gmpDbLocation,

View File

@ -1,6 +1,7 @@
package arch
import (
"context"
"strings"
"github.com/anchore/packageurl-go"
@ -9,7 +10,7 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func newPackage(m *parsedData, release *linux.Release, dbLocation file.Location, otherLocations ...file.Location) pkg.Package {
func newPackage(ctx context.Context, m *parsedData, release *linux.Release, dbLocation file.Location, otherLocations ...file.Location) pkg.Package {
licenseCandidates := strings.Split(m.Licenses, "\n")
locs := file.NewLocationSet(dbLocation)
@ -19,7 +20,7 @@ func newPackage(m *parsedData, release *linux.Release, dbLocation file.Location,
Name: m.Package,
Version: m.Version,
Locations: locs,
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation.WithoutAnnotations(), licenseCandidates...)...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocationWithContext(ctx, dbLocation.WithoutAnnotations(), licenseCandidates...)...),
Type: pkg.AlpmPkg,
PURL: packageURL(m, release),
Metadata: m.AlpmDBEntry,

View File

@ -41,7 +41,7 @@ type parsedData struct {
}
// parseAlpmDB parses the arch linux pacman database flat-files and returns the packages and relationships found within.
func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parseAlpmDB(ctx context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var errs error
data, err := parseAlpmDBEntry(reader)
@ -78,6 +78,7 @@ func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environ
return []pkg.Package{
newPackage(
ctx,
data,
env.LinuxRelease,
reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),

View File

@ -1,6 +1,8 @@
package binary
import (
"context"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/cpe"
@ -8,11 +10,11 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func newELFPackage(metadata elfBinaryPackageNotes, locations file.LocationSet) pkg.Package {
func newELFPackage(ctx context.Context, metadata elfBinaryPackageNotes, locations file.LocationSet) pkg.Package {
p := pkg.Package{
Name: metadata.Name,
Version: metadata.Version,
Licenses: pkg.NewLicenseSet(pkg.NewLicense(metadata.License)),
Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, metadata.License)),
PURL: packageURL(metadata),
Type: pkgType(metadata.Type),
Locations: locations,

View File

@ -52,7 +52,7 @@ func (c *elfPackageCataloger) Name() string {
return "elf-binary-package-cataloger"
}
func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
func (c *elfPackageCataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
var errs error
locations, err := resolver.FilesByMIMEType(mimetype.ExecutableMIMETypeSet.List()...)
if err != nil {
@ -84,7 +84,7 @@ func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver)
}
// create a package for each unique name/version pair (based on the first note found)
pkgs = append(pkgs, newELFPackage(notes[0], noteLocations))
pkgs = append(pkgs, newELFPackage(ctx, notes[0], noteLocations))
}
// why not return relationships? We have an executable cataloger that will note the dynamic libraries imported by

View File

@ -1,6 +1,7 @@
package binary
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
@ -135,6 +136,7 @@ func Test_packageURL(t *testing.T) {
}
func Test_newELFPackage(t *testing.T) {
ctx := context.TODO()
tests := []struct {
name string
metadata elfBinaryPackageNotes
@ -168,7 +170,7 @@ func Test_newELFPackage(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := newELFPackage(test.metadata, file.NewLocationSet())
actual := newELFPackage(ctx, test.metadata, file.NewLocationSet())
if diff := cmp.Diff(test.expected, actual, cmpopts.IgnoreFields(pkg.Package{}, "id"), cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationSet{}, pkg.LicenseSet{})); diff != "" {
t.Errorf("newELFPackage() mismatch (-want +got):\n%s", diff)
}

View File

@ -1,6 +1,7 @@
package bitnami
import (
"context"
"testing"
"github.com/stretchr/testify/require"
@ -23,14 +24,15 @@ func mustCPEs(s ...string) (c []cpe.CPE) {
}
func TestBitnamiCataloger(t *testing.T) {
ctx := context.TODO()
postgresqlMainPkg := pkg.Package{
Name: "postgresql",
Version: "17.2.0-8",
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("PostgreSQL", license.Concluded),
pkg.NewLicenseFromType("PostgreSQL", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "PostgreSQL", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "PostgreSQL", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/postgresql@17.2.0-8?arch=arm64&distro=debian-12",
@ -56,8 +58,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("LGPL-2.1-only", license.Concluded),
pkg.NewLicenseFromType("LGPL-2.1-only", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "LGPL-2.1-only", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "LGPL-2.1-only", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/geos@3.13.0?arch=arm64&distro=debian-12",
@ -78,8 +80,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("MIT", license.Concluded),
pkg.NewLicenseFromType("MIT", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "MIT", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "MIT", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/proj@6.3.2?arch=arm64&distro=debian-12",
@ -100,8 +102,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("MIT", license.Concluded),
pkg.NewLicenseFromType("MIT", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "MIT", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "MIT", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/gdal@3.10.1?arch=arm64&distro=debian-12",
@ -122,8 +124,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("MIT", license.Concluded),
pkg.NewLicenseFromType("MIT", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "MIT", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "MIT", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/json-c@0.16.20220414?arch=arm64&distro=debian-12",
@ -144,8 +146,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("0BSD", license.Concluded),
pkg.NewLicenseFromType("0BSD", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "0BSD", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "0BSD", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/orafce@4.14.1?arch=arm64&distro=debian-12",
@ -166,8 +168,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("BSD-3-Clause", license.Concluded),
pkg.NewLicenseFromType("BSD-3-Clause", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "BSD-3-Clause", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "BSD-3-Clause", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/pljava@1.6.8?arch=arm64&distro=debian-12",
@ -193,8 +195,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("LGPL-2.1-only", license.Concluded),
pkg.NewLicenseFromType("LGPL-2.1-only", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "LGPL-2.1-only", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "LGPL-2.1-only", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/unixodbc@2.3.12?arch=arm64&distro=debian-12",
@ -215,8 +217,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("LGPL-3.0-only", license.Concluded),
pkg.NewLicenseFromType("LGPL-3.0-only", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "LGPL-3.0-only", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "LGPL-3.0-only", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/psqlodbc@16.0.0?arch=arm64&distro=debian-12",
@ -237,8 +239,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("BSD-3-Clause", license.Concluded),
pkg.NewLicenseFromType("BSD-3-Clause", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "BSD-3-Clause", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "BSD-3-Clause", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/protobuf@3.21.12?arch=arm64&distro=debian-12",
@ -259,8 +261,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("BSD-2-Clause", license.Concluded),
pkg.NewLicenseFromType("BSD-2-Clause", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "BSD-2-Clause", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "BSD-2-Clause", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/protobuf-c@1.5.1?arch=arm64&distro=debian-12",
@ -281,8 +283,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("GPL-2.0-or-later", license.Concluded),
pkg.NewLicenseFromType("GPL-2.0-or-later", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "GPL-2.0-or-later", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "GPL-2.0-or-later", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/postgis@3.4.4?arch=arm64&distro=debian-12",
@ -303,8 +305,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("PostgreSQL", license.Concluded),
pkg.NewLicenseFromType("PostgreSQL", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "PostgreSQL", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "PostgreSQL", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/pgaudit@17.0.0?arch=arm64&distro=debian-12",
@ -322,8 +324,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("MIT", license.Concluded),
pkg.NewLicenseFromType("MIT", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "MIT", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "MIT", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/pgbackrest@2.54.2?arch=arm64&distro=debian-12",
@ -344,8 +346,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("BSD-3-Clause", license.Concluded),
pkg.NewLicenseFromType("BSD-3-Clause", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "BSD-3-Clause", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "BSD-3-Clause", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/wal2json@2.6.0?arch=arm64&distro=debian-12",
@ -366,8 +368,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/postgresql/.spdx-postgresql.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("BSD-3-Clause", license.Concluded),
pkg.NewLicenseFromType("BSD-3-Clause", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "BSD-3-Clause", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "BSD-3-Clause", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/nss_wrapper@1.1.16?arch=arm64&distro=debian-12",
@ -402,8 +404,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/render-template/.spdx-render-template.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("Apache-2.0", license.Concluded),
pkg.NewLicenseFromType("Apache-2.0", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "Apache-2.0", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "Apache-2.0", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/render-template@1.0.7-4?arch=arm64&distro=debian-12",
@ -427,8 +429,8 @@ func TestBitnamiCataloger(t *testing.T) {
Type: pkg.BitnamiPkg,
Locations: file.NewLocationSet(file.NewLocation("opt/bitnami/redis/.spdx-redis.spdx")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromType("RSALv2", license.Concluded),
pkg.NewLicenseFromType("RSALv2", license.Declared),
pkg.NewLicenseFromTypeWithContext(ctx, "RSALv2", license.Concluded),
pkg.NewLicenseFromTypeWithContext(ctx, "RSALv2", license.Declared),
),
FoundBy: catalogerName,
PURL: "pkg:bitnami/redis@7.4.0-0?arch=arm64&distro=debian-12",

View File

@ -13,6 +13,7 @@ import (
)
func TestDpkgCataloger(t *testing.T) {
ctx := context.TODO()
tests := []struct {
name string
expected []pkg.Package
@ -25,9 +26,9 @@ func TestDpkgCataloger(t *testing.T) {
Version: "1.1.8-3.6",
FoundBy: "dpkg-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("GPL-1", file.NewLocation("/usr/share/doc/libpam-runtime/copyright")),
pkg.NewLicenseFromLocations("GPL-2", file.NewLocation("/usr/share/doc/libpam-runtime/copyright")),
pkg.NewLicenseFromLocations("LGPL-2.1", file.NewLocation("/usr/share/doc/libpam-runtime/copyright")),
pkg.NewLicenseFromLocationsWithContext(ctx, "GPL-1", file.NewLocation("/usr/share/doc/libpam-runtime/copyright")),
pkg.NewLicenseFromLocationsWithContext(ctx, "GPL-2", file.NewLocation("/usr/share/doc/libpam-runtime/copyright")),
pkg.NewLicenseFromLocationsWithContext(ctx, "LGPL-2.1", file.NewLocation("/usr/share/doc/libpam-runtime/copyright")),
),
Locations: file.NewLocationSet(
file.NewLocation("/var/lib/dpkg/status").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
@ -100,9 +101,9 @@ func TestDpkgCataloger(t *testing.T) {
Version: "3.34.1-3",
FoundBy: "dpkg-db-cataloger",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("public-domain", file.NewLocation("/usr/share/doc/libsqlite3-0/copyright")),
pkg.NewLicenseFromLocations("GPL-2+", file.NewLocation("/usr/share/doc/libsqlite3-0/copyright")),
pkg.NewLicenseFromLocations("GPL-2", file.NewLocation("/usr/share/doc/libsqlite3-0/copyright")),
pkg.NewLicenseFromLocationsWithContext(ctx, "public-domain", file.NewLocation("/usr/share/doc/libsqlite3-0/copyright")),
pkg.NewLicenseFromLocationsWithContext(ctx, "GPL-2+", file.NewLocation("/usr/share/doc/libsqlite3-0/copyright")),
pkg.NewLicenseFromLocationsWithContext(ctx, "GPL-2", file.NewLocation("/usr/share/doc/libsqlite3-0/copyright")),
),
Locations: file.NewLocationSet(
file.NewLocation("/var/lib/dpkg/status.d/libsqlite3-0").WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
@ -226,6 +227,7 @@ func Test_CatalogerRelationships(t *testing.T) {
}
func TestDpkgArchiveCataloger(t *testing.T) {
ctx := context.TODO()
tests := []struct {
name string
expected []pkg.Package
@ -241,7 +243,7 @@ func TestDpkgArchiveCataloger(t *testing.T) {
file.NewLocation("/zlib1g.deb"),
),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("Zlib"),
pkg.NewLicenseFromLocationsWithContext(ctx, "Zlib"),
),
PURL: "pkg:deb/zlib1g@1%3A1.3.dfsg-3.1ubuntu2.1?arch=amd64&upstream=zlib",
Type: pkg.DebPkg,

View File

@ -1,6 +1,7 @@
package debian
import (
"context"
"fmt"
"io"
"path"
@ -22,7 +23,7 @@ const (
docsPath = "/usr/share/doc"
)
func newDpkgPackage(d pkg.DpkgDBEntry, dbLocation file.Location, resolver file.Resolver, release *linux.Release, evidence ...file.Location) pkg.Package {
func newDpkgPackage(ctx context.Context, d pkg.DpkgDBEntry, dbLocation file.Location, resolver file.Resolver, release *linux.Release, evidence ...file.Location) pkg.Package {
// TODO: separate pr to license refactor, but explore extracting dpkg-specific license parsing into a separate function
var licenses []pkg.License
@ -46,7 +47,7 @@ func newDpkgPackage(d pkg.DpkgDBEntry, dbLocation file.Location, resolver file.R
mergeFileListing(resolver, dbLocation, &p)
// fetch additional data from the copyright file to derive the license information
addLicenses(resolver, dbLocation, &p)
addLicenses(ctx, resolver, dbLocation, &p)
}
p.SetID()
@ -54,11 +55,11 @@ func newDpkgPackage(d pkg.DpkgDBEntry, dbLocation file.Location, resolver file.R
return p
}
func newDebArchivePackage(location file.Location, metadata pkg.DpkgArchiveEntry, licenseStrings []string) pkg.Package {
func newDebArchivePackage(ctx context.Context, location file.Location, metadata pkg.DpkgArchiveEntry, licenseStrings []string) pkg.Package {
p := pkg.Package{
Name: metadata.Package,
Version: metadata.Version,
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValues(licenseStrings...)...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromValuesWithContext(ctx, licenseStrings...)...),
Type: pkg.DebPkg,
PURL: packageURL(
pkg.DpkgDBEntry(metadata),
@ -108,7 +109,7 @@ func packageURL(m pkg.DpkgDBEntry, distro *linux.Release) string {
).ToString()
}
func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) {
func addLicenses(ctx context.Context, resolver file.Resolver, dbLocation file.Location, p *pkg.Package) {
metadata, ok := p.Metadata.(pkg.DpkgDBEntry)
if !ok {
log.WithFields("package", p).Trace("unable to extract DPKG metadata to add licenses")
@ -123,7 +124,7 @@ func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Packag
// attach the licenses
licenseStrs := parseLicensesFromCopyright(copyrightReader)
for _, licenseStr := range licenseStrs {
p.Licenses.Add(pkg.NewLicenseFromLocations(licenseStr, copyrightLocation.WithoutAnnotations()))
p.Licenses.Add(pkg.NewLicenseFromLocationsWithContext(ctx, licenseStr, copyrightLocation.WithoutAnnotations()))
}
// keep a record of the file where this was discovered
p.Locations.Add(*copyrightLocation)

View File

@ -72,7 +72,7 @@ func parseDebArchive(ctx context.Context, _ file.Resolver, _ *generic.Environmen
}
return []pkg.Package{
newDebArchivePackage(reader.Location, *metadata, licenses),
newDebArchivePackage(ctx, reader.Location, *metadata, licenses),
}, nil, nil
}

View File

@ -39,7 +39,7 @@ func parseDpkgDB(ctx context.Context, resolver file.Resolver, env *generic.Envir
dbLoc := reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)
var pkgs []pkg.Package
_ = sync.CollectSlice(&ctx, cataloging.ExecutorFile, sync.ToSeq(metadata), func(m pkg.DpkgDBEntry) (pkg.Package, error) {
return newDpkgPackage(m, dbLoc, resolver, env.LinuxRelease, findDpkgInfoFiles(m.Package, resolver, reader.Location)...), nil
return newDpkgPackage(ctx, m, dbLoc, resolver, env.LinuxRelease, findDpkgInfoFiles(m.Package, resolver, reader.Location)...), nil
}, &pkgs)
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")

View File

@ -24,7 +24,7 @@ var (
)
// parses individual CONTENTS files from the portage flat-file store (e.g. /var/db/pkg/*/*/CONTENTS).
func parsePortageContents(_ context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parsePortageContents(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
cpvMatch := cpvRe.FindStringSubmatch(reader.RealPath)
if cpvMatch == nil {
return nil, nil, fmt.Errorf("failed to match package and version in %s", reader.RealPath)
@ -43,7 +43,7 @@ func parsePortageContents(_ context.Context, resolver file.Resolver, _ *generic.
locations := file.NewLocationSet(reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
licenses, licenseLocations := addLicenses(resolver, reader.Location, &m)
licenses, licenseLocations := addLicenses(ctx, resolver, reader.Location, &m)
locations.Add(licenseLocations...)
locations.Add(addSize(resolver, reader.Location, &m)...)
addFiles(resolver, reader.Location, &m)
@ -57,7 +57,6 @@ func parsePortageContents(_ context.Context, resolver file.Resolver, _ *generic.
Type: pkg.PortagePkg,
Metadata: m,
}
p.SetID()
return []pkg.Package{p}, nil, nil
@ -89,7 +88,7 @@ func addFiles(resolver file.Resolver, dbLocation file.Location, entry *pkg.Porta
}
}
func addLicenses(resolver file.Resolver, dbLocation file.Location, entry *pkg.PortageEntry) (pkg.LicenseSet, []file.Location) {
func addLicenses(ctx context.Context, resolver file.Resolver, dbLocation file.Location, entry *pkg.PortageEntry) (pkg.LicenseSet, []file.Location) {
parentPath := filepath.Dir(dbLocation.RealPath)
location := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "LICENSE"))
@ -108,12 +107,8 @@ func addLicenses(resolver file.Resolver, dbLocation file.Location, entry *pkg.Po
og, spdxExpression := extractLicenses(resolver, location, licenseReader)
entry.Licenses = og
return pkg.NewLicenseSet(
pkg.NewLicenseFromLocations(spdxExpression, *location),
),
[]file.Location{
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation),
}
return pkg.NewLicenseSet(pkg.NewLicenseFromLocationsWithContext(ctx, spdxExpression, *location)), []file.Location{
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)}
}
func addSize(resolver file.Resolver, dbLocation file.Location, entry *pkg.PortageEntry) []file.Location {

View File

@ -31,5 +31,5 @@ func NewGoModuleBinaryCataloger(opts CatalogerConfig) pkg.Cataloger {
newGoBinaryCataloger(opts).parseGoBinary,
mimetype.ExecutableMIMETypeSet.List()...,
).
WithProcessors(stdlibProcessor)
WithResolvingProcessors(stdlibProcessor)
}

View File

@ -26,25 +26,15 @@ import (
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/license"
"github.com/anchore/syft/syft/pkg"
)
type goLicense struct {
Value string `json:"val,omitempty"`
SPDXExpression string `json:"spdx,omitempty"`
Type license.Type `json:"type,omitempty"`
URLs []string `json:"urls,omitempty"`
Locations []string `json:"locations,omitempty"`
Contents string `json:"contents,omitempty"`
}
type goLicenseResolver struct {
catalogerName string
opts CatalogerConfig
localModCacheDir fs.FS
localVendorDir fs.FS
licenseCache cache.Resolver[[]goLicense]
licenseCache cache.Resolver[[]pkg.License]
lowerLicenseFileNames *strset.Set
}
@ -73,7 +63,7 @@ func newGoLicenseResolver(catalogerName string, opts CatalogerConfig) goLicenseR
opts: opts,
localModCacheDir: localModCacheDir,
localVendorDir: localVendorDir,
licenseCache: cache.GetResolverCachingErrors[[]goLicense]("golang", "v1"),
licenseCache: cache.GetResolverCachingErrors[[]pkg.License]("golang", "v2"),
lowerLicenseFileNames: strset.New(lowercaseLicenseFiles()...),
}
}
@ -97,52 +87,52 @@ func remotesForModule(proxies []string, noProxy []string, module string) []strin
return proxies
}
func (c *goLicenseResolver) getLicenses(ctx context.Context, scanner licenses.Scanner, resolver file.Resolver, moduleName, moduleVersion string) []pkg.License {
func (c *goLicenseResolver) getLicenses(ctx context.Context, resolver file.Resolver, moduleName, moduleVersion string) []pkg.License {
// search the scan target first, ignoring local and remote sources
goLicenses, err := c.findLicensesInSource(ctx, scanner, resolver,
pkgLicenses, err := c.findLicensesInSource(ctx, resolver,
fmt.Sprintf(`**/go/pkg/mod/%s@%s/*`, processCaps(moduleName), moduleVersion),
)
if err != nil {
log.WithFields("error", err, "module", moduleName, "version", moduleVersion).Trace("unable to read golang licenses from source")
}
if len(goLicenses) > 0 {
return toPkgLicenses(goLicenses)
if len(pkgLicenses) > 0 {
return pkgLicenses
}
// look in the local host mod directory...
if c.opts.SearchLocalModCacheLicenses {
goLicenses, err = c.getLicensesFromLocal(ctx, scanner, moduleName, moduleVersion)
pkgLicenses, err = c.getLicensesFromLocal(ctx, moduleName, moduleVersion)
if err != nil {
log.WithFields("error", err, "module", moduleName, "version", moduleVersion).Trace("unable to read golang licenses local")
}
if len(goLicenses) > 0 {
return toPkgLicenses(goLicenses)
if len(pkgLicenses) > 0 {
return pkgLicenses
}
}
// look in the local vendor directory...
if c.opts.SearchLocalVendorLicenses {
goLicenses, err = c.getLicensesFromLocalVendor(ctx, scanner, moduleName)
pkgLicenses, err = c.getLicensesFromLocalVendor(ctx, moduleName)
if err != nil {
log.WithFields("error", err, "module", moduleName, "version", moduleVersion).Trace("unable to read golang licenses vendor")
}
if len(goLicenses) > 0 {
return toPkgLicenses(goLicenses)
if len(pkgLicenses) > 0 {
return pkgLicenses
}
}
// download from remote sources
if c.opts.SearchRemoteLicenses {
goLicenses, err = c.getLicensesFromRemote(ctx, scanner, moduleName, moduleVersion)
pkgLicenses, err = c.getLicensesFromRemote(ctx, moduleName, moduleVersion)
if err != nil {
log.WithFields("error", err, "module", moduleName, "version", moduleVersion).Debug("unable to read golang licenses remote")
}
}
return toPkgLicenses(goLicenses)
return pkgLicenses
}
func (c *goLicenseResolver) getLicensesFromLocal(ctx context.Context, scanner licenses.Scanner, moduleName, moduleVersion string) ([]goLicense, error) {
func (c *goLicenseResolver) getLicensesFromLocal(ctx context.Context, moduleName, moduleVersion string) ([]pkg.License, error) {
if c.localModCacheDir == nil {
return nil, nil
}
@ -158,10 +148,10 @@ func (c *goLicenseResolver) getLicensesFromLocal(ctx context.Context, scanner li
// if we're running against a directory on the filesystem, it may not include the
// user's homedir / GOPATH, so we defer to using the localModCacheResolver
// we use $GOPATH/pkg/mod to avoid leaking information about the user's system
return c.findLicensesInFS(ctx, scanner, "file://$GOPATH/pkg/mod/"+subdir+"/", dir)
return c.findLicensesInFS(ctx, "file://$GOPATH/pkg/mod/"+subdir+"/", dir)
}
func (c *goLicenseResolver) getLicensesFromLocalVendor(ctx context.Context, scanner licenses.Scanner, moduleName string) ([]goLicense, error) {
func (c *goLicenseResolver) getLicensesFromLocalVendor(ctx context.Context, moduleName string) ([]pkg.License, error) {
if c.localVendorDir == nil {
return nil, nil
}
@ -177,11 +167,11 @@ func (c *goLicenseResolver) getLicensesFromLocalVendor(ctx context.Context, scan
// if we're running against a directory on the filesystem, it may not include the
// user's homedir / GOPATH, so we defer to using the localModCacheResolver
// we use $GOPATH/pkg/mod to avoid leaking information about the user's system
return c.findLicensesInFS(ctx, scanner, "file://$GO_VENDOR/"+subdir+"/", dir)
return c.findLicensesInFS(ctx, "file://$GO_VENDOR/"+subdir+"/", dir)
}
func (c *goLicenseResolver) getLicensesFromRemote(ctx context.Context, scanner licenses.Scanner, moduleName, moduleVersion string) ([]goLicense, error) {
return c.licenseCache.Resolve(fmt.Sprintf("%s/%s", moduleName, moduleVersion), func() ([]goLicense, error) {
func (c *goLicenseResolver) getLicensesFromRemote(ctx context.Context, moduleName, moduleVersion string) ([]pkg.License, error) {
return c.licenseCache.Resolve(fmt.Sprintf("%s/%s", moduleName, moduleVersion), func() ([]pkg.License, error) {
proxies := remotesForModule(c.opts.Proxies, c.opts.NoProxy, moduleName)
urlPrefix, fsys, err := getModule(proxies, moduleName, moduleVersion)
@ -189,12 +179,12 @@ func (c *goLicenseResolver) getLicensesFromRemote(ctx context.Context, scanner l
return nil, err
}
return c.findLicensesInFS(ctx, scanner, urlPrefix, fsys)
return c.findLicensesInFS(ctx, urlPrefix, fsys)
})
}
func (c *goLicenseResolver) findLicensesInFS(ctx context.Context, scanner licenses.Scanner, urlPrefix string, fsys fs.FS) ([]goLicense, error) {
var out []goLicense
func (c *goLicenseResolver) findLicensesInFS(ctx context.Context, urlPrefix string, fsys fs.FS) ([]pkg.License, error) {
var out []pkg.License
err := fs.WalkDir(fsys, ".", func(filePath string, d fs.DirEntry, err error) error {
if err != nil {
log.Debugf("error reading %s#%s: %v", urlPrefix, filePath, err)
@ -213,18 +203,13 @@ func (c *goLicenseResolver) findLicensesInFS(ctx context.Context, scanner licens
return nil
}
defer internal.CloseAndLogError(rdr, filePath)
parsed, err := scanner.PkgSearch(ctx, file.NewLocationReadCloser(file.NewLocation(filePath), rdr))
if err != nil {
log.Debugf("error parsing license file %s: %v", filePath, err)
return nil
}
licenses := pkg.NewLicensesFromReadCloserWithContext(ctx, file.NewLocationReadCloser(file.NewLocation(filePath), rdr))
// since these licenses are found in an external fs.FS, not in the scanned source,
// get rid of the locations but keep information about the where the license was found
// by prepending the urlPrefix to the internal path for an accurate representation
for _, l := range toGoLicenses(parsed) {
for _, l := range licenses {
l.URLs = []string{urlPrefix + filePath}
l.Locations = nil
l.Locations = file.NewLocationSet()
out = append(out, l)
}
return nil
@ -232,15 +217,15 @@ func (c *goLicenseResolver) findLicensesInFS(ctx context.Context, scanner licens
return out, err
}
func (c *goLicenseResolver) findLicensesInSource(ctx context.Context, scanner licenses.Scanner, resolver file.Resolver, globMatch string) ([]goLicense, error) {
var out []goLicense
func (c *goLicenseResolver) findLicensesInSource(ctx context.Context, resolver file.Resolver, globMatch string) ([]pkg.License, error) {
var out []pkg.License
locations, err := resolver.FilesByGlob(globMatch)
if err != nil {
return nil, err
}
for _, l := range locations {
parsed, err := c.parseLicenseFromLocation(ctx, scanner, l, resolver)
parsed, err := c.parseLicenseFromLocation(ctx, l, resolver)
if err != nil {
return nil, err
}
@ -258,8 +243,8 @@ func (c *goLicenseResolver) findLicensesInSource(ctx context.Context, scanner li
return out, nil
}
func (c *goLicenseResolver) parseLicenseFromLocation(ctx context.Context, scanner licenses.Scanner, l file.Location, resolver file.Resolver) ([]goLicense, error) {
var out []goLicense
func (c *goLicenseResolver) parseLicenseFromLocation(ctx context.Context, l file.Location, resolver file.Resolver) ([]pkg.License, error) {
var out []pkg.License
fileName := path.Base(l.RealPath)
if c.lowerLicenseFileNames.Has(strings.ToLower(fileName)) {
contents, err := resolver.FileContentsByLocation(l)
@ -267,12 +252,7 @@ func (c *goLicenseResolver) parseLicenseFromLocation(ctx context.Context, scanne
return nil, err
}
defer internal.CloseAndLogError(contents, l.RealPath)
parsed, err := scanner.PkgSearch(ctx, file.NewLocationReadCloser(l, contents))
if err != nil {
return nil, err
}
out = append(out, toGoLicenses(parsed)...)
out = pkg.NewLicensesFromReadCloserWithContext(ctx, file.NewLocationReadCloser(l, contents))
}
return out, nil
}
@ -281,13 +261,6 @@ func moduleDir(moduleName, moduleVersion string) string {
return fmt.Sprintf("%s@%s", processCaps(moduleName), moduleVersion)
}
func requireCollection[T any](licenses []T) []T {
if licenses == nil {
return make([]T, 0)
}
return licenses
}
var capReplacer = regexp.MustCompile("[A-Z]")
func processCaps(s string) string {
@ -440,49 +413,3 @@ func (l noLicensesFound) Error() string {
}
var _ error = (*noLicensesFound)(nil)
func toPkgLicenses(goLicenses []goLicense) []pkg.License {
var out []pkg.License
for _, l := range goLicenses {
out = append(out, pkg.License{
Value: l.Value,
SPDXExpression: l.SPDXExpression,
Type: l.Type,
URLs: l.URLs,
Locations: toPkgLocations(l.Locations),
Contents: l.Contents,
})
}
return requireCollection(out)
}
func toPkgLocations(goLocations []string) file.LocationSet {
out := file.NewLocationSet()
for _, l := range goLocations {
out.Add(file.NewLocation(l))
}
return out
}
func toGoLicenses(pkgLicenses []pkg.License) []goLicense {
var out []goLicense
for _, l := range pkgLicenses {
out = append(out, goLicense{
Value: l.Value,
SPDXExpression: l.SPDXExpression,
Type: l.Type,
URLs: l.URLs,
Locations: toGoLocations(l.Locations),
Contents: l.Contents,
})
}
return out
}
func toGoLocations(locations file.LocationSet) []string {
var out []string
for _, l := range locations.ToSlice() {
out = append(out, l.RealPath)
}
return out
}

View File

@ -14,17 +14,18 @@ import (
"strings"
"testing"
"github.com/google/licensecheck"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/fileresolver"
"github.com/anchore/syft/syft/license"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func Test_LicenseSearch(t *testing.T) {
ctx := pkgtest.Context()
loc1 := file.NewLocation("github.com/someorg/somename@v0.3.2/LICENSE")
loc2 := file.NewLocation("github.com/!cap!o!r!g/!cap!project@v4.111.5/LICENSE.txt")
loc3 := file.NewLocation("github.com/someorg/strangelicense@v1.2.3/LiCeNsE.tXt")
@ -71,13 +72,6 @@ func Test_LicenseSearch(t *testing.T) {
localVendorDir := filepath.Join(wd, "test-fixtures", "licenses-vendor")
sc := &licenses.ScannerConfig{
CoverageThreshold: 75,
Scanner: licensecheck.Scan,
}
licenseScanner, err := licenses.NewScanner(sc)
require.NoError(t, err)
tests := []struct {
name string
version string
@ -95,6 +89,7 @@ func Test_LicenseSearch(t *testing.T) {
Value: "Apache-2.0",
SPDXExpression: "Apache-2.0",
Type: license.Concluded,
Contents: mustContentsFromLocation(t, loc1),
URLs: []string{"file://$GOPATH/pkg/mod/" + loc1.RealPath},
Locations: file.NewLocationSet(),
}},
@ -110,6 +105,7 @@ func Test_LicenseSearch(t *testing.T) {
Value: "MIT",
SPDXExpression: "MIT",
Type: license.Concluded,
Contents: mustContentsFromLocation(t, loc2, 23, 1105),
URLs: []string{"file://$GOPATH/pkg/mod/" + loc2.RealPath},
Locations: file.NewLocationSet(),
}},
@ -125,6 +121,7 @@ func Test_LicenseSearch(t *testing.T) {
Value: "Apache-2.0",
SPDXExpression: "Apache-2.0",
Type: license.Concluded,
Contents: mustContentsFromLocation(t, loc3),
URLs: []string{"file://$GOPATH/pkg/mod/" + loc3.RealPath},
Locations: file.NewLocationSet(),
}},
@ -139,6 +136,7 @@ func Test_LicenseSearch(t *testing.T) {
expected: []pkg.License{{
Value: "Apache-2.0",
SPDXExpression: "Apache-2.0",
Contents: mustContentsFromLocation(t, loc1),
Type: license.Concluded,
URLs: []string{server.URL + "/github.com/someorg/somename/@v/v0.3.2.zip#" + loc1.RealPath},
Locations: file.NewLocationSet(),
@ -154,6 +152,7 @@ func Test_LicenseSearch(t *testing.T) {
expected: []pkg.License{{
Value: "MIT",
SPDXExpression: "MIT",
Contents: mustContentsFromLocation(t, loc2, 23, 1105), // offset for correct scanner contents
Type: license.Concluded,
URLs: []string{server.URL + "/github.com/CapORG/CapProject/@v/v4.111.5.zip#" + loc2.RealPath},
Locations: file.NewLocationSet(),
@ -171,6 +170,7 @@ func Test_LicenseSearch(t *testing.T) {
expected: []pkg.License{{
Value: "MIT",
SPDXExpression: "MIT",
Contents: mustContentsFromLocation(t, loc2, 23, 1105), // offset for correct scanner contents
Type: license.Concluded,
URLs: []string{server.URL + "/github.com/CapORG/CapProject/@v/v4.111.5.zip#" + loc2.RealPath},
Locations: file.NewLocationSet(),
@ -187,6 +187,7 @@ func Test_LicenseSearch(t *testing.T) {
Value: "Apache-2.0",
SPDXExpression: "Apache-2.0",
Type: license.Concluded,
Contents: mustContentsFromLocation(t, loc1),
URLs: []string{"file://$GO_VENDOR/github.com/someorg/somename/LICENSE"},
Locations: file.NewLocationSet(),
}},
@ -201,6 +202,7 @@ func Test_LicenseSearch(t *testing.T) {
expected: []pkg.License{{
Value: "MIT",
SPDXExpression: "MIT",
Contents: mustContentsFromLocation(t, loc2, 23, 1105), // offset for correct scanner contents
Type: license.Concluded,
URLs: []string{"file://$GO_VENDOR/github.com/!cap!o!r!g/!cap!project/LICENSE.txt"},
Locations: file.NewLocationSet(),
@ -216,6 +218,7 @@ func Test_LicenseSearch(t *testing.T) {
expected: []pkg.License{{
Value: "Apache-2.0",
SPDXExpression: "Apache-2.0",
Contents: mustContentsFromLocation(t, loc1),
Type: license.Concluded,
URLs: []string{"file://$GO_VENDOR/github.com/someorg/strangelicense/LiCeNsE.tXt"},
Locations: file.NewLocationSet(),
@ -226,7 +229,7 @@ func Test_LicenseSearch(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
l := newGoLicenseResolver("", test.config)
lics := l.getLicenses(context.Background(), licenseScanner, fileresolver.Empty{}, test.name, test.version)
lics := l.getLicenses(ctx, fileresolver.Empty{}, test.name, test.version)
require.EqualValues(t, test.expected, lics)
})
}
@ -301,10 +304,7 @@ func Test_findVersionPath(t *testing.T) {
func Test_walkDirErrors(t *testing.T) {
resolver := newGoLicenseResolver("", CatalogerConfig{})
sc := &licenses.ScannerConfig{Scanner: licensecheck.Scan, CoverageThreshold: 75}
scanner, err := licenses.NewScanner(sc)
require.NoError(t, err)
_, err = resolver.findLicensesInFS(context.Background(), scanner, "somewhere", badFS{})
_, err := resolver.findLicensesInFS(context.Background(), "somewhere", badFS{})
require.Error(t, err)
}
@ -321,10 +321,7 @@ func Test_noLocalGoModDir(t *testing.T) {
validTmp := t.TempDir()
require.NoError(t, os.MkdirAll(filepath.Join(validTmp, "mod@ver"), 0700|os.ModeDir))
sc := &licenses.ScannerConfig{Scanner: licensecheck.Scan, CoverageThreshold: 75}
licenseScanner, err := licenses.NewScanner(sc)
require.NoError(t, err)
ctx := pkgtest.Context()
tests := []struct {
name string
dir string
@ -358,35 +355,29 @@ func Test_noLocalGoModDir(t *testing.T) {
SearchLocalModCacheLicenses: true,
LocalModCacheDir: test.dir,
})
_, err := resolver.getLicensesFromLocal(context.Background(), licenseScanner, "mod", "ver")
_, err := resolver.getLicensesFromLocal(ctx, "mod", "ver")
test.wantErr(t, err)
})
}
}
func TestLicenseConversion(t *testing.T) {
inputLicenses := []pkg.License{
{
Value: "Apache-2.0",
SPDXExpression: "Apache-2.0",
Type: "concluded",
URLs: nil,
Locations: file.NewLocationSet(file.NewLocation("LICENSE")),
Contents: "",
},
{
Value: "UNKNOWN",
SPDXExpression: "UNKNOWN_4d1cffe420916f2b706300ab63fcafaf35226a0ad3725cb9f95b26036cefae32",
Type: "declared",
URLs: nil,
Locations: file.NewLocationSet(file.NewLocation("LICENSE2")),
Contents: "NVIDIA Software License Agreement and CUDA Supplement to Software License Agreement",
},
func mustContentsFromLocation(t *testing.T, loc file.Location, offset ...int) string {
t.Helper()
contentsPath := "test-fixtures/licenses/pkg/mod/" + loc.RealPath
contents, err := os.ReadFile(contentsPath)
require.NoErrorf(t, err, "could not open contents for fixture at %s", contentsPath)
if len(offset) == 0 {
return string(contents)
}
goLicenses := toGoLicenses(inputLicenses)
require.Equal(t, 2, len(offset), "invalid offset provided, expected two integers: start and end")
result := toPkgLicenses(goLicenses)
start, end := offset[0], offset[1]
require.GreaterOrEqual(t, start, 0, "offset start must be >= 0")
require.LessOrEqual(t, end, len(contents), "offset end must be <= content length")
require.LessOrEqual(t, start, end, "offset start must be <= end")
require.Equal(t, inputLicenses, result)
return string(contents[start:end])
}

View File

@ -18,7 +18,6 @@ import (
"golang.org/x/mod/module"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
@ -63,11 +62,6 @@ func newGoBinaryCataloger(opts CatalogerConfig) *goBinaryCataloger {
func (c *goBinaryCataloger) parseGoBinary(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package
licenseScanner, err := licenses.ContextLicenseScanner(ctx)
if err != nil {
return nil, nil, err
}
unionReader, err := unionreader.GetUnionReader(reader.ReadCloser)
if err != nil {
return nil, nil, err
@ -79,7 +73,7 @@ func (c *goBinaryCataloger) parseGoBinary(ctx context.Context, resolver file.Res
var rels []artifact.Relationship
for _, mod := range mods {
var depPkgs []pkg.Package
mainPkg, depPkgs := c.buildGoPkgInfo(ctx, licenseScanner, resolver, reader.Location, mod, mod.arch, unionReader)
mainPkg, depPkgs := c.buildGoPkgInfo(ctx, resolver, reader.Location, mod, mod.arch, unionReader)
if mainPkg != nil {
rels = createModuleRelationships(*mainPkg, depPkgs)
pkgs = append(pkgs, *mainPkg)
@ -107,7 +101,7 @@ func createModuleRelationships(main pkg.Package, deps []pkg.Package) []artifact.
var emptyModule debug.Module
var moduleFromPartialPackageBuild = debug.Module{Path: "command-line-arguments"}
func (c *goBinaryCataloger) buildGoPkgInfo(ctx context.Context, licenseScanner licenses.Scanner, resolver file.Resolver, location file.Location, mod *extendedBuildInfo, arch string, reader io.ReadSeekCloser) (*pkg.Package, []pkg.Package) {
func (c *goBinaryCataloger) buildGoPkgInfo(ctx context.Context, resolver file.Resolver, location file.Location, mod *extendedBuildInfo, arch string, reader io.ReadSeekCloser) (*pkg.Package, []pkg.Package) {
if mod == nil {
return nil, nil
}
@ -122,7 +116,7 @@ func (c *goBinaryCataloger) buildGoPkgInfo(ctx context.Context, licenseScanner l
continue
}
lics := c.licenseResolver.getLicenses(ctx, licenseScanner, resolver, dep.Path, dep.Version)
lics := c.licenseResolver.getLicenses(ctx, resolver, dep.Path, dep.Version)
gover, experiments := getExperimentsFromVersion(mod.GoVersion)
m := newBinaryMetadata(
@ -150,7 +144,7 @@ func (c *goBinaryCataloger) buildGoPkgInfo(ctx context.Context, licenseScanner l
return nil, pkgs
}
main := c.makeGoMainPackage(ctx, licenseScanner, resolver, mod, arch, location, reader)
main := c.makeGoMainPackage(ctx, resolver, mod, arch, location, reader)
return &main, pkgs
}
@ -165,9 +159,9 @@ func missingMainModule(mod *extendedBuildInfo) bool {
return mod.Main == moduleFromPartialPackageBuild
}
func (c *goBinaryCataloger) makeGoMainPackage(ctx context.Context, licenseScanner licenses.Scanner, resolver file.Resolver, mod *extendedBuildInfo, arch string, location file.Location, reader io.ReadSeekCloser) pkg.Package {
func (c *goBinaryCataloger) makeGoMainPackage(ctx context.Context, resolver file.Resolver, mod *extendedBuildInfo, arch string, location file.Location, reader io.ReadSeekCloser) pkg.Package {
gbs := getBuildSettings(mod.Settings)
lics := c.licenseResolver.getLicenses(ctx, licenseScanner, resolver, mod.Main.Path, mod.Main.Version)
lics := c.licenseResolver.getLicenses(ctx, resolver, mod.Main.Path, mod.Main.Version)
gover, experiments := getExperimentsFromVersion(mod.GoVersion)
m := newBinaryMetadata(

View File

@ -15,11 +15,9 @@ import (
"syscall"
"testing"
"github.com/google/licensecheck"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/fileresolver"
"github.com/anchore/syft/syft/internal/unionreader"
@ -170,10 +168,6 @@ func TestBuildGoPkgInfo(t *testing.T) {
},
}
sc := &licenses.ScannerConfig{Scanner: licensecheck.Scan, CoverageThreshold: 75}
licenseScanner, err := licenses.NewScanner(sc)
require.NoError(t, err)
tests := []struct {
name string
mod *extendedBuildInfo
@ -1074,7 +1068,7 @@ func TestBuildGoPkgInfo(t *testing.T) {
c := newGoBinaryCataloger(*test.cfg)
reader, err := unionreader.GetUnionReader(io.NopCloser(strings.NewReader(test.binaryContent)))
require.NoError(t, err)
mainPkg, pkgs := c.buildGoPkgInfo(context.Background(), licenseScanner, fileresolver.Empty{}, location, test.mod, test.mod.arch, reader)
mainPkg, pkgs := c.buildGoPkgInfo(context.Background(), fileresolver.Empty{}, location, test.mod, test.mod.arch, reader)
if mainPkg != nil {
pkgs = append(pkgs, *mainPkg)
}

View File

@ -11,7 +11,6 @@ import (
"golang.org/x/mod/modfile"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
@ -35,11 +34,6 @@ func newGoModCataloger(opts CatalogerConfig) *goModCataloger {
func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
packages := make(map[string]pkg.Package)
licenseScanner, err := licenses.ContextLicenseScanner(ctx)
if err != nil {
return nil, nil, fmt.Errorf("unable to create default license scanner: %w", err)
}
contents, err := io.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("failed to read go module: %w", err)
@ -56,7 +50,7 @@ func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resol
}
for _, m := range f.Require {
lics := c.licenseResolver.getLicenses(ctx, licenseScanner, resolver, m.Mod.Path, m.Mod.Version)
lics := c.licenseResolver.getLicenses(ctx, resolver, m.Mod.Path, m.Mod.Version)
packages[m.Mod.Path] = pkg.Package{
Name: m.Mod.Path,
Version: m.Mod.Version,
@ -73,7 +67,7 @@ func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resol
// remove any old packages and replace with new ones...
for _, m := range f.Replace {
lics := c.licenseResolver.getLicenses(ctx, licenseScanner, resolver, m.New.Path, m.New.Version)
lics := c.licenseResolver.getLicenses(ctx, resolver, m.New.Path, m.New.Version)
// the old path and new path may be the same, in which case this is a noop,
// but if they're different we need to remove the old package.

View File

@ -1,6 +1,7 @@
package golang
import (
"context"
"fmt"
"strings"
@ -11,12 +12,12 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func stdlibProcessor(pkgs []pkg.Package, relationships []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) {
compilerPkgs, newRelationships := stdlibPackageAndRelationships(pkgs)
func stdlibProcessor(ctx context.Context, _ file.Resolver, pkgs []pkg.Package, relationships []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) {
compilerPkgs, newRelationships := stdlibPackageAndRelationships(ctx, pkgs)
return append(pkgs, compilerPkgs...), append(relationships, newRelationships...), err
}
func stdlibPackageAndRelationships(pkgs []pkg.Package) ([]pkg.Package, []artifact.Relationship) {
func stdlibPackageAndRelationships(ctx context.Context, pkgs []pkg.Package) ([]pkg.Package, []artifact.Relationship) {
var goCompilerPkgs []pkg.Package
var relationships []artifact.Relationship
totalLocations := file.NewLocationSet()
@ -32,7 +33,7 @@ func stdlibPackageAndRelationships(pkgs []pkg.Package) ([]pkg.Package, []artifac
continue
}
stdLibPkg := newGoStdLib(mValue.GoCompiledVersion, goPkg.Locations)
stdLibPkg := newGoStdLib(ctx, mValue.GoCompiledVersion, goPkg.Locations)
if stdLibPkg == nil {
continue
}
@ -49,7 +50,7 @@ func stdlibPackageAndRelationships(pkgs []pkg.Package) ([]pkg.Package, []artifac
return goCompilerPkgs, relationships
}
func newGoStdLib(version string, location file.LocationSet) *pkg.Package {
func newGoStdLib(ctx context.Context, version string, location file.LocationSet) *pkg.Package {
stdlibCpe, err := generateStdlibCpe(version)
if err != nil {
return nil
@ -60,7 +61,7 @@ func newGoStdLib(version string, location file.LocationSet) *pkg.Package {
PURL: packageURL("stdlib", strings.TrimPrefix(version, "go")),
CPEs: []cpe.CPE{stdlibCpe},
Locations: location,
Licenses: pkg.NewLicenseSet(pkg.NewLicense("BSD-3-Clause")),
Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, "BSD-3-Clause")),
Language: pkg.Go,
Type: pkg.GoModulePkg,
Metadata: pkg.GolangBinaryBuildinfoEntry{

View File

@ -1,6 +1,7 @@
package golang
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
@ -15,7 +16,7 @@ import (
)
func Test_stdlibPackageAndRelationships(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
pkgs []pkg.Package
@ -87,7 +88,7 @@ func Test_stdlibPackageAndRelationships(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotPkgs, gotRels := stdlibPackageAndRelationships(tt.pkgs)
gotPkgs, gotRels := stdlibPackageAndRelationships(ctx, tt.pkgs)
assert.Len(t, gotPkgs, tt.wantPkgs)
assert.Len(t, gotRels, tt.wantRels)
})
@ -97,6 +98,7 @@ func Test_stdlibPackageAndRelationships(t *testing.T) {
func Test_stdlibPackageAndRelationships_values(t *testing.T) {
loc := file.NewLocation("/bin/my-app")
locSet := file.NewLocationSet(loc)
ctx := context.TODO()
p := pkg.Package{
Name: "github.com/something/go",
Version: "1.0.0",
@ -114,7 +116,7 @@ func Test_stdlibPackageAndRelationships_values(t *testing.T) {
PURL: packageURL("stdlib", "1.22.2"),
Language: pkg.Go,
Type: pkg.GoModulePkg,
Licenses: pkg.NewLicenseSet(pkg.NewLicense("BSD-3-Clause")),
Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, "BSD-3-Clause")),
CPEs: []cpe.CPE{
{
Attributes: cpe.MustAttributes("cpe:2.3:a:golang:go:1.22.2:-:*:*:*:*:*:*"),
@ -135,7 +137,7 @@ func Test_stdlibPackageAndRelationships_values(t *testing.T) {
Type: artifact.DependencyOfRelationship,
}
gotPkgs, gotRels := stdlibPackageAndRelationships([]pkg.Package{p})
gotPkgs, gotRels := stdlibPackageAndRelationships(ctx, []pkg.Package{p})
require.Len(t, gotPkgs, 1)
gotPkg := gotPkgs[0]

View File

@ -280,7 +280,7 @@ func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package,
func (j *archiveParser) discoverNameVersionLicense(ctx context.Context, manifest *pkg.JavaManifest) (string, string, []pkg.License, error) {
// we use j.location because we want to associate the license declaration with where we discovered the contents in the manifest
// TODO: when we support locations of paths within archives we should start passing the specific manifest location object instead of the top jar
lics := pkg.NewLicensesFromLocation(j.location, selectLicenses(manifest)...)
lics := pkg.NewLicensesFromLocationWithContext(ctx, j.location, selectLicenses(manifest)...)
/*
We should name and version from, in this order:
1. pom.properties if we find exactly 1
@ -351,10 +351,10 @@ func (j *archiveParser) findLicenseFromJavaMetadata(ctx context.Context, groupID
}
}
return toPkgLicenses(&j.location, pomLicenses)
return toPkgLicenses(ctx, &j.location, pomLicenses)
}
func toPkgLicenses(location *file.Location, licenses []maven.License) []pkg.License {
func toPkgLicenses(ctx context.Context, location *file.Location, licenses []maven.License) []pkg.License {
var out []pkg.License
for _, license := range licenses {
name := ""
@ -365,10 +365,14 @@ func toPkgLicenses(location *file.Location, licenses []maven.License) []pkg.Lice
if license.URL != nil {
url = *license.URL
}
// note: it is possible to:
// - have a license without a URL
// - have license and a URL
// - have a URL without a license (this is weird, but can happen)
if name == "" && url == "" {
continue
}
out = append(out, pkg.NewLicenseFromFields(name, url, location))
out = append(out, pkg.NewLicenseFromFieldsWithContext(ctx, name, url, location))
}
return out
}
@ -492,7 +496,7 @@ func getDigestsFromArchive(ctx context.Context, archivePath string) ([]file.Dige
}
func (j *archiveParser) getLicenseFromFileInArchive(ctx context.Context) ([]pkg.License, error) {
var fileLicenses []pkg.License
var out []pkg.License
for _, filename := range licenses.FileNames() {
licenseMatches := j.fileManifest.GlobMatch(true, "/META-INF/"+filename)
if len(licenseMatches) == 0 {
@ -509,19 +513,15 @@ func (j *archiveParser) getLicenseFromFileInArchive(ctx context.Context) ([]pkg.
for _, licenseMatch := range licenseMatches {
licenseContents := contents[licenseMatch]
r := strings.NewReader(licenseContents)
parsed, err := j.licenseScanner.PkgSearch(ctx, file.NewLocationReadCloser(j.location, io.NopCloser(r)))
if err != nil {
return nil, err
}
if len(parsed) > 0 {
fileLicenses = append(fileLicenses, parsed...)
lics := pkg.NewLicensesFromReadCloserWithContext(ctx, file.NewLocationReadCloser(j.location, io.NopCloser(r)))
if len(lics) > 0 {
out = append(out, lics...)
}
}
}
}
return fileLicenses, nil
return out, nil
}
func (j *archiveParser) discoverPkgsFromNestedArchives(ctx context.Context, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
@ -692,7 +692,7 @@ func newPackageFromMavenData(ctx context.Context, r *maven.Resolver, pomProperti
log.WithFields("error", err, "mavenID", maven.NewID(pomProperties.GroupID, pomProperties.ArtifactID, pomProperties.Version)).Trace("error attempting to resolve licenses")
}
licenseSet := pkg.NewLicenseSet(toPkgLicenses(&location, pomLicenses)...)
licenseSet := pkg.NewLicenseSet(toPkgLicenses(ctx, &location, pomLicenses)...)
p := pkg.Package{
Name: pomProperties.ArtifactID,

View File

@ -14,13 +14,11 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/licensecheck"
"github.com/gookit/color"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/license"
@ -32,10 +30,7 @@ import (
func TestSearchMavenForLicenses(t *testing.T) {
url := maventest.MockRepo(t, "internal/maven/test-fixtures/maven-repo")
sc := &licenses.ScannerConfig{Scanner: licensecheck.Scan, CoverageThreshold: 75}
scanner, err := licenses.NewScanner(sc)
require.NoError(t, err)
ctx := licenses.SetContextLicenseScanner(context.Background(), scanner)
ctx := pkgtest.Context()
tests := []struct {
name string
@ -88,17 +83,13 @@ func TestSearchMavenForLicenses(t *testing.T) {
// assert licenses are discovered from upstream
_, _, _, parsedPom := ap.discoverMainPackageFromPomInfo(context.Background())
resolvedLicenses, _ := ap.maven.ResolveLicenses(context.Background(), parsedPom.project)
assert.Equal(t, tc.expectedLicenses, toPkgLicenses(nil, resolvedLicenses))
assert.Equal(t, tc.expectedLicenses, toPkgLicenses(ctx, nil, resolvedLicenses))
})
}
}
func TestParseJar(t *testing.T) {
sc := &licenses.ScannerConfig{Scanner: licensecheck.Scan, CoverageThreshold: 75}
scanner, err := licenses.NewScanner(sc)
require.NoError(t, err)
ctx := licenses.SetContextLicenseScanner(context.Background(), scanner)
ctx := pkgtest.Context()
tests := []struct {
name string
fixture string
@ -121,7 +112,7 @@ func TestParseJar(t *testing.T) {
Version: "1.0-SNAPSHOT",
PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT License", file.NewLocation("test-fixtures/java-builds/packages/example-jenkins-plugin.hpi")),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT License", file.NewLocation("test-fixtures/java-builds/packages/example-jenkins-plugin.hpi")),
),
Language: pkg.Java,
Type: pkg.JenkinsPluginPkg,
@ -207,14 +198,10 @@ func TestParseJar(t *testing.T) {
Language: pkg.Java,
Type: pkg.JavaPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromFields(
"Apache 2",
"http://www.apache.org/licenses/LICENSE-2.0.txt",
func() *file.Location {
l := file.NewLocation("test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar")
return &l
}(),
),
pkg.NewLicenseFromFieldsWithContext(ctx, "Apache 2", "http://www.apache.org/licenses/LICENSE-2.0.txt", func() *file.Location {
l := file.NewLocation("test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar")
return &l
}()),
),
Metadata: pkg.JavaArchive{
// ensure that nested packages with different names than that of the parent are appended as
@ -306,14 +293,10 @@ func TestParseJar(t *testing.T) {
Version: "2.9.2",
PURL: "pkg:maven/joda-time/joda-time@2.9.2",
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromFields(
"Apache 2",
"http://www.apache.org/licenses/LICENSE-2.0.txt",
func() *file.Location {
l := file.NewLocation("test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar")
return &l
}(),
),
pkg.NewLicenseFromFieldsWithContext(ctx, "Apache 2", "http://www.apache.org/licenses/LICENSE-2.0.txt", func() *file.Location {
l := file.NewLocation("test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar")
return &l
}()),
),
Language: pkg.Java,
Type: pkg.JavaPkg,
@ -369,7 +352,7 @@ func TestParseJar(t *testing.T) {
defer cleanupFn()
require.NoError(t, err)
actual, _, err := parser.parse(context.Background(), nil)
actual, _, err := parser.parse(ctx, nil)
if test.wantErr != nil {
test.wantErr(t, err)
} else {
@ -432,11 +415,18 @@ func TestParseJar(t *testing.T) {
metadata.Manifest.Main = newMain
}
}
// write censored data back
a.Metadata = metadata
pkgtest.AssertPackagesEqual(t, e, a)
// we can't use cmpopts.IgnoreFields for the license contents because of the set structure
// drop the license contents from the comparison
licenses := a.Licenses.ToSlice()
for i := range licenses {
licenses[i].Contents = ""
}
a.Licenses = pkg.NewLicenseSet(licenses...)
pkgtest.AssertPackagesEqual(t, e, a, cmpopts.IgnoreFields(pkg.License{}, "Contents"))
}
})
}
@ -1102,6 +1092,7 @@ func Test_artifactIDMatchesFilename(t *testing.T) {
}
func Test_parseJavaArchive_regressions(t *testing.T) {
ctx := context.TODO()
apiAll := pkg.Package{
Name: "api-all",
Version: "2.0.0",
@ -1192,7 +1183,8 @@ func Test_parseJavaArchive_regressions(t *testing.T) {
PURL: "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.2",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/jackson-core-2.15.2.jar")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicensesFromLocation(
pkg.NewLicensesFromLocationWithContext(
ctx,
file.NewLocation("test-fixtures/jar-metadata/cache/jackson-core-2.15.2.jar"),
"https://www.apache.org/licenses/LICENSE-2.0.txt",
)...,
@ -1246,7 +1238,8 @@ func Test_parseJavaArchive_regressions(t *testing.T) {
PURL: "pkg:maven/com.fasterxml.jackson.core/jackson-core@2.15.2",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/com.fasterxml.jackson.core.jackson-core-2.15.2.jar")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicensesFromLocation(
pkg.NewLicensesFromLocationWithContext(
ctx,
file.NewLocation("test-fixtures/jar-metadata/cache/com.fasterxml.jackson.core.jackson-core-2.15.2.jar"),
"https://www.apache.org/licenses/LICENSE-2.0.txt",
)...,
@ -1380,11 +1373,7 @@ func Test_parseJavaArchive_regressions(t *testing.T) {
}
func Test_deterministicMatchingPomProperties(t *testing.T) {
sc := &licenses.ScannerConfig{Scanner: licensecheck.Scan, CoverageThreshold: 75}
scanner, err := licenses.NewScanner(sc)
require.NoError(t, err)
ctx := licenses.SetContextLicenseScanner(context.Background(), scanner)
ctx := pkgtest.Context()
tests := []struct {
fixture string
expected maven.ID

View File

@ -117,7 +117,7 @@ func newPackageFromMavenPom(ctx context.Context, r *maven.Resolver, pom *maven.P
if err != nil {
log.Tracef("error resolving licenses: %v", err)
}
licenses := toPkgLicenses(&location, pomLicenses)
licenses := toPkgLicenses(ctx, &location, pomLicenses)
m := pkg.JavaArchive{
PomProject: &pkg.JavaPomProject{
@ -240,7 +240,7 @@ func newPackageFromDependency(ctx context.Context, r *maven.Resolver, pom *maven
var pomProject *pkg.JavaPomProject
if dependencyPom != nil {
depLicenses, _ := r.ResolveLicenses(ctx, dependencyPom)
licenses = append(licenses, toPkgLicenses(nil, depLicenses)...)
licenses = append(licenses, toPkgLicenses(ctx, nil, depLicenses)...)
pomProject = &pkg.JavaPomProject{
Parent: pomParent(ctx, r, dependencyPom),
GroupID: id.GroupID,

View File

@ -194,6 +194,7 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) {
func Test_parsePomXMLProject(t *testing.T) {
// TODO: ideally we would have the path to the contained pom.xml, not the jar
jarLocation := file.NewLocation("path/to/archive.jar")
ctx := context.TODO()
tests := []struct {
name string
project *pkg.JavaPomProject
@ -270,7 +271,7 @@ func Test_parsePomXMLProject(t *testing.T) {
licenses, err := r.ResolveLicenses(context.Background(), pom)
//assert.NoError(t, err)
assert.Equal(t, test.licenses, toPkgLicenses(&jarLocation, licenses))
assert.Equal(t, test.licenses, toPkgLicenses(ctx, &jarLocation, licenses))
})
}
}

View File

@ -1,6 +1,7 @@
package javascript
import (
"context"
"testing"
"github.com/anchore/syft/syft/file"
@ -9,6 +10,7 @@ import (
)
func Test_JavascriptCataloger(t *testing.T) {
ctx := context.TODO()
locationSet := file.NewLocationSet(file.NewLocation("package-lock.json"))
expectedPkgs := []pkg.Package{
{
@ -20,7 +22,7 @@ func Test_JavascriptCataloger(t *testing.T) {
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation("package-lock.json")),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("package-lock.json")),
),
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz", Integrity: "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw=="},
},
@ -43,7 +45,7 @@ func Test_JavascriptCataloger(t *testing.T) {
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation("package-lock.json")),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("package-lock.json")),
),
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/cowsay/-/cowsay-1.4.0.tgz", Integrity: "sha512-rdg5k5PsHFVJheO/pmE3aDg2rUDDTfPJau6yYkZYlHFktUz+UxbE+IgnUAEyyCyv4noL5ltxXD0gZzmHPCy/9g=="},
},

View File

@ -1,6 +1,7 @@
package javascript
import (
"context"
"encoding/json"
"fmt"
"io"
@ -17,13 +18,13 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func newPackageJSONPackage(u packageJSON, indexLocation file.Location) pkg.Package {
func newPackageJSONPackage(ctx context.Context, u packageJSON, indexLocation file.Location) pkg.Package {
licenseCandidates, err := u.licensesFromJSON()
if err != nil {
log.Debugf("unable to extract licenses from javascript package.json: %+v", err)
}
license := pkg.NewLicensesFromLocation(indexLocation, licenseCandidates...)
license := pkg.NewLicensesFromLocationWithContext(ctx, indexLocation, licenseCandidates...)
p := pkg.Package{
Name: u.Name,
Version: u.Version,
@ -48,7 +49,7 @@ func newPackageJSONPackage(u packageJSON, indexLocation file.Location) pkg.Packa
return p
}
func newPackageLockV1Package(cfg CatalogerConfig, resolver file.Resolver, location file.Location, name string, u lockDependency) pkg.Package {
func newPackageLockV1Package(ctx context.Context, cfg CatalogerConfig, resolver file.Resolver, location file.Location, name string, u lockDependency) pkg.Package {
version := u.Version
const aliasPrefixPackageLockV1 = "npm:"
@ -69,7 +70,7 @@ func newPackageLockV1Package(cfg CatalogerConfig, resolver file.Resolver, locati
if cfg.SearchRemoteLicenses {
license, err := getLicenseFromNpmRegistry(cfg.NPMBaseURL, name, version)
if err == nil && license != "" {
licenses := pkg.NewLicensesFromValues(license)
licenses := pkg.NewLicensesFromValuesWithContext(ctx, license)
licenseSet = pkg.NewLicenseSet(licenses...)
}
if err != nil {
@ -78,6 +79,7 @@ func newPackageLockV1Package(cfg CatalogerConfig, resolver file.Resolver, locati
}
return finalizeLockPkg(
ctx,
resolver,
location,
pkg.Package{
@ -93,15 +95,15 @@ func newPackageLockV1Package(cfg CatalogerConfig, resolver file.Resolver, locati
)
}
func newPackageLockV2Package(cfg CatalogerConfig, resolver file.Resolver, location file.Location, name string, u lockPackage) pkg.Package {
func newPackageLockV2Package(ctx context.Context, cfg CatalogerConfig, resolver file.Resolver, location file.Location, name string, u lockPackage) pkg.Package {
var licenseSet pkg.LicenseSet
if u.License != nil {
licenseSet = pkg.NewLicenseSet(pkg.NewLicensesFromLocation(location, u.License...)...)
licenseSet = pkg.NewLicenseSet(pkg.NewLicensesFromLocationWithContext(ctx, location, u.License...)...)
} else if cfg.SearchRemoteLicenses {
license, err := getLicenseFromNpmRegistry(cfg.NPMBaseURL, name, u.Version)
if err == nil && license != "" {
licenses := pkg.NewLicensesFromValues(license)
licenses := pkg.NewLicensesFromValuesWithContext(ctx, license)
licenseSet = pkg.NewLicenseSet(licenses...)
}
if err != nil {
@ -110,6 +112,7 @@ func newPackageLockV2Package(cfg CatalogerConfig, resolver file.Resolver, locati
}
return finalizeLockPkg(
ctx,
resolver,
location,
pkg.Package{
@ -125,8 +128,9 @@ func newPackageLockV2Package(cfg CatalogerConfig, resolver file.Resolver, locati
)
}
func newPnpmPackage(resolver file.Resolver, location file.Location, name, version string) pkg.Package {
func newPnpmPackage(ctx context.Context, resolver file.Resolver, location file.Location, name, version string) pkg.Package {
return finalizeLockPkg(
ctx,
resolver,
location,
pkg.Package{
@ -140,13 +144,13 @@ func newPnpmPackage(resolver file.Resolver, location file.Location, name, versio
)
}
func newYarnLockPackage(cfg CatalogerConfig, resolver file.Resolver, location file.Location, name, version string, resolved string, integrity string) pkg.Package {
func newYarnLockPackage(ctx context.Context, cfg CatalogerConfig, resolver file.Resolver, location file.Location, name, version string, resolved string, integrity string) pkg.Package {
var licenseSet pkg.LicenseSet
if cfg.SearchRemoteLicenses {
license, err := getLicenseFromNpmRegistry(cfg.NPMBaseURL, name, version)
if err == nil && license != "" {
licenses := pkg.NewLicensesFromValues(license)
licenses := pkg.NewLicensesFromValuesWithContext(ctx, license)
licenseSet = pkg.NewLicenseSet(licenses...)
}
if err != nil {
@ -154,6 +158,7 @@ func newYarnLockPackage(cfg CatalogerConfig, resolver file.Resolver, location fi
}
}
return finalizeLockPkg(
ctx,
resolver,
location,
pkg.Package{
@ -226,9 +231,9 @@ func getLicenseFromNpmRegistry(baseURL, packageName, version string) (string, er
return license.License, nil
}
func finalizeLockPkg(resolver file.Resolver, location file.Location, p pkg.Package) pkg.Package {
func finalizeLockPkg(ctx context.Context, resolver file.Resolver, location file.Location, p pkg.Package) pkg.Package {
licenseCandidate := addLicenses(p.Name, resolver, location)
p.Licenses.Add(pkg.NewLicensesFromLocation(location, licenseCandidate...)...)
p.Licenses.Add(pkg.NewLicensesFromLocationWithContext(ctx, location, licenseCandidate...)...)
p.SetID()
return p
}

View File

@ -51,7 +51,7 @@ type repository struct {
var authorPattern = regexp.MustCompile(`^\s*(?P<name>[^<(]*)(\s+<(?P<email>.*)>)?(\s\((?P<url>.*)\))?\s*$`)
// parsePackageJSON parses a package.json and returns the discovered JavaScript packages.
func parsePackageJSON(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parsePackageJSON(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package
dec := json.NewDecoder(reader)
@ -67,7 +67,7 @@ func parsePackageJSON(_ context.Context, _ file.Resolver, _ *generic.Environment
// a compliance filter later will remove these packages based on compliance rules
pkgs = append(
pkgs,
newPackageJSONPackage(p, reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
newPackageJSONPackage(ctx, p, reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
)
}

View File

@ -1,6 +1,7 @@
package javascript
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
@ -11,6 +12,7 @@ import (
)
func TestParsePackageJSON(t *testing.T) {
ctx := context.TODO()
tests := []struct {
Fixture string
ExpectedPkg pkg.Package
@ -24,7 +26,7 @@ func TestParsePackageJSON(t *testing.T) {
Type: pkg.NpmPkg,
Language: pkg.JavaScript,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package.json")),
pkg.NewLicenseFromLocationsWithContext(ctx, "Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package.json")),
),
Metadata: pkg.NpmPackage{
Name: "npm",
@ -45,7 +47,7 @@ func TestParsePackageJSON(t *testing.T) {
Type: pkg.NpmPkg,
Language: pkg.JavaScript,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("ISC", file.NewLocation("test-fixtures/pkg-json/package-license-object.json")),
pkg.NewLicenseFromLocationsWithContext(ctx, "ISC", file.NewLocation("test-fixtures/pkg-json/package-license-object.json")),
),
Metadata: pkg.NpmPackage{
Name: "npm",
@ -65,8 +67,8 @@ func TestParsePackageJSON(t *testing.T) {
PURL: "pkg:npm/npm@6.14.6",
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation("test-fixtures/pkg-json/package-license-objects.json")),
pkg.NewLicenseFromLocations("Apache-2.0", file.NewLocation("test-fixtures/pkg-json/package-license-objects.json")),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("test-fixtures/pkg-json/package-license-objects.json")),
pkg.NewLicenseFromLocationsWithContext(ctx, "Apache-2.0", file.NewLocation("test-fixtures/pkg-json/package-license-objects.json")),
),
Language: pkg.JavaScript,
Metadata: pkg.NpmPackage{
@ -123,7 +125,7 @@ func TestParsePackageJSON(t *testing.T) {
PURL: "pkg:npm/npm@6.14.6",
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-nested-author.json")),
pkg.NewLicenseFromLocationsWithContext(ctx, "Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-nested-author.json")),
),
Language: pkg.JavaScript,
Metadata: pkg.NpmPackage{
@ -144,7 +146,7 @@ func TestParsePackageJSON(t *testing.T) {
PURL: "pkg:npm/function-bind@1.1.1",
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation("test-fixtures/pkg-json/package-repo-string.json")),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("test-fixtures/pkg-json/package-repo-string.json")),
),
Language: pkg.JavaScript,
Metadata: pkg.NpmPackage{
@ -165,7 +167,7 @@ func TestParsePackageJSON(t *testing.T) {
PURL: "pkg:npm/npm@6.14.6",
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-private.json")),
pkg.NewLicenseFromLocationsWithContext(ctx, "Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-private.json")),
),
Language: pkg.JavaScript,
Metadata: pkg.NpmPackage{
@ -187,7 +189,7 @@ func TestParsePackageJSON(t *testing.T) {
PURL: "pkg:npm/npm@6.14.6",
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-author-non-standard.json")),
pkg.NewLicenseFromLocationsWithContext(ctx, "Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-author-non-standard.json")),
),
Language: pkg.JavaScript,
Metadata: pkg.NpmPackage{

View File

@ -55,7 +55,7 @@ func newGenericPackageLockAdapter(cfg CatalogerConfig) genericPackageLockAdapter
}
// parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages.
func (a genericPackageLockAdapter) parsePackageLock(_ context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func (a genericPackageLockAdapter) parsePackageLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
// in the case we find package-lock.json files in the node_modules directories, skip those
// as the whole purpose of the lock file is for the specific dependencies of the root project
if pathContainsNodeModulesDirectory(reader.Path()) {
@ -81,7 +81,7 @@ func (a genericPackageLockAdapter) parsePackageLock(_ context.Context, resolver
continue
}
pkgs = append(pkgs, newPackageLockV1Package(a.cfg, resolver, reader.Location, name, pkgMeta))
pkgs = append(pkgs, newPackageLockV1Package(ctx, a.cfg, resolver, reader.Location, name, pkgMeta))
}
}
@ -106,7 +106,7 @@ func (a genericPackageLockAdapter) parsePackageLock(_ context.Context, resolver
pkgs = append(
pkgs,
newPackageLockV2Package(a.cfg, resolver, reader.Location, getNameFromPath(name), pkgMeta),
newPackageLockV2Package(ctx, a.cfg, resolver, reader.Location, getNameFromPath(name), pkgMeta),
)
}
}

View File

@ -1,6 +1,7 @@
package javascript
import (
"context"
"testing"
"github.com/anchore/syft/syft/artifact"
@ -111,6 +112,7 @@ func TestParsePackageLock(t *testing.T) {
}
func TestParsePackageLockV2(t *testing.T) {
ctx := context.TODO()
fixture := "test-fixtures/pkg-lock/package-lock-2.json"
var expectedRelationships []artifact.Relationship
expectedPkgs := []pkg.Package{
@ -129,7 +131,7 @@ func TestParsePackageLockV2(t *testing.T) {
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation(fixture)),
),
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", Integrity: "sha1-XxnSuFqY6VWANvajysyIGUIPBc8="},
},
@ -140,7 +142,7 @@ func TestParsePackageLockV2(t *testing.T) {
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation(fixture)),
),
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", Integrity: "sha1-RYPZwyLWfv5LOak10iPtzHBQzPQ="},
},
@ -151,7 +153,7 @@ func TestParsePackageLockV2(t *testing.T) {
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation(fixture)),
),
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", Integrity: "sha1-GmL4lSVyPd4kuhsBsJK/XfitTTk="},
},
@ -162,7 +164,7 @@ func TestParsePackageLockV2(t *testing.T) {
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation(fixture)),
),
Metadata: pkg.NpmPackageLockEntry{Resolved: "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", Integrity: "sha1-TdysNxjXh8+d8NG30VAzklyPKfI="},
},
@ -227,6 +229,7 @@ func TestParsePackageLockV3(t *testing.T) {
}
func TestParsePackageLockAlias(t *testing.T) {
ctx := context.TODO()
var expectedRelationships []artifact.Relationship
commonPkgs := []pkg.Package{
{
@ -266,7 +269,7 @@ func TestParsePackageLockAlias(t *testing.T) {
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("ISC", file.NewLocation(packageLockV2)),
pkg.NewLicenseFromLocationsWithContext(ctx, "ISC", file.NewLocation(packageLockV2)),
),
Metadata: pkg.NpmPackageLockEntry{},
}
@ -288,6 +291,7 @@ func TestParsePackageLockAlias(t *testing.T) {
}
func TestParsePackageLockLicenseWithArray(t *testing.T) {
ctx := context.TODO()
fixture := "test-fixtures/pkg-lock/array-license-package-lock.json"
var expectedRelationships []artifact.Relationship
expectedPkgs := []pkg.Package{
@ -297,7 +301,7 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) {
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("ISC", file.NewLocation(fixture)),
pkg.NewLicenseFromLocationsWithContext(ctx, "ISC", file.NewLocation(fixture)),
),
PURL: "pkg:npm/tmp@1.0.0",
Metadata: pkg.NpmPackageLockEntry{},
@ -309,8 +313,8 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) {
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)),
pkg.NewLicenseFromLocations("Apache2", file.NewLocation(fixture)),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation(fixture)),
pkg.NewLicenseFromLocationsWithContext(ctx, "Apache2", file.NewLocation(fixture)),
),
PURL: "pkg:npm/pause-stream@0.0.11",
Metadata: pkg.NpmPackageLockEntry{},
@ -321,7 +325,7 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) {
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation(fixture)),
),
PURL: "pkg:npm/through@2.3.8",
Metadata: pkg.NpmPackageLockEntry{},

View File

@ -27,7 +27,7 @@ type pnpmLockYaml struct {
Packages map[string]interface{} `json:"packages" yaml:"packages"`
}
func parsePnpmLock(_ context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parsePnpmLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
bytes, err := io.ReadAll(reader)
if err != nil {
return nil, nil, fmt.Errorf("failed to load pnpm-lock.yaml file: %w", err)
@ -66,7 +66,7 @@ func parsePnpmLock(_ context.Context, resolver file.Resolver, _ *generic.Environ
continue
}
pkgs = append(pkgs, newPnpmPackage(resolver, reader.Location, name, version))
pkgs = append(pkgs, newPnpmPackage(ctx, resolver, reader.Location, name, version))
}
packageNameRegex := regexp.MustCompile(`^/?([^(]*)(?:\(.*\))*$`)
@ -90,7 +90,7 @@ func parsePnpmLock(_ context.Context, resolver file.Resolver, _ *generic.Environ
continue
}
pkgs = append(pkgs, newPnpmPackage(resolver, reader.Location, name, version))
pkgs = append(pkgs, newPnpmPackage(ctx, resolver, reader.Location, name, version))
}
pkg.Sort(pkgs)

View File

@ -59,7 +59,7 @@ func newGenericYarnLockAdapter(cfg CatalogerConfig) genericYarnLockAdapter {
}
}
func (a genericYarnLockAdapter) parseYarnLock(_ context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func (a genericYarnLockAdapter) parseYarnLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
// in the case we find yarn.lock files in the node_modules directories, skip those
// as the whole purpose of the lock file is for the specific dependencies of the project
if pathContainsNodeModulesDirectory(reader.Path()) {
@ -78,7 +78,7 @@ func (a genericYarnLockAdapter) parseYarnLock(_ context.Context, resolver file.R
if packageName := findPackageName(line); packageName != "" {
// When we find a new package, check if we have unsaved identifiers
if currentPackage != "" && currentVersion != "" && !parsedPackages.Has(currentPackage+"@"+currentVersion) {
pkgs = append(pkgs, newYarnLockPackage(a.cfg, resolver, reader.Location, currentPackage, currentVersion, currentResolved, currentIntegrity))
pkgs = append(pkgs, newYarnLockPackage(ctx, a.cfg, resolver, reader.Location, currentPackage, currentVersion, currentResolved, currentIntegrity))
parsedPackages.Add(currentPackage + "@" + currentVersion)
}
@ -90,7 +90,7 @@ func (a genericYarnLockAdapter) parseYarnLock(_ context.Context, resolver file.R
currentPackage = packageName
currentVersion = version
} else if integrity := findIntegrity(line); integrity != "" && !parsedPackages.Has(currentPackage+"@"+currentVersion) {
pkgs = append(pkgs, newYarnLockPackage(a.cfg, resolver, reader.Location, currentPackage, currentVersion, currentResolved, integrity))
pkgs = append(pkgs, newYarnLockPackage(ctx, a.cfg, resolver, reader.Location, currentPackage, currentVersion, currentResolved, integrity))
parsedPackages.Add(currentPackage + "@" + currentVersion)
// Cleanup to indicate no unsaved identifiers
@ -103,7 +103,7 @@ func (a genericYarnLockAdapter) parseYarnLock(_ context.Context, resolver file.R
// check if we have valid unsaved data after end-of-file has reached
if currentPackage != "" && currentVersion != "" && !parsedPackages.Has(currentPackage+"@"+currentVersion) {
pkgs = append(pkgs, newYarnLockPackage(a.cfg, resolver, reader.Location, currentPackage, currentVersion, currentResolved, currentIntegrity))
pkgs = append(pkgs, newYarnLockPackage(ctx, a.cfg, resolver, reader.Location, currentPackage, currentVersion, currentResolved, currentIntegrity))
parsedPackages.Add(currentPackage + "@" + currentVersion)
}

View File

@ -1,6 +1,7 @@
package javascript
import (
"context"
"io"
"net/http"
"net/http/httptest"
@ -235,6 +236,7 @@ type handlerPath struct {
}
func TestSearchYarnForLicenses(t *testing.T) {
ctx := context.TODO()
fixture := "test-fixtures/yarn-remote/yarn.lock"
locations := file.NewLocationSet(file.NewLocation(fixture))
mux, url, teardown := setup()
@ -262,7 +264,7 @@ func TestSearchYarnForLicenses(t *testing.T) {
Version: "7.10.4",
Locations: locations,
PURL: "pkg:npm/%40babel/code-frame@7.10.4",
Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")),
Licenses: pkg.NewLicenseSet(pkg.NewLicenseWithContext(ctx, "MIT")),
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Metadata: pkg.YarnLockEntry{

View File

@ -1,6 +1,7 @@
package kernel
import (
"context"
"testing"
"github.com/anchore/syft/syft/artifact"
@ -11,6 +12,7 @@ import (
)
func Test_KernelCataloger(t *testing.T) {
ctx := context.TODO()
kernelPkg := pkg.Package{
Name: "linux-kernel",
Version: "6.0.7-301.fc37.x86_64",
@ -49,7 +51,7 @@ func Test_KernelCataloger(t *testing.T) {
),
),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("GPL v2",
pkg.NewLicenseFromLocationsWithContext(ctx, "GPL v2",
file.NewVirtualLocation(
"/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko",
"/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko",

View File

@ -1,6 +1,7 @@
package kernel
import (
"context"
"strings"
"github.com/anchore/packageurl-go"
@ -40,12 +41,12 @@ func newLinuxKernelPackage(metadata pkg.LinuxKernel, archiveLocation file.Locati
return p
}
func newLinuxKernelModulePackage(metadata pkg.LinuxKernelModule, kmLocation file.Location) pkg.Package {
func newLinuxKernelModulePackage(ctx context.Context, metadata pkg.LinuxKernelModule, kmLocation file.Location) pkg.Package {
p := pkg.Package{
Name: metadata.Name,
Version: metadata.Version,
Locations: file.NewLocationSet(kmLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(kmLocation, metadata.License)...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocationWithContext(ctx, kmLocation, metadata.License)...),
PURL: packageURL(metadata.Name, metadata.Version),
Type: pkg.LinuxKernelModulePkg,
Metadata: metadata,

View File

@ -15,7 +15,7 @@ import (
const modinfoName = ".modinfo"
func parseLinuxKernelModuleFile(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parseLinuxKernelModuleFile(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
unionReader, err := unionreader.GetUnionReader(reader)
if err != nil {
return nil, nil, fmt.Errorf("unable to get union reader for file: %w", err)
@ -32,6 +32,7 @@ func parseLinuxKernelModuleFile(_ context.Context, _ file.Resolver, _ *generic.E
return []pkg.Package{
newLinuxKernelModulePackage(
ctx,
*metadata,
reader.Location,
),

View File

@ -1,13 +1,15 @@
package lua
import (
"context"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
func newLuaRocksPackage(u luaRocksPackage, indexLocation file.Location) pkg.Package {
license := pkg.NewLicensesFromLocation(indexLocation, u.License)
func newLuaRocksPackage(ctx context.Context, u luaRocksPackage, indexLocation file.Location) pkg.Package {
license := pkg.NewLicensesFromLocationWithContext(ctx, indexLocation, u.License)
p := pkg.Package{
Name: u.Name,
Version: u.Version,

View File

@ -27,7 +27,7 @@ type repository struct {
}
// parseRockspec parses a package.rockspec and returns the discovered Lua packages.
func parseRockspec(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parseRockspec(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
doc, err := parseRockspecData(reader)
if err != nil {
log.WithFields("error", err).Trace("unable to parse Rockspec app")
@ -64,6 +64,7 @@ func parseRockspec(_ context.Context, _ file.Resolver, _ *generic.Environment, r
}
p := newLuaRocksPackage(
ctx,
luaRocksPackage{
Name: name,
Version: version,

View File

@ -1,6 +1,7 @@
package lua
import (
"context"
"testing"
"github.com/anchore/syft/syft/file"
@ -9,6 +10,7 @@ import (
)
func TestParseRockspec(t *testing.T) {
ctx := context.TODO()
tests := []struct {
Fixture string
ExpectedPkg pkg.Package
@ -22,7 +24,7 @@ func TestParseRockspec(t *testing.T) {
Type: pkg.LuaRocksPkg,
Language: pkg.Lua,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("Apache-2.0", file.NewLocation("test-fixtures/rockspec/kong-3.7.0-0.rockspec")),
pkg.NewLicenseFromLocationsWithContext(ctx, "Apache-2.0", file.NewLocation("test-fixtures/rockspec/kong-3.7.0-0.rockspec")),
),
Metadata: pkg.LuaRocksPackage{
Name: "kong",
@ -43,7 +45,7 @@ func TestParseRockspec(t *testing.T) {
Type: pkg.LuaRocksPkg,
Language: pkg.Lua,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT/X11", file.NewLocation("test-fixtures/rockspec/lpeg-1.0.2-1.rockspec")),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT/X11", file.NewLocation("test-fixtures/rockspec/lpeg-1.0.2-1.rockspec")),
),
Metadata: pkg.LuaRocksPackage{
Name: "LPeg",
@ -64,7 +66,7 @@ func TestParseRockspec(t *testing.T) {
Type: pkg.LuaRocksPkg,
Language: pkg.Lua,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation("test-fixtures/rockspec/kong-pgmoon-1.16.2-1.rockspec")),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("test-fixtures/rockspec/kong-pgmoon-1.16.2-1.rockspec")),
),
Metadata: pkg.LuaRocksPackage{
Name: "kong-pgmoon",
@ -85,7 +87,7 @@ func TestParseRockspec(t *testing.T) {
Type: pkg.LuaRocksPkg,
Language: pkg.Lua,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT/X11", file.NewLocation("test-fixtures/rockspec/luasyslog-2.0.1-1.rockspec")),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT/X11", file.NewLocation("test-fixtures/rockspec/luasyslog-2.0.1-1.rockspec")),
),
Metadata: pkg.LuaRocksPackage{
Name: "luasyslog",

View File

@ -1,16 +1,18 @@
package ocaml
import (
"context"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
func newOpamPackage(m pkg.OpamPackage, fileLocation file.Location) pkg.Package {
func newOpamPackage(ctx context.Context, m pkg.OpamPackage, fileLocation file.Location) pkg.Package {
p := pkg.Package{
Name: m.Name,
Version: m.Version,
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(fileLocation, m.Licenses...)...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocationWithContext(ctx, fileLocation, m.Licenses...)...),
PURL: opamPackageURL(m.Name, m.Version),
Locations: file.NewLocationSet(fileLocation),
Type: pkg.OpamPkg,

View File

@ -16,7 +16,7 @@ import (
)
//nolint:funlen
func parseOpamPackage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parseOpamPackage(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package
opamVersionRe := regexp.MustCompile(`(?m)opam-version:\s*"[0-9]+\.[0-9]+"`)
@ -94,6 +94,7 @@ func parseOpamPackage(_ context.Context, _ file.Resolver, _ *generic.Environment
pkgs = append(
pkgs,
newOpamPackage(
ctx,
entry,
reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),

View File

@ -1,6 +1,7 @@
package ocaml
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
@ -17,7 +18,7 @@ func TestParseOpamPackage(t *testing.T) {
fixture2 := "test-fixtures/alcotest.opam"
location2 := file.NewLocation(fixture2)
ctx := context.TODO()
tests := []struct {
fixture string
want []pkg.Package
@ -31,10 +32,7 @@ func TestParseOpamPackage(t *testing.T) {
PURL: "pkg:opam/ocaml-base-compiler@4.14.0",
Locations: file.NewLocationSet(location1),
Licenses: pkg.NewLicenseSet(
pkg.NewLicensesFromLocation(
location1,
"LGPL-2.1-or-later WITH OCaml-LGPL-linking-exception",
)...,
pkg.NewLicensesFromLocationWithContext(ctx, location1, "LGPL-2.1-or-later WITH OCaml-LGPL-linking-exception")...,
),
Language: pkg.OCaml,
Type: pkg.OpamPkg,
@ -60,7 +58,8 @@ func TestParseOpamPackage(t *testing.T) {
PURL: "pkg:opam/alcotest@1.5.0",
Locations: file.NewLocationSet(location2),
Licenses: pkg.NewLicenseSet(
pkg.NewLicensesFromLocation(
pkg.NewLicensesFromLocationWithContext(
ctx,
location2,
"ISC",
)...,

View File

@ -1,6 +1,7 @@
package php
import (
"context"
"strings"
"github.com/anchore/packageurl-go"
@ -8,12 +9,12 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func newComposerLockPackage(pd parsedLockData, indexLocation file.Location) pkg.Package {
func newComposerLockPackage(ctx context.Context, pd parsedLockData, indexLocation file.Location) pkg.Package {
p := pkg.Package{
Name: pd.Name,
Version: pd.Version,
Locations: file.NewLocationSet(indexLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(indexLocation, pd.License...)...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocationWithContext(ctx, indexLocation, pd.License...)...),
PURL: packageURLFromComposer(pd.Name, pd.Version),
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
@ -24,12 +25,12 @@ func newComposerLockPackage(pd parsedLockData, indexLocation file.Location) pkg.
return p
}
func newComposerInstalledPackage(pd parsedInstalledData, indexLocation file.Location) pkg.Package {
func newComposerInstalledPackage(ctx context.Context, pd parsedInstalledData, indexLocation file.Location) pkg.Package {
p := pkg.Package{
Name: pd.Name,
Version: pd.Version,
Locations: file.NewLocationSet(indexLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(indexLocation, pd.License...)...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocationWithContext(ctx, indexLocation, pd.License...)...),
PURL: packageURLFromComposer(pd.Name, pd.Version),
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
@ -40,12 +41,12 @@ func newComposerInstalledPackage(pd parsedInstalledData, indexLocation file.Loca
return p
}
func newPearPackage(pd peclPearData, indexLocation file.Location) pkg.Package {
func newPearPackage(ctx context.Context, pd peclPearData, indexLocation file.Location) pkg.Package {
p := pkg.Package{
Name: pd.Name,
Version: pd.Version,
Locations: file.NewLocationSet(indexLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(indexLocation, pd.License...)...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocationWithContext(ctx, indexLocation, pd.License...)...),
PURL: packageURLFromPear(pd.Name, pd.Channel, pd.Version),
Language: pkg.PHP,
Type: pkg.PhpPearPkg,
@ -56,12 +57,12 @@ func newPearPackage(pd peclPearData, indexLocation file.Location) pkg.Package {
return p
}
func newPeclPackage(pd peclPearData, indexLocation file.Location) pkg.Package {
func newPeclPackage(ctx context.Context, pd peclPearData, indexLocation file.Location) pkg.Package {
p := pkg.Package{
Name: pd.Name,
Version: pd.Version,
Locations: file.NewLocationSet(indexLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(indexLocation, pd.License...)...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocationWithContext(ctx, indexLocation, pd.License...)...),
PURL: packageURLFromPear(pd.Name, pd.Channel, pd.Version),
Language: pkg.PHP,
Type: pkg.PhpPeclPkg,

View File

@ -27,7 +27,7 @@ type composerLock struct {
}
// parseComposerLock is a parser function for Composer.lock contents, returning "Default" php packages discovered.
func parseComposerLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parseComposerLock(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
pkgs := make([]pkg.Package, 0)
dec := json.NewDecoder(reader)
@ -42,6 +42,7 @@ func parseComposerLock(_ context.Context, _ file.Resolver, _ *generic.Environmen
pkgs = append(
pkgs,
newComposerLockPackage(
ctx,
pd,
reader.Location,
),

View File

@ -1,6 +1,7 @@
package php
import (
"context"
"testing"
"github.com/anchore/syft/syft/artifact"
@ -10,6 +11,7 @@ import (
)
func TestParseComposerFileLock(t *testing.T) {
ctx := context.Background()
var expectedRelationships []artifact.Relationship
fixture := "test-fixtures/composer.lock"
locations := file.NewLocationSet(file.NewLocation(fixture))
@ -20,7 +22,7 @@ func TestParseComposerFileLock(t *testing.T) {
PURL: "pkg:composer/adoy/fastcgi-client@1.0.2",
Locations: locations,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation(fixture)),
),
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
@ -60,7 +62,7 @@ func TestParseComposerFileLock(t *testing.T) {
PURL: "pkg:composer/alcaeus/mongo-php-adapter@1.1.11",
Language: pkg.PHP,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation(fixture)),
),
Type: pkg.PhpComposerPkg,
Metadata: pkg.PhpComposerLockEntry{

View File

@ -48,7 +48,7 @@ func (w *installedJSONComposerV2) UnmarshalJSON(data []byte) error {
}
// parseInstalledJSON is a parser function for Composer.lock contents, returning "Default" php packages discovered.
func parseInstalledJSON(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parseInstalledJSON(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package
dec := json.NewDecoder(reader)
@ -63,6 +63,7 @@ func parseInstalledJSON(_ context.Context, _ file.Resolver, _ *generic.Environme
pkgs = append(
pkgs,
newComposerInstalledPackage(
ctx,
pd,
reader.Location,
),

View File

@ -1,6 +1,7 @@
package php
import (
"context"
"testing"
"github.com/anchore/syft/syft/artifact"
@ -10,6 +11,7 @@ import (
)
func TestParseInstalledJsonComposerV1(t *testing.T) {
ctx := context.TODO()
fixtures := []string{
"test-fixtures/vendor/composer_1/installed.json",
"test-fixtures/vendor/composer_2/installed.json",
@ -24,7 +26,7 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
pkg.NewLicenseWithContext(ctx, "MIT"),
),
Metadata: pkg.PhpComposerInstalledEntry{
Name: "asm89/stack-cors",
@ -73,7 +75,7 @@ func TestParseInstalledJsonComposerV1(t *testing.T) {
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicense("MIT"),
pkg.NewLicenseWithContext(ctx, "MIT"),
),
Metadata: pkg.PhpComposerInstalledEntry{
Name: "behat/mink",

View File

@ -34,7 +34,7 @@ func (p *peclPearData) ToPecl() pkg.PhpPeclEntry {
return pkg.PhpPeclEntry(p.ToPear())
}
func parsePecl(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parsePecl(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
m, err := parsePeclPearSerialized(reader)
if err != nil {
return nil, nil, err
@ -42,10 +42,10 @@ func parsePecl(_ context.Context, _ file.Resolver, _ *generic.Environment, reade
if m == nil {
return nil, nil, unknown.New(reader.Location, fmt.Errorf("no pecl package found"))
}
return []pkg.Package{newPeclPackage(*m, reader.Location)}, nil, nil
return []pkg.Package{newPeclPackage(ctx, *m, reader.Location)}, nil, nil
}
func parsePear(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parsePear(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
m, err := parsePeclPearSerialized(reader)
if err != nil {
return nil, nil, err
@ -53,7 +53,7 @@ func parsePear(_ context.Context, _ file.Resolver, _ *generic.Environment, reade
if m == nil {
return nil, nil, unknown.New(reader.Location, fmt.Errorf("no pear package found"))
}
return []pkg.Package{newPearPackage(*m, reader.Location)}, nil, nil
return []pkg.Package{newPearPackage(ctx, *m, reader.Location)}, nil, nil
}
// parsePeclPearSerialized is a parser function for Pear metadata contents, returning "Default" php packages discovered.

View File

@ -1,6 +1,7 @@
package php
import (
"context"
"testing"
"github.com/anchore/syft/syft/artifact"
@ -10,6 +11,7 @@ import (
)
func TestParsePear(t *testing.T) {
ctx := context.TODO()
tests := []struct {
name string
fixture string
@ -26,7 +28,7 @@ func TestParsePear(t *testing.T) {
PURL: "pkg:pear/pecl.php.net/memcached@3.2.0",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/memcached-v6-format.reg")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("PHP License", file.NewLocation("test-fixtures/memcached-v6-format.reg")),
pkg.NewLicenseFromLocationsWithContext(ctx, "PHP License", file.NewLocation("test-fixtures/memcached-v6-format.reg")),
),
Language: pkg.PHP,
Type: pkg.PhpPearPkg,
@ -49,7 +51,7 @@ func TestParsePear(t *testing.T) {
PURL: "pkg:pear/pecl.php.net/memcached@3.2.0",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/memcached-v5-format.reg")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("PHP License", file.NewLocation("test-fixtures/memcached-v5-format.reg")),
pkg.NewLicenseFromLocationsWithContext(ctx, "PHP License", file.NewLocation("test-fixtures/memcached-v5-format.reg")),
),
Language: pkg.PHP,
Type: pkg.PhpPearPkg,
@ -71,6 +73,7 @@ func TestParsePear(t *testing.T) {
}
func TestParsePecl(t *testing.T) {
ctx := context.TODO()
tests := []struct {
name string
fixture string
@ -87,7 +90,7 @@ func TestParsePecl(t *testing.T) {
PURL: "pkg:pear/pecl.php.net/memcached@3.2.0",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/memcached-v6-format.reg")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("PHP License", file.NewLocation("test-fixtures/memcached-v6-format.reg")),
pkg.NewLicenseFromLocationsWithContext(ctx, "PHP License", file.NewLocation("test-fixtures/memcached-v6-format.reg")),
),
Language: pkg.PHP,
Type: pkg.PhpPeclPkg, // important!
@ -110,7 +113,7 @@ func TestParsePecl(t *testing.T) {
PURL: "pkg:pear/pecl.php.net/memcached@3.2.0",
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/memcached-v5-format.reg")),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("PHP License", file.NewLocation("test-fixtures/memcached-v5-format.reg")),
pkg.NewLicenseFromLocationsWithContext(ctx, "PHP License", file.NewLocation("test-fixtures/memcached-v5-format.reg")),
),
Language: pkg.PHP,
Type: pkg.PhpPeclPkg, // important!

View File

@ -1,7 +1,9 @@
package python
import (
"context"
"fmt"
"os"
"path"
"testing"
@ -13,6 +15,7 @@ import (
)
func Test_PackageCataloger(t *testing.T) {
ctx := context.TODO()
tests := []struct {
name string
fixture string
@ -55,7 +58,7 @@ func Test_PackageCataloger(t *testing.T) {
Licenses: pkg.NewLicenseSet(
// here we only used the license that was declared in the METADATA file, we did not go searching for other licenses
// this is the better source of truth when there is no explicit LicenseFile given
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("dist-name/dist-info/METADATA")),
pkg.NewLicenseFromLocationsWithContext(ctx, "BSD License", file.NewLocation("dist-name/dist-info/METADATA")),
),
FoundBy: "python-installed-package-cataloger",
Metadata: pkg.PythonPackage{
@ -94,7 +97,7 @@ func Test_PackageCataloger(t *testing.T) {
file.NewLocation("egg-name/egg-info/top_level.txt"),
),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("egg-name/egg-info/PKG-INFO")),
pkg.NewLicenseFromLocationsWithContext(ctx, "Apache 2.0", file.NewLocation("egg-name/egg-info/PKG-INFO")),
),
FoundBy: "python-installed-package-cataloger",
Metadata: pkg.PythonPackage{
@ -138,7 +141,7 @@ func Test_PackageCataloger(t *testing.T) {
Licenses: pkg.NewLicenseSet(
// here we only used the license that was declared in the METADATA file, we did not go searching for other licenses
// this is the better source of truth when there is no explicit LicenseFile given
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("dist-name/DIST-INFO/METADATA")),
pkg.NewLicenseFromLocationsWithContext(ctx, "BSD License", file.NewLocation("dist-name/DIST-INFO/METADATA")),
),
FoundBy: "python-installed-package-cataloger",
Metadata: pkg.PythonPackage{
@ -174,7 +177,7 @@ func Test_PackageCataloger(t *testing.T) {
file.NewLocation("egg-name/EGG-INFO/top_level.txt"),
),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("egg-name/EGG-INFO/PKG-INFO")),
pkg.NewLicenseFromLocationsWithContext(ctx, "Apache 2.0", file.NewLocation("egg-name/EGG-INFO/PKG-INFO")),
),
FoundBy: "python-installed-package-cataloger",
Metadata: pkg.PythonPackage{
@ -220,6 +223,7 @@ func Test_PackageCataloger(t *testing.T) {
Value: "BSD-3-Clause",
SPDXExpression: "BSD-3-Clause",
Type: "concluded",
Contents: mustContentsFromLocation(t, "test-fixtures/site-packages/license/with-license-file-declared.dist-info/LICENSE.txt", 0, 1475),
// we read the path from the LicenseFile field in the METADATA file, then read the license file directly
Locations: file.NewLocationSet(file.NewLocation("with-license-file-declared.dist-info/LICENSE.txt")),
},
@ -266,8 +270,8 @@ func Test_PackageCataloger(t *testing.T) {
Value: "BSD-3-Clause",
SPDXExpression: "BSD-3-Clause",
Type: "concluded",
// we discover license files automatically
Locations: file.NewLocationSet(file.NewLocation("without-license-file-declared.dist-info/LICENSE.txt")),
Contents: mustContentsFromLocation(t, "test-fixtures/site-packages/license/with-license-file-declared.dist-info/LICENSE.txt", 0, 1475),
Locations: file.NewLocationSet(file.NewLocation("without-license-file-declared.dist-info/LICENSE.txt")),
},
),
FoundBy: "python-installed-package-cataloger",
@ -312,7 +316,7 @@ func Test_PackageCataloger(t *testing.T) {
file.NewLocation("dist-info/RECORD"),
),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("dist-info/METADATA")),
pkg.NewLicenseFromLocationsWithContext(ctx, "BSD License", file.NewLocation("dist-info/METADATA")),
),
FoundBy: "python-installed-package-cataloger",
Metadata: pkg.PythonPackage{
@ -349,7 +353,7 @@ func Test_PackageCataloger(t *testing.T) {
file.NewLocation("METADATA"),
),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("BSD License", file.NewLocation("METADATA")),
pkg.NewLicenseFromLocationsWithContext(ctx, "BSD License", file.NewLocation("METADATA")),
),
FoundBy: "python-installed-package-cataloger",
Metadata: pkg.PythonPackage{
@ -378,7 +382,7 @@ func Test_PackageCataloger(t *testing.T) {
file.NewLocation("test.egg-info"),
),
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("test.egg-info")),
pkg.NewLicenseFromLocationsWithContext(ctx, "Apache 2.0", file.NewLocation("test.egg-info")),
),
FoundBy: "python-installed-package-cataloger",
Metadata: pkg.PythonPackage{
@ -398,10 +402,10 @@ func Test_PackageCataloger(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pkgtest.NewCatalogTester().
(pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture).
Expects(test.expectedPackages, nil).
TestCataloger(t, NewInstalledPackageCataloger())
TestCataloger(t, NewInstalledPackageCataloger()))
})
}
}
@ -802,3 +806,26 @@ func stringPackage(p pkg.Package) string {
return fmt.Sprintf("%s @ %s (%s)", p.Name, p.Version, loc)
}
func mustContentsFromLocation(t *testing.T, contentsPath string, offset ...int) string {
t.Helper() // Marks this function as a test helper for cleaner error reporting
contents, err := os.ReadFile(contentsPath)
if err != nil {
t.Fatalf("failed to read file %s: %v", contentsPath, err)
}
if len(offset) == 0 {
return string(contents)
}
if len(offset) != 2 {
t.Fatalf("invalid offset provided, expected two integers: start and end")
}
start, end := offset[0], offset[1]
if start < 0 || end > len(contents) || start > end {
t.Fatalf("invalid offset range: start=%d, end=%d, content length=%d", start, end, len(contents))
}
return string(contents[start:end])
}

View File

@ -25,10 +25,6 @@ import (
// parseWheelOrEgg takes the primary metadata file reference and returns the python package it represents. Contained
// fields are governed by the PyPA core metadata specification (https://packaging.python.org/en/latest/specifications/core-metadata/).
func parseWheelOrEgg(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
licenseScanner, err := licenses.ContextLicenseScanner(ctx)
if err != nil {
return nil, nil, err
}
pd, sources, err := assembleEggOrWheelMetadata(resolver, reader.Location)
if err != nil {
return nil, nil, err
@ -46,7 +42,7 @@ func parseWheelOrEgg(ctx context.Context, resolver file.Resolver, _ *generic.Env
pkgs := []pkg.Package{
newPackageForPackage(
*pd,
findLicenses(ctx, licenseScanner, resolver, *pd),
findLicenses(ctx, resolver, *pd),
sources...,
),
}
@ -253,7 +249,7 @@ func assembleEggOrWheelMetadata(resolver file.Resolver, metadataLocation file.Lo
return &pd, sources, nil
}
func findLicenses(ctx context.Context, scanner licenses.Scanner, resolver file.Resolver, m parsedData) pkg.LicenseSet {
func findLicenses(ctx context.Context, resolver file.Resolver, m parsedData) pkg.LicenseSet {
var licenseSet pkg.LicenseSet
licenseLocations := file.NewLocationSet()
@ -268,9 +264,9 @@ func findLicenses(ctx context.Context, scanner licenses.Scanner, resolver file.R
switch {
case m.LicenseExpression != "" || m.Licenses != "":
licenseSet = getLicenseSetFromValues(licenseLocations.ToSlice(), m.LicenseExpression, m.Licenses)
licenseSet = getLicenseSetFromValues(ctx, licenseLocations.ToSlice(), m.LicenseExpression, m.Licenses)
case !licenseLocations.Empty():
licenseSet = getLicenseSetFromFiles(ctx, scanner, resolver, licenseLocations.ToSlice()...)
licenseSet = getLicenseSetFromFiles(ctx, resolver, licenseLocations.ToSlice()...)
default:
// search for known license paths from RECORDS file
@ -302,14 +298,14 @@ func findLicenses(ctx context.Context, scanner licenses.Scanner, resolver file.R
locationSet.Add(locs...)
}
licenseSet = getLicenseSetFromFiles(ctx, scanner, resolver, locationSet.ToSlice()...)
licenseSet = getLicenseSetFromFiles(ctx, resolver, locationSet.ToSlice()...)
}
return licenseSet
}
func getLicenseSetFromValues(locations []file.Location, licenseValues ...string) pkg.LicenseSet {
func getLicenseSetFromValues(ctx context.Context, locations []file.Location, licenseValues ...string) pkg.LicenseSet {
if len(locations) == 0 {
return pkg.NewLicenseSet(pkg.NewLicensesFromValues(licenseValues...)...)
return pkg.NewLicenseSet(pkg.NewLicensesFromValuesWithContext(ctx, licenseValues...)...)
}
licenseSet := pkg.NewLicenseSet()
@ -318,31 +314,25 @@ func getLicenseSetFromValues(locations []file.Location, licenseValues ...string)
continue
}
licenseSet.Add(pkg.NewLicenseFromLocations(value, locations...))
licenseSet.Add(pkg.NewLicenseFromLocationsWithContext(ctx, value, locations...))
}
return licenseSet
}
func getLicenseSetFromFiles(ctx context.Context, scanner licenses.Scanner, resolver file.Resolver, locations ...file.Location) pkg.LicenseSet {
func getLicenseSetFromFiles(ctx context.Context, resolver file.Resolver, locations ...file.Location) pkg.LicenseSet {
licenseSet := pkg.NewLicenseSet()
for _, loc := range locations {
licenseSet.Add(getLicenseSetFromFile(ctx, scanner, resolver, loc)...)
licenseSet.Add(getLicenseSetFromFile(ctx, resolver, loc)...)
}
return licenseSet
}
func getLicenseSetFromFile(ctx context.Context, scanner licenses.Scanner, resolver file.Resolver, location file.Location) []pkg.License {
func getLicenseSetFromFile(ctx context.Context, resolver file.Resolver, location file.Location) []pkg.License {
metadataContents, err := resolver.FileContentsByLocation(location)
if err != nil {
log.WithFields("error", err, "path", location.Path()).Trace("unable to read file contents")
return nil
}
defer internal.CloseAndLogError(metadataContents, location.Path())
parsed, err := scanner.PkgSearch(ctx, file.NewLocationReadCloser(location, metadataContents))
if err != nil {
log.WithFields("error", err, "path", location.Path()).Trace("unable to parse a license from the file")
return nil
}
return parsed
return pkg.NewLicensesFromReadCloserWithContext(ctx, file.NewLocationReadCloser(location, metadataContents))
}

View File

@ -1,6 +1,7 @@
package r
import (
"context"
"testing"
"github.com/anchore/syft/syft/artifact"
@ -10,13 +11,14 @@ import (
)
func TestRPackageCataloger(t *testing.T) {
ctx := context.Background()
expectedPkgs := []pkg.Package{
{
Name: "base",
Version: "4.3.0",
FoundBy: "r-package-cataloger",
Locations: file.NewLocationSet(file.NewLocation("base/DESCRIPTION")),
Licenses: pkg.NewLicenseSet([]pkg.License{pkg.NewLicense("Part of R 4.3.0")}...),
Licenses: pkg.NewLicenseSet([]pkg.License{pkg.NewLicenseWithContext(ctx, "Part of R 4.3.0")}...),
Language: pkg.R,
Type: pkg.Rpkg,
PURL: "pkg:cran/base@4.3.0",
@ -34,7 +36,7 @@ func TestRPackageCataloger(t *testing.T) {
Version: "1.5.0.9000",
FoundBy: "r-package-cataloger",
Locations: file.NewLocationSet(file.NewLocation("stringr/DESCRIPTION")),
Licenses: pkg.NewLicenseSet([]pkg.License{pkg.NewLicense("MIT")}...),
Licenses: pkg.NewLicenseSet([]pkg.License{pkg.NewLicenseWithContext(ctx, "MIT")}...),
Language: pkg.R,
Type: pkg.Rpkg,
PURL: "pkg:cran/stringr@1.5.0.9000",

View File

@ -1,6 +1,7 @@
package r
import (
"context"
"strings"
"github.com/anchore/packageurl-go"
@ -8,13 +9,13 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func newPackage(pd parseData, locations ...file.Location) pkg.Package {
func newPackage(ctx context.Context, pd parseData, locations ...file.Location) pkg.Package {
locationSet := file.NewLocationSet()
for _, loc := range locations {
locationSet.Add(loc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
}
licenses := parseLicenseData(pd.License)
licenses := parseLicenseData(ctx, pd.License)
result := pkg.Package{
Name: pd.Package,
@ -44,7 +45,7 @@ func packageURL(m parseData) string {
// 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 ...file.Location) []pkg.License {
func parseLicenseData(ctx context.Context, license string, locations ...file.Location) []pkg.License {
licenses := make([]pkg.License, 0)
// check if multiple licenses are separated by |
@ -56,7 +57,7 @@ func parseLicenseData(license string, locations ...file.Location) []pkg.License
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...))
licenses = append(licenses, pkg.NewLicenseFromLocationsWithContext(ctx, l, locations...))
continue
}
}
@ -65,7 +66,7 @@ func parseLicenseData(license string, locations ...file.Location) []pkg.License
if strings.Contains(l, "+") && strings.Contains(l, "LICENSE") {
splitField := strings.Split(l, " ")
if len(splitField) > 0 {
licenses = append(licenses, pkg.NewLicenseFromLocations(splitField[0], locations...))
licenses = append(licenses, pkg.NewLicenseFromLocationsWithContext(ctx, splitField[0], locations...))
continue
}
}
@ -77,7 +78,7 @@ func parseLicenseData(license string, locations ...file.Location) []pkg.License
// 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...))
licenses = append(licenses, pkg.NewLicenseFromLocationsWithContext(ctx, l, locations...))
continue
}
return licenses

View File

@ -1,12 +1,14 @@
package r
import (
"context"
"testing"
"github.com/anchore/syft/syft/pkg"
)
func Test_NewPackageLicenses(t *testing.T) {
ctx := context.TODO()
testCases := []struct {
name string
pd parseData
@ -20,7 +22,7 @@ func Test_NewPackageLicenses(t *testing.T) {
License: "MIT",
},
[]pkg.License{
pkg.NewLicense("MIT"),
pkg.NewLicenseWithContext(ctx, "MIT"),
},
},
{
@ -31,7 +33,7 @@ func Test_NewPackageLicenses(t *testing.T) {
License: "LGPL (== 2.0)",
},
[]pkg.License{
pkg.NewLicense("LGPL2.0"),
pkg.NewLicenseWithContext(ctx, "LGPL2.0"),
},
},
{
@ -42,7 +44,7 @@ func Test_NewPackageLicenses(t *testing.T) {
License: "LGPL (>= 2.0, < 3)",
},
[]pkg.License{
pkg.NewLicense("LGPL2.0+"),
pkg.NewLicenseWithContext(ctx, "LGPL2.0+"),
},
},
{
@ -53,7 +55,7 @@ func Test_NewPackageLicenses(t *testing.T) {
License: "GPL-2 + file LICENSE",
},
[]pkg.License{
pkg.NewLicense("GPL-2"),
pkg.NewLicenseWithContext(ctx, "GPL-2"),
},
},
{
@ -64,7 +66,7 @@ func Test_NewPackageLicenses(t *testing.T) {
License: "Mozilla Public License",
},
[]pkg.License{
pkg.NewLicense("Mozilla Public License"),
pkg.NewLicenseWithContext(ctx, "Mozilla Public License"),
},
},
{
@ -75,15 +77,15 @@ func Test_NewPackageLicenses(t *testing.T) {
License: "GPL-2 | file LICENSE | LGPL (>= 2.0)",
},
[]pkg.License{
pkg.NewLicense("GPL-2"),
pkg.NewLicense("LGPL2.0+"),
pkg.NewLicenseWithContext(ctx, "GPL-2"),
pkg.NewLicenseWithContext(ctx, "LGPL2.0+"),
},
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
got := parseLicenseData(tt.pd.License)
got := parseLicenseData(ctx, tt.pd.License)
if len(got) != len(tt.want) {
t.Errorf("unexpected number of licenses: got=%d, want=%d", len(got), len(tt.want))
}

View File

@ -29,10 +29,10 @@ License: Part of R 4.3.0
License: Unlimited
*/
func parseDescriptionFile(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parseDescriptionFile(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
values := extractFieldsFromDescriptionFile(reader)
m := parseDataFromDescriptionMap(values)
p := newPackage(m, []file.Location{reader.Location}...)
p := newPackage(ctx, m, []file.Location{reader.Location}...)
if p.Name == "" || p.Version == "" {
return nil, nil, nil
}

View File

@ -1,6 +1,7 @@
package redhat
import (
"context"
"errors"
"testing"
@ -15,7 +16,7 @@ import (
)
func Test_DBCataloger(t *testing.T) {
ctx := context.TODO()
dbLocation := file.NewLocation("/var/lib/rpm/rpmdb.sqlite")
locations := file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
@ -24,7 +25,7 @@ func Test_DBCataloger(t *testing.T) {
Version: "11-13.el9",
Type: pkg.RpmPkg,
Locations: locations,
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocations("Public Domain", dbLocation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocationsWithContext(ctx, "Public Domain", dbLocation)),
FoundBy: "rpm-db-cataloger",
PURL: "pkg:rpm/basesystem@11-13.el9?arch=noarch&upstream=basesystem-11-13.el9.src.rpm",
Metadata: pkg.RpmDBEntry{
@ -54,7 +55,7 @@ func Test_DBCataloger(t *testing.T) {
Version: "5.1.8-6.el9_1",
Type: pkg.RpmPkg,
Locations: locations,
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocations("GPLv3+", dbLocation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocationsWithContext(ctx, "GPLv3+", dbLocation)),
FoundBy: "rpm-db-cataloger",
PURL: "pkg:rpm/bash@5.1.8-6.el9_1?arch=x86_64&upstream=bash-5.1.8-6.el9_1.src.rpm",
Metadata: pkg.RpmDBEntry{
@ -106,7 +107,7 @@ func Test_DBCataloger(t *testing.T) {
Version: "3.16-2.el9",
Type: pkg.RpmPkg,
Locations: locations,
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocations("Public Domain", dbLocation)),
Licenses: pkg.NewLicenseSet(pkg.NewLicenseFromLocationsWithContext(ctx, "Public Domain", dbLocation)),
FoundBy: "rpm-db-cataloger",
PURL: "pkg:rpm/filesystem@3.16-2.el9?arch=x86_64&upstream=filesystem-3.16-2.el9.src.rpm",
Metadata: pkg.RpmDBEntry{
@ -246,7 +247,6 @@ func Test_RPMFileCataloger_Globs(t *testing.T) {
}
func Test_denySelfReferences(t *testing.T) {
a := pkg.Package{
Name: "a",
}

View File

@ -1,6 +1,7 @@
package redhat
import (
"context"
"fmt"
"strconv"
"strings"
@ -11,11 +12,11 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func newDBPackage(dbOrRpmLocation file.Location, m pkg.RpmDBEntry, distro *linux.Release, licenses []string) pkg.Package {
func newDBPackage(ctx context.Context, dbOrRpmLocation file.Location, m pkg.RpmDBEntry, distro *linux.Release, licenses []string) pkg.Package {
p := pkg.Package{
Name: m.Name,
Version: toELVersion(m.Epoch, m.Version, m.Release),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbOrRpmLocation, licenses...)...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocationWithContext(ctx, dbOrRpmLocation, licenses...)...),
PURL: packageURL(m.Name, m.Arch, m.Epoch, m.SourceRpm, m.Version, m.Release, distro),
Locations: file.NewLocationSet(dbOrRpmLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Type: pkg.RpmPkg,
@ -26,11 +27,11 @@ func newDBPackage(dbOrRpmLocation file.Location, m pkg.RpmDBEntry, distro *linux
return p
}
func newArchivePackage(archiveLocation file.Location, m pkg.RpmArchive, licenses []string) pkg.Package {
func newArchivePackage(ctx context.Context, archiveLocation file.Location, m pkg.RpmArchive, licenses []string) pkg.Package {
p := pkg.Package{
Name: m.Name,
Version: toELVersion(m.Epoch, m.Version, m.Release),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(archiveLocation, licenses...)...),
Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocationWithContext(ctx, archiveLocation, licenses...)...),
PURL: packageURL(m.Name, m.Arch, m.Epoch, m.SourceRpm, m.Version, m.Release, nil),
Locations: file.NewLocationSet(archiveLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Type: pkg.RpmPkg,

View File

@ -16,7 +16,7 @@ import (
)
// parseRpmArchive parses a single RPM
func parseRpmArchive(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
func parseRpmArchive(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
rpm, err := rpmutils.ReadRpm(reader)
if err != nil {
return nil, nil, fmt.Errorf("RPM file found but unable to read: %s (%w)", reader.RealPath, err)
@ -56,7 +56,7 @@ func parseRpmArchive(_ context.Context, _ file.Resolver, _ *generic.Environment,
Files: mapFiles(files, digestAlgorithm),
}
return []pkg.Package{newArchivePackage(reader.Location, metadata, licenses)}, nil, nil
return []pkg.Package{newArchivePackage(ctx, reader.Location, metadata, licenses)}, nil, nil
}
func getDigestAlgorithm(location file.Location, header *rpmutils.RpmHeader) string {

View File

@ -1,6 +1,7 @@
package redhat
import (
"context"
"testing"
"github.com/anchore/syft/syft/file"
@ -9,6 +10,7 @@ import (
)
func TestParseRpmFiles(t *testing.T) {
ctx := context.TODO()
abcRpmLocation := file.NewLocation("abc-1.01-9.hg20160905.el7.x86_64.rpm")
zorkRpmLocation := file.NewLocation("zork-1.0.3-1.el7.x86_64.rpm")
tests := []struct {
@ -26,7 +28,7 @@ func TestParseRpmFiles(t *testing.T) {
FoundBy: "rpm-archive-cataloger",
Type: pkg.RpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("MIT", abcRpmLocation),
pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", abcRpmLocation),
),
Metadata: pkg.RpmArchive{
Name: "abc",
@ -54,7 +56,7 @@ func TestParseRpmFiles(t *testing.T) {
FoundBy: "rpm-archive-cataloger",
Type: pkg.RpmPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("Public Domain", zorkRpmLocation),
pkg.NewLicenseFromLocationsWithContext(ctx, "Public Domain", zorkRpmLocation),
),
Metadata: pkg.RpmArchive{
Name: "zork",

Some files were not shown because too many files have changed in this diff Show More