mirror of
https://github.com/anchore/syft.git
synced 2026-10-17 00:27:21 +02:00
Compare commits
+1
-1
@@ -111,7 +111,7 @@ tools:
|
||||
# used for triggering a release
|
||||
- name: gh
|
||||
version:
|
||||
want: v2.59.0
|
||||
want: v2.60.1
|
||||
method: github-release
|
||||
with:
|
||||
repo: cli/cli
|
||||
|
||||
@@ -17,13 +17,13 @@ jobs:
|
||||
# the job by event.
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore base benchmark result
|
||||
uses: actions/cache@3624ceb22c1c5a301c8db4169662070a689d9ea8 #v4.1.1
|
||||
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a #v4.1.2
|
||||
with:
|
||||
path: test/results/benchmark-main.txt
|
||||
# use base sha for PR or new commit hash for main push in benchmark result key
|
||||
|
||||
@@ -36,16 +36,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 #v5.0.2
|
||||
uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed #v5.1.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b #v3.26.13
|
||||
uses: github/codeql-action/init@662472033e021d55d94146f66f6058822b0b39fd #v3.27.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@f779452ac5af1c261dce0346a8f964149f49322b #v3.26.13
|
||||
uses: github/codeql-action/autobuild@662472033e021d55d94146f66f6058822b0b39fd #v3.27.0
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -70,4 +70,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b #v3.26.13
|
||||
uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd #v3.27.0
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- run: python .github/scripts/labeler.py
|
||||
env:
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Update version file
|
||||
run: make ci-release-version-file
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
environment: release
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Check if running on main
|
||||
if: github.ref != 'refs/heads/main'
|
||||
@@ -105,7 +105,7 @@ jobs:
|
||||
# required for goreleaser signs section with cosign
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
# for updating brew formula in anchore/homebrew-syft
|
||||
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
|
||||
|
||||
- uses: anchore/sbom-action@8d0a6505bf28ced3e85154d13dc6af83299e13f1 #v0.17.4
|
||||
- uses: anchore/sbom-action@251a468eed47e5082b105c3ba6ee500c0e65a764 #v0.17.6
|
||||
continue-on-error: true
|
||||
with:
|
||||
artifact-name: sbom.spdx.json
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
permissions:
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
@@ -17,9 +17,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'anchore/syft' # only run for main repo
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 #v5.0.2
|
||||
- uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed #v5.1.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
name: "Static analysis"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
name: "Unit tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
name: "Integration tests"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
name: "Build snapshot artifacts"
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
|
||||
# see https://github.com/actions/upload-artifact/issues/199 for more info
|
||||
- name: Upload snapshot artifacts
|
||||
uses: actions/cache/save@3624ceb22c1c5a301c8db4169662070a689d9ea8 #v4.1.1
|
||||
uses: actions/cache/save@6849a6489940f00c2f30c0fb92c6274307ccb58a #v4.1.2
|
||||
with:
|
||||
# we need to preserve the snapshot data itself as well as the task data that confirms if the
|
||||
# snapshot build is stale or not. Otherwise the downstream jobs will attempt to rebuild the snapshot
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -104,7 +104,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@3624ceb22c1c5a301c8db4169662070a689d9ea8 #v4.1.1
|
||||
uses: actions/cache/restore@6849a6489940f00c2f30c0fb92c6274307ccb58a #v4.1.2
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -141,7 +141,7 @@ jobs:
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3.7.0
|
||||
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -152,7 +152,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@3624ceb22c1c5a301c8db4169662070a689d9ea8 #v4.1.1
|
||||
uses: actions/cache/restore@6849a6489940f00c2f30c0fb92c6274307ccb58a #v4.1.2
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
needs: [Build-Snapshot-Artifacts]
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Bootstrap environment
|
||||
uses: ./.github/actions/bootstrap
|
||||
@@ -187,7 +187,7 @@ jobs:
|
||||
|
||||
- name: Download snapshot build
|
||||
id: snapshot-cache
|
||||
uses: actions/cache/restore@3624ceb22c1c5a301c8db4169662070a689d9ea8 #v4.1.1
|
||||
uses: actions/cache/restore@6849a6489940f00c2f30c0fb92c6274307ccb58a #v4.1.2
|
||||
with:
|
||||
path: |
|
||||
snapshot
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
- Acceptance-Mac
|
||||
- Cli-Linux
|
||||
steps:
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
|
||||
- name: Delete snapshot cache
|
||||
run: gh cache delete "snapshot-build-${{ github.run_id }}"
|
||||
|
||||
@@ -20,18 +20,18 @@ func AppClioSetupConfig(id clio.Identification, out io.Writer) *clio.SetupConfig
|
||||
WithConfigInRootHelp(). // --help on the root command renders the full application config in the help text
|
||||
WithUIConstructor(
|
||||
// select a UI based on the logging configuration and state of stdin (if stdin is a tty)
|
||||
func(cfg clio.Config) ([]clio.UI, error) {
|
||||
func(cfg clio.Config) (*clio.UICollection, error) {
|
||||
noUI := ui.None(out, cfg.Log.Quiet)
|
||||
if !cfg.Log.AllowUI(os.Stdin) || cfg.Log.Quiet {
|
||||
return []clio.UI{noUI}, nil
|
||||
return clio.NewUICollection(noUI), nil
|
||||
}
|
||||
|
||||
return []clio.UI{
|
||||
return clio.NewUICollection(
|
||||
ui.New(out, cfg.Log.Quiet,
|
||||
ui2.New(ui2.DefaultHandlerConfig()),
|
||||
),
|
||||
noUI,
|
||||
}, nil
|
||||
), nil
|
||||
},
|
||||
).
|
||||
WithInitializers(
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
const (
|
||||
convertExample = ` {{.appName}} {{.command}} img.syft.json -o spdx-json convert a syft SBOM to spdx-json, output goes to stdout
|
||||
{{.appName}} {{.command}} img.syft.json -o cyclonedx-json=img.cdx.json convert a syft SBOM to CycloneDX, output is written to the file "img.cdx.json""
|
||||
{{.appName}} {{.command}} img.syft.json -o cyclonedx-json=img.cdx.json convert a syft SBOM to CycloneDX, output is written to the file "img.cdx.json"
|
||||
{{.appName}} {{.command}} - -o spdx-json convert an SBOM from STDIN to spdx-json
|
||||
`
|
||||
)
|
||||
@@ -39,7 +39,7 @@ func Convert(app clio.Application) *cobra.Command {
|
||||
return app.SetupCommand(&cobra.Command{
|
||||
Use: "convert [SOURCE-SBOM] -o [FORMAT]",
|
||||
Short: "Convert between SBOM formats",
|
||||
Long: "[Experimental] Convert SBOM files to, and from, SPDX, CycloneDX and Syft's format. For more info about data loss between formats see https://github.com/anchore/syft#format-conversion-experimental",
|
||||
Long: "[Experimental] Convert SBOM files to, and from, SPDX, CycloneDX and Syft's format. For more info about data loss between formats see https://github.com/anchore/syft/wiki/format-conversion",
|
||||
Example: internal.Tprintf(convertExample, map[string]interface{}{
|
||||
"appName": id.Name,
|
||||
"command": "convert",
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/fangs"
|
||||
"github.com/anchore/go-collections"
|
||||
"github.com/anchore/stereoscope"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
@@ -109,7 +110,7 @@ func (o *scanOptions) PostLoad() error {
|
||||
}
|
||||
|
||||
func (o *scanOptions) validateLegacyOptionsNotUsed() error {
|
||||
if o.Config.ConfigFile == "" {
|
||||
if len(fangs.Flatten(o.Config.ConfigFile)) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -121,32 +122,33 @@ func (o *scanOptions) validateLegacyOptionsNotUsed() error {
|
||||
File any `yaml:"file" json:"file" mapstructure:"file"`
|
||||
}
|
||||
|
||||
by, err := os.ReadFile(o.Config.ConfigFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read config file during validations %q: %w", o.Config.ConfigFile, err)
|
||||
}
|
||||
for _, f := range fangs.Flatten(o.Config.ConfigFile) {
|
||||
by, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read config file during validations %q: %w", f, err)
|
||||
}
|
||||
|
||||
var legacy legacyConfig
|
||||
if err := yaml.Unmarshal(by, &legacy); err != nil {
|
||||
return fmt.Errorf("unable to parse config file during validations %q: %w", o.Config.ConfigFile, err)
|
||||
}
|
||||
var legacy legacyConfig
|
||||
if err := yaml.Unmarshal(by, &legacy); err != nil {
|
||||
return fmt.Errorf("unable to parse config file during validations %q: %w", f, err)
|
||||
}
|
||||
|
||||
if legacy.DefaultImagePullSource != nil {
|
||||
return fmt.Errorf("the config file option 'default-image-pull-source' has been removed, please use 'source.image.default-pull-source' instead")
|
||||
}
|
||||
if legacy.DefaultImagePullSource != nil {
|
||||
return fmt.Errorf("the config file option 'default-image-pull-source' has been removed, please use 'source.image.default-pull-source' instead")
|
||||
}
|
||||
|
||||
if legacy.ExcludeBinaryOverlapByOwnership != nil {
|
||||
return fmt.Errorf("the config file option 'exclude-binary-overlap-by-ownership' has been removed, please use 'package.exclude-binary-overlap-by-ownership' instead")
|
||||
}
|
||||
if legacy.ExcludeBinaryOverlapByOwnership != nil {
|
||||
return fmt.Errorf("the config file option 'exclude-binary-overlap-by-ownership' has been removed, please use 'package.exclude-binary-overlap-by-ownership' instead")
|
||||
}
|
||||
|
||||
if legacy.BasePath != nil {
|
||||
return fmt.Errorf("the config file option 'base-path' has been removed, please use 'source.base-path' instead")
|
||||
}
|
||||
if legacy.BasePath != nil {
|
||||
return fmt.Errorf("the config file option 'base-path' has been removed, please use 'source.base-path' instead")
|
||||
}
|
||||
|
||||
if legacy.File != nil && reflect.TypeOf(legacy.File).Kind() == reflect.String {
|
||||
return fmt.Errorf("the config file option 'file' has been removed, please use 'outputs' instead")
|
||||
if legacy.File != nil && reflect.TypeOf(legacy.File).Kind() == reflect.String {
|
||||
return fmt.Errorf("the config file option 'file' has been removed, please use 'outputs' instead")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -166,6 +166,7 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
||||
WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags),
|
||||
),
|
||||
JavaScript: javascript.DefaultCatalogerConfig().
|
||||
WithIncludeDevDependencies(*multiLevelOption(false, cfg.JavaScript.IncludeDevDependencies)).
|
||||
WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.JavaScript, task.Node, task.NPM), cfg.JavaScript.SearchRemoteLicenses)).
|
||||
WithNpmBaseURL(cfg.JavaScript.NpmBaseURL),
|
||||
LinuxKernel: kernel.LinuxKernelCatalogerConfig{
|
||||
@@ -179,7 +180,8 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
|
||||
WithMavenLocalRepositoryDir(cfg.Java.MavenLocalRepositoryDir).
|
||||
WithUseNetwork(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Java, task.Maven), cfg.Java.UseNetwork)).
|
||||
WithMavenBaseURL(cfg.Java.MavenURL).
|
||||
WithArchiveTraversal(archiveSearch, cfg.Java.MaxParentRecursiveDepth),
|
||||
WithArchiveTraversal(archiveSearch, cfg.Java.MaxParentRecursiveDepth).
|
||||
WithResolveTransitiveDependencies(cfg.Java.ResolveTransitiveDependencies),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@ package options
|
||||
|
||||
import "github.com/anchore/fangs"
|
||||
|
||||
// Config holds a reference to the specific config file that was used to load application configuration
|
||||
// Config holds a reference to the specific config file(s) that were used to load application configuration
|
||||
type Config struct {
|
||||
ConfigFile string `yaml:"config" json:"config" mapstructure:"config"`
|
||||
}
|
||||
|
||||
func (cfg *Config) DescribeFields(descriptions fangs.FieldDescriptionSet) {
|
||||
descriptions.Add(&cfg.ConfigFile, "the configuration file that was used to load application configuration")
|
||||
descriptions.Add(&cfg.ConfigFile, "the configuration file(s) used to load application configuration")
|
||||
}
|
||||
|
||||
@@ -6,22 +6,24 @@ import (
|
||||
)
|
||||
|
||||
type javaConfig struct {
|
||||
UseNetwork *bool `yaml:"use-network" json:"use-network" mapstructure:"use-network"`
|
||||
UseMavenLocalRepository *bool `yaml:"use-maven-local-repository" json:"use-maven-local-repository" mapstructure:"use-maven-local-repository"`
|
||||
MavenLocalRepositoryDir string `yaml:"maven-local-repository-dir" json:"maven-local-repository-dir" mapstructure:"maven-local-repository-dir"`
|
||||
MavenURL string `yaml:"maven-url" json:"maven-url" mapstructure:"maven-url"`
|
||||
MaxParentRecursiveDepth int `yaml:"max-parent-recursive-depth" json:"max-parent-recursive-depth" mapstructure:"max-parent-recursive-depth"`
|
||||
UseNetwork *bool `yaml:"use-network" json:"use-network" mapstructure:"use-network"`
|
||||
UseMavenLocalRepository *bool `yaml:"use-maven-local-repository" json:"use-maven-local-repository" mapstructure:"use-maven-local-repository"`
|
||||
MavenLocalRepositoryDir string `yaml:"maven-local-repository-dir" json:"maven-local-repository-dir" mapstructure:"maven-local-repository-dir"`
|
||||
MavenURL string `yaml:"maven-url" json:"maven-url" mapstructure:"maven-url"`
|
||||
MaxParentRecursiveDepth int `yaml:"max-parent-recursive-depth" json:"max-parent-recursive-depth" mapstructure:"max-parent-recursive-depth"`
|
||||
ResolveTransitiveDependencies bool `yaml:"resolve-transitive-dependencies" json:"resolve-transitive-dependencies" mapstructure:"resolve-transitive-dependencies"`
|
||||
}
|
||||
|
||||
func defaultJavaConfig() javaConfig {
|
||||
def := java.DefaultArchiveCatalogerConfig()
|
||||
|
||||
return javaConfig{
|
||||
UseNetwork: nil, // this defaults to false, which is the API default
|
||||
MaxParentRecursiveDepth: def.MaxParentRecursiveDepth,
|
||||
UseMavenLocalRepository: nil, // this defaults to false, which is the API default
|
||||
MavenLocalRepositoryDir: def.MavenLocalRepositoryDir,
|
||||
MavenURL: def.MavenBaseURL,
|
||||
UseNetwork: nil, // this defaults to false, which is the API default
|
||||
MaxParentRecursiveDepth: def.MaxParentRecursiveDepth,
|
||||
UseMavenLocalRepository: nil, // this defaults to false, which is the API default
|
||||
MavenLocalRepositoryDir: def.MavenLocalRepositoryDir,
|
||||
MavenURL: def.MavenBaseURL,
|
||||
ResolveTransitiveDependencies: def.ResolveTransitiveDependencies,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,4 +45,5 @@ TIP: If you want to download all required pom files to the local repository with
|
||||
build, run 'mvn help:effective-pom' before performing the scan with syft.`)
|
||||
descriptions.Add(&o.MavenLocalRepositoryDir, `override the default location of the local Maven repository.
|
||||
the default is the subdirectory '.m2/repository' in your home directory`)
|
||||
descriptions.Add(&o.ResolveTransitiveDependencies, `resolve transient dependencies such as those defined in a dependency's POM on Maven central`)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ package options
|
||||
import "github.com/anchore/clio"
|
||||
|
||||
type javaScriptConfig struct {
|
||||
SearchRemoteLicenses *bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
NpmBaseURL string `json:"npm-base-url" yaml:"npm-base-url" mapstructure:"npm-base-url"`
|
||||
SearchRemoteLicenses *bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
NpmBaseURL string `json:"npm-base-url" yaml:"npm-base-url" mapstructure:"npm-base-url"`
|
||||
IncludeDevDependencies *bool `json:"include-dev-dependencies" yaml:"include-dev-dependencies" mapstructure:"include-dev-dependencies"`
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
@@ -14,4 +15,5 @@ var _ interface {
|
||||
func (o *javaScriptConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&o.SearchRemoteLicenses, `enables Syft to use the network to fill in more detailed license information`)
|
||||
descriptions.Add(&o.NpmBaseURL, `base NPM url to use`)
|
||||
descriptions.Add(&o.IncludeDevDependencies, `include development-scoped dependencies`)
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ var dirOnlyTestCases = []testCase{
|
||||
name: "find java packages including pom.xml", // directory scans can include packages that have yet to be installed
|
||||
pkgType: pkg.JavaPkg,
|
||||
pkgLanguage: pkg.Java,
|
||||
duplicates: 1, // joda-time is included in both pom.xml AND the .jar collection
|
||||
duplicates: 2, // joda-time and example-java-app-maven are included in both pom.xml AND the .jar collection
|
||||
pkgInfo: map[string]string{
|
||||
"example-java-app-maven": "0.1.0",
|
||||
"joda-time": "2.9.2",
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestNpmPackageLockDirectory(t *testing.T) {
|
||||
}
|
||||
|
||||
// ensure that integration test commonTestCases stay in sync with the available catalogers
|
||||
const expectedPackageCount = 6
|
||||
const expectedPackageCount = 2
|
||||
if foundPackages.Size() != expectedPackageCount {
|
||||
t.Errorf("found the wrong set of npm package-lock.json packages (expected: %d, actual: %d)", expectedPackageCount, foundPackages.Size())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
module github.com/anchore/syft
|
||||
|
||||
go 1.22.5
|
||||
|
||||
toolchain go1.22.8
|
||||
go 1.22.8
|
||||
|
||||
require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.9.1
|
||||
@@ -11,23 +9,23 @@ require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9
|
||||
github.com/anchore/clio v0.0.0-20240522144804-d81e109008aa
|
||||
github.com/anchore/fangs v0.0.0-20240903175602-e716ef12c23d
|
||||
github.com/anchore/clio v0.0.0-20241015191535-f538a9016e10
|
||||
github.com/anchore/fangs v0.0.0-20241014201141-b6e4b3469f10
|
||||
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f
|
||||
github.com/anchore/stereoscope v0.0.5-0.20241018131503-a38c93517fc7
|
||||
github.com/anchore/stereoscope v0.0.6-0.20241101185849-cbd43fb4e5d3
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||
// we are hinting brotli to latest due to warning when installing archiver v3:
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1
|
||||
github.com/charmbracelet/bubbles v0.20.0
|
||||
github.com/charmbracelet/bubbletea v1.1.1
|
||||
github.com/charmbracelet/lipgloss v0.13.0
|
||||
github.com/charmbracelet/bubbletea v1.1.2
|
||||
github.com/charmbracelet/lipgloss v1.0.0
|
||||
github.com/dave/jennifer v1.7.1
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/distribution/reference v0.6.0
|
||||
@@ -37,7 +35,7 @@ require (
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
github.com/github/go-spdx/v2 v2.3.2
|
||||
github.com/gkampitakis/go-snaps v0.5.7
|
||||
github.com/go-git/go-billy/v5 v5.5.0
|
||||
github.com/go-git/go-billy/v5 v5.6.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
github.com/go-test/deep v1.1.1
|
||||
github.com/google/go-cmp v0.6.0
|
||||
@@ -91,9 +89,9 @@ require google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirec
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.4.0
|
||||
github.com/OneOfOne/xxhash v1.2.8
|
||||
github.com/adrg/xdg v0.5.1
|
||||
github.com/adrg/xdg v0.5.3
|
||||
github.com/magiconair/properties v1.8.7
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -113,7 +111,7 @@ require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/becheran/wildmatch-go v1.0.0 // indirect
|
||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.2.3 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.4.2 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.8 // indirect
|
||||
github.com/containerd/cgroups v1.1.0 // indirect
|
||||
@@ -127,7 +125,7 @@ require (
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/containerd/ttrpc v1.2.5 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.1.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/docker/cli v27.3.1+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
@@ -212,7 +210,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.19.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/sylabs/sif/v2 v2.19.1 // indirect
|
||||
github.com/sylabs/sif/v2 v2.19.2 // indirect
|
||||
github.com/sylabs/squashfs v1.0.0 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/tidwall/gjson v1.17.0 // indirect
|
||||
|
||||
@@ -87,8 +87,8 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE=
|
||||
github.com/acobaugh/osrelease v0.1.0/go.mod h1:4bFEs0MtgHNHBrmHCt67gNisnabCRAlzdVasCEGHTWY=
|
||||
github.com/adrg/xdg v0.5.1 h1:Im8iDbEFARltY09yOJlSGu4Asjk2vF85+3Dyru8uJ0U=
|
||||
github.com/adrg/xdg v0.5.1/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
@@ -97,10 +97,10 @@ github.com/anchore/archiver/v3 v3.5.2 h1:Bjemm2NzuRhmHy3m0lRe5tNoClB9A4zYyDV58Pa
|
||||
github.com/anchore/archiver/v3 v3.5.2/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
|
||||
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9 h1:p0ZIe0htYOX284Y4axJaGBvXHU0VCCzLN5Wf5XbKStU=
|
||||
github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9/go.mod h1:3ZsFB9tzW3vl4gEiUeuSOMDnwroWxIxJelOOHUp8dSw=
|
||||
github.com/anchore/clio v0.0.0-20240522144804-d81e109008aa h1:pwlAn4O9SBUnlgfa69YcqIynbUyobLVFYu8HxSoCffA=
|
||||
github.com/anchore/clio v0.0.0-20240522144804-d81e109008aa/go.mod h1:nD3H5uIvjxlfmakOBgtyFQbk5Zjp3l538kxfpHPslzI=
|
||||
github.com/anchore/fangs v0.0.0-20240903175602-e716ef12c23d h1:ZD4wdCBgJJzJybjTUIEiiupLF7B9H3WLuBTjspBO2Mc=
|
||||
github.com/anchore/fangs v0.0.0-20240903175602-e716ef12c23d/go.mod h1:Xh4ObY3fmoMzOEVXwDtS1uK44JC7+nRD0n29/1KYFYg=
|
||||
github.com/anchore/clio v0.0.0-20241015191535-f538a9016e10 h1:3xmanFdoQEH0REvPA+gLm3Km0/981F4z2a/7ADTlv8k=
|
||||
github.com/anchore/clio v0.0.0-20241015191535-f538a9016e10/go.mod h1:h6Ly2hlKjQoPtI3rA8oB5afSmB/XimhcY55xbuW4Dwo=
|
||||
github.com/anchore/fangs v0.0.0-20241014201141-b6e4b3469f10 h1:w+HibE+e/heP6ysADh7sWxg5LhYdVqrpB1A4Hmgjyx8=
|
||||
github.com/anchore/fangs v0.0.0-20241014201141-b6e4b3469f10/go.mod h1:s0L1//Sxn6Rq0Dcxx+dmT/RRmD9HhsaJjJkPUJHLJLM=
|
||||
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537 h1:GjNGuwK5jWjJMyVppBjYS54eOiiSNv4Ba869k4wh72Q=
|
||||
github.com/anchore/go-collections v0.0.0-20240216171411-9321230ce537/go.mod h1:1aiktV46ATCkuVg0O573ZrH56BUawTECPETbZyBcqT8=
|
||||
github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw=
|
||||
@@ -115,8 +115,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f h1:dAQPIrQ3a5PBqZeZ+B9NGZsGmodk4NO9OjDIsQmQyQM=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20241018175412-5c22e6360c4f/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI=
|
||||
github.com/anchore/stereoscope v0.0.5-0.20241018131503-a38c93517fc7 h1:CBKDfuMWKRt12EnKtgT/an9FQSQI83mOU1KT57z81IU=
|
||||
github.com/anchore/stereoscope v0.0.5-0.20241018131503-a38c93517fc7/go.mod h1:wH79ZauZcd3erKPocg9+qhDC6Q/z125sD+Isi4d61a4=
|
||||
github.com/anchore/stereoscope v0.0.6-0.20241101185849-cbd43fb4e5d3 h1:T1LMkKwzSg1s8+xylq51xroPoo83Nt3zQPvdRRDB9Bw=
|
||||
github.com/anchore/stereoscope v0.0.6-0.20241101185849-cbd43fb4e5d3/go.mod h1:jgLIzIwEkfMhjhKmuzaSEeU2/vFCqlO3XTrSeSLfOaM=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
@@ -159,14 +159,14 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
|
||||
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
|
||||
github.com/charmbracelet/bubbletea v1.1.1 h1:KJ2/DnmpfqFtDNVTvYZ6zpPFL9iRCRr0qqKOCvppbPY=
|
||||
github.com/charmbracelet/bubbletea v1.1.1/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
|
||||
github.com/charmbracelet/bubbletea v1.1.2 h1:naQXF2laRxyLyil/i7fxdpiz1/k06IKquhm4vBfHsIc=
|
||||
github.com/charmbracelet/bubbletea v1.1.2/go.mod h1:9HIU/hBV24qKjlehyj8z1r/tR9TYTQEag+cWZnuXo8E=
|
||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
||||
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
|
||||
github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
|
||||
github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
|
||||
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
|
||||
github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk=
|
||||
github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
|
||||
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
@@ -217,8 +217,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
|
||||
github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
|
||||
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -305,8 +305,8 @@ github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
|
||||
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||
@@ -613,8 +613,8 @@ github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7
|
||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
@@ -762,8 +762,8 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/sylabs/sif/v2 v2.19.1 h1:1eeMmFc8elqJe60ZiWwXgL3gMheb0IP4GmNZ4q0IEA0=
|
||||
github.com/sylabs/sif/v2 v2.19.1/go.mod h1:U1SUhvl8X1JIxAylC0DYz1fa/Xba6EMZD1dGPGBH83E=
|
||||
github.com/sylabs/sif/v2 v2.19.2 h1:KKcUKnbnT69rN1WWHRYoAVKFpqnXpNJ36kmQLpp86Uc=
|
||||
github.com/sylabs/sif/v2 v2.19.2/go.mod h1:nhX6D/CJntHDWspNLXLe+yct0cd5lm8HJ7VIW6hgKrw=
|
||||
github.com/sylabs/squashfs v1.0.0 h1:xAyMS21ogglkuR5HaY55PCfqY3H32ma9GkasTYo28Zg=
|
||||
github.com/sylabs/squashfs v1.0.0/go.mod h1:rhWzvgefq1X+R+LZdts10hfMsTg3g74OfGunW8tvg/4=
|
||||
github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo=
|
||||
@@ -882,8 +882,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 h1:mchzmB1XO2pMaKFRqk/+MV3mgGG96aqaPXaMifQU47w=
|
||||
golang.org/x/exp v0.0.0-20231108232855-2478ac86f678/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -1156,8 +1156,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -57,14 +58,14 @@ func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsy
|
||||
}
|
||||
|
||||
err := runTaskSafely(ctx, tsk, resolver, s)
|
||||
unknowns, err := unknown.ExtractCoordinateErrors(err)
|
||||
unknowns, remainingErrors := unknown.ExtractCoordinateErrors(err)
|
||||
if len(unknowns) > 0 {
|
||||
appendUnknowns(s, tsk.Name(), unknowns)
|
||||
}
|
||||
if err != nil {
|
||||
if remainingErrors != nil {
|
||||
withLock(func() {
|
||||
errs = multierror.Append(errs, fmt.Errorf("failed to run task: %w", err))
|
||||
prog.SetError(err)
|
||||
errs = multierror.Append(errs, fmt.Errorf("failed to run task: %w", remainingErrors))
|
||||
prog.SetError(remainingErrors)
|
||||
})
|
||||
}
|
||||
prog.Increment()
|
||||
@@ -84,7 +85,13 @@ func appendUnknowns(builder sbomsync.Builder, taskName string, unknowns []unknow
|
||||
if sb.Artifacts.Unknowns == nil {
|
||||
sb.Artifacts.Unknowns = map[file.Coordinates][]string{}
|
||||
}
|
||||
sb.Artifacts.Unknowns[u.Coordinates] = append(sb.Artifacts.Unknowns[u.Coordinates], formatUnknown(u.Reason.Error(), taskName))
|
||||
unknownText := formatUnknown(u.Reason.Error(), taskName)
|
||||
existing := sb.Artifacts.Unknowns[u.Coordinates]
|
||||
// don't include duplicate unknowns
|
||||
if slices.Contains(existing, unknownText) {
|
||||
continue
|
||||
}
|
||||
sb.Artifacts.Unknowns[u.Coordinates] = append(existing, unknownText)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
|
||||
newSimplePackageTaskFactory(php.NewComposerInstalledCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "php", "composer"),
|
||||
newSimplePackageTaskFactory(r.NewPackageCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "r"),
|
||||
newSimplePackageTaskFactory(ruby.NewInstalledGemSpecCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "ruby", "gem", "gemspec"),
|
||||
newSimplePackageTaskFactory(rust.NewAuditBinaryCataloger, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "rust", "binary"),
|
||||
newSimplePackageTaskFactory(rust.NewAuditBinaryCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "rust", "binary"),
|
||||
|
||||
// language-specific package declared catalogers ///////////////////////////////////////////////////////////////////////////
|
||||
newSimplePackageTaskFactory(cpp.NewConanCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "cpp", "conan"),
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package unknown
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
var pathErrorRegex = regexp.MustCompile(`.*path="([^"]+)".*`)
|
||||
|
||||
// ProcessPathErrors replaces "path" errors returned from the file.Resolver into unknowns,
|
||||
// and warn logs non-unknown errors, returning only the unknown errors
|
||||
func ProcessPathErrors(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
errText := err.Error()
|
||||
if pathErrorRegex.MatchString(errText) {
|
||||
foundPath := pathErrorRegex.ReplaceAllString(err.Error(), "$1")
|
||||
if foundPath != "" {
|
||||
return New(file.NewLocation(foundPath), err)
|
||||
}
|
||||
}
|
||||
unknowns, remainingErrors := ExtractCoordinateErrors(err)
|
||||
log.Warn(remainingErrors)
|
||||
|
||||
var out []error
|
||||
for _, u := range unknowns {
|
||||
out = append(out, &u)
|
||||
}
|
||||
return Join(out...)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package unknown
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
func Test_ProcessPathErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
errorText string
|
||||
expected error
|
||||
}{
|
||||
{
|
||||
errorText: `prefix path="/var/lib/thing" suffix`,
|
||||
expected: &CoordinateError{
|
||||
Coordinates: file.Coordinates{
|
||||
RealPath: "/var/lib/thing",
|
||||
},
|
||||
Reason: fmt.Errorf(`prefix path="/var/lib/thing" suffix`),
|
||||
},
|
||||
},
|
||||
{
|
||||
errorText: `prefix path="/var/lib/thing"`,
|
||||
expected: &CoordinateError{
|
||||
Coordinates: file.Coordinates{
|
||||
RealPath: "/var/lib/thing",
|
||||
},
|
||||
Reason: fmt.Errorf(`prefix path="/var/lib/thing"`),
|
||||
},
|
||||
},
|
||||
{
|
||||
errorText: `path="/var/lib/thing" suffix`,
|
||||
expected: &CoordinateError{
|
||||
Coordinates: file.Coordinates{
|
||||
RealPath: "/var/lib/thing",
|
||||
},
|
||||
Reason: fmt.Errorf(`path="/var/lib/thing" suffix`),
|
||||
},
|
||||
},
|
||||
{
|
||||
errorText: "all your base are belong to us",
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.errorText, func(t *testing.T) {
|
||||
got := ProcessPathErrors(fmt.Errorf("%s", test.errorText))
|
||||
require.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,7 @@ func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, er
|
||||
var errs error
|
||||
locations, err := resolver.FilesByGlob(cls.FileGlob)
|
||||
if err != nil {
|
||||
err = unknown.ProcessPathErrors(err) // convert any file.Resolver path errors to unknowns with locations
|
||||
return nil, err
|
||||
}
|
||||
for _, location := range locations {
|
||||
|
||||
@@ -1668,7 +1668,7 @@ func Test_Cataloger_ResilientToErrors(t *testing.T) {
|
||||
|
||||
resolver := &panicyResolver{}
|
||||
_, _, err := c.Catalog(context.Background(), resolver)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, err) // non-coordinate-based FindBy* errors are now logged and not returned
|
||||
assert.True(t, resolver.searchCalled)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,6 @@ func NewDBCataloger() pkg.Cataloger {
|
||||
return generic.NewCataloger("dpkg-db-cataloger").
|
||||
// note: these globs have been intentionally split up in order to improve search performance,
|
||||
// please do NOT combine into: "**/var/lib/dpkg/{status,status.d/*}"
|
||||
WithParserByGlobs(parseDpkgDB, "**/var/lib/dpkg/status", "**/var/lib/dpkg/status.d/*", "**/lib/opkg/info/*.control", "**/lib/opkg/status").
|
||||
WithParserByGlobs(parseDpkgDB, "**/lib/dpkg/status", "**/lib/dpkg/status.d/*", "**/lib/opkg/info/*.control", "**/lib/opkg/status").
|
||||
WithProcessors(dependency.Processor(dbEntryDependencySpecifier))
|
||||
}
|
||||
|
||||
@@ -234,10 +234,12 @@ func TestCataloger_Globs(t *testing.T) {
|
||||
name: "obtain db status files",
|
||||
fixture: "test-fixtures/glob-paths",
|
||||
expected: []string{
|
||||
"usr/lib/dpkg/status",
|
||||
"var/lib/dpkg/status",
|
||||
"usr/lib/dpkg/status.d/pkg-1.0",
|
||||
"var/lib/dpkg/status.d/pkg-1.0",
|
||||
"usr/lib/opkg/status",
|
||||
"usr/lib/opkg/info/pkg-1.0.control",
|
||||
"usr/lib/opkg/status",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
bogus status
|
||||
@@ -0,0 +1 @@
|
||||
bogus package
|
||||
@@ -108,7 +108,7 @@ func newSpyReturningFileResolver(s *spyingIoReadCloser, paths ...string) file.Re
|
||||
}
|
||||
|
||||
func (s *spyingIoReadCloser) Read(p []byte) (n int, err error) {
|
||||
return s.Read(p)
|
||||
return s.rc.Read(p)
|
||||
}
|
||||
|
||||
func (s *spyingIoReadCloser) Close() error {
|
||||
|
||||
@@ -7269,6 +7269,9 @@
|
||||
"add-to-cart-direct-checkout-for-woocommerce": [
|
||||
"cpe:2.3:a:piwebsolution:add-to-cart-direct-checkout-for-woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"add-widget-after-content": [
|
||||
"cpe:2.3:a:arelthiaphillips:add_widget_after_content:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"addon-elements-for-elementor-page-builder": [
|
||||
"cpe:2.3:a:webtechstreet:elementor_addon_elements:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -7541,7 +7544,7 @@
|
||||
"cpe:2.3:a:plugins360:all-in-one_video_gallery:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"all-in-one-wp-migration": [
|
||||
"cpe:2.3:a:servmask:all-in-one_wp_migration:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:servmask:all-in-one_wp_migration:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"all-in-one-wp-security-and-firewall": [
|
||||
"cpe:2.3:a:tips_and_tricks_hq:all_in_one_wordpress_security_and_firewall:*:*:*:*:*:wordpress:*:*",
|
||||
@@ -7646,6 +7649,9 @@
|
||||
"analytics-tracker": [
|
||||
"cpe:2.3:a:analytics_tracker_project:analytics_tracker:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"anchor-episodes-index": [
|
||||
"cpe:2.3:a:jesweb:anchor_episodes_index:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"animate-it": [
|
||||
"cpe:2.3:a:eleopard:animate_it\\!:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -8051,6 +8057,9 @@
|
||||
"bdthemes-element-pack-lite#tags": [
|
||||
"cpe:2.3:a:bdthemes:element_pack:*:*:*:*:lite:wordpress:*:*"
|
||||
],
|
||||
"bdthemes-prime-slider-lite": [
|
||||
"cpe:2.3:a:bdthemes:prime_slider:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"bdvs-password-reset": [
|
||||
"cpe:2.3:a:bedevious:password_reset_with_code_for_wordpress_rest_api:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -8765,6 +8774,9 @@
|
||||
"clerkio": [
|
||||
"cpe:2.3:a:clerk:clerk.io:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"clever-fox": [
|
||||
"cpe:2.3:a:nayrathemes:clever_fox:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"click-datos-lopd": [
|
||||
"cpe:2.3:a:clickdatos:proteccion_de_datos_rgpd:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -9260,6 +9272,9 @@
|
||||
"cpe:2.3:a:codeless:cowidgets_-_elementor:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:codeless:cowidgets_elementor_addons:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"cozy-addons": [
|
||||
"cpe:2.3:a:cozythemes:cozy_blocks:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"cp-appointment-calendar": [
|
||||
"cpe:2.3:a:codepeople:cp_appointment_calendar:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -10226,6 +10241,9 @@
|
||||
"enable-svg-webp-ico-upload": [
|
||||
"cpe:2.3:a:ideastocode:enable_svg\\,_webp_\\\u0026_ico_upload:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"endless-posts-navigation": [
|
||||
"cpe:2.3:a:androidbubbles:endless_posts_navigation:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"english-wp-admin": [
|
||||
"cpe:2.3:a:english_wordpress_admin_project:english_wordpress_admin:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -10280,7 +10298,8 @@
|
||||
"cpe:2.3:a:eshop_project:eshop:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"essential-addons-for-elementor-lite": [
|
||||
"cpe:2.3:a:wpdeveloper:essential_addons_for_elementor:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:wpdeveloper:essential_addons_for_elementor:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:wpdeveloper:essential_addons_for_elementor:*:*:*:*:lite:wordpress:*:*"
|
||||
],
|
||||
"essential-blocks": [
|
||||
"cpe:2.3:a:wpdeveloper:essential_blocks:*:*:*:*:*:wordpress:*:*",
|
||||
@@ -10443,7 +10462,7 @@
|
||||
"cpe:2.3:a:export_users_to_csv_project:export_users_to_csv:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"export-woocommerce": [
|
||||
"cpe:2.3:a:wpfactory:products\\,_order_\\\u0026_customers_export_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:wpfactory:products\\,_order_\\\u0026_customers_export_for_woocommerce:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"export-woocommerce-customer-list": [
|
||||
"cpe:2.3:a:piwebsolution:export_customers_list_csv_for_woocommerce:*:*:*:*:*:*:*:*"
|
||||
@@ -12082,6 +12101,9 @@
|
||||
"kangu": [
|
||||
"cpe:2.3:a:kangu:kangu:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"kata-plus": [
|
||||
"cpe:2.3:a:climaxthemes:kata_plus:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"kau-boys-backend-localization": [
|
||||
"cpe:2.3:a:kau-boys:backend_localization:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -12604,6 +12626,9 @@
|
||||
"martins-link-network": [
|
||||
"cpe:2.3:a:martinstools:free_\\\u0026_easy_link_building:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"mas-addons-for-elementor": [
|
||||
"cpe:2.3:a:madrasthemes:mas_elementor:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"mas-static-content": [
|
||||
"cpe:2.3:a:madrasthemes:mas_static_content:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -12710,12 +12735,18 @@
|
||||
"meks-audio-player": [
|
||||
"cpe:2.3:a:mekshq:meks_audio_player:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"meks-easy-ads-widget": [
|
||||
"cpe:2.3:a:mekshq:meks_easy_ads_widget:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"meks-easy-instagram-widget": [
|
||||
"cpe:2.3:a:mekshq:meks_easy_photo_feed_widget:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"meks-flexible-shortcodes": [
|
||||
"cpe:2.3:a:mekshq:meks_flexible_shortcodes:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"meks-smart-author-widget": [
|
||||
"cpe:2.3:a:mekshq:meks_smart_author_widget:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"meks-smart-social-widget": [
|
||||
"cpe:2.3:a:mekshq:meks_smart_social_widget:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -12852,6 +12883,9 @@
|
||||
"mobile-login-woocommerce": [
|
||||
"cpe:2.3:a:xootix:otp_login_woocommerce_\\\u0026_gravity_forms:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"mobile-menu": [
|
||||
"cpe:2.3:a:freshlightlab:wp_mobile_menu:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"modal-dialog": [
|
||||
"cpe:2.3:a:modal_dialog_project:modal_dialog:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -13220,6 +13254,9 @@
|
||||
"cpe:2.3:a:ninjaforms:contact_form:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:ninjaforms:ninja_forms:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ninja-gdpr-compliance": [
|
||||
"cpe:2.3:a:ninjateam:gdpr_ccpa_compliance_\\\u0026_cookie_consent_banner:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"ninja-job-board": [
|
||||
"cpe:2.3:a:wpmanageninja:ninja_job_board:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -14159,6 +14196,9 @@
|
||||
"profilegrid-user-profiles-groups-and-communities": [
|
||||
"cpe:2.3:a:metagauss:profilegrid:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"profilepress-pro": [
|
||||
"cpe:2.3:a:properfraction:profilepress:*:*:*:*:pro:wordpress:*:*"
|
||||
],
|
||||
"profit-products-tables-for-woocommerce": [
|
||||
"cpe:2.3:a:pluginus:woot:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -15045,7 +15085,8 @@
|
||||
"cpe:2.3:a:bainternet:shortcodes_ui:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"shortcodes-ultimate": [
|
||||
"cpe:2.3:a:getshortcodes:shortcodes_ultimate:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:getshortcodes:shortcodes_ultimate:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:getshortcodes:shortcodes_ultimate:*:*:*:*:-:wordpress:*:*"
|
||||
],
|
||||
"shortcut-macros": [
|
||||
"cpe:2.3:a:shortcut_macros_project:shortcut_macros:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -15840,7 +15881,7 @@
|
||||
"cpe:2.3:a:instawp:string_locator:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"strong-testimonials": [
|
||||
"cpe:2.3:a:machothemes:strong_testimonials:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:wpchill:strong_testimonials:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"structured-content": [
|
||||
"cpe:2.3:a:wpsc-plugin:structured_content:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -16284,6 +16325,9 @@
|
||||
"transbank-webpay-plus-rest": [
|
||||
"cpe:2.3:a:transbank:transbank_webpay_rest:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"transients-manager": [
|
||||
"cpe:2.3:a:wpbeginner:transients_manager:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"transition-slider-lite": [
|
||||
"cpe:2.3:a:creativeinteractivemedia:transition_slider:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -16790,6 +16834,9 @@
|
||||
"vm-backups": [
|
||||
"cpe:2.3:a:vm_backups_project:vm_backups:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"vod-infomaniak": [
|
||||
"cpe:2.3:a:infomaniak:vod_infomaniak:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"vospari-forms": [
|
||||
"cpe:2.3:a:vospari_forms_project:vospari_forms:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -17209,6 +17256,9 @@
|
||||
"woo-related-products-refresh-on-reload": [
|
||||
"cpe:2.3:a:peachpay:related_products_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"woo-save-abandoned-carts": [
|
||||
"cpe:2.3:a:majas-lapu-izstrade:cartbounty:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"woo-shipping-dpd-baltic": [
|
||||
"cpe:2.3:a:dpdgroup:woocommerce_shipping:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -17246,6 +17296,9 @@
|
||||
"woo-zoho": [
|
||||
"cpe:2.3:a:crmperks:integration_for_woocommerce_and_zoho_crm\\,_books\\,_invoice\\,_inventory\\,_bigin:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"wooCommerce-order-proposal": [
|
||||
"cpe:2.3:a:wpovernight:woocommerce_order_proposal:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"woocommerce": [
|
||||
"cpe:2.3:a:woocommerce:woocommerce:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:woocommerce:woocommerce:*:*:*:*:free:wordpress:*:*",
|
||||
@@ -17882,6 +17935,9 @@
|
||||
"wp-football": [
|
||||
"cpe:2.3:a:wp-football_project:wp-football:*:*:*:*:*:*:*:*"
|
||||
],
|
||||
"wp-force-ssl": [
|
||||
"cpe:2.3:a:webfactoryltd:wp_force_ssl:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"wp-forms-puzzle-captcha": [
|
||||
"cpe:2.3:a:nitinrathod:wp_forms_puzzle_captcha:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -18034,6 +18090,9 @@
|
||||
"wp-jobs": [
|
||||
"cpe:2.3:a:intensewp:wp_jobs:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-jquery-lightbox": [
|
||||
"cpe:2.3:a:lightpress:lightbox:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-js": [
|
||||
"cpe:2.3:a:wp-js_project:wp-js:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -18141,7 +18200,7 @@
|
||||
"cpe:2.3:a:jeffrey-wp:media_library_categories:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-members": [
|
||||
"cpe:2.3:a:wp-members_project:wp-members:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:butlerblog:wp-members:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wp-memory": [
|
||||
"cpe:2.3:a:wp-memory_project:wp-memory:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -18632,7 +18691,8 @@
|
||||
"wp-user-avatar": [
|
||||
"cpe:2.3:a:profilepress:profilepress:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:profilepress:user_registration\\,_login_form\\,_user_profile_\\\u0026_membership:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:properfraction:profilepress:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:properfraction:profilepress:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:properfraction:profilepress:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"wp-user-frontend": [
|
||||
"cpe:2.3:a:wedevs:wp_user_frontend:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -18729,6 +18789,9 @@
|
||||
"wpbulky-wp-bulk-edit-post-types": [
|
||||
"cpe:2.3:a:villatheme:wpbulky:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wpc-shop-as-customer": [
|
||||
"cpe:2.3:a:wpclever:wpc_shop_as_a_customer_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wpcalc": [
|
||||
"cpe:2.3:a:wow-company:wpcalc:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -18892,7 +18955,7 @@
|
||||
"cpe:2.3:a:wpvivid:migration\\,_backup\\,_staging:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wpvr": [
|
||||
"cpe:2.3:a:coderex:wp_vr:*:*:*:*:*:wordpress:*:*"
|
||||
"cpe:2.3:a:rextheme:wp_vr:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"wpzoom-elementor-addons": [
|
||||
"cpe:2.3:a:wpzoom:wpzoom_elementor_addons:*:*:*:*:*:wordpress:*:*"
|
||||
@@ -19007,6 +19070,10 @@
|
||||
"cpe:2.3:a:codeparrots:easy_forms_for_mailchimp:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:yikesinc:easy_forms_for_mailchimp:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"yith-custom-login": [
|
||||
"cpe:2.3:a:yithemes:yith_custom_login:*:*:*:*:*:wordpress:*:*",
|
||||
"cpe:2.3:a:yithemes:yith_custom_login:*:*:*:*:wordpress:*:*:*"
|
||||
],
|
||||
"yith-desktop-notifications-for-woocommerce": [
|
||||
"cpe:2.3:a:yithemes:yith_desktop_notifications_for_woocommerce:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -19289,6 +19356,9 @@
|
||||
"fluida": [
|
||||
"cpe:2.3:a:cryoutcreations:fluida:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"formula": [
|
||||
"cpe:2.3:a:awplife:formula:*:*:*:*:free:wordpress:*:*"
|
||||
],
|
||||
"fullbase": [
|
||||
"cpe:2.3:a:marchettidesign:fullbase:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
@@ -19367,6 +19437,9 @@
|
||||
"nexter": [
|
||||
"cpe:2.3:a:posimyth:nexter:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"nioland": [
|
||||
"cpe:2.3:a:steelthemes:nioland:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
"nirvana": [
|
||||
"cpe:2.3:a:cryoutcreations:nirvana:*:*:*:*:*:wordpress:*:*"
|
||||
],
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/vifraa/gopom"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
@@ -22,6 +21,7 @@ import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java/internal/maven"
|
||||
)
|
||||
|
||||
var archiveFormatGlobs = []string{
|
||||
@@ -57,7 +57,7 @@ type archiveParser struct {
|
||||
fileInfo archiveFilename
|
||||
detectNested bool
|
||||
cfg ArchiveCatalogerConfig
|
||||
maven *mavenResolver
|
||||
maven *maven.Resolver
|
||||
licenseScanner licenses.Scanner
|
||||
}
|
||||
|
||||
@@ -69,15 +69,20 @@ func newGenericArchiveParserAdapter(cfg ArchiveCatalogerConfig) genericArchivePa
|
||||
return genericArchiveParserAdapter{cfg: cfg}
|
||||
}
|
||||
|
||||
// parseJavaArchive is a parser function for java archive contents, returning all Java libraries and nested archives.
|
||||
// parseJavaArchive is a parser function for java archive contents, returning all Java libraries and nested archives
|
||||
func (gap genericArchiveParserAdapter) parseJavaArchive(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
return gap.processJavaArchive(ctx, reader, nil)
|
||||
}
|
||||
|
||||
// processJavaArchive processes an archive for java contents, returning all Java libraries and nested archives
|
||||
func (gap genericArchiveParserAdapter) processJavaArchive(ctx context.Context, reader file.LocationReadCloser, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
parser, cleanupFn, err := newJavaArchiveParser(ctx, reader, true, gap.cfg)
|
||||
// note: even on error, we should always run cleanup functions
|
||||
defer cleanupFn()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return parser.parse(ctx)
|
||||
return parser.parse(ctx, parentPkg)
|
||||
}
|
||||
|
||||
// uniquePkgKey creates a unique string to identify the given package.
|
||||
@@ -115,34 +120,62 @@ func newJavaArchiveParser(ctx context.Context, reader file.LocationReadCloser, d
|
||||
fileInfo: newJavaArchiveFilename(currentFilepath),
|
||||
detectNested: detectNested,
|
||||
cfg: cfg,
|
||||
maven: newMavenResolver(nil, cfg),
|
||||
maven: maven.NewResolver(nil, cfg.mavenConfig()),
|
||||
licenseScanner: licenseScanner,
|
||||
}, cleanupFn, nil
|
||||
}
|
||||
|
||||
// parse the loaded archive and return all packages found.
|
||||
func (j *archiveParser) parse(ctx context.Context) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
func (j *archiveParser) parse(ctx context.Context, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
// find the parent package from the java manifest
|
||||
parentPkg, err := j.discoverMainPackage(ctx)
|
||||
mainPkg, err := j.discoverMainPackage(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not generate package from %s: %w", j.location, err)
|
||||
}
|
||||
|
||||
// find aux packages from pom.properties/pom.xml and potentially modify the existing parentPkg
|
||||
// NOTE: we cannot generate sha1 digests from packages discovered via pom.properties/pom.xml
|
||||
auxPkgs, err := j.discoverPkgsFromAllMavenFiles(ctx, parentPkg)
|
||||
// IMPORTANT!: discoverPkgsFromAllMavenFiles may change mainPkg information, so needs to be called before SetID and before copying for relationships, etc.
|
||||
auxPkgs, err := j.discoverPkgsFromAllMavenFiles(ctx, mainPkg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pkgs = append(pkgs, auxPkgs...)
|
||||
|
||||
if mainPkg != nil {
|
||||
finalizePackage(mainPkg)
|
||||
pkgs = append(pkgs, *mainPkg)
|
||||
|
||||
if parentPkg != nil {
|
||||
relationships = append(relationships, artifact.Relationship{
|
||||
From: *mainPkg,
|
||||
To: *parentPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for i := range auxPkgs {
|
||||
auxPkg := &auxPkgs[i]
|
||||
|
||||
finalizePackage(auxPkg)
|
||||
pkgs = append(pkgs, *auxPkg)
|
||||
|
||||
if mainPkg != nil {
|
||||
relationships = append(relationships, artifact.Relationship{
|
||||
From: *auxPkg,
|
||||
To: *mainPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var errs error
|
||||
if j.detectNested {
|
||||
// find nested java archive packages
|
||||
nestedPkgs, nestedRelationships, err := j.discoverPkgsFromNestedArchives(ctx, parentPkg)
|
||||
nestedPkgs, nestedRelationships, err := j.discoverPkgsFromNestedArchives(ctx, mainPkg)
|
||||
if err != nil {
|
||||
errs = unknown.Append(errs, j.location, err)
|
||||
}
|
||||
@@ -157,29 +190,6 @@ func (j *archiveParser) parse(ctx context.Context) ([]pkg.Package, []artifact.Re
|
||||
}
|
||||
}
|
||||
|
||||
// lastly, add the parent package to the list (assuming the parent exists)
|
||||
if parentPkg != nil {
|
||||
pkgs = append([]pkg.Package{*parentPkg}, pkgs...)
|
||||
}
|
||||
|
||||
// add pURLs to all packages found
|
||||
// note: since package information may change after initial creation when parsing multiple locations within the
|
||||
// jar, we wait until the conclusion of the parsing process before synthesizing pURLs.
|
||||
for i := range pkgs {
|
||||
p := &pkgs[i]
|
||||
if m, ok := p.Metadata.(pkg.JavaArchive); ok {
|
||||
p.PURL = packageURL(p.Name, p.Version, m)
|
||||
|
||||
if strings.Contains(p.PURL, "io.jenkins.plugins") || strings.Contains(p.PURL, "org.jenkins-ci.plugins") {
|
||||
p.Type = pkg.JenkinsPluginPkg
|
||||
}
|
||||
} else {
|
||||
log.WithFields("package", p.String()).Warn("unable to extract java metadata to generate purl")
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
if len(pkgs) == 0 {
|
||||
errs = unknown.Appendf(errs, j.location, "no package identified in archive")
|
||||
}
|
||||
@@ -187,6 +197,22 @@ func (j *archiveParser) parse(ctx context.Context) ([]pkg.Package, []artifact.Re
|
||||
return pkgs, relationships, errs
|
||||
}
|
||||
|
||||
// finalizePackage potentially updates some package information such as classifying the package as a Jenkins plugin,
|
||||
// sets the PURL, and calls p.SetID()
|
||||
func finalizePackage(p *pkg.Package) {
|
||||
if m, ok := p.Metadata.(pkg.JavaArchive); ok {
|
||||
p.PURL = packageURL(p.Name, p.Version, m)
|
||||
|
||||
if strings.Contains(p.PURL, "io.jenkins.plugins") || strings.Contains(p.PURL, "org.jenkins-ci.plugins") {
|
||||
p.Type = pkg.JenkinsPluginPkg
|
||||
}
|
||||
} else {
|
||||
log.WithFields("package", p.String()).Warn("unable to extract java metadata to generate purl")
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
// discoverMainPackage parses the root Java manifest used as the parent package to all discovered nested packages.
|
||||
func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package, error) {
|
||||
// search and parse java manifest files
|
||||
@@ -297,18 +323,18 @@ func (j *archiveParser) findLicenseFromJavaMetadata(ctx context.Context, groupID
|
||||
}
|
||||
|
||||
var err error
|
||||
var pomLicenses []gopom.License
|
||||
var pomLicenses []maven.License
|
||||
if parsedPom != nil {
|
||||
pomLicenses, err = j.maven.resolveLicenses(ctx, parsedPom.project)
|
||||
pomLicenses, err = j.maven.ResolveLicenses(ctx, parsedPom.project)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "mavenID", j.maven.getMavenID(ctx, parsedPom.project)).Trace("error attempting to resolve pom licenses")
|
||||
log.WithFields("error", err, "mavenID", j.maven.ResolveID(ctx, parsedPom.project)).Trace("error attempting to resolve pom licenses")
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil && len(pomLicenses) == 0 {
|
||||
pomLicenses, err = j.maven.findLicenses(ctx, groupID, artifactID, version)
|
||||
pomLicenses, err = j.maven.FindLicenses(ctx, groupID, artifactID, version)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "mavenID", mavenID{groupID, artifactID, version}).Trace("error attempting to find licenses")
|
||||
log.WithFields("error", err, "mavenID", maven.NewID(groupID, artifactID, version)).Trace("error attempting to find licenses")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,26 +342,37 @@ func (j *archiveParser) findLicenseFromJavaMetadata(ctx context.Context, groupID
|
||||
// Try removing the last part of the groupId, as sometimes it duplicates the artifactId
|
||||
packages := strings.Split(groupID, ".")
|
||||
groupID = strings.Join(packages[:len(packages)-1], ".")
|
||||
pomLicenses, err = j.maven.findLicenses(ctx, groupID, artifactID, version)
|
||||
pomLicenses, err = j.maven.FindLicenses(ctx, groupID, artifactID, version)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "mavenID", mavenID{groupID, artifactID, version}).Trace("error attempting to find sub-group licenses")
|
||||
log.WithFields("error", err, "mavenID", maven.NewID(groupID, artifactID, version)).Trace("error attempting to find sub-group licenses")
|
||||
}
|
||||
}
|
||||
|
||||
return toPkgLicenses(&j.location, pomLicenses)
|
||||
}
|
||||
|
||||
func toPkgLicenses(location *file.Location, licenses []gopom.License) []pkg.License {
|
||||
func toPkgLicenses(location *file.Location, licenses []maven.License) []pkg.License {
|
||||
var out []pkg.License
|
||||
for _, license := range licenses {
|
||||
out = append(out, pkg.NewLicenseFromFields(deref(license.Name), deref(license.URL), location))
|
||||
name := ""
|
||||
if license.Name != nil {
|
||||
name = *license.Name
|
||||
}
|
||||
url := ""
|
||||
if license.URL != nil {
|
||||
url = *license.URL
|
||||
}
|
||||
if name == "" && url == "" {
|
||||
continue
|
||||
}
|
||||
out = append(out, pkg.NewLicenseFromFields(name, url, location))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type parsedPomProject struct {
|
||||
path string
|
||||
project *gopom.Project
|
||||
project *maven.Project
|
||||
}
|
||||
|
||||
// discoverMainPackageFromPomInfo attempts to resolve maven groupId, artifactId, version and other info from found pom information
|
||||
@@ -370,7 +407,7 @@ func (j *archiveParser) discoverMainPackageFromPomInfo(ctx context.Context) (gro
|
||||
version = pomProperties.Version
|
||||
|
||||
if parsedPom != nil && parsedPom.project != nil {
|
||||
id := j.maven.getMavenID(ctx, parsedPom.project)
|
||||
id := j.maven.ResolveID(ctx, parsedPom.project)
|
||||
if group == "" {
|
||||
group = id.GroupID
|
||||
}
|
||||
@@ -507,7 +544,7 @@ func discoverPkgsFromOpeners(ctx context.Context, location file.Location, opener
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
for pathWithinArchive, archiveOpener := range openers {
|
||||
nestedPkgs, nestedRelationships, err := discoverPkgsFromOpener(ctx, location, pathWithinArchive, archiveOpener, cfg)
|
||||
nestedPkgs, nestedRelationships, err := discoverPkgsFromOpener(ctx, location, pathWithinArchive, archiveOpener, cfg, parentPkg)
|
||||
if err != nil {
|
||||
log.WithFields("location", location.Path()).Warnf("unable to discover java packages from opener: %+v", err)
|
||||
continue
|
||||
@@ -531,7 +568,7 @@ func discoverPkgsFromOpeners(ctx context.Context, location file.Location, opener
|
||||
}
|
||||
|
||||
// discoverPkgsFromOpener finds Java archives within the given file.
|
||||
func discoverPkgsFromOpener(ctx context.Context, location file.Location, pathWithinArchive string, archiveOpener intFile.Opener, cfg ArchiveCatalogerConfig) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
func discoverPkgsFromOpener(ctx context.Context, location file.Location, pathWithinArchive string, archiveOpener intFile.Opener, cfg ArchiveCatalogerConfig, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
archiveReadCloser, err := archiveOpener.Open()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to open archived file from tempdir: %w", err)
|
||||
@@ -546,10 +583,10 @@ func discoverPkgsFromOpener(ctx context.Context, location file.Location, pathWit
|
||||
nestedLocation := file.NewLocationFromCoordinates(location.Coordinates)
|
||||
nestedLocation.AccessPath = nestedPath
|
||||
gap := newGenericArchiveParserAdapter(cfg)
|
||||
nestedPkgs, nestedRelationships, err := gap.parseJavaArchive(ctx, nil, nil, file.LocationReadCloser{
|
||||
nestedPkgs, nestedRelationships, err := gap.processJavaArchive(ctx, file.LocationReadCloser{
|
||||
Location: nestedLocation,
|
||||
ReadCloser: archiveReadCloser,
|
||||
})
|
||||
}, parentPkg)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to process nested java archive (%s): %w", pathWithinArchive, err)
|
||||
}
|
||||
@@ -595,7 +632,7 @@ func pomProjectByParentPath(archivePath string, location file.Location, extractP
|
||||
projectByParentPath := make(map[string]*parsedPomProject)
|
||||
for filePath, fileContents := range contentsOfMavenProjectFiles {
|
||||
// TODO: when we support locations of paths within archives we should start passing the specific pom.xml location object instead of the top jar
|
||||
pom, err := decodePomXML(strings.NewReader(fileContents))
|
||||
pom, err := maven.ParsePomXML(strings.NewReader(fileContents))
|
||||
if err != nil {
|
||||
log.WithFields("contents-path", filePath, "location", location.Path()).Warnf("failed to parse pom.xml: %+v", err)
|
||||
continue
|
||||
@@ -614,7 +651,7 @@ func pomProjectByParentPath(archivePath string, location file.Location, extractP
|
||||
|
||||
// newPackageFromMavenData processes a single Maven POM properties for a given parent package, returning all listed Java packages found and
|
||||
// associating each discovered package to the given parent package. Note the pom.xml is optional, the pom.properties is not.
|
||||
func newPackageFromMavenData(ctx context.Context, r *mavenResolver, pomProperties pkg.JavaPomProperties, parsedPom *parsedPomProject, parentPkg *pkg.Package, location file.Location) *pkg.Package {
|
||||
func newPackageFromMavenData(ctx context.Context, r *maven.Resolver, pomProperties pkg.JavaPomProperties, parsedPom *parsedPomProject, parentPkg *pkg.Package, location file.Location) *pkg.Package {
|
||||
// keep the artifact name within the virtual path if this package does not match the parent package
|
||||
vPathSuffix := ""
|
||||
groupID := ""
|
||||
@@ -639,23 +676,20 @@ func newPackageFromMavenData(ctx context.Context, r *mavenResolver, pomPropertie
|
||||
var pkgPomProject *pkg.JavaPomProject
|
||||
|
||||
var err error
|
||||
var pomLicenses []gopom.License
|
||||
var pomLicenses []maven.License
|
||||
if parsedPom == nil {
|
||||
// If we have no pom.xml, check maven central using pom.properties
|
||||
pomLicenses, err = r.findLicenses(ctx, pomProperties.GroupID, pomProperties.ArtifactID, pomProperties.Version)
|
||||
pomLicenses, err = r.FindLicenses(ctx, pomProperties.GroupID, pomProperties.ArtifactID, pomProperties.Version)
|
||||
} else {
|
||||
pkgPomProject = newPomProject(ctx, r, parsedPom.path, parsedPom.project)
|
||||
pomLicenses, err = r.resolveLicenses(ctx, parsedPom.project)
|
||||
pomLicenses, err = r.ResolveLicenses(ctx, parsedPom.project)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "mavenID", mavenID{pomProperties.GroupID, pomProperties.ArtifactID, pomProperties.Version}).Trace("error attempting to resolve licenses")
|
||||
log.WithFields("error", err, "mavenID", maven.NewID(pomProperties.GroupID, pomProperties.ArtifactID, pomProperties.Version)).Trace("error attempting to resolve licenses")
|
||||
}
|
||||
|
||||
licenses := make([]pkg.License, 0)
|
||||
for _, license := range pomLicenses {
|
||||
licenses = append(licenses, pkg.NewLicenseFromFields(deref(license.Name), deref(license.URL), &location))
|
||||
}
|
||||
licenseSet := pkg.NewLicenseSet(toPkgLicenses(&location, pomLicenses)...)
|
||||
|
||||
p := pkg.Package{
|
||||
Name: pomProperties.ArtifactID,
|
||||
@@ -663,7 +697,7 @@ func newPackageFromMavenData(ctx context.Context, r *mavenResolver, pomPropertie
|
||||
Locations: file.NewLocationSet(
|
||||
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Licenses: licenseSet,
|
||||
Language: pkg.Java,
|
||||
Type: pomProperties.PkgTypeIndicated(),
|
||||
Metadata: pkg.JavaArchive{
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vifraa/gopom"
|
||||
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
@@ -26,10 +25,12 @@ import (
|
||||
"github.com/anchore/syft/syft/license"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java/internal/maven"
|
||||
maventest "github.com/anchore/syft/syft/pkg/cataloger/java/internal/maven/test"
|
||||
)
|
||||
|
||||
func TestSearchMavenForLicenses(t *testing.T) {
|
||||
url := mockMavenRepo(t)
|
||||
url := maventest.MockRepo(t, "internal/maven/test-fixtures/maven-repo")
|
||||
|
||||
ctx := licenses.SetContextLicenseScanner(context.Background(), licenses.TestingOnlyScanner())
|
||||
|
||||
@@ -83,8 +84,8 @@ func TestSearchMavenForLicenses(t *testing.T) {
|
||||
|
||||
// assert licenses are discovered from upstream
|
||||
_, _, _, parsedPom := ap.discoverMainPackageFromPomInfo(context.Background())
|
||||
licenses, _ := ap.maven.resolveLicenses(context.Background(), parsedPom.project)
|
||||
assert.Equal(t, tc.expectedLicenses, toPkgLicenses(nil, licenses))
|
||||
resolvedLicenses, _ := ap.maven.ResolveLicenses(context.Background(), parsedPom.project)
|
||||
assert.Equal(t, tc.expectedLicenses, toPkgLicenses(nil, resolvedLicenses))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -362,9 +363,11 @@ func TestParseJar(t *testing.T) {
|
||||
defer cleanupFn()
|
||||
require.NoError(t, err)
|
||||
|
||||
actual, _, err := parser.parse(context.Background())
|
||||
actual, _, err := parser.parse(context.Background(), nil)
|
||||
if test.wantErr != nil {
|
||||
test.wantErr(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
if len(actual) != len(test.expected) {
|
||||
@@ -635,10 +638,10 @@ func TestParseNestedJar(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{})
|
||||
|
||||
actual, _, err := gap.parseJavaArchive(context.Background(), nil, nil, file.LocationReadCloser{
|
||||
actual, _, err := gap.processJavaArchive(context.Background(), file.LocationReadCloser{
|
||||
Location: file.NewLocation(fixture.Name()),
|
||||
ReadCloser: fixture,
|
||||
})
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedNameVersionPairSet := strset.New()
|
||||
@@ -776,8 +779,8 @@ func Test_newPackageFromMavenData(t *testing.T) {
|
||||
Version: "1.0",
|
||||
},
|
||||
project: &parsedPomProject{
|
||||
project: &gopom.Project{
|
||||
Parent: &gopom.Parent{
|
||||
project: &maven.Project{
|
||||
Parent: &maven.Parent{
|
||||
GroupID: ptr("some-parent-group-id"),
|
||||
ArtifactID: ptr("some-parent-artifact-id"),
|
||||
Version: ptr("1.0-parent"),
|
||||
@@ -788,7 +791,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
|
||||
Version: ptr("1.0"),
|
||||
Description: ptr("desc"),
|
||||
URL: ptr("aweso.me"),
|
||||
Licenses: &[]gopom.License{
|
||||
Licenses: &[]maven.License{
|
||||
{
|
||||
Name: ptr("MIT"),
|
||||
URL: ptr("https://opensource.org/licenses/MIT"),
|
||||
@@ -1052,7 +1055,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
|
||||
}
|
||||
test.expectedParent.Locations = locations
|
||||
|
||||
r := newMavenResolver(nil, DefaultArchiveCatalogerConfig())
|
||||
r := maven.NewResolver(nil, maven.DefaultConfig())
|
||||
actualPackage := newPackageFromMavenData(context.Background(), r, test.props, test.project, test.parent, file.NewLocation(virtualPath))
|
||||
if test.expectedPackage == nil {
|
||||
require.Nil(t, actualPackage)
|
||||
@@ -1093,6 +1096,76 @@ func Test_artifactIDMatchesFilename(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_parseJavaArchive_regressions(t *testing.T) {
|
||||
apiAll := pkg.Package{
|
||||
Name: "api-all",
|
||||
Version: "2.0.0",
|
||||
Type: pkg.JavaPkg,
|
||||
Language: pkg.Java,
|
||||
PURL: "pkg:maven/org.apache.directory.api/api-all@2.0.0",
|
||||
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar")),
|
||||
Metadata: pkg.JavaArchive{
|
||||
VirtualPath: "test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar",
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: []pkg.KeyValue{
|
||||
{
|
||||
Key: "Manifest-Version",
|
||||
Value: "1.0",
|
||||
},
|
||||
{
|
||||
Key: "Built-By",
|
||||
Value: "elecharny",
|
||||
},
|
||||
{
|
||||
Key: "Created-By",
|
||||
Value: "Apache Maven 3.6.0",
|
||||
},
|
||||
{
|
||||
Key: "Build-Jdk",
|
||||
Value: "1.8.0_191",
|
||||
},
|
||||
},
|
||||
},
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
Path: "META-INF/maven/org.apache.directory.api/api-all/pom.properties",
|
||||
GroupID: "org.apache.directory.api",
|
||||
ArtifactID: "api-all",
|
||||
Version: "2.0.0",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
apiAsn1Api := pkg.Package{
|
||||
Name: "api-asn1-api",
|
||||
Version: "2.0.0",
|
||||
PURL: "pkg:maven/org.apache.directory.api/api-asn1-api@2.0.0",
|
||||
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar")),
|
||||
Type: pkg.JavaPkg,
|
||||
Language: pkg.Java,
|
||||
Metadata: pkg.JavaArchive{
|
||||
VirtualPath: "test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar:org.apache.directory.api:api-asn1-api",
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
Path: "META-INF/maven/org.apache.directory.api/api-asn1-api/pom.properties",
|
||||
GroupID: "org.apache.directory.api",
|
||||
ArtifactID: "api-asn1-api",
|
||||
Version: "2.0.0",
|
||||
},
|
||||
PomProject: &pkg.JavaPomProject{
|
||||
Path: "META-INF/maven/org.apache.directory.api/api-asn1-api/pom.xml",
|
||||
ArtifactID: "api-asn1-api",
|
||||
GroupID: "org.apache.directory.api",
|
||||
Version: "2.0.0",
|
||||
Name: "Apache Directory API ASN.1 API",
|
||||
Description: "ASN.1 API",
|
||||
Parent: &pkg.JavaPomParent{
|
||||
GroupID: "org.apache.directory.api",
|
||||
ArtifactID: "api-asn1-parent",
|
||||
Version: "2.0.0",
|
||||
},
|
||||
},
|
||||
Parent: &apiAll,
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixtureName string
|
||||
@@ -1214,73 +1287,14 @@ func Test_parseJavaArchive_regressions(t *testing.T) {
|
||||
fixtureName: "api-all-2.0.0-sources",
|
||||
assignParent: true,
|
||||
expectedPkgs: []pkg.Package{
|
||||
apiAll,
|
||||
apiAsn1Api,
|
||||
},
|
||||
expectedRelationships: []artifact.Relationship{
|
||||
{
|
||||
Name: "api-all",
|
||||
Version: "2.0.0",
|
||||
Type: pkg.JavaPkg,
|
||||
Language: pkg.Java,
|
||||
PURL: "pkg:maven/org.apache.directory.api/api-all@2.0.0",
|
||||
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar")),
|
||||
Metadata: pkg.JavaArchive{
|
||||
VirtualPath: "test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar",
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: []pkg.KeyValue{
|
||||
{
|
||||
Key: "Manifest-Version",
|
||||
Value: "1.0",
|
||||
},
|
||||
{
|
||||
Key: "Built-By",
|
||||
Value: "elecharny",
|
||||
},
|
||||
{
|
||||
Key: "Created-By",
|
||||
Value: "Apache Maven 3.6.0",
|
||||
},
|
||||
{
|
||||
Key: "Build-Jdk",
|
||||
Value: "1.8.0_191",
|
||||
},
|
||||
},
|
||||
},
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
Path: "META-INF/maven/org.apache.directory.api/api-all/pom.properties",
|
||||
GroupID: "org.apache.directory.api",
|
||||
ArtifactID: "api-all",
|
||||
Version: "2.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "api-asn1-api",
|
||||
Version: "2.0.0",
|
||||
PURL: "pkg:maven/org.apache.directory.api/api-asn1-api@2.0.0",
|
||||
Locations: file.NewLocationSet(file.NewLocation("test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar")),
|
||||
Type: pkg.JavaPkg,
|
||||
Language: pkg.Java,
|
||||
Metadata: pkg.JavaArchive{
|
||||
VirtualPath: "test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar:org.apache.directory.api:api-asn1-api",
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
Path: "META-INF/maven/org.apache.directory.api/api-asn1-api/pom.properties",
|
||||
GroupID: "org.apache.directory.api",
|
||||
ArtifactID: "api-asn1-api",
|
||||
Version: "2.0.0",
|
||||
},
|
||||
PomProject: &pkg.JavaPomProject{
|
||||
Path: "META-INF/maven/org.apache.directory.api/api-asn1-api/pom.xml",
|
||||
ArtifactID: "api-asn1-api",
|
||||
GroupID: "org.apache.directory.api",
|
||||
Version: "2.0.0",
|
||||
Name: "Apache Directory API ASN.1 API",
|
||||
Description: "ASN.1 API",
|
||||
Parent: &pkg.JavaPomParent{
|
||||
GroupID: "org.apache.directory.api",
|
||||
ArtifactID: "api-asn1-parent",
|
||||
Version: "2.0.0",
|
||||
},
|
||||
},
|
||||
Parent: nil,
|
||||
},
|
||||
From: apiAsn1Api,
|
||||
To: apiAll,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1364,11 +1378,11 @@ func Test_deterministicMatchingPomProperties(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
fixture string
|
||||
expected mavenID
|
||||
expected maven.ID
|
||||
}{
|
||||
{
|
||||
fixture: "multiple-matching-2.11.5",
|
||||
expected: mavenID{"org.multiple", "multiple-matching-1", "2.11.5"},
|
||||
expected: maven.NewID("org.multiple", "multiple-matching-1", "2.11.5"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1391,7 +1405,7 @@ func Test_deterministicMatchingPomProperties(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
groupID, artifactID, version, _ := parser.discoverMainPackageFromPomInfo(context.TODO())
|
||||
require.Equal(t, test.expected, mavenID{groupID, artifactID, version})
|
||||
require.Equal(t, test.expected, maven.NewID(groupID, artifactID, version))
|
||||
}()
|
||||
}
|
||||
})
|
||||
@@ -1401,10 +1415,7 @@ func Test_deterministicMatchingPomProperties(t *testing.T) {
|
||||
func assignParent(parent *pkg.Package, childPackages ...pkg.Package) {
|
||||
for i, jp := range childPackages {
|
||||
if v, ok := jp.Metadata.(pkg.JavaArchive); ok {
|
||||
parent := *parent
|
||||
// PURL are not calculated after the fact for parent
|
||||
parent.PURL = ""
|
||||
v.Parent = &parent
|
||||
v.Parent = parent
|
||||
childPackages[i].Metadata = v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package java
|
||||
|
||||
import "github.com/anchore/syft/syft/cataloging"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
const mavenBaseURL = "https://repo1.maven.org/maven2"
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java/internal/maven"
|
||||
)
|
||||
|
||||
type ArchiveCatalogerConfig struct {
|
||||
cataloging.ArchiveSearchConfig `yaml:",inline" json:"" mapstructure:",squash"`
|
||||
@@ -11,16 +14,19 @@ type ArchiveCatalogerConfig struct {
|
||||
MavenLocalRepositoryDir string `yaml:"maven-localrepository-dir" json:"maven-localrepository-dir" mapstructure:"maven-localrepository-dir"`
|
||||
MavenBaseURL string `yaml:"maven-base-url" json:"maven-base-url" mapstructure:"maven-base-url"`
|
||||
MaxParentRecursiveDepth int `yaml:"max-parent-recursive-depth" json:"max-parent-recursive-depth" mapstructure:"max-parent-recursive-depth"`
|
||||
ResolveTransitiveDependencies bool `yaml:"resolve-transitive-dependencies" json:"resolve-transitive-dependencies" mapstructure:"resolve-transitive-dependencies"`
|
||||
}
|
||||
|
||||
func DefaultArchiveCatalogerConfig() ArchiveCatalogerConfig {
|
||||
mavenCfg := maven.DefaultConfig()
|
||||
return ArchiveCatalogerConfig{
|
||||
ArchiveSearchConfig: cataloging.DefaultArchiveSearchConfig(),
|
||||
UseNetwork: false,
|
||||
UseMavenLocalRepository: false,
|
||||
MavenLocalRepositoryDir: defaultMavenLocalRepoDir(),
|
||||
MavenBaseURL: mavenBaseURL,
|
||||
MaxParentRecursiveDepth: 0, // unlimited
|
||||
ArchiveSearchConfig: cataloging.DefaultArchiveSearchConfig(),
|
||||
UseNetwork: mavenCfg.UseNetwork,
|
||||
UseMavenLocalRepository: mavenCfg.UseLocalRepository,
|
||||
MavenLocalRepositoryDir: mavenCfg.LocalRepositoryDir,
|
||||
MavenBaseURL: strings.Join(mavenCfg.Repositories, ","),
|
||||
MaxParentRecursiveDepth: mavenCfg.MaxParentRecursiveDepth,
|
||||
ResolveTransitiveDependencies: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +52,23 @@ func (j ArchiveCatalogerConfig) WithMavenBaseURL(input string) ArchiveCatalogerC
|
||||
return j
|
||||
}
|
||||
|
||||
func (j ArchiveCatalogerConfig) WithResolveTransitiveDependencies(resolveTransitiveDependencies bool) ArchiveCatalogerConfig {
|
||||
j.ResolveTransitiveDependencies = resolveTransitiveDependencies
|
||||
return j
|
||||
}
|
||||
|
||||
func (j ArchiveCatalogerConfig) WithArchiveTraversal(search cataloging.ArchiveSearchConfig, maxDepth int) ArchiveCatalogerConfig {
|
||||
j.MaxParentRecursiveDepth = maxDepth
|
||||
j.ArchiveSearchConfig = search
|
||||
return j
|
||||
}
|
||||
|
||||
func (j ArchiveCatalogerConfig) mavenConfig() maven.Config {
|
||||
return maven.Config{
|
||||
UseNetwork: j.UseNetwork,
|
||||
UseLocalRepository: j.UseMavenLocalRepository,
|
||||
LocalRepositoryDir: j.MavenLocalRepositoryDir,
|
||||
Repositories: strings.Split(j.MavenBaseURL, ","),
|
||||
MaxParentRecursiveDepth: j.MaxParentRecursiveDepth,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"debug/macho"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -18,34 +17,14 @@ import (
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/mimetype"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/format/cyclonedxjson"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
type nativeImageCycloneDX struct {
|
||||
BomFormat string `json:"bomFormat"`
|
||||
SpecVersion string `json:"specVersion"`
|
||||
Version int `json:"version"`
|
||||
Components []nativeImageComponent `json:"components"`
|
||||
}
|
||||
|
||||
type nativeImageComponent struct {
|
||||
Type string `json:"type"`
|
||||
Group string `json:"group"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Properties []nativeImageCPE `json:"properties"`
|
||||
}
|
||||
|
||||
type nativeImageCPE struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type nativeImage interface {
|
||||
fetchPkgs() ([]pkg.Package, error)
|
||||
fetchPkgs() ([]pkg.Package, []artifact.Relationship, error)
|
||||
}
|
||||
|
||||
type nativeImageElf struct {
|
||||
@@ -113,40 +92,12 @@ func (c *nativeImageCataloger) Name() string {
|
||||
return nativeImageCatalogerName
|
||||
}
|
||||
|
||||
// getPackage returns the package given within a NativeImageComponent.
|
||||
func getPackage(component nativeImageComponent) pkg.Package {
|
||||
var cpes []cpe.CPE
|
||||
for _, property := range component.Properties {
|
||||
c, err := cpe.New(property.Value, cpe.DeclaredSource)
|
||||
if err != nil {
|
||||
log.Debugf("unable to parse Attributes: %v", err)
|
||||
continue
|
||||
}
|
||||
cpes = append(cpes, c)
|
||||
}
|
||||
return pkg.Package{
|
||||
Name: component.Name,
|
||||
Version: component.Version,
|
||||
Language: pkg.Java,
|
||||
Type: pkg.GraalVMNativeImagePkg,
|
||||
FoundBy: nativeImageCatalogerName,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: component.Group,
|
||||
},
|
||||
},
|
||||
CPEs: cpes,
|
||||
}
|
||||
}
|
||||
|
||||
// decompressSbom returns the packages given within a native image executable's SBOM.
|
||||
func decompressSbom(dataBuf []byte, sbomStart uint64, lengthStart uint64) ([]pkg.Package, error) {
|
||||
var pkgs []pkg.Package
|
||||
|
||||
func decompressSbom(dataBuf []byte, sbomStart uint64, lengthStart uint64) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
lengthEnd := lengthStart + 8
|
||||
bufLen := len(dataBuf)
|
||||
if lengthEnd > uint64(bufLen) {
|
||||
return nil, errors.New("the 'sbom_length' symbol overflows the binary")
|
||||
return nil, nil, errors.New("the 'sbom_length' symbol overflows the binary")
|
||||
}
|
||||
|
||||
length := dataBuf[lengthStart:lengthEnd]
|
||||
@@ -154,39 +105,31 @@ func decompressSbom(dataBuf []byte, sbomStart uint64, lengthStart uint64) ([]pkg
|
||||
var storedLength uint64
|
||||
err := binary.Read(p, binary.LittleEndian, &storedLength)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read from binary file: %w", err)
|
||||
return nil, nil, fmt.Errorf("could not read from binary file: %w", err)
|
||||
}
|
||||
|
||||
log.WithFields("len", storedLength).Trace("found java native-image SBOM")
|
||||
sbomEnd := sbomStart + storedLength
|
||||
if sbomEnd > uint64(bufLen) {
|
||||
return nil, errors.New("the sbom symbol overflows the binary")
|
||||
return nil, nil, errors.New("the sbom symbol overflows the binary")
|
||||
}
|
||||
|
||||
sbomCompressed := dataBuf[sbomStart:sbomEnd]
|
||||
p = bytes.NewBuffer(sbomCompressed)
|
||||
gzreader, err := gzip.NewReader(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not decompress the java native-image SBOM: %w", err)
|
||||
return nil, nil, fmt.Errorf("could not decompress the java native-image SBOM: %w", err)
|
||||
}
|
||||
|
||||
output, err := io.ReadAll(gzreader)
|
||||
sbom, _, _, err := cyclonedxjson.NewFormatDecoder().Decode(gzreader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read the java native-image SBOM: %w", err)
|
||||
return nil, nil, fmt.Errorf("could not unmarshal the java native-image SBOM: %w", err)
|
||||
}
|
||||
|
||||
var sbomContent nativeImageCycloneDX
|
||||
err = json.Unmarshal(output, &sbomContent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal the java native-image SBOM: %w", err)
|
||||
}
|
||||
|
||||
for _, component := range sbomContent.Components {
|
||||
p := getPackage(component)
|
||||
var pkgs []pkg.Package
|
||||
for p := range sbom.Artifacts.Packages.Enumerate() {
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
|
||||
return pkgs, nil
|
||||
return pkgs, sbom.Relationships, nil
|
||||
}
|
||||
|
||||
// fileError logs an error message when an executable cannot be read.
|
||||
@@ -294,7 +237,7 @@ func newPE(filename string, r io.ReaderAt) (nativeImage, error) {
|
||||
}
|
||||
|
||||
// fetchPkgs obtains the packages given in the binary.
|
||||
func (ni nativeImageElf) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
func (ni nativeImageElf) fetchPkgs() (pkgs []pkg.Package, relationships []artifact.Relationship, retErr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// this can happen in cases where a malformed binary is passed in can be initially parsed, but not
|
||||
@@ -310,10 +253,10 @@ func (ni nativeImageElf) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
|
||||
si, err := bi.Symbols()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no symbols found in binary: %w", err)
|
||||
return nil, nil, fmt.Errorf("no symbols found in binary: %w", err)
|
||||
}
|
||||
if si == nil {
|
||||
return nil, errors.New(nativeImageMissingSymbolsError)
|
||||
return nil, nil, errors.New(nativeImageMissingSymbolsError)
|
||||
}
|
||||
for _, s := range si {
|
||||
switch s.Name {
|
||||
@@ -326,16 +269,16 @@ func (ni nativeImageElf) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
}
|
||||
}
|
||||
if sbom.Value == 0 || sbomLength.Value == 0 || svmVersion.Value == 0 {
|
||||
return nil, errors.New(nativeImageMissingSymbolsError)
|
||||
return nil, nil, errors.New(nativeImageMissingSymbolsError)
|
||||
}
|
||||
dataSection := bi.Section(".data")
|
||||
if dataSection == nil {
|
||||
return nil, fmt.Errorf("no .data section found in binary: %w", err)
|
||||
return nil, nil, fmt.Errorf("no .data section found in binary: %w", err)
|
||||
}
|
||||
dataSectionBase := dataSection.SectionHeader.Addr
|
||||
data, err := dataSection.Data()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot read the .data section: %w", err)
|
||||
return nil, nil, fmt.Errorf("cannot read the .data section: %w", err)
|
||||
}
|
||||
sbomLocation := sbom.Value - dataSectionBase
|
||||
lengthLocation := sbomLength.Value - dataSectionBase
|
||||
@@ -344,7 +287,7 @@ func (ni nativeImageElf) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
}
|
||||
|
||||
// fetchPkgs obtains the packages from a Native Image given as a Mach O file.
|
||||
func (ni nativeImageMachO) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
func (ni nativeImageMachO) fetchPkgs() (pkgs []pkg.Package, relationships []artifact.Relationship, retErr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// this can happen in cases where a malformed binary is passed in can be initially parsed, but not
|
||||
@@ -359,7 +302,7 @@ func (ni nativeImageMachO) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
|
||||
bi := ni.file
|
||||
if bi.Symtab == nil {
|
||||
return nil, errors.New(nativeImageMissingSymbolsError)
|
||||
return nil, nil, errors.New(nativeImageMissingSymbolsError)
|
||||
}
|
||||
for _, s := range bi.Symtab.Syms {
|
||||
switch s.Name {
|
||||
@@ -372,17 +315,17 @@ func (ni nativeImageMachO) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
}
|
||||
}
|
||||
if sbom.Value == 0 || sbomLength.Value == 0 || svmVersion.Value == 0 {
|
||||
return nil, errors.New(nativeImageMissingSymbolsError)
|
||||
return nil, nil, errors.New(nativeImageMissingSymbolsError)
|
||||
}
|
||||
|
||||
dataSegment := bi.Segment("__DATA")
|
||||
if dataSegment == nil {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
dataBuf, err := dataSegment.Data()
|
||||
if err != nil {
|
||||
log.Tracef("cannot obtain buffer from data segment")
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
sbomLocation := sbom.Value - dataSegment.Addr
|
||||
lengthLocation := sbomLength.Value - dataSegment.Addr
|
||||
@@ -494,7 +437,7 @@ func (ni nativeImagePE) fetchSbomSymbols(content *exportContentPE) {
|
||||
}
|
||||
|
||||
// fetchPkgs obtains the packages from a Native Image given as a PE file.
|
||||
func (ni nativeImagePE) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
func (ni nativeImagePE) fetchPkgs() (pkgs []pkg.Package, relationships []artifact.Relationship, retErr error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// this can happen in cases where a malformed binary is passed in can be initially parsed, but not
|
||||
@@ -506,32 +449,32 @@ func (ni nativeImagePE) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
content, err := ni.fetchExportContent()
|
||||
if err != nil {
|
||||
log.Debugf("could not fetch the content of the export directory entry: %v", err)
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
ni.fetchSbomSymbols(content)
|
||||
if content.addressOfSbom == uint32(0) || content.addressOfSbomLength == uint32(0) || content.addressOfSvmVersion == uint32(0) {
|
||||
return nil, errors.New(nativeImageMissingSymbolsError)
|
||||
return nil, nil, errors.New(nativeImageMissingSymbolsError)
|
||||
}
|
||||
functionsBase := content.addressOfFunctions - ni.exportSymbols.VirtualAddress
|
||||
sbomOffset := content.addressOfSbom
|
||||
sbomAddress, err := ni.fetchExportFunctionPointer(functionsBase, sbomOffset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not fetch SBOM pointer from exported functions: %w", err)
|
||||
return nil, nil, fmt.Errorf("could not fetch SBOM pointer from exported functions: %w", err)
|
||||
}
|
||||
sbomLengthOffset := content.addressOfSbomLength
|
||||
sbomLengthAddress, err := ni.fetchExportFunctionPointer(functionsBase, sbomLengthOffset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not fetch SBOM length pointer from exported functions: %w", err)
|
||||
return nil, nil, fmt.Errorf("could not fetch SBOM length pointer from exported functions: %w", err)
|
||||
}
|
||||
bi := ni.file
|
||||
dataSection := bi.Section(".data")
|
||||
if dataSection == nil {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
dataBuf, err := dataSection.Data()
|
||||
if err != nil {
|
||||
log.Tracef("cannot obtain buffer from the java native-image .data section")
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
sbomLocation := sbomAddress - dataSection.VirtualAddress
|
||||
lengthLocation := sbomLengthAddress - dataSection.VirtualAddress
|
||||
@@ -540,8 +483,9 @@ func (ni nativeImagePE) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
}
|
||||
|
||||
// fetchPkgs provides the packages available in a UnionReader.
|
||||
func fetchPkgs(reader unionreader.UnionReader, filename string) []pkg.Package {
|
||||
func fetchPkgs(reader unionreader.UnionReader, filename string) ([]pkg.Package, []artifact.Relationship) {
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
imageFormats := []func(string, io.ReaderAt) (nativeImage, error){newElf, newMachO, newPE}
|
||||
|
||||
// NOTE: multiple readers are returned to cover universal binaries, which are files
|
||||
@@ -549,7 +493,7 @@ func fetchPkgs(reader unionreader.UnionReader, filename string) []pkg.Package {
|
||||
readers, err := unionreader.GetReaders(reader)
|
||||
if err != nil {
|
||||
log.Debugf("failed to open the java native-image binary: %v", err)
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
for _, r := range readers {
|
||||
for _, makeNativeImage := range imageFormats {
|
||||
@@ -560,47 +504,51 @@ func fetchPkgs(reader unionreader.UnionReader, filename string) []pkg.Package {
|
||||
if ni == nil {
|
||||
continue
|
||||
}
|
||||
newPkgs, err := ni.fetchPkgs()
|
||||
newPkgs, newRelationships, err := ni.fetchPkgs()
|
||||
if err != nil {
|
||||
log.Tracef("unable to extract SBOM from possible java native-image %s: %v", filename, err)
|
||||
continue
|
||||
}
|
||||
pkgs = append(pkgs, newPkgs...)
|
||||
relationships = append(relationships, newRelationships...)
|
||||
}
|
||||
}
|
||||
return pkgs
|
||||
return pkgs, relationships
|
||||
}
|
||||
|
||||
// Catalog attempts to find any native image executables reachable from a resolver.
|
||||
func (c *nativeImageCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
fileMatches, err := resolver.FilesByMIMEType(mimetype.ExecutableMIMETypeSet.List()...)
|
||||
if err != nil {
|
||||
return pkgs, nil, fmt.Errorf("failed to find binaries by mime types: %w", err)
|
||||
}
|
||||
|
||||
for _, location := range fileMatches {
|
||||
newPkgs, err := processLocation(location, resolver)
|
||||
newPkgs, newRelationships, err := processLocation(location, resolver)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pkgs = append(pkgs, newPkgs...)
|
||||
relationships = append(relationships, newRelationships...)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
return pkgs, relationships, nil
|
||||
}
|
||||
|
||||
func processLocation(location file.Location, resolver file.Resolver) ([]pkg.Package, error) {
|
||||
func processLocation(location file.Location, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
readerCloser, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
log.Debugf("error opening file: %v", err)
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
defer internal.CloseAndLogError(readerCloser, location.RealPath)
|
||||
|
||||
reader, err := unionreader.GetUnionReader(readerCloser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
return fetchPkgs(reader, location.RealPath), nil
|
||||
pkgs, relationships := fetchPkgs(reader, location.RealPath)
|
||||
return pkgs, relationships, nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -40,10 +42,11 @@ func TestParseNativeImage(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
parsed := false
|
||||
readers, err := unionreader.GetReaders(reader)
|
||||
assert.NoError(t, err)
|
||||
for _, r := range readers {
|
||||
ni, err := test.newFn(test.fixture, r)
|
||||
assert.NoError(t, err)
|
||||
_, err = ni.fetchPkgs()
|
||||
_, _, err = ni.fetchPkgs()
|
||||
if err == nil {
|
||||
t.Fatalf("should have failed to extract SBOM.")
|
||||
}
|
||||
@@ -60,77 +63,205 @@ func TestParseNativeImage(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestParseNativeImageSbom(t *testing.T) {
|
||||
const (
|
||||
nettyPurl = "pkg:maven/io.netty/netty-codec-http2@4.1.104.Final"
|
||||
micronautPurl = "pkg:maven/io.micronaut/core@4.2.3"
|
||||
mainAppPurl = "pkg:maven/com.oracle/main-test-app@1.0-SNAPSHOT"
|
||||
)
|
||||
|
||||
mainAppPkg := makePackage("main-test-app", "1.0-SNAPSHOT", mainAppPurl, []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "app",
|
||||
Product: "main-test-app",
|
||||
Version: "1.0-SNAPSHOT",
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
})
|
||||
|
||||
nettyPkg := makePackage("netty-codec-http2", "4.1.73.Final", nettyPurl, []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "codec",
|
||||
Product: "codec",
|
||||
Version: "4.1.73.Final",
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "codec",
|
||||
Product: "netty-codec-http2",
|
||||
Version: "4.1.73.Final",
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "codec",
|
||||
Product: "netty_codec_http2",
|
||||
Version: "4.1.73.Final",
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
})
|
||||
|
||||
micronautPkg := makePackage("core", "4.2.3", micronautPurl, []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "core",
|
||||
Product: "core",
|
||||
Version: "4.2.3",
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "micronaut",
|
||||
Product: "core",
|
||||
Version: "4.2.3",
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
})
|
||||
|
||||
basicPkg := makePackage("basic-lib", "1.0", "", nil)
|
||||
|
||||
tests := []struct {
|
||||
fixture string
|
||||
expected []pkg.Package
|
||||
fixture string
|
||||
expectedPackages []pkg.Package
|
||||
expectedRelations []artifact.Relationship
|
||||
}{
|
||||
{
|
||||
fixture: "test-fixtures/graalvm-sbom/micronaut.json",
|
||||
expected: []pkg.Package{
|
||||
fixture: "test-fixtures/graalvm-sbom/micronaut.json",
|
||||
expectedPackages: []pkg.Package{nettyPkg, micronautPkg, basicPkg, mainAppPkg},
|
||||
expectedRelations: []artifact.Relationship{
|
||||
{
|
||||
Name: "netty-codec-http2",
|
||||
Version: "4.1.73.Final",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.GraalVMNativeImagePkg,
|
||||
FoundBy: nativeImageCatalogerName,
|
||||
Metadata: pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: "io.netty",
|
||||
},
|
||||
},
|
||||
CPEs: []cpe.CPE{
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "codec",
|
||||
Product: "codec",
|
||||
Version: "4.1.73.Final",
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "codec",
|
||||
Product: "netty-codec-http2",
|
||||
Version: "4.1.73.Final",
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
{
|
||||
Attributes: cpe.Attributes{
|
||||
Part: "a",
|
||||
Vendor: "codec",
|
||||
Product: "netty_codec_http2",
|
||||
Version: "4.1.73.Final",
|
||||
},
|
||||
Source: "declared",
|
||||
},
|
||||
},
|
||||
From: nettyPkg,
|
||||
To: micronautPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(path.Base(test.fixture), func(t *testing.T) {
|
||||
// Create a buffer to resemble a compressed SBOM in a native image.
|
||||
sbom, err := os.ReadFile(test.fixture)
|
||||
assert.NoError(t, err)
|
||||
var b bytes.Buffer
|
||||
writebytes := bufio.NewWriter(&b)
|
||||
z := gzip.NewWriter(writebytes)
|
||||
_, err = z.Write(sbom)
|
||||
assert.NoError(t, err)
|
||||
_ = z.Close()
|
||||
_ = writebytes.Flush()
|
||||
compressedsbom := b.Bytes()
|
||||
sbomlength := uint64(len(compressedsbom))
|
||||
_ = binary.Write(writebytes, binary.LittleEndian, sbomlength)
|
||||
_ = writebytes.Flush()
|
||||
compressedsbom = b.Bytes()
|
||||
actual, err := decompressSbom(compressedsbom, 0, sbomlength)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
compressed, length := createCompressedSbom(t, test.fixture)
|
||||
actualPkgs, actualRels, err := decompressSbom(compressed, 0, length)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
verifyPackages(t, test.expectedPackages, actualPkgs)
|
||||
verifyRelationships(t, test.expectedRelations, actualRels)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// makePackage makes a package using the data that we know must be in the parsed package
|
||||
func makePackage(name, version, purl string, cpes []cpe.CPE) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
CPEs: cpes,
|
||||
PURL: purl,
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// createCompressedSbom creates a compressed a buffer to resemble a compressed SBOM in a native image.
|
||||
func createCompressedSbom(t *testing.T, filename string) ([]byte, uint64) {
|
||||
sbom, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
w := bufio.NewWriter(&b)
|
||||
z := gzip.NewWriter(w)
|
||||
|
||||
if _, err := z.Write(sbom); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
z.Close()
|
||||
w.Flush()
|
||||
|
||||
compressedSbom := b.Bytes()
|
||||
sbomLength := uint64(len(compressedSbom))
|
||||
|
||||
if err := binary.Write(w, binary.LittleEndian, sbomLength); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.Flush()
|
||||
|
||||
return b.Bytes(), sbomLength
|
||||
}
|
||||
|
||||
func verifyPackages(t *testing.T, expected, actual []pkg.Package) {
|
||||
expectedPkgMap := buildPackageMap(expected)
|
||||
actualPkgMap := buildPackageMap(actual)
|
||||
|
||||
for name, expectedPkg := range expectedPkgMap {
|
||||
actualPkg, exists := actualPkgMap[name]
|
||||
if !exists {
|
||||
t.Errorf("Expected package %s not found in actual packages", name)
|
||||
continue
|
||||
}
|
||||
verifyPackageFields(t, expectedPkg, actualPkg)
|
||||
}
|
||||
}
|
||||
|
||||
func buildPackageMap(packages []pkg.Package) map[string]pkg.Package {
|
||||
pkgMap := make(map[string]pkg.Package)
|
||||
for _, p := range packages {
|
||||
pkgMap[p.Name] = p
|
||||
}
|
||||
return pkgMap
|
||||
}
|
||||
|
||||
func verifyPackageFields(t *testing.T, expected, actual pkg.Package) {
|
||||
assert.Equal(t, expected.Name, actual.Name)
|
||||
assert.Equal(t, expected.Version, actual.Version)
|
||||
assert.Equal(t, expected.FoundBy, actual.FoundBy)
|
||||
assert.Equal(t, expected.PURL, actual.PURL)
|
||||
assert.ElementsMatch(t, expected.CPEs, actual.CPEs)
|
||||
}
|
||||
|
||||
func verifyRelationships(t *testing.T, expected, actual []artifact.Relationship) {
|
||||
expectedRelMap := buildRelationshipMap(expected)
|
||||
actualRelMap := buildRelationshipMap(actual)
|
||||
|
||||
for key, expectedRels := range expectedRelMap {
|
||||
actualRels, exists := actualRelMap[key]
|
||||
if !exists {
|
||||
t.Errorf("Expected relationship %s not found in actual relationships", key)
|
||||
continue
|
||||
}
|
||||
verifyRelationshipFields(t, expectedRels, actualRels)
|
||||
}
|
||||
}
|
||||
|
||||
func buildRelationshipMap(relationships []artifact.Relationship) map[string][]artifact.Relationship {
|
||||
relMap := make(map[string][]artifact.Relationship)
|
||||
for _, rel := range relationships {
|
||||
// we cannot control the id, so use the names instead
|
||||
key := fmt.Sprintf("%s->%s", rel.From.(pkg.Package).Name, rel.To.(pkg.Package).Name)
|
||||
relMap[key] = append(relMap[key], rel)
|
||||
}
|
||||
return relMap
|
||||
}
|
||||
|
||||
func verifyRelationshipFields(t *testing.T, expected, actual []artifact.Relationship) {
|
||||
assert.Equal(t, len(expected), len(actual))
|
||||
for i := range expected {
|
||||
assert.Equal(t, expected[i].Type, actual[i].Type)
|
||||
}
|
||||
}
|
||||
|
||||
+30
-10
@@ -1,4 +1,4 @@
|
||||
package java
|
||||
package maven
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
@@ -15,6 +15,35 @@ import (
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
const mavenBaseURL = "https://repo1.maven.org/maven2"
|
||||
|
||||
type Config struct {
|
||||
// UseNetwork instructs the maven resolver to use network operations to resolve maven artifacts
|
||||
UseNetwork bool `yaml:"use-network" json:"use-network" mapstructure:"use-network"`
|
||||
|
||||
// Repositories are the set of remote repositories the network resolution should use
|
||||
Repositories []string `yaml:"maven-repositories" json:"maven-repositories" mapstructure:"maven-repositories"`
|
||||
|
||||
// UseLocalRepository instructs the maven resolver to look in the host maven cache, usually ~/.m2/repository
|
||||
UseLocalRepository bool `yaml:"use-maven-local-repository" json:"use-maven-local-repository" mapstructure:"use-maven-local-repository"`
|
||||
|
||||
// LocalRepositoryDir is an alternate directory to use to look up the local repository
|
||||
LocalRepositoryDir string `yaml:"maven-local-repository-dir" json:"maven-local-repository-dir" mapstructure:"maven-local-repository-dir"`
|
||||
|
||||
// MaxParentRecursiveDepth allows for a maximum depth to use when recursively resolving parent poms and other information, 0 disables any maximum
|
||||
MaxParentRecursiveDepth int `yaml:"max-parent-recursive-depth" json:"max-parent-recursive-depth" mapstructure:"max-parent-recursive-depth"`
|
||||
}
|
||||
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
UseNetwork: false,
|
||||
Repositories: []string{mavenBaseURL},
|
||||
UseLocalRepository: false,
|
||||
LocalRepositoryDir: defaultMavenLocalRepoDir(),
|
||||
MaxParentRecursiveDepth: 0, // unlimited
|
||||
}
|
||||
}
|
||||
|
||||
// defaultMavenLocalRepoDir gets default location of the Maven local repository, generally at <USER HOME DIR>/.m2/repository
|
||||
func defaultMavenLocalRepoDir() string {
|
||||
homeDir, err := homedir.Dir()
|
||||
@@ -49,15 +78,6 @@ func getSettingsXMLLocalRepository(settingsXML io.Reader) string {
|
||||
return s.LocalRepository
|
||||
}
|
||||
|
||||
// deref dereferences ptr if not nil, or returns the type default value if ptr is nil
|
||||
func deref[T any](ptr *T) T {
|
||||
if ptr == nil {
|
||||
var t T
|
||||
return t
|
||||
}
|
||||
return *ptr
|
||||
}
|
||||
|
||||
// remotePomURL returns a URL to download a POM from a remote repository
|
||||
func remotePomURL(repoURL, groupID, artifactID, version string) (requestURL string, err error) {
|
||||
// groupID needs to go from maven.org -> maven/org
|
||||
+5
-3
@@ -1,4 +1,4 @@
|
||||
package java
|
||||
package maven
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
)
|
||||
|
||||
func Test_defaultMavenLocalRepoDir(t *testing.T) {
|
||||
@@ -69,7 +71,7 @@ func Test_getSettingsXmlLocalRepository(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.expected, func(t *testing.T) {
|
||||
f, _ := os.Open(test.file)
|
||||
defer f.Close()
|
||||
defer internal.CloseAndLogError(f, test.file)
|
||||
got := getSettingsXMLLocalRepository(f)
|
||||
require.Equal(t, test.expected, got)
|
||||
})
|
||||
@@ -85,7 +87,7 @@ func Test_remotePomURL(t *testing.T) {
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "formatMavenURL correctly assembles the pom URL",
|
||||
name: "remotePomURL correctly assembles the pom URL",
|
||||
groupID: "org.springframework.boot",
|
||||
artifactID: "spring-boot-starter-test",
|
||||
version: "3.1.5",
|
||||
@@ -0,0 +1,67 @@
|
||||
package maven
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/saintfish/chardet"
|
||||
"github.com/vifraa/gopom"
|
||||
"golang.org/x/net/html/charset"
|
||||
)
|
||||
|
||||
type (
|
||||
Project = gopom.Project
|
||||
Properties = gopom.Properties
|
||||
Parent = gopom.Parent
|
||||
Dependency = gopom.Dependency
|
||||
License = gopom.License
|
||||
)
|
||||
|
||||
// ParsePomXML decodes a pom XML file, detecting and converting non-UTF-8 charsets. this DOES NOT perform any logic to resolve properties such as groupID, artifactID, and version
|
||||
func ParsePomXML(content io.Reader) (project *Project, err error) {
|
||||
inputReader, err := getUtf8Reader(content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read pom.xml: %w", err)
|
||||
}
|
||||
|
||||
decoder := xml.NewDecoder(inputReader)
|
||||
// when an xml file has a character set declaration (e.g. '<?xml version="1.0" encoding="ISO-8859-1"?>') read that and use the correct decoder
|
||||
decoder.CharsetReader = charset.NewReaderLabel
|
||||
|
||||
project = &Project{}
|
||||
if err := decoder.Decode(project); err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal pom.xml: %w", err)
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func getUtf8Reader(content io.Reader) (io.Reader, error) {
|
||||
pomContents, err := io.ReadAll(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
detector := chardet.NewTextDetector()
|
||||
detection, err := detector.DetectBest(pomContents)
|
||||
|
||||
var inputReader io.Reader
|
||||
if err == nil && detection != nil {
|
||||
if detection.Charset == "UTF-8" {
|
||||
inputReader = bytes.NewReader(pomContents)
|
||||
} else {
|
||||
inputReader, err = charset.NewReaderLabel(detection.Charset, bytes.NewReader(pomContents))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get encoding: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we could not detect the encoding, but we want a valid file to read. Replace unreadable
|
||||
// characters with the UTF-8 replacement character.
|
||||
inputReader = strings.NewReader(strings.ToValidUTF8(string(pomContents), "�"))
|
||||
}
|
||||
return inputReader, nil
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package maven
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
)
|
||||
|
||||
func Test_getUtf8Reader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
contents string
|
||||
}{
|
||||
{
|
||||
name: "unknown encoding",
|
||||
// random binary contents
|
||||
contents: "BkiJz02JyEWE0nXR6TH///9NicpJweEETIucJIgAAABJicxPjQwhTY1JCE05WQh0BU2J0eunTYshTIusJIAAAAAPHwBNOeV1BUUx2+tWTIlUJDhMiUwkSEyJRCQgSIl8JFBMiQ==",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(tt.contents))
|
||||
|
||||
got, err := getUtf8Reader(decoder)
|
||||
require.NoError(t, err)
|
||||
gotBytes, err := io.ReadAll(got)
|
||||
require.NoError(t, err)
|
||||
// if we couldn't decode the section as UTF-8, we should get a replacement character
|
||||
assert.Contains(t, string(gotBytes), "�")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_decodePomXML_surviveNonUtf8Encoding(t *testing.T) {
|
||||
// regression for https://github.com/anchore/syft/issues/2044
|
||||
|
||||
// we are storing the base64 contents of the pom.xml file. We are doing this to prevent accidental changes to the
|
||||
// file, which is extremely important for this test.
|
||||
|
||||
// for instance, even changing a single character in the file and saving in an IntelliJ IDE will automatically
|
||||
// convert the file to UTF-8, which will break this test:
|
||||
|
||||
// xxd with the original pom.xml
|
||||
// 00000780: 6964 3e0d 0a20 2020 2020 2020 2020 2020 id>..
|
||||
// 00000790: 203c 6e61 6d65 3e4a e972 f46d 6520 4d69 <name>J.r.me Mi
|
||||
// 000007a0: 7263 3c2f 6e61 6d65 3e0d 0a20 2020 2020 rc</name>..
|
||||
|
||||
// xxd with the pom.xml converted to UTF-8 (from a simple change with IntelliJ)
|
||||
// 00000780: 6964 3e0d 0a20 2020 2020 2020 2020 2020 id>..
|
||||
// 00000790: 203c 6e61 6d65 3e4a efbf bd72 efbf bd6d <name>J...r...m
|
||||
// 000007a0: 6520 4d69 7263 3c2f 6e61 6d65 3e0d 0a20 e Mirc</name>..
|
||||
|
||||
// Note that the name "Jérôme Mirc" was originally interpreted as "J.r.me Mi" and after the save
|
||||
// is now encoded as "J...r...m" which is not what we want (note the extra bytes for each non UTF-8 character.
|
||||
// The original 0xe9 byte (é) was converted to 0xefbfbd (�) which is the UTF-8 replacement character.
|
||||
// This is quite silly on the part of IntelliJ, but it is what it is.
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
fixture string
|
||||
}{
|
||||
{
|
||||
name: "undeclared encoding",
|
||||
fixture: "test-fixtures/undeclared-iso-8859-encoded-pom.xml.base64",
|
||||
},
|
||||
{
|
||||
name: "declared encoding",
|
||||
fixture: "test-fixtures/declared-iso-8859-encoded-pom.xml.base64",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
fh, err := os.Open(c.fixture)
|
||||
require.NoError(t, err)
|
||||
defer internal.CloseAndLogError(fh, c.fixture)
|
||||
|
||||
decoder := base64.NewDecoder(base64.StdEncoding, fh)
|
||||
|
||||
proj, err := ParsePomXML(decoder)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, proj.Developers)
|
||||
})
|
||||
}
|
||||
}
|
||||
+197
-105
@@ -1,4 +1,4 @@
|
||||
package java
|
||||
package maven
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -24,56 +24,69 @@ import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
// mavenID is the unique identifier for a package in Maven
|
||||
type mavenID struct {
|
||||
// ID is the unique identifier for a package in Maven
|
||||
type ID struct {
|
||||
GroupID string
|
||||
ArtifactID string
|
||||
Version string
|
||||
}
|
||||
|
||||
func (m mavenID) String() string {
|
||||
func NewID(groupID, artifactID, version string) ID {
|
||||
return ID{
|
||||
GroupID: groupID,
|
||||
ArtifactID: artifactID,
|
||||
Version: version,
|
||||
}
|
||||
}
|
||||
|
||||
func (m ID) String() string {
|
||||
return fmt.Sprintf("(groupId: %s artifactId: %s version: %s)", m.GroupID, m.ArtifactID, m.Version)
|
||||
}
|
||||
|
||||
// Valid indicates that the given maven ID has values for groupId, artifactId, and version
|
||||
func (m ID) Valid() bool {
|
||||
return m.GroupID != "" && m.ArtifactID != "" && m.Version != ""
|
||||
}
|
||||
|
||||
var expressionMatcher = regexp.MustCompile("[$][{][^}]+[}]")
|
||||
|
||||
// mavenResolver is a short-lived utility to resolve maven poms from multiple sources, including:
|
||||
// Resolver is a short-lived utility to resolve maven poms from multiple sources, including:
|
||||
// the scanned filesystem, local maven cache directories, remote maven repositories, and the syft cache
|
||||
type mavenResolver struct {
|
||||
cfg ArchiveCatalogerConfig
|
||||
type Resolver struct {
|
||||
cfg Config
|
||||
cache cache.Cache
|
||||
resolved map[mavenID]*gopom.Project
|
||||
resolved map[ID]*Project
|
||||
remoteRequestTimeout time.Duration
|
||||
checkedLocalRepo bool
|
||||
// fileResolver and pomLocations are used to resolve parent poms by relativePath
|
||||
fileResolver file.Resolver
|
||||
pomLocations map[*gopom.Project]file.Location
|
||||
pomLocations map[*Project]file.Location
|
||||
}
|
||||
|
||||
// newMavenResolver constructs a new mavenResolver with the given configuration.
|
||||
// NewResolver constructs a new Resolver with the given configuration.
|
||||
// NOTE: the fileResolver is optional and if provided will be used to resolve parent poms by relative path
|
||||
func newMavenResolver(fileResolver file.Resolver, cfg ArchiveCatalogerConfig) *mavenResolver {
|
||||
return &mavenResolver{
|
||||
func NewResolver(fileResolver file.Resolver, cfg Config) *Resolver {
|
||||
return &Resolver{
|
||||
cfg: cfg,
|
||||
cache: cache.GetManager().GetCache("java/maven/repo", "v1"),
|
||||
resolved: map[mavenID]*gopom.Project{},
|
||||
resolved: map[ID]*Project{},
|
||||
remoteRequestTimeout: time.Second * 10,
|
||||
fileResolver: fileResolver,
|
||||
pomLocations: map[*gopom.Project]file.Location{},
|
||||
pomLocations: map[*Project]file.Location{},
|
||||
}
|
||||
}
|
||||
|
||||
// getPropertyValue gets property values by emulating maven property resolution logic, looking in the project's variables
|
||||
// ResolveProperty gets property values by emulating maven property resolution logic, looking in the project's variables
|
||||
// as well as supporting the project expressions like ${project.parent.groupId}.
|
||||
// Properties which are not resolved result in empty string ""
|
||||
func (r *mavenResolver) getPropertyValue(ctx context.Context, propertyValue *string, resolutionContext ...*gopom.Project) string {
|
||||
return r.resolvePropertyValue(ctx, propertyValue, nil, resolutionContext...)
|
||||
func (r *Resolver) ResolveProperty(ctx context.Context, pom *Project, propertyValue *string) string {
|
||||
return r.resolvePropertyValue(ctx, propertyValue, nil, pom)
|
||||
}
|
||||
|
||||
// resolvePropertyValue resolves property values by emulating maven property resolution logic, looking in the project's variables
|
||||
// as well as supporting the project expressions like ${project.parent.groupId}.
|
||||
// Properties which are not resolved result in empty string ""
|
||||
func (r *mavenResolver) resolvePropertyValue(ctx context.Context, propertyValue *string, resolvingProperties []string, resolutionContext ...*gopom.Project) string {
|
||||
func (r *Resolver) resolvePropertyValue(ctx context.Context, propertyValue *string, resolvingProperties []string, resolutionContext ...*Project) string {
|
||||
if propertyValue == nil {
|
||||
return ""
|
||||
}
|
||||
@@ -86,7 +99,7 @@ func (r *mavenResolver) resolvePropertyValue(ctx context.Context, propertyValue
|
||||
}
|
||||
|
||||
// resolveExpression resolves an expression, which may be a plain string or a string with ${ property.references }
|
||||
func (r *mavenResolver) resolveExpression(ctx context.Context, resolutionContext []*gopom.Project, expression string, resolvingProperties []string) (string, error) {
|
||||
func (r *Resolver) resolveExpression(ctx context.Context, resolutionContext []*Project, expression string, resolvingProperties []string) (string, error) {
|
||||
log.Tracef("resolving expression: '%v' in context: %v", expression, resolutionContext)
|
||||
|
||||
var errs error
|
||||
@@ -103,7 +116,7 @@ func (r *mavenResolver) resolveExpression(ctx context.Context, resolutionContext
|
||||
}
|
||||
|
||||
// resolveProperty resolves properties recursively from the root project
|
||||
func (r *mavenResolver) resolveProperty(ctx context.Context, resolutionContext []*gopom.Project, propertyExpression string, resolvingProperties []string) (string, error) {
|
||||
func (r *Resolver) resolveProperty(ctx context.Context, resolutionContext []*Project, propertyExpression string, resolvingProperties []string) (string, error) {
|
||||
// prevent cycles
|
||||
if slices.Contains(resolvingProperties, propertyExpression) {
|
||||
return "", fmt.Errorf("cycle detected resolving: %s", propertyExpression)
|
||||
@@ -122,9 +135,14 @@ func (r *mavenResolver) resolveProperty(ctx context.Context, resolutionContext [
|
||||
return value, nil
|
||||
}
|
||||
|
||||
var resolvingParents []*Project
|
||||
for _, pom := range resolutionContext {
|
||||
current := pom
|
||||
for parentDepth := 0; current != nil; parentDepth++ {
|
||||
if slices.Contains(resolvingParents, current) {
|
||||
log.WithFields("property", propertyExpression, "mavenID", r.resolveID(ctx, resolvingProperties, resolvingParents...)).Error("got circular reference while resolving property")
|
||||
break // some sort of circular reference -- we've already seen this project
|
||||
}
|
||||
if r.cfg.MaxParentRecursiveDepth > 0 && parentDepth > r.cfg.MaxParentRecursiveDepth {
|
||||
return "", fmt.Errorf("maximum parent recursive depth (%v) reached resolving property: %v", r.cfg.MaxParentRecursiveDepth, propertyExpression)
|
||||
}
|
||||
@@ -133,6 +151,7 @@ func (r *mavenResolver) resolveProperty(ctx context.Context, resolutionContext [
|
||||
return r.resolveExpression(ctx, resolutionContext, value, resolvingProperties) // property values can contain expressions
|
||||
}
|
||||
}
|
||||
resolvingParents = append(resolvingParents, current)
|
||||
current, err = r.resolveParent(ctx, current, resolvingProperties...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -146,7 +165,7 @@ func (r *mavenResolver) resolveProperty(ctx context.Context, resolutionContext [
|
||||
// resolveProjectProperty resolves properties on the project
|
||||
//
|
||||
//nolint:gocognit
|
||||
func (r *mavenResolver) resolveProjectProperty(ctx context.Context, resolutionContext []*gopom.Project, pom *gopom.Project, propertyExpression string, resolving []string) (string, error) {
|
||||
func (r *Resolver) resolveProjectProperty(ctx context.Context, resolutionContext []*Project, pom *Project, propertyExpression string, resolving []string) (string, error) {
|
||||
// see if we have a project.x expression and process this based
|
||||
// on the xml tags in gopom
|
||||
parts := strings.Split(propertyExpression, ".")
|
||||
@@ -210,79 +229,107 @@ func (r *mavenResolver) resolveProjectProperty(ctx context.Context, resolutionCo
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// getMavenID creates a new mavenID from a pom, resolving parent information as necessary
|
||||
func (r *mavenResolver) getMavenID(ctx context.Context, resolutionContext ...*gopom.Project) mavenID {
|
||||
return r.resolveMavenID(ctx, nil, resolutionContext...)
|
||||
// ResolveParent resolves the parent definition, and returns a POM for the parent, which is possibly incomplete, or nil
|
||||
func (r *Resolver) ResolveParent(ctx context.Context, pom *Project) (*Project, error) {
|
||||
if pom == nil || pom.Parent == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
parent, err := r.resolveParent(ctx, pom)
|
||||
if parent != nil {
|
||||
return parent, err
|
||||
}
|
||||
|
||||
groupID := r.ResolveProperty(ctx, pom, pom.Parent.GroupID)
|
||||
if groupID == "" {
|
||||
groupID = r.ResolveProperty(ctx, pom, pom.GroupID)
|
||||
}
|
||||
artifactID := r.ResolveProperty(ctx, pom, pom.Parent.ArtifactID)
|
||||
version := r.ResolveProperty(ctx, pom, pom.Parent.Version)
|
||||
|
||||
if artifactID != "" && version != "" {
|
||||
return &Project{
|
||||
GroupID: &groupID,
|
||||
ArtifactID: &artifactID,
|
||||
Version: &version,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unsufficient information to create a parent pom project, id: %s", NewID(groupID, artifactID, version))
|
||||
}
|
||||
|
||||
// resolveMavenID creates a new mavenID from a pom, resolving parent information as necessary
|
||||
func (r *mavenResolver) resolveMavenID(ctx context.Context, resolvingProperties []string, resolutionContext ...*gopom.Project) mavenID {
|
||||
// ResolveID creates an ID from a pom, resolving parent information as necessary
|
||||
func (r *Resolver) ResolveID(ctx context.Context, pom *Project) ID {
|
||||
return r.resolveID(ctx, nil, pom)
|
||||
}
|
||||
|
||||
// resolveID creates a new ID from a pom, resolving parent information as necessary
|
||||
func (r *Resolver) resolveID(ctx context.Context, resolvingProperties []string, resolutionContext ...*Project) ID {
|
||||
if len(resolutionContext) == 0 || resolutionContext[0] == nil {
|
||||
return mavenID{}
|
||||
return ID{}
|
||||
}
|
||||
pom := resolutionContext[len(resolutionContext)-1] // get topmost pom
|
||||
if pom == nil {
|
||||
return mavenID{}
|
||||
return ID{}
|
||||
}
|
||||
|
||||
groupID := r.resolvePropertyValue(ctx, pom.GroupID, resolvingProperties, resolutionContext...)
|
||||
artifactID := r.resolvePropertyValue(ctx, pom.ArtifactID, resolvingProperties, resolutionContext...)
|
||||
version := r.resolvePropertyValue(ctx, pom.Version, resolvingProperties, resolutionContext...)
|
||||
if pom.Parent != nil {
|
||||
if groupID == "" {
|
||||
// groupId and version are able to be inherited from the parent, but importantly: not artifactId. see:
|
||||
// https://maven.apache.org/guides/introduction/introduction-to-the-pom.html#the-solution
|
||||
if groupID == "" && deref(pom.GroupID) == "" {
|
||||
groupID = r.resolvePropertyValue(ctx, pom.Parent.GroupID, resolvingProperties, resolutionContext...)
|
||||
}
|
||||
if artifactID == "" {
|
||||
artifactID = r.resolvePropertyValue(ctx, pom.Parent.ArtifactID, resolvingProperties, resolutionContext...)
|
||||
}
|
||||
if version == "" {
|
||||
if version == "" && deref(pom.Version) == "" {
|
||||
version = r.resolvePropertyValue(ctx, pom.Parent.Version, resolvingProperties, resolutionContext...)
|
||||
}
|
||||
}
|
||||
return mavenID{groupID, artifactID, version}
|
||||
return ID{groupID, artifactID, version}
|
||||
}
|
||||
|
||||
// resolveDependencyID creates a new mavenID from a dependency element in a pom, resolving information as necessary
|
||||
func (r *mavenResolver) resolveDependencyID(ctx context.Context, pom *gopom.Project, dep gopom.Dependency) mavenID {
|
||||
// ResolveDependencyID creates an ID from a dependency element in a pom, resolving information as necessary
|
||||
func (r *Resolver) ResolveDependencyID(ctx context.Context, pom *Project, dep Dependency) ID {
|
||||
if pom == nil {
|
||||
return mavenID{}
|
||||
return ID{}
|
||||
}
|
||||
|
||||
groupID := r.getPropertyValue(ctx, dep.GroupID, pom)
|
||||
artifactID := r.getPropertyValue(ctx, dep.ArtifactID, pom)
|
||||
version := r.getPropertyValue(ctx, dep.Version, pom)
|
||||
groupID := r.resolvePropertyValue(ctx, dep.GroupID, nil, pom)
|
||||
artifactID := r.resolvePropertyValue(ctx, dep.ArtifactID, nil, pom)
|
||||
version := r.resolvePropertyValue(ctx, dep.Version, nil, pom)
|
||||
|
||||
var err error
|
||||
if version == "" {
|
||||
version, err = r.findInheritedVersion(ctx, pom, groupID, artifactID)
|
||||
version, err = r.resolveInheritedVersion(ctx, pom, groupID, artifactID)
|
||||
}
|
||||
|
||||
depID := mavenID{groupID, artifactID, version}
|
||||
depID := ID{groupID, artifactID, version}
|
||||
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "mavenID", r.getMavenID(ctx, pom), "dependencyID", depID)
|
||||
log.WithFields("error", err, "ID", r.ResolveID(ctx, pom), "dependencyID", depID)
|
||||
}
|
||||
|
||||
return depID
|
||||
}
|
||||
|
||||
// findPom gets a pom from cache, local repository, or from a remote Maven repository depending on configuration
|
||||
func (r *mavenResolver) findPom(ctx context.Context, groupID, artifactID, version string) (*gopom.Project, error) {
|
||||
// FindPom gets a pom from cache, local repository, or from a remote Maven repository depending on configuration
|
||||
func (r *Resolver) FindPom(ctx context.Context, groupID, artifactID, version string) (*Project, error) {
|
||||
if groupID == "" || artifactID == "" || version == "" {
|
||||
return nil, fmt.Errorf("invalid maven pom specification, require non-empty values for groupID: '%s', artifactID: '%s', version: '%s'", groupID, artifactID, version)
|
||||
}
|
||||
|
||||
id := mavenID{groupID, artifactID, version}
|
||||
pom := r.resolved[id]
|
||||
id := ID{groupID, artifactID, version}
|
||||
existingPom := r.resolved[id]
|
||||
|
||||
if pom != nil {
|
||||
return pom, nil
|
||||
if existingPom != nil {
|
||||
return existingPom, nil
|
||||
}
|
||||
|
||||
var errs error
|
||||
|
||||
// try to resolve first from local maven repo
|
||||
if r.cfg.UseMavenLocalRepository {
|
||||
if r.cfg.UseLocalRepository {
|
||||
pom, err := r.findPomInLocalRepository(groupID, artifactID, version)
|
||||
if pom != nil {
|
||||
r.resolved[id] = pom
|
||||
@@ -292,8 +339,8 @@ func (r *mavenResolver) findPom(ctx context.Context, groupID, artifactID, versio
|
||||
}
|
||||
|
||||
// resolve via network maven repository
|
||||
if pom == nil && r.cfg.UseNetwork {
|
||||
pom, err := r.findPomInRemoteRepository(ctx, groupID, artifactID, version)
|
||||
if r.cfg.UseNetwork {
|
||||
pom, err := r.findPomInRemotes(ctx, groupID, artifactID, version)
|
||||
if pom != nil {
|
||||
r.resolved[id] = pom
|
||||
return pom, nil
|
||||
@@ -305,35 +352,50 @@ func (r *mavenResolver) findPom(ctx context.Context, groupID, artifactID, versio
|
||||
}
|
||||
|
||||
// findPomInLocalRepository attempts to get the POM from the users local maven repository
|
||||
func (r *mavenResolver) findPomInLocalRepository(groupID, artifactID, version string) (*gopom.Project, error) {
|
||||
func (r *Resolver) findPomInLocalRepository(groupID, artifactID, version string) (*Project, error) {
|
||||
groupPath := filepath.Join(strings.Split(groupID, ".")...)
|
||||
pomFilePath := filepath.Join(r.cfg.MavenLocalRepositoryDir, groupPath, artifactID, version, artifactID+"-"+version+".pom")
|
||||
pomFilePath := filepath.Join(r.cfg.LocalRepositoryDir, groupPath, artifactID, version, artifactID+"-"+version+".pom")
|
||||
pomFile, err := os.Open(pomFilePath)
|
||||
if err != nil {
|
||||
if !r.checkedLocalRepo && errors.Is(err, os.ErrNotExist) {
|
||||
r.checkedLocalRepo = true
|
||||
// check if the directory exists at all, and if not just stop trying to resolve local maven files
|
||||
fi, err := os.Stat(r.cfg.MavenLocalRepositoryDir)
|
||||
fi, err := os.Stat(r.cfg.LocalRepositoryDir)
|
||||
if errors.Is(err, os.ErrNotExist) || !fi.IsDir() {
|
||||
log.WithFields("error", err, "repositoryDir", r.cfg.MavenLocalRepositoryDir).
|
||||
log.WithFields("error", err, "repositoryDir", r.cfg.LocalRepositoryDir).
|
||||
Info("local maven repository is not a readable directory, stopping local resolution")
|
||||
r.cfg.UseMavenLocalRepository = false
|
||||
r.cfg.UseLocalRepository = false
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer internal.CloseAndLogError(pomFile, pomFilePath)
|
||||
|
||||
return decodePomXML(pomFile)
|
||||
return ParsePomXML(pomFile)
|
||||
}
|
||||
|
||||
// findPomInRemotes download the pom file from all configured Maven repositories over HTTP
|
||||
func (r *Resolver) findPomInRemotes(ctx context.Context, groupID, artifactID, version string) (*Project, error) {
|
||||
var errs error
|
||||
for _, repo := range r.cfg.Repositories {
|
||||
pom, err := r.findPomInRemoteRepository(ctx, repo, groupID, artifactID, version)
|
||||
if err != nil {
|
||||
errs = errors.Join(errs, err)
|
||||
}
|
||||
if pom != nil {
|
||||
return pom, err
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("pom for %v not found in any remote repository: %w", ID{groupID, artifactID, version}, errs)
|
||||
}
|
||||
|
||||
// findPomInRemoteRepository download the pom file from a (remote) Maven repository over HTTP
|
||||
func (r *mavenResolver) findPomInRemoteRepository(ctx context.Context, groupID, artifactID, version string) (*gopom.Project, error) {
|
||||
func (r *Resolver) findPomInRemoteRepository(ctx context.Context, repo string, groupID, artifactID, version string) (*Project, error) {
|
||||
if groupID == "" || artifactID == "" || version == "" {
|
||||
return nil, fmt.Errorf("missing/incomplete maven artifact coordinates -- groupId: '%s' artifactId: '%s', version: '%s'", groupID, artifactID, version)
|
||||
}
|
||||
|
||||
requestURL, err := remotePomURL(r.cfg.MavenBaseURL, groupID, artifactID, version)
|
||||
requestURL, err := remotePomURL(repo, groupID, artifactID, version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to find pom in remote due to: %w", err)
|
||||
}
|
||||
@@ -377,7 +439,7 @@ func (r *mavenResolver) findPomInRemoteRepository(ctx context.Context, groupID,
|
||||
if reader, ok := reader.(io.Closer); ok {
|
||||
defer internal.CloseAndLogError(reader, requestURL)
|
||||
}
|
||||
pom, err := decodePomXML(reader)
|
||||
pom, err := ParsePomXML(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse pom from Maven repository url %v: %w", requestURL, err)
|
||||
}
|
||||
@@ -388,7 +450,7 @@ func (r *mavenResolver) findPomInRemoteRepository(ctx context.Context, groupID,
|
||||
// this function is guaranteed to return an unread reader for the correct contents.
|
||||
// NOTE: this could be promoted to the internal cache package as a specialized version of the cache.Resolver
|
||||
// if there are more users of this functionality
|
||||
func (r *mavenResolver) cacheResolveReader(key string, resolve func() (io.ReadCloser, error)) (io.Reader, error) {
|
||||
func (r *Resolver) cacheResolveReader(key string, resolve func() (io.ReadCloser, error)) (io.Reader, error) {
|
||||
reader, err := r.cache.Read(key)
|
||||
if err == nil && reader != nil {
|
||||
return reader, err
|
||||
@@ -410,7 +472,7 @@ func (r *mavenResolver) cacheResolveReader(key string, resolve func() (io.ReadCl
|
||||
}
|
||||
|
||||
// resolveParent attempts to resolve the parent for the given pom
|
||||
func (r *mavenResolver) resolveParent(ctx context.Context, pom *gopom.Project, resolvingProperties ...string) (*gopom.Project, error) {
|
||||
func (r *Resolver) resolveParent(ctx context.Context, pom *Project, resolvingProperties ...string) (*Project, error) {
|
||||
if pom == nil || pom.Parent == nil {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -422,7 +484,7 @@ func (r *mavenResolver) resolveParent(ctx context.Context, pom *gopom.Project, r
|
||||
version := r.resolvePropertyValue(ctx, parent.Version, resolvingProperties, &pomWithoutParent)
|
||||
|
||||
// check cache before resolving
|
||||
parentID := mavenID{groupID, artifactID, version}
|
||||
parentID := ID{groupID, artifactID, version}
|
||||
if resolvedParent, ok := r.resolved[parentID]; ok {
|
||||
return resolvedParent, nil
|
||||
}
|
||||
@@ -434,21 +496,21 @@ func (r *mavenResolver) resolveParent(ctx context.Context, pom *gopom.Project, r
|
||||
}
|
||||
|
||||
// find POM normally
|
||||
return r.findPom(ctx, groupID, artifactID, version)
|
||||
return r.FindPom(ctx, groupID, artifactID, version)
|
||||
}
|
||||
|
||||
// findInheritedVersion attempts to find the version of a dependency (groupID, artifactID) by searching all parent poms and imported managed dependencies
|
||||
// resolveInheritedVersion attempts to find the version of a dependency (groupID, artifactID) by searching all parent poms and imported managed dependencies
|
||||
//
|
||||
//nolint:gocognit,funlen
|
||||
func (r *mavenResolver) findInheritedVersion(ctx context.Context, pom *gopom.Project, groupID, artifactID string, resolutionContext ...*gopom.Project) (string, error) {
|
||||
func (r *Resolver) resolveInheritedVersion(ctx context.Context, pom *Project, groupID, artifactID string, resolutionContext ...*Project) (string, error) {
|
||||
if pom == nil {
|
||||
return "", fmt.Errorf("nil pom provided to findInheritedVersion")
|
||||
}
|
||||
if r.cfg.MaxParentRecursiveDepth > 0 && len(resolutionContext) > r.cfg.MaxParentRecursiveDepth {
|
||||
return "", fmt.Errorf("maximum depth reached attempting to resolve version for: %s:%s at: %v", groupID, artifactID, r.getMavenID(ctx, pom))
|
||||
return "", fmt.Errorf("maximum depth reached attempting to resolve version for: %s:%s at: %v", groupID, artifactID, r.ResolveID(ctx, pom))
|
||||
}
|
||||
if slices.Contains(resolutionContext, pom) {
|
||||
return "", fmt.Errorf("cycle detected attempting to resolve version for: %s:%s at: %v", groupID, artifactID, r.getMavenID(ctx, pom))
|
||||
return "", fmt.Errorf("cycle detected attempting to resolve version for: %s:%s at: %v", groupID, artifactID, r.ResolveID(ctx, pom))
|
||||
}
|
||||
resolutionContext = append(resolutionContext, pom)
|
||||
|
||||
@@ -457,10 +519,10 @@ func (r *mavenResolver) findInheritedVersion(ctx context.Context, pom *gopom.Pro
|
||||
|
||||
// check for entries in dependencyManagement first
|
||||
for _, dep := range pomManagedDependencies(pom) {
|
||||
depGroupID := r.getPropertyValue(ctx, dep.GroupID, resolutionContext...)
|
||||
depArtifactID := r.getPropertyValue(ctx, dep.ArtifactID, resolutionContext...)
|
||||
depGroupID := r.resolvePropertyValue(ctx, dep.GroupID, nil, resolutionContext...)
|
||||
depArtifactID := r.resolvePropertyValue(ctx, dep.ArtifactID, nil, resolutionContext...)
|
||||
if depGroupID == groupID && depArtifactID == artifactID {
|
||||
version = r.getPropertyValue(ctx, dep.Version, resolutionContext...)
|
||||
version = r.resolvePropertyValue(ctx, dep.Version, nil, resolutionContext...)
|
||||
if version != "" {
|
||||
return version, nil
|
||||
}
|
||||
@@ -468,17 +530,17 @@ func (r *mavenResolver) findInheritedVersion(ctx context.Context, pom *gopom.Pro
|
||||
|
||||
// imported pom files should be treated just like parent poms, they are used to define versions of dependencies
|
||||
if deref(dep.Type) == "pom" && deref(dep.Scope) == "import" {
|
||||
depVersion := r.getPropertyValue(ctx, dep.Version, resolutionContext...)
|
||||
depVersion := r.resolvePropertyValue(ctx, dep.Version, nil, resolutionContext...)
|
||||
|
||||
depPom, err := r.findPom(ctx, depGroupID, depArtifactID, depVersion)
|
||||
depPom, err := r.FindPom(ctx, depGroupID, depArtifactID, depVersion)
|
||||
if err != nil || depPom == nil {
|
||||
log.WithFields("error", err, "mavenID", r.getMavenID(ctx, pom), "dependencyID", mavenID{depGroupID, depArtifactID, depVersion}).
|
||||
log.WithFields("error", err, "ID", r.ResolveID(ctx, pom), "dependencyID", ID{depGroupID, depArtifactID, depVersion}).
|
||||
Debug("unable to find imported pom looking for managed dependencies")
|
||||
continue
|
||||
}
|
||||
version, err = r.findInheritedVersion(ctx, depPom, groupID, artifactID, resolutionContext...)
|
||||
version, err = r.resolveInheritedVersion(ctx, depPom, groupID, artifactID, resolutionContext...)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "mavenID", r.getMavenID(ctx, pom), "dependencyID", mavenID{depGroupID, depArtifactID, depVersion}).
|
||||
log.WithFields("error", err, "ID", r.ResolveID(ctx, pom), "dependencyID", ID{depGroupID, depArtifactID, depVersion}).
|
||||
Debug("error during findInheritedVersion")
|
||||
}
|
||||
if version != "" {
|
||||
@@ -493,7 +555,7 @@ func (r *mavenResolver) findInheritedVersion(ctx context.Context, pom *gopom.Pro
|
||||
return "", err
|
||||
}
|
||||
if parent != nil {
|
||||
version, err = r.findInheritedVersion(ctx, parent, groupID, artifactID, resolutionContext...)
|
||||
version, err = r.resolveInheritedVersion(ctx, parent, groupID, artifactID, resolutionContext...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -503,11 +565,11 @@ func (r *mavenResolver) findInheritedVersion(ctx context.Context, pom *gopom.Pro
|
||||
}
|
||||
|
||||
// check for inherited dependencies
|
||||
for _, dep := range pomDependencies(pom) {
|
||||
depGroupID := r.getPropertyValue(ctx, dep.GroupID, resolutionContext...)
|
||||
depArtifactID := r.getPropertyValue(ctx, dep.ArtifactID, resolutionContext...)
|
||||
for _, dep := range DirectPomDependencies(pom) {
|
||||
depGroupID := r.resolvePropertyValue(ctx, dep.GroupID, nil, resolutionContext...)
|
||||
depArtifactID := r.resolvePropertyValue(ctx, dep.ArtifactID, nil, resolutionContext...)
|
||||
if depGroupID == groupID && depArtifactID == artifactID {
|
||||
version = r.getPropertyValue(ctx, dep.Version, resolutionContext...)
|
||||
version = r.resolvePropertyValue(ctx, dep.Version, nil, resolutionContext...)
|
||||
if version != "" {
|
||||
return version, nil
|
||||
}
|
||||
@@ -517,18 +579,24 @@ func (r *mavenResolver) findInheritedVersion(ctx context.Context, pom *gopom.Pro
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// findLicenses search pom for license, traversing parent poms if needed
|
||||
func (r *mavenResolver) findLicenses(ctx context.Context, groupID, artifactID, version string) ([]gopom.License, error) {
|
||||
pom, err := r.findPom(ctx, groupID, artifactID, version)
|
||||
// FindLicenses attempts to find a pom, and once found attempts to resolve licenses traversing
|
||||
// parent poms as necessary
|
||||
func (r *Resolver) FindLicenses(ctx context.Context, groupID, artifactID, version string) ([]gopom.License, error) {
|
||||
pom, err := r.FindPom(ctx, groupID, artifactID, version)
|
||||
if pom == nil || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.resolveLicenses(ctx, pom)
|
||||
}
|
||||
|
||||
// ResolveLicenses searches the pom for license, resolving and traversing parent poms if needed
|
||||
func (r *Resolver) ResolveLicenses(ctx context.Context, pom *Project) ([]License, error) {
|
||||
return r.resolveLicenses(ctx, pom)
|
||||
}
|
||||
|
||||
// resolveLicenses searches the pom for license, traversing parent poms if needed
|
||||
func (r *mavenResolver) resolveLicenses(ctx context.Context, pom *gopom.Project, processing ...mavenID) ([]gopom.License, error) {
|
||||
id := r.getMavenID(ctx, pom)
|
||||
func (r *Resolver) resolveLicenses(ctx context.Context, pom *Project, processing ...ID) ([]License, error) {
|
||||
id := r.ResolveID(ctx, pom)
|
||||
if slices.Contains(processing, id) {
|
||||
return nil, fmt.Errorf("cycle detected resolving licenses for: %v", id)
|
||||
}
|
||||
@@ -552,12 +620,12 @@ func (r *mavenResolver) resolveLicenses(ctx context.Context, pom *gopom.Project,
|
||||
}
|
||||
|
||||
// pomLicenses appends the directly specified licenses with non-empty name or url
|
||||
func (r *mavenResolver) pomLicenses(ctx context.Context, pom *gopom.Project) []gopom.License {
|
||||
var out []gopom.License
|
||||
func (r *Resolver) pomLicenses(ctx context.Context, pom *Project) []License {
|
||||
var out []License
|
||||
for _, license := range deref(pom.Licenses) {
|
||||
// if we find non-empty licenses, return them
|
||||
name := r.getPropertyValue(ctx, license.Name, pom)
|
||||
url := r.getPropertyValue(ctx, license.URL, pom)
|
||||
name := r.resolvePropertyValue(ctx, license.Name, nil, pom)
|
||||
url := r.resolvePropertyValue(ctx, license.URL, nil, pom)
|
||||
if name != "" || url != "" {
|
||||
out = append(out, license)
|
||||
}
|
||||
@@ -565,8 +633,8 @@ func (r *mavenResolver) pomLicenses(ctx context.Context, pom *gopom.Project) []g
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *mavenResolver) findParentPomByRelativePath(ctx context.Context, pom *gopom.Project, parentID mavenID, resolvingProperties []string) *gopom.Project {
|
||||
// don't resolve if no resolver
|
||||
func (r *Resolver) findParentPomByRelativePath(ctx context.Context, pom *Project, parentID ID, resolvingProperties []string) *Project {
|
||||
// can't resolve without a file resolver
|
||||
if r.fileResolver == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -588,7 +656,7 @@ func (r *mavenResolver) findParentPomByRelativePath(ctx context.Context, pom *go
|
||||
}
|
||||
parentLocations, err := r.fileResolver.FilesByPath(p)
|
||||
if err != nil || len(parentLocations) == 0 {
|
||||
log.WithFields("error", err, "mavenID", r.resolveMavenID(ctx, resolvingProperties, pom), "parentID", parentID, "relativePath", relativePath).
|
||||
log.WithFields("error", err, "mavenID", r.resolveID(ctx, resolvingProperties, pom), "parentID", parentID, "relativePath", relativePath).
|
||||
Trace("parent pom not found by relative path")
|
||||
return nil
|
||||
}
|
||||
@@ -596,34 +664,49 @@ func (r *mavenResolver) findParentPomByRelativePath(ctx context.Context, pom *go
|
||||
|
||||
parentContents, err := r.fileResolver.FileContentsByLocation(parentLocation)
|
||||
if err != nil || parentContents == nil {
|
||||
log.WithFields("error", err, "mavenID", r.resolveMavenID(ctx, resolvingProperties, pom), "parentID", parentID, "parentLocation", parentLocation).
|
||||
log.WithFields("error", err, "mavenID", r.resolveID(ctx, resolvingProperties, pom), "parentID", parentID, "parentLocation", parentLocation).
|
||||
Debug("unable to get contents of parent pom by relative path")
|
||||
return nil
|
||||
}
|
||||
defer internal.CloseAndLogError(parentContents, parentLocation.RealPath)
|
||||
parentPom, err := decodePomXML(parentContents)
|
||||
parentPom, err := ParsePomXML(parentContents)
|
||||
if err != nil || parentPom == nil {
|
||||
log.WithFields("error", err, "mavenID", r.resolveMavenID(ctx, resolvingProperties, pom), "parentID", parentID, "parentLocation", parentLocation).
|
||||
log.WithFields("error", err, "mavenID", r.resolveID(ctx, resolvingProperties, pom), "parentID", parentID, "parentLocation", parentLocation).
|
||||
Debug("unable to parse parent pom")
|
||||
return nil
|
||||
}
|
||||
// ensure parent matches
|
||||
newParentID := r.resolveMavenID(ctx, resolvingProperties, parentPom)
|
||||
newParentID := r.resolveID(ctx, resolvingProperties, parentPom)
|
||||
if newParentID.ArtifactID != parentID.ArtifactID {
|
||||
log.WithFields("newParentID", newParentID, "mavenID", r.resolveMavenID(ctx, resolvingProperties, pom), "parentID", parentID, "parentLocation", parentLocation).
|
||||
log.WithFields("newParentID", newParentID, "mavenID", r.resolveID(ctx, resolvingProperties, pom), "parentID", parentID, "parentLocation", parentLocation).
|
||||
Debug("parent IDs do not match resolving parent by relative path")
|
||||
return nil
|
||||
}
|
||||
|
||||
r.resolved[parentID] = parentPom
|
||||
r.pomLocations[parentPom] = parentLocation // for any future parent relativepath lookups
|
||||
r.pomLocations[parentPom] = parentLocation // for any future parent relativePath lookups
|
||||
|
||||
return parentPom
|
||||
}
|
||||
|
||||
// pomDependencies returns all dependencies directly defined in a project, including all defined in profiles.
|
||||
// does not resolve parent dependencies
|
||||
func pomDependencies(pom *gopom.Project) []gopom.Dependency {
|
||||
// AddPom allows for adding known pom files with locations within the file resolver, these locations may be used
|
||||
// while resolving parent poms by relative path
|
||||
func (r *Resolver) AddPom(ctx context.Context, pom *Project, location file.Location) {
|
||||
r.pomLocations[pom] = location
|
||||
// by calling resolve ID here, this will lookup necessary parent poms by relative path, and
|
||||
// track any poms we found with complete version information if enough is available to resolve
|
||||
id := r.ResolveID(ctx, pom)
|
||||
if id.Valid() {
|
||||
_, existing := r.resolved[id]
|
||||
if !existing {
|
||||
r.resolved[id] = pom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DirectPomDependencies returns all dependencies directly defined in a project, including all defined in profiles.
|
||||
// This does not resolve any parent or transitive dependencies
|
||||
func DirectPomDependencies(pom *Project) []Dependency {
|
||||
dependencies := deref(pom.Dependencies)
|
||||
for _, profile := range deref(pom.Profiles) {
|
||||
dependencies = append(dependencies, deref(profile.Dependencies)...)
|
||||
@@ -633,8 +716,8 @@ func pomDependencies(pom *gopom.Project) []gopom.Dependency {
|
||||
|
||||
// pomManagedDependencies returns all directly defined managed dependencies in a project pom, including all defined in profiles.
|
||||
// does not resolve parent managed dependencies
|
||||
func pomManagedDependencies(pom *gopom.Project) []gopom.Dependency {
|
||||
var dependencies []gopom.Dependency
|
||||
func pomManagedDependencies(pom *Project) []Dependency {
|
||||
var dependencies []Dependency
|
||||
if pom.DependencyManagement != nil {
|
||||
dependencies = append(dependencies, deref(pom.DependencyManagement.Dependencies)...)
|
||||
}
|
||||
@@ -645,3 +728,12 @@ func pomManagedDependencies(pom *gopom.Project) []gopom.Dependency {
|
||||
}
|
||||
return dependencies
|
||||
}
|
||||
|
||||
// deref dereferences ptr if not nil, or returns the type default value if ptr is nil
|
||||
func deref[T any](ptr *T) T {
|
||||
if ptr == nil {
|
||||
var t T
|
||||
return t
|
||||
}
|
||||
return *ptr
|
||||
}
|
||||
+83
-112
@@ -1,34 +1,30 @@
|
||||
package java
|
||||
package maven
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vifraa/gopom"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/internal/fileresolver"
|
||||
maventest "github.com/anchore/syft/syft/pkg/cataloger/java/internal/maven/test"
|
||||
)
|
||||
|
||||
func Test_resolveProperty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
property string
|
||||
pom gopom.Project
|
||||
pom Project
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "property",
|
||||
property: "${version.number}",
|
||||
pom: gopom.Project{
|
||||
Properties: &gopom.Properties{
|
||||
pom: Project{
|
||||
Properties: &Properties{
|
||||
Entries: map[string]string{
|
||||
"version.number": "12.5.0",
|
||||
},
|
||||
@@ -39,7 +35,7 @@ func Test_resolveProperty(t *testing.T) {
|
||||
{
|
||||
name: "groupId",
|
||||
property: "${project.groupId}",
|
||||
pom: gopom.Project{
|
||||
pom: Project{
|
||||
GroupID: ptr("org.some.group"),
|
||||
},
|
||||
expected: "org.some.group",
|
||||
@@ -47,8 +43,8 @@ func Test_resolveProperty(t *testing.T) {
|
||||
{
|
||||
name: "parent groupId",
|
||||
property: "${project.parent.groupId}",
|
||||
pom: gopom.Project{
|
||||
Parent: &gopom.Parent{
|
||||
pom: Project{
|
||||
Parent: &Parent{
|
||||
GroupID: ptr("org.some.parent"),
|
||||
},
|
||||
},
|
||||
@@ -57,7 +53,7 @@ func Test_resolveProperty(t *testing.T) {
|
||||
{
|
||||
name: "nil pointer halts search",
|
||||
property: "${project.parent.groupId}",
|
||||
pom: gopom.Project{
|
||||
pom: Project{
|
||||
Parent: nil,
|
||||
},
|
||||
expected: "",
|
||||
@@ -65,8 +61,8 @@ func Test_resolveProperty(t *testing.T) {
|
||||
{
|
||||
name: "nil string pointer halts search",
|
||||
property: "${project.parent.groupId}",
|
||||
pom: gopom.Project{
|
||||
Parent: &gopom.Parent{
|
||||
pom: Project{
|
||||
Parent: &Parent{
|
||||
GroupID: nil,
|
||||
},
|
||||
},
|
||||
@@ -75,11 +71,11 @@ func Test_resolveProperty(t *testing.T) {
|
||||
{
|
||||
name: "double dereference",
|
||||
property: "${springboot.version}",
|
||||
pom: gopom.Project{
|
||||
Parent: &gopom.Parent{
|
||||
pom: Project{
|
||||
Parent: &Parent{
|
||||
Version: ptr("1.2.3"),
|
||||
},
|
||||
Properties: &gopom.Properties{
|
||||
Properties: &Properties{
|
||||
Entries: map[string]string{
|
||||
"springboot.version": "${project.parent.version}",
|
||||
},
|
||||
@@ -90,8 +86,8 @@ func Test_resolveProperty(t *testing.T) {
|
||||
{
|
||||
name: "map missing stops double dereference",
|
||||
property: "${springboot.version}",
|
||||
pom: gopom.Project{
|
||||
Parent: &gopom.Parent{
|
||||
pom: Project{
|
||||
Parent: &Parent{
|
||||
Version: ptr("1.2.3"),
|
||||
},
|
||||
},
|
||||
@@ -100,11 +96,11 @@ func Test_resolveProperty(t *testing.T) {
|
||||
{
|
||||
name: "resolution halts even if it resolves to a variable",
|
||||
property: "${springboot.version}",
|
||||
pom: gopom.Project{
|
||||
Parent: &gopom.Parent{
|
||||
pom: Project{
|
||||
Parent: &Parent{
|
||||
Version: ptr("${undefined.version}"),
|
||||
},
|
||||
Properties: &gopom.Properties{
|
||||
Properties: &Properties{
|
||||
Entries: map[string]string{
|
||||
"springboot.version": "${project.parent.version}",
|
||||
},
|
||||
@@ -115,8 +111,8 @@ func Test_resolveProperty(t *testing.T) {
|
||||
{
|
||||
name: "resolution halts even if cyclic",
|
||||
property: "${springboot.version}",
|
||||
pom: gopom.Project{
|
||||
Properties: &gopom.Properties{
|
||||
pom: Project{
|
||||
Properties: &Properties{
|
||||
Entries: map[string]string{
|
||||
"springboot.version": "${springboot.version}",
|
||||
},
|
||||
@@ -127,8 +123,8 @@ func Test_resolveProperty(t *testing.T) {
|
||||
{
|
||||
name: "resolution halts even if cyclic more steps",
|
||||
property: "${cyclic.version}",
|
||||
pom: gopom.Project{
|
||||
Properties: &gopom.Properties{
|
||||
pom: Project{
|
||||
Properties: &Properties{
|
||||
Entries: map[string]string{
|
||||
"other.version": "${cyclic.version}",
|
||||
"springboot.version": "${other.version}",
|
||||
@@ -141,11 +137,11 @@ func Test_resolveProperty(t *testing.T) {
|
||||
{
|
||||
name: "resolution halts even if cyclic involving parent",
|
||||
property: "${cyclic.version}",
|
||||
pom: gopom.Project{
|
||||
Parent: &gopom.Parent{
|
||||
pom: Project{
|
||||
Parent: &Parent{
|
||||
Version: ptr("${cyclic.version}"),
|
||||
},
|
||||
Properties: &gopom.Properties{
|
||||
Properties: &Properties{
|
||||
Entries: map[string]string{
|
||||
"other.version": "${parent.version}",
|
||||
"springboot.version": "${other.version}",
|
||||
@@ -159,15 +155,15 @@ func Test_resolveProperty(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := newMavenResolver(nil, DefaultArchiveCatalogerConfig())
|
||||
resolved := r.getPropertyValue(context.Background(), ptr(test.property), &test.pom)
|
||||
r := NewResolver(nil, DefaultConfig())
|
||||
resolved := r.ResolveProperty(context.Background(), &test.pom, ptr(test.property))
|
||||
require.Equal(t, test.expected, resolved)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_mavenResolverLocal(t *testing.T) {
|
||||
dir, err := filepath.Abs("test-fixtures/pom/maven-repo")
|
||||
dir, err := filepath.Abs("test-fixtures/maven-repo")
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
@@ -211,26 +207,26 @@ func Test_mavenResolverLocal(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
r := newMavenResolver(nil, ArchiveCatalogerConfig{
|
||||
r := NewResolver(nil, Config{
|
||||
UseNetwork: false,
|
||||
UseMavenLocalRepository: true,
|
||||
MavenLocalRepositoryDir: dir,
|
||||
UseLocalRepository: true,
|
||||
LocalRepositoryDir: dir,
|
||||
MaxParentRecursiveDepth: test.maxDepth,
|
||||
})
|
||||
pom, err := r.findPom(ctx, test.groupID, test.artifactID, test.version)
|
||||
pom, err := r.FindPom(ctx, test.groupID, test.artifactID, test.version)
|
||||
if test.wantErr != nil {
|
||||
test.wantErr(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got := r.getPropertyValue(context.Background(), &test.expression, pom)
|
||||
got := r.ResolveProperty(context.Background(), pom, &test.expression)
|
||||
require.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_mavenResolverRemote(t *testing.T) {
|
||||
url := mockMavenRepo(t)
|
||||
url := maventest.MockRepo(t, "test-fixtures/maven-repo")
|
||||
|
||||
tests := []struct {
|
||||
groupID string
|
||||
@@ -247,30 +243,37 @@ func Test_mavenResolverRemote(t *testing.T) {
|
||||
expression: "${project.one}",
|
||||
expected: "1",
|
||||
},
|
||||
{
|
||||
groupID: "my.org", // this particular package has a circular reference
|
||||
artifactID: "circular",
|
||||
version: "1.2.3",
|
||||
expression: "${unresolved}",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.artifactID, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
r := newMavenResolver(nil, ArchiveCatalogerConfig{
|
||||
UseNetwork: true,
|
||||
UseMavenLocalRepository: false,
|
||||
MavenBaseURL: url,
|
||||
r := NewResolver(nil, Config{
|
||||
UseNetwork: true,
|
||||
UseLocalRepository: false,
|
||||
Repositories: strings.Split(url, ","),
|
||||
})
|
||||
pom, err := r.findPom(ctx, test.groupID, test.artifactID, test.version)
|
||||
pom, err := r.FindPom(ctx, test.groupID, test.artifactID, test.version)
|
||||
if test.wantErr != nil {
|
||||
test.wantErr(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got := r.getPropertyValue(context.Background(), &test.expression, pom)
|
||||
got := r.ResolveProperty(context.Background(), pom, &test.expression)
|
||||
require.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_relativePathParent(t *testing.T) {
|
||||
resolver, err := fileresolver.NewFromDirectory("test-fixtures/pom/local", "")
|
||||
resolver, err := fileresolver.NewFromDirectory("test-fixtures/local", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -278,12 +281,12 @@ func Test_relativePathParent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pom string
|
||||
validate func(t *testing.T, r *mavenResolver, pom *gopom.Project)
|
||||
validate func(t *testing.T, r *Resolver, pom *Project)
|
||||
}{
|
||||
{
|
||||
name: "basic",
|
||||
pom: "child-1/pom.xml",
|
||||
validate: func(t *testing.T, r *mavenResolver, pom *gopom.Project) {
|
||||
validate: func(t *testing.T, r *Resolver, pom *Project) {
|
||||
parent, err := r.resolveParent(ctx, pom)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, r.pomLocations, parent)
|
||||
@@ -292,16 +295,15 @@ func Test_relativePathParent(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, r.pomLocations, parent)
|
||||
|
||||
got := r.getPropertyValue(ctx, ptr("${commons-exec_subversion}"), pom)
|
||||
got := r.ResolveProperty(ctx, pom, ptr("${commons-exec_subversion}"))
|
||||
require.Equal(t, "3", got)
|
||||
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "parent property",
|
||||
pom: "child-2/pom.xml",
|
||||
validate: func(t *testing.T, r *mavenResolver, pom *gopom.Project) {
|
||||
id := r.getMavenID(ctx, pom)
|
||||
validate: func(t *testing.T, r *Resolver, pom *Project) {
|
||||
id := r.ResolveID(ctx, pom)
|
||||
// child.parent.version = ${revision}
|
||||
// parent.revision = 3.3.3
|
||||
require.Equal(t, id.Version, "3.3.3")
|
||||
@@ -310,18 +312,39 @@ func Test_relativePathParent(t *testing.T) {
|
||||
{
|
||||
name: "invalid parent",
|
||||
pom: "child-3/pom.xml",
|
||||
validate: func(t *testing.T, r *mavenResolver, pom *gopom.Project) {
|
||||
validate: func(t *testing.T, r *Resolver, pom *Project) {
|
||||
require.NotNil(t, pom)
|
||||
id := r.getMavenID(ctx, pom)
|
||||
id := r.ResolveID(ctx, pom)
|
||||
// version should not be resolved to anything
|
||||
require.Equal(t, "", id.Version)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "circular resolving ID variables",
|
||||
pom: "circular-1/pom.xml",
|
||||
validate: func(t *testing.T, r *Resolver, pom *Project) {
|
||||
require.NotNil(t, pom)
|
||||
id := r.ResolveID(ctx, pom)
|
||||
// version should be resolved, but not artifactId
|
||||
require.Equal(t, "1.2.3", id.Version)
|
||||
require.Equal(t, "", id.ArtifactID)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "circular parent only",
|
||||
pom: "circular-2/pom.xml",
|
||||
validate: func(t *testing.T, r *Resolver, pom *Project) {
|
||||
require.NotNil(t, pom)
|
||||
id := r.ResolveID(ctx, pom)
|
||||
require.Equal(t, "", id.Version)
|
||||
require.Equal(t, "something", id.ArtifactID)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := newMavenResolver(resolver, DefaultArchiveCatalogerConfig())
|
||||
r := NewResolver(resolver, DefaultConfig())
|
||||
locs, err := resolver.FilesByPath(test.pom)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, locs, 1)
|
||||
@@ -331,7 +354,7 @@ func Test_relativePathParent(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer internal.CloseAndLogError(contents, loc.RealPath)
|
||||
|
||||
pom, err := decodePomXML(contents)
|
||||
pom, err := ParsePomXML(contents)
|
||||
require.NoError(t, err)
|
||||
|
||||
r.pomLocations[pom] = loc
|
||||
@@ -341,59 +364,7 @@ func Test_relativePathParent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// mockMavenRepo starts a remote maven repo serving all the pom files found in test-fixtures/pom/maven-repo
|
||||
func mockMavenRepo(t *testing.T) (url string) {
|
||||
t.Helper()
|
||||
|
||||
return mockMavenRepoAt(t, "test-fixtures/pom/maven-repo")
|
||||
}
|
||||
|
||||
// mockMavenRepoAt starts a remote maven repo serving all the pom files found in the given directory
|
||||
func mockMavenRepoAt(t *testing.T, dir string) (url string) {
|
||||
t.Helper()
|
||||
|
||||
// mux is the HTTP request multiplexer used with the test server.
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// We want to ensure that tests catch mistakes where the endpoint URL is
|
||||
// specified as absolute rather than relative. It only makes a difference
|
||||
// when there's a non-empty base URL path. So, use that. See issue #752.
|
||||
apiHandler := http.NewServeMux()
|
||||
apiHandler.Handle("/", mux)
|
||||
// server is a test HTTP server used to provide mock API responses.
|
||||
server := httptest.NewServer(apiHandler)
|
||||
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
matches, err := doublestar.Glob(os.DirFS(dir), filepath.Join("**", "*.pom"))
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, match := range matches {
|
||||
fullPath, err := filepath.Abs(filepath.Join(dir, match))
|
||||
require.NoError(t, err)
|
||||
match = "/" + filepath.ToSlash(match)
|
||||
mux.HandleFunc(match, mockMavenHandler(fullPath))
|
||||
}
|
||||
|
||||
return server.URL
|
||||
}
|
||||
|
||||
func mockMavenHandler(responseFixture string) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// Set the Content-Type header to indicate that the response is XML
|
||||
w.Header().Set("Content-Type", "application/xml")
|
||||
// Copy the file's content to the response writer
|
||||
f, err := os.Open(responseFixture)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer internal.CloseAndLogError(f, responseFixture)
|
||||
_, err = io.Copy(w, f)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
// ptr returns a pointer to the given value
|
||||
func ptr[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>${groupId}</groupId>
|
||||
<artifactId>${artifactId}</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>my.org</groupId>
|
||||
<artifactId>circular</artifactId>
|
||||
<version>1.2.3</version>
|
||||
</parent>
|
||||
</project>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>something</artifactId>
|
||||
<version>${unresolvable}</version>
|
||||
|
||||
<parent>
|
||||
<groupId>my.org</groupId>
|
||||
<artifactId>circular</artifactId>
|
||||
<version>1.2.3</version>
|
||||
</parent>
|
||||
</project>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>circular</artifactId>
|
||||
<version>1.2.3</version>
|
||||
|
||||
<parent>
|
||||
<groupId>my.org</groupId>
|
||||
<artifactId>circular</artifactId>
|
||||
<version>1.2.3</version>
|
||||
</parent>
|
||||
</project>
|
||||
@@ -0,0 +1,65 @@
|
||||
package maventest
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
)
|
||||
|
||||
// MockRepo starts a remote maven repo serving all the pom files found in a maven-structured directory
|
||||
func MockRepo(t *testing.T, dir string) (url string) {
|
||||
t.Helper()
|
||||
|
||||
// mux is the HTTP request multiplexer used with the test server.
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// We want to ensure that tests catch mistakes where the endpoint URL is
|
||||
// specified as absolute rather than relative. It only makes a difference
|
||||
// when there's a non-empty base URL path. So, use that. See issue #752.
|
||||
apiHandler := http.NewServeMux()
|
||||
apiHandler.Handle("/", mux)
|
||||
// server is a test HTTP server used to provide mock API responses.
|
||||
server := httptest.NewServer(apiHandler)
|
||||
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
matches, err := doublestar.Glob(os.DirFS(dir), filepath.Join("**", "*.pom"))
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, match := range matches {
|
||||
fullPath, err := filepath.Abs(filepath.Join(dir, match))
|
||||
require.NoError(t, err)
|
||||
match = "/" + filepath.ToSlash(match)
|
||||
mux.HandleFunc(match, mockMavenHandler(fullPath))
|
||||
}
|
||||
|
||||
return server.URL
|
||||
}
|
||||
|
||||
func mockMavenHandler(responseFixture string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// Set the Content-Type header to indicate that the response is XML
|
||||
w.Header().Set("Content-Type", "application/xml")
|
||||
// Copy the file's content to the response writer
|
||||
f, err := os.Open(responseFixture)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer internal.CloseAndLogError(f, responseFixture)
|
||||
_, err = io.Copy(w, f)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,17 @@
|
||||
package java
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/saintfish/chardet"
|
||||
"github.com/vifraa/gopom"
|
||||
"golang.org/x/net/html/charset"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/unknown"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java/internal/maven"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -40,10 +33,11 @@ func (p pomXMLCataloger) Catalog(ctx context.Context, fileResolver file.Resolver
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
r := newMavenResolver(fileResolver, p.cfg)
|
||||
r := maven.NewResolver(fileResolver, p.cfg.mavenConfig())
|
||||
|
||||
var errs error
|
||||
var poms []*gopom.Project
|
||||
var poms []*maven.Project
|
||||
pomLocations := map[*maven.Project]file.Location{}
|
||||
for _, pomLocation := range locations {
|
||||
pom, err := readPomFromLocation(fileResolver, pomLocation)
|
||||
if err != nil || pom == nil {
|
||||
@@ -53,59 +47,172 @@ func (p pomXMLCataloger) Catalog(ctx context.Context, fileResolver file.Resolver
|
||||
}
|
||||
|
||||
poms = append(poms, pom)
|
||||
|
||||
// store information about this pom for future lookups
|
||||
r.pomLocations[pom] = pomLocation
|
||||
r.resolved[r.getMavenID(ctx, pom)] = pom
|
||||
pomLocations[pom] = pomLocation
|
||||
r.AddPom(ctx, pom, pomLocation)
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
resolved := map[maven.ID]*pkg.Package{}
|
||||
|
||||
// catalog all the main packages first so these can be referenced later when building the dependency graph
|
||||
for _, pom := range poms {
|
||||
pkgs = append(pkgs, processPomXML(ctx, r, pom, r.pomLocations[pom])...)
|
||||
location := pomLocations[pom] // should always exist
|
||||
|
||||
id := r.ResolveID(ctx, pom)
|
||||
mainPkg := newPackageFromMavenPom(ctx, r, pom, location)
|
||||
if mainPkg == nil {
|
||||
continue
|
||||
}
|
||||
resolved[id] = mainPkg
|
||||
pkgs = append(pkgs, *mainPkg)
|
||||
}
|
||||
return pkgs, nil, errs
|
||||
|
||||
// catalog all dependencies
|
||||
for _, pom := range poms {
|
||||
location := pomLocations[pom] // should always exist
|
||||
|
||||
id := r.ResolveID(ctx, pom)
|
||||
mainPkg := resolved[id]
|
||||
|
||||
newPkgs, newRelationships, newErrs := collectDependencies(ctx, r, resolved, mainPkg, pom, location, p.cfg.ResolveTransitiveDependencies)
|
||||
pkgs = append(pkgs, newPkgs...)
|
||||
relationships = append(relationships, newRelationships...)
|
||||
errs = unknown.Join(errs, newErrs)
|
||||
}
|
||||
|
||||
return pkgs, relationships, errs
|
||||
}
|
||||
|
||||
func readPomFromLocation(fileResolver file.Resolver, pomLocation file.Location) (*gopom.Project, error) {
|
||||
func readPomFromLocation(fileResolver file.Resolver, pomLocation file.Location) (*maven.Project, error) {
|
||||
contents, err := fileResolver.FileContentsByLocation(pomLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer internal.CloseAndLogError(contents, pomLocation.RealPath)
|
||||
return decodePomXML(contents)
|
||||
return maven.ParsePomXML(contents)
|
||||
}
|
||||
|
||||
func processPomXML(ctx context.Context, r *mavenResolver, pom *gopom.Project, loc file.Location) []pkg.Package {
|
||||
var pkgs []pkg.Package
|
||||
|
||||
pomID := r.getMavenID(ctx, pom)
|
||||
for _, dep := range pomDependencies(pom) {
|
||||
depID := r.resolveDependencyID(ctx, pom, dep)
|
||||
log.WithFields("pomLocation", loc, "mavenID", pomID, "dependencyID", depID).Trace("adding maven pom dependency")
|
||||
|
||||
p, err := newPackageFromDependency(
|
||||
ctx,
|
||||
r,
|
||||
pom,
|
||||
dep,
|
||||
loc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "pomLocation", loc, "mavenID", pomID, "dependencyID", depID).Debugf("error adding dependency")
|
||||
}
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
pkgs = append(pkgs, *p)
|
||||
// newPackageFromMavenPom processes a single Maven POM for a given parent package, returning only the main package from the pom
|
||||
func newPackageFromMavenPom(ctx context.Context, r *maven.Resolver, pom *maven.Project, location file.Location) *pkg.Package {
|
||||
id := r.ResolveID(ctx, pom)
|
||||
parent, err := r.ResolveParent(ctx, pom)
|
||||
if err != nil {
|
||||
// this is expected in many cases, there will be no network access and the maven resolver is unable to
|
||||
// look up information, so we can continue with what little information we have
|
||||
log.Trace("unable to resolve parent due to: %v", err)
|
||||
}
|
||||
|
||||
return pkgs
|
||||
var javaPomParent *pkg.JavaPomParent
|
||||
if parent != nil { // parent is returned in both cases: when it is resolved or synthesized from the pom.parent info
|
||||
parentID := r.ResolveID(ctx, parent)
|
||||
javaPomParent = &pkg.JavaPomParent{
|
||||
GroupID: parentID.GroupID,
|
||||
ArtifactID: parentID.ArtifactID,
|
||||
Version: parentID.Version,
|
||||
}
|
||||
}
|
||||
|
||||
pomLicenses, err := r.ResolveLicenses(ctx, pom)
|
||||
if err != nil {
|
||||
log.Tracef("error resolving licenses: %v", err)
|
||||
}
|
||||
licenses := toPkgLicenses(&location, pomLicenses)
|
||||
|
||||
m := pkg.JavaArchive{
|
||||
PomProject: &pkg.JavaPomProject{
|
||||
Parent: javaPomParent,
|
||||
GroupID: id.GroupID,
|
||||
ArtifactID: id.ArtifactID,
|
||||
Version: id.Version,
|
||||
Name: r.ResolveProperty(ctx, pom, pom.Name),
|
||||
Description: r.ResolveProperty(ctx, pom, pom.Description),
|
||||
URL: r.ResolveProperty(ctx, pom, pom.URL),
|
||||
},
|
||||
}
|
||||
|
||||
p := &pkg.Package{
|
||||
Name: id.ArtifactID,
|
||||
Version: id.Version,
|
||||
Locations: file.NewLocationSet(
|
||||
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Licenses: pkg.NewLicenseSet(licenses...),
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
FoundBy: pomCatalogerName,
|
||||
PURL: packageURL(id.ArtifactID, id.Version, m),
|
||||
Metadata: m,
|
||||
}
|
||||
|
||||
finalizePackage(p)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func newPomProject(ctx context.Context, r *mavenResolver, path string, pom *gopom.Project) *pkg.JavaPomProject {
|
||||
id := r.getMavenID(ctx, pom)
|
||||
name := r.getPropertyValue(ctx, pom.Name, pom)
|
||||
projectURL := r.getPropertyValue(ctx, pom.URL, pom)
|
||||
func collectDependencies(ctx context.Context, r *maven.Resolver, resolved map[maven.ID]*pkg.Package, parentPkg *pkg.Package, pom *maven.Project, loc file.Location, includeTransitiveDependencies bool) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var errs error
|
||||
var pkgs []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
pomID := r.ResolveID(ctx, pom)
|
||||
for _, dep := range maven.DirectPomDependencies(pom) {
|
||||
depID := r.ResolveDependencyID(ctx, pom, dep)
|
||||
log.WithFields("pomLocation", loc, "mavenID", pomID, "dependencyID", depID).Trace("adding maven pom dependency")
|
||||
|
||||
// we may have a reference to a package pointing to an existing pom on the filesystem, but we don't want to duplicate these entries
|
||||
depPkg := resolved[depID]
|
||||
if depPkg == nil {
|
||||
p, err := newPackageFromDependency(
|
||||
ctx,
|
||||
r,
|
||||
pom,
|
||||
dep,
|
||||
loc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
if err != nil {
|
||||
log.WithFields("error", err, "pomLocation", loc, "mavenID", pomID, "dependencyID", depID).Debugf("error adding dependency")
|
||||
}
|
||||
|
||||
if p == nil {
|
||||
// we don't have a valid package, just continue to the next dependency
|
||||
continue
|
||||
}
|
||||
depPkg = p
|
||||
resolved[depID] = depPkg
|
||||
|
||||
// only resolve transitive dependencies if we're not already looking these up for the specific package
|
||||
if includeTransitiveDependencies && depID.Valid() {
|
||||
depPom, err := r.FindPom(ctx, depID.GroupID, depID.ArtifactID, depID.Version)
|
||||
if err != nil {
|
||||
log.WithFields("mavenID", depID, "error", err).Debug("error finding pom")
|
||||
}
|
||||
if depPom != nil {
|
||||
transitivePkgs, transitiveRelationships, transitiveErrs := collectDependencies(ctx, r, resolved, depPkg, depPom, loc, includeTransitiveDependencies)
|
||||
pkgs = append(pkgs, transitivePkgs...)
|
||||
relationships = append(relationships, transitiveRelationships...)
|
||||
errs = unknown.Join(errs, transitiveErrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, *depPkg)
|
||||
if parentPkg != nil {
|
||||
relationships = append(relationships, artifact.Relationship{
|
||||
From: *depPkg,
|
||||
To: *parentPkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, relationships, errs
|
||||
}
|
||||
|
||||
func newPomProject(ctx context.Context, r *maven.Resolver, path string, pom *maven.Project) *pkg.JavaPomProject {
|
||||
id := r.ResolveID(ctx, pom)
|
||||
name := r.ResolveProperty(ctx, pom, pom.Name)
|
||||
projectURL := r.ResolveProperty(ctx, pom, pom.URL)
|
||||
|
||||
log.WithFields("path", path, "artifactID", id.ArtifactID, "name", name, "projectURL", projectURL).Trace("parsing pom.xml")
|
||||
return &pkg.JavaPomProject{
|
||||
@@ -115,34 +222,43 @@ func newPomProject(ctx context.Context, r *mavenResolver, path string, pom *gopo
|
||||
ArtifactID: id.ArtifactID,
|
||||
Version: id.Version,
|
||||
Name: name,
|
||||
Description: cleanDescription(r.getPropertyValue(ctx, pom.Description, pom)),
|
||||
Description: cleanDescription(r.ResolveProperty(ctx, pom, pom.Description)),
|
||||
URL: projectURL,
|
||||
}
|
||||
}
|
||||
|
||||
func newPackageFromDependency(ctx context.Context, r *mavenResolver, pom *gopom.Project, dep gopom.Dependency, locations ...file.Location) (*pkg.Package, error) {
|
||||
id := r.resolveDependencyID(ctx, pom, dep)
|
||||
func newPackageFromDependency(ctx context.Context, r *maven.Resolver, pom *maven.Project, dep maven.Dependency, locations ...file.Location) (*pkg.Package, error) {
|
||||
id := r.ResolveDependencyID(ctx, pom, dep)
|
||||
|
||||
var err error
|
||||
var licenses []pkg.License
|
||||
dependencyPom, depErr := r.FindPom(ctx, id.GroupID, id.ArtifactID, id.Version)
|
||||
if depErr != nil {
|
||||
err = errors.Join(err, depErr)
|
||||
}
|
||||
|
||||
var pomProject *pkg.JavaPomProject
|
||||
if dependencyPom != nil {
|
||||
depLicenses, _ := r.ResolveLicenses(ctx, dependencyPom)
|
||||
licenses = append(licenses, toPkgLicenses(nil, depLicenses)...)
|
||||
pomProject = &pkg.JavaPomProject{
|
||||
Parent: pomParent(ctx, r, dependencyPom),
|
||||
GroupID: id.GroupID,
|
||||
ArtifactID: id.ArtifactID,
|
||||
Version: id.Version,
|
||||
Name: r.ResolveProperty(ctx, pom, pom.Name),
|
||||
Description: r.ResolveProperty(ctx, pom, pom.Description),
|
||||
URL: r.ResolveProperty(ctx, pom, pom.URL),
|
||||
}
|
||||
}
|
||||
|
||||
m := pkg.JavaArchive{
|
||||
PomProperties: &pkg.JavaPomProperties{
|
||||
GroupID: id.GroupID,
|
||||
ArtifactID: id.ArtifactID,
|
||||
Scope: r.getPropertyValue(ctx, dep.Scope, pom),
|
||||
Scope: r.ResolveProperty(ctx, pom, dep.Scope),
|
||||
},
|
||||
}
|
||||
|
||||
var err error
|
||||
var licenses []pkg.License
|
||||
dependencyPom, depErr := r.findPom(ctx, id.GroupID, id.ArtifactID, id.Version)
|
||||
if depErr != nil {
|
||||
err = errors.Join(err, depErr)
|
||||
}
|
||||
|
||||
if dependencyPom != nil {
|
||||
depLicenses, _ := r.resolveLicenses(ctx, dependencyPom)
|
||||
for _, license := range depLicenses {
|
||||
licenses = append(licenses, pkg.NewLicenseFromFields(deref(license.Name), deref(license.URL), nil))
|
||||
}
|
||||
PomProject: pomProject,
|
||||
}
|
||||
|
||||
p := &pkg.Package{
|
||||
@@ -157,65 +273,19 @@ func newPackageFromDependency(ctx context.Context, r *mavenResolver, pom *gopom.
|
||||
Metadata: m,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
finalizePackage(p)
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
// decodePomXML decodes a pom XML file, detecting and converting non-UTF-8 charsets. this DOES NOT perform any logic to resolve properties such as groupID, artifactID, and version
|
||||
func decodePomXML(content io.Reader) (project *gopom.Project, err error) {
|
||||
inputReader, err := getUtf8Reader(content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read pom.xml: %w", err)
|
||||
}
|
||||
|
||||
decoder := xml.NewDecoder(inputReader)
|
||||
// when an xml file has a character set declaration (e.g. '<?xml version="1.0" encoding="ISO-8859-1"?>') read that and use the correct decoder
|
||||
decoder.CharsetReader = charset.NewReaderLabel
|
||||
|
||||
project = &gopom.Project{}
|
||||
if err := decoder.Decode(project); err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal pom.xml: %w", err)
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func getUtf8Reader(content io.Reader) (io.Reader, error) {
|
||||
pomContents, err := io.ReadAll(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
detector := chardet.NewTextDetector()
|
||||
detection, err := detector.DetectBest(pomContents)
|
||||
|
||||
var inputReader io.Reader
|
||||
if err == nil && detection != nil {
|
||||
if detection.Charset == "UTF-8" {
|
||||
inputReader = bytes.NewReader(pomContents)
|
||||
} else {
|
||||
inputReader, err = charset.NewReaderLabel(detection.Charset, bytes.NewReader(pomContents))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get encoding: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we could not detect the encoding, but we want a valid file to read. Replace unreadable
|
||||
// characters with the UTF-8 replacement character.
|
||||
inputReader = strings.NewReader(strings.ToValidUTF8(string(pomContents), "�"))
|
||||
}
|
||||
return inputReader, nil
|
||||
}
|
||||
|
||||
func pomParent(ctx context.Context, r *mavenResolver, pom *gopom.Project) *pkg.JavaPomParent {
|
||||
func pomParent(ctx context.Context, r *maven.Resolver, pom *maven.Project) *pkg.JavaPomParent {
|
||||
if pom == nil || pom.Parent == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
groupID := r.getPropertyValue(ctx, pom.Parent.GroupID, pom)
|
||||
artifactID := r.getPropertyValue(ctx, pom.Parent.ArtifactID, pom)
|
||||
version := r.getPropertyValue(ctx, pom.Parent.Version, pom)
|
||||
groupID := r.ResolveProperty(ctx, pom, pom.Parent.GroupID)
|
||||
artifactID := r.ResolveProperty(ctx, pom, pom.Parent.ArtifactID)
|
||||
version := r.ResolveProperty(ctx, pom, pom.Parent.Version)
|
||||
|
||||
if groupID == "" && artifactID == "" && version == "" {
|
||||
return nil
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,32 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"specVersion": "1.6",
|
||||
"version": 1,
|
||||
"serialNumber": "urn:uuid:43538af4-f715-3d85-9629-336fdd3790ad",
|
||||
"metadata": {
|
||||
"component": {
|
||||
"type": "library",
|
||||
"group": "com.test",
|
||||
"name": "main-test-app",
|
||||
"version": "1.0-SNAPSHOT",
|
||||
"purl": "pkg:maven/com.oracle/main-test-app@1.0-SNAPSHOT",
|
||||
"bom-ref": "pkg:maven/com.oracle/main-test-app@1.0-SNAPSHOT",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:app:main-test-app:1.0-SNAPSHOT:*:*:*:*:*:*:*"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"type": "library",
|
||||
"group": "io.netty",
|
||||
"name": "netty-codec-http2",
|
||||
"version": "4.1.73.Final",
|
||||
"purl": "pkg:maven/io.netty/netty-codec-http2@4.1.104.Final",
|
||||
"bom-ref": "pkg:maven/io.netty/netty-codec-http2@4.1.104.Final",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
@@ -22,7 +41,38 @@
|
||||
"value": "cpe:2.3:a:codec:netty_codec_http2:4.1.73.Final:*:*:*:*:*:*:*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"group": "io.micronaut",
|
||||
"name": "core",
|
||||
"version": "4.2.3",
|
||||
"purl": "pkg:maven/io.micronaut/core@4.2.3",
|
||||
"bom-ref": "pkg:maven/io.micronaut/core@4.2.3",
|
||||
"properties": [
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:core:core:4.2.3:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"name": "syft:cpe23",
|
||||
"value": "cpe:2.3:a:micronaut:core:4.2.3:*:*:*:*:*:*:*"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "library",
|
||||
"group": "org.example",
|
||||
"name": "basic-lib",
|
||||
"version": "1.0"
|
||||
}
|
||||
],
|
||||
"serialNumber": "urn:uuid:43538af4-f715-3d85-9629-336fdd3790ad"
|
||||
}
|
||||
"dependencies": [
|
||||
{
|
||||
"ref": "pkg:maven/io.micronaut/core@4.2.3",
|
||||
"dependsOn": [
|
||||
"pkg:maven/io.netty/netty-codec-http2@4.1.104.Final"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>my.other.group</groupId>
|
||||
<artifactId>transitive-top-level</artifactId>
|
||||
<version>99</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>my.org</groupId>
|
||||
<artifactId>child-one</artifactId>
|
||||
<version>1.3.6</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>my.org</groupId>
|
||||
<artifactId>child-one</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>my.org</groupId>
|
||||
<artifactId>child-two</artifactId>
|
||||
<version>2.1.90</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -3,8 +3,9 @@ package javascript
|
||||
const npmBaseURL = "https://registry.npmjs.org"
|
||||
|
||||
type CatalogerConfig struct {
|
||||
SearchRemoteLicenses bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
NPMBaseURL string `json:"npm-base-url" yaml:"npm-base-url" mapstructure:"npm-base-url"`
|
||||
SearchRemoteLicenses bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
NPMBaseURL string `json:"npm-base-url" yaml:"npm-base-url" mapstructure:"npm-base-url"`
|
||||
IncludeDevDependencies bool `json:"include-dev-dependencies" yaml:"include-dev-dependencies" mapstructure:"include-dev-dependencies"`
|
||||
}
|
||||
|
||||
func DefaultCatalogerConfig() CatalogerConfig {
|
||||
@@ -25,3 +26,8 @@ func (j CatalogerConfig) WithNpmBaseURL(input string) CatalogerConfig {
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
func (j CatalogerConfig) WithIncludeDevDependencies(input bool) CatalogerConfig {
|
||||
j.IncludeDevDependencies = input
|
||||
return j
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ type lockDependency struct {
|
||||
Version string `json:"version"`
|
||||
Resolved string `json:"resolved"`
|
||||
Integrity string `json:"integrity"`
|
||||
Dev bool `json:"dev"`
|
||||
}
|
||||
|
||||
type lockPackage struct {
|
||||
@@ -37,6 +38,7 @@ type lockPackage struct {
|
||||
Resolved string `json:"resolved"`
|
||||
Integrity string `json:"integrity"`
|
||||
License packageLockLicense `json:"license"`
|
||||
Dev bool `json:"dev"`
|
||||
}
|
||||
|
||||
// packageLockLicense
|
||||
@@ -74,6 +76,11 @@ func (a genericPackageLockAdapter) parsePackageLock(_ context.Context, resolver
|
||||
|
||||
if lock.LockfileVersion == 1 {
|
||||
for name, pkgMeta := range lock.Dependencies {
|
||||
// skip packages that are only present as a dev dependency
|
||||
if !a.cfg.IncludeDevDependencies && pkgMeta.Dev {
|
||||
continue
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, newPackageLockV1Package(a.cfg, resolver, reader.Location, name, pkgMeta))
|
||||
}
|
||||
}
|
||||
@@ -87,6 +94,11 @@ func (a genericPackageLockAdapter) parsePackageLock(_ context.Context, resolver
|
||||
name = pkgMeta.Name
|
||||
}
|
||||
|
||||
// skip packages that are only present as a dev dependency
|
||||
if !a.cfg.IncludeDevDependencies && pkgMeta.Dev {
|
||||
continue
|
||||
}
|
||||
|
||||
// handles alias names
|
||||
if pkgMeta.Name != "" {
|
||||
name = pkgMeta.Name
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
"version": "6.14.6",
|
||||
"dependencies": {
|
||||
"@types/react": "^18.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "^3.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
@@ -39,6 +42,12 @@
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
|
||||
"integrity": "sha1-TdysNxjXh8+d8NG30VAzklyPKfI=",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
||||
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
|
||||
"dev": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -66,6 +75,12 @@
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
|
||||
"integrity": "sha1-TdysNxjXh8+d8NG30VAzklyPKfI="
|
||||
},
|
||||
"async": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
||||
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@types/react": "^18.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "^3.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
@@ -35,6 +38,12 @@
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
|
||||
"integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
||||
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,12 @@
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
||||
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func Test_configLoading(t *testing.T) {
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, os.Chdir(cwd)) }()
|
||||
|
||||
configsDir := filepath.Join(cwd, "test-fixtures", "configs")
|
||||
path := func(path string) string {
|
||||
return filepath.Join(configsDir, filepath.Join(strings.Split(path, "/")...))
|
||||
}
|
||||
|
||||
type creds struct {
|
||||
Authority string `yaml:"authority"`
|
||||
}
|
||||
|
||||
type registry struct {
|
||||
Credentials []creds `yaml:"auth"`
|
||||
}
|
||||
|
||||
type config struct {
|
||||
Registry registry `yaml:"registry"`
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
home string
|
||||
cwd string
|
||||
args []string
|
||||
expected []creds
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "single explicit config",
|
||||
home: configsDir,
|
||||
cwd: cwd,
|
||||
args: []string{
|
||||
"-c",
|
||||
path("dir1/.syft.yaml"),
|
||||
},
|
||||
expected: []creds{
|
||||
{
|
||||
Authority: "dir1-authority",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple explicit config",
|
||||
home: configsDir,
|
||||
cwd: cwd,
|
||||
args: []string{
|
||||
"-c",
|
||||
path("dir1/.syft.yaml"),
|
||||
"-c",
|
||||
path("dir2/.syft.yaml"),
|
||||
},
|
||||
expected: []creds{
|
||||
{
|
||||
Authority: "dir1-authority",
|
||||
},
|
||||
{
|
||||
Authority: "dir2-authority",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty profile override",
|
||||
home: configsDir,
|
||||
cwd: cwd,
|
||||
args: []string{
|
||||
"-c",
|
||||
path("dir1/.syft.yaml"),
|
||||
"-c",
|
||||
path("dir2/.syft.yaml"),
|
||||
"--profile",
|
||||
"no-auth",
|
||||
},
|
||||
expected: []creds{},
|
||||
},
|
||||
{
|
||||
name: "no profiles defined",
|
||||
home: configsDir,
|
||||
cwd: configsDir,
|
||||
args: []string{
|
||||
"--profile",
|
||||
"invalid",
|
||||
},
|
||||
err: "not found in any configuration files",
|
||||
},
|
||||
{
|
||||
name: "invalid profile name",
|
||||
home: configsDir,
|
||||
cwd: cwd,
|
||||
args: []string{
|
||||
"-c",
|
||||
path("dir1/.syft.yaml"),
|
||||
"-c",
|
||||
path("dir2/.syft.yaml"),
|
||||
"--profile",
|
||||
"alt",
|
||||
},
|
||||
err: "profile not found",
|
||||
},
|
||||
{
|
||||
name: "explicit with profile override",
|
||||
home: configsDir,
|
||||
cwd: cwd,
|
||||
args: []string{
|
||||
"-c",
|
||||
path("dir1/.syft.yaml"),
|
||||
"-c",
|
||||
path("dir2/.syft.yaml"),
|
||||
"--profile",
|
||||
"alt-auth",
|
||||
},
|
||||
expected: []creds{
|
||||
{
|
||||
Authority: "dir1-alt-authority", // dir1 is still first
|
||||
},
|
||||
{
|
||||
Authority: "dir2-alt-authority",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single in cwd",
|
||||
home: configsDir,
|
||||
cwd: path("dir2"),
|
||||
args: []string{},
|
||||
expected: []creds{
|
||||
{
|
||||
Authority: "dir2-authority",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single in home",
|
||||
home: path("dir2"),
|
||||
cwd: configsDir,
|
||||
args: []string{},
|
||||
expected: []creds{
|
||||
{
|
||||
Authority: "dir2-authority",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inherited in cwd",
|
||||
home: path("dir1"),
|
||||
cwd: path("dir2"),
|
||||
args: []string{},
|
||||
expected: []creds{
|
||||
{
|
||||
Authority: "dir2-authority", // dir2 is in cwd, giving higher priority
|
||||
},
|
||||
{
|
||||
Authority: "dir1-authority", // home has "lower priority and should be after"
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inherited profile override",
|
||||
home: path("dir1"),
|
||||
cwd: path("dir2"),
|
||||
args: []string{
|
||||
"--profile",
|
||||
"alt-auth",
|
||||
},
|
||||
expected: []creds{
|
||||
{
|
||||
Authority: "dir2-alt-authority", // dir2 is in cwd, giving higher priority
|
||||
},
|
||||
{
|
||||
Authority: "dir1-alt-authority", // dir1 is home, lower priority
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
require.NoError(t, os.Chdir(test.cwd))
|
||||
defer func() { require.NoError(t, os.Chdir(cwd)) }()
|
||||
env := map[string]string{
|
||||
"HOME": test.home,
|
||||
"XDG_CONFIG_HOME": test.home,
|
||||
}
|
||||
_, stdout, stderr := runSyft(t, env, append([]string{"config", "--load"}, test.args...)...)
|
||||
if test.err != "" {
|
||||
require.Contains(t, stderr, test.err)
|
||||
return
|
||||
} else {
|
||||
require.Empty(t, stderr)
|
||||
}
|
||||
got := config{}
|
||||
err = yaml.NewDecoder(strings.NewReader(stdout)).Decode(&got)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.expected, got.Registry.Credentials)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
registry:
|
||||
auth:
|
||||
- authority: dir1-authority
|
||||
|
||||
profiles:
|
||||
no-auth:
|
||||
registry:
|
||||
auth: []
|
||||
|
||||
alt-auth:
|
||||
registry:
|
||||
auth:
|
||||
- authority: dir1-alt-authority
|
||||
@@ -0,0 +1,9 @@
|
||||
registry:
|
||||
auth:
|
||||
- authority: dir2-authority
|
||||
|
||||
profiles:
|
||||
alt-auth:
|
||||
registry:
|
||||
auth:
|
||||
- authority: dir2-alt-authority
|
||||
@@ -1,3 +1,5 @@
|
||||
FROM alpine@sha256:c5c5fda71656f28e49ac9c5416b3643eaa6a108a8093151d6d1afc9463be8e33
|
||||
RUN rm -rf /lib/apk/db/installed
|
||||
COPY . /home/files
|
||||
# add a circular reference that will result in a failure while executing FindByGlob:
|
||||
RUN mkdir -p /etc/alternatives && ln -s /etc/alternatives/java2 /etc/alternatives/java && ln -s /etc/alternatives/java /etc/alternatives/java2
|
||||
|
||||
@@ -22,6 +22,7 @@ func Test_Unknowns(t *testing.T) {
|
||||
assertInOutput(`no package identified in executable file`),
|
||||
assertInOutput(`unable to read files from java archive`),
|
||||
assertInOutput(`no package identified in archive`),
|
||||
assertInOutput(`cycle during symlink resolution`),
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user