Compare commits

..
Author SHA1 Message Date
Alan Pope bb952ed25a Draft: Identity proof - do NOT merge
Using this to prove my identity.

KJIc1D4zXJbRybKEJrWd

Signed-off-by: Alan Pope <alan.pope@anchore.com>
2024-08-07 08:12:41 +01:00
dependabot[bot] dcd87d1fef chore(deps): bump actions/upload-artifact from 4.3.4 to 4.3.5 (#3095)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.4 to 4.3.5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/0b2256b8c012f0828dc542b3febcab082c67f72b...89ef406dd8d7e03cfd12d9e0a4a378f454709029)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-06 13:17:36 -04:00
anchore-actions-token-generator[bot]andwagoodman 214a0498e0 chore(deps): update CPE dictionary index (#3094)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2024-08-06 13:07:48 -04:00
dependabot[bot] 0f9df805c1 chore(deps): bump golang.org/x/mod from 0.19.0 to 0.20.0 (#3096)
Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.19.0 to 0.20.0.
- [Commits](https://github.com/golang/mod/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/mod
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-06 13:07:33 -04:00
dependabot[bot] 703330abd0 chore(deps): bump github.com/gkampitakis/go-snaps from 0.5.6 to 0.5.7 (#3097)
Bumps [github.com/gkampitakis/go-snaps](https://github.com/gkampitakis/go-snaps) from 0.5.6 to 0.5.7.
- [Release notes](https://github.com/gkampitakis/go-snaps/releases)
- [Commits](https://github.com/gkampitakis/go-snaps/compare/v0.5.6...v0.5.7)

---
updated-dependencies:
- dependency-name: github.com/gkampitakis/go-snaps
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-06 13:07:21 -04:00
Gijs CalisandKeith Zantow 9d40d1152e feat: improved java maven property resolution (#2769)
Signed-off-by: Gijs Calis <51088038+GijsCalis@users.noreply.github.com>
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Co-authored-by: Keith Zantow <kzantow@gmail.com>
2024-08-05 11:30:47 -04:00
Harippriya Sivapatham cc15edca62 fix: use organization for package supplier when reading Java vendor fields (#3093)
Signed-off-by: Harippriya Sivapatham <harippriyasivapatham@gmail.com>
2024-08-03 16:00:55 -04:00
anchore-actions-token-generator[bot]andspiffcs 623532e3ed chore(deps): update tools to latest versions (#3091)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2024-08-02 13:25:09 -04:00
Dor Hayunanddor-hayun 48f1e975f0 fix: update 'guessMainPackageNameAndVersionFromPomInfo' and 'artifactIDMatchesFilename' (#3054)
- Correct retrieval of package name when main POM file exists
- Address issue where wrong package name was retrieved for certain jars
- Example case: 'jansi' jar containing multiple jars like 'jansi-win32'
- Ensure true is returned when filename matches the artifact ID, prevent random retrieval by checking prefix and suffix
- Use fallback check with suffix and prefix if no POM properties file matches the exact artifact name

Signed-off-by: dor-hayun <dor.hayun@mend.io>
Co-authored-by: dor-hayun <dor.hayun@mend.io>
2024-08-01 13:47:15 -04:00
Christopher Angelo Phillips c84cb2cf84 fix: update mainModuleVersion function to always prefix v to findings (#3087)
* chore: basic fix
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
* test: make sure ldflags are prefixed with v
---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2024-08-01 11:29:07 -04:00
Keith Zantow 05a10e8bed chore: update release script to use gh from binny (#3084)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2024-07-31 20:10:17 -04:00
Laurent Goderre 92d63df6f5 Added the SWI Prolog (swipl) ecosystem (#3076)
* Add binary classifier for swipl

Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>

* Added cataloger for SWI Prolog Pack packages

Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>

---------

Signed-off-by: Laurent Goderre <laurent.goderre@docker.com>
2024-07-31 16:13:26 -04:00
75 changed files with 6204 additions and 1571 deletions
+1 -1
View File
@@ -111,7 +111,7 @@ tools:
# used for triggering a release
- name: gh
version:
want: v2.53.0
want: v2.54.0
method: github-release
with:
repo: cli/cli
+14 -7
View File
@@ -4,12 +4,19 @@ set -eu
bold=$(tput bold)
normal=$(tput sgr0)
if ! [ -x "$(command -v gh)" ]; then
echo "The GitHub CLI could not be found. To continue follow the instructions at https://github.com/cli/cli#installation"
GH_CLI=.tool/gh
if ! [ -x "$(command -v $GH_CLI)" ]; then
echo "The GitHub CLI could not be found. run: make bootstrap"
exit 1
fi
gh auth status
$GH_CLI auth status
# set the default repo in cases where multiple remotes are defined
$GH_CLI repo set-default anchore/syft
export GITHUB_TOKEN="${GITHUB_TOKEN-"$($GH_CLI auth token)"}"
# we need all of the git state to determine the next version. Since tagging is done by
# the release pipeline it is possible to not have all of the tags from previous releases.
@@ -37,7 +44,7 @@ done
echo "${bold}Kicking off release for ${NEXT_VERSION}${normal}..."
echo
gh workflow run release.yaml -f version=${NEXT_VERSION}
$GH_CLI workflow run release.yaml -f version=${NEXT_VERSION}
echo
echo "${bold}Waiting for release to start...${normal}"
@@ -45,6 +52,6 @@ sleep 10
set +e
echo "${bold}Head to the release workflow to monitor the release:${normal} $(gh run list --workflow=release.yaml --limit=1 --json url --jq '.[].url')"
id=$(gh run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[].databaseId')
gh run watch $id --exit-status || (echo ; echo "${bold}Logs of failed step:${normal}" && GH_PAGER="" gh run view $id --log-failed)
echo "${bold}Head to the release workflow to monitor the release:${normal} $($GH_CLI run list --workflow=release.yaml --limit=1 --json url --jq '.[].url')"
id=$($GH_CLI run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[].databaseId')
$GH_CLI run watch $id --exit-status || (echo ; echo "${bold}Logs of failed step:${normal}" && GH_PAGER="" $GH_CLI run view $id --log-failed)
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
OUTPUT="${OUTPUT//$'\r'/'%0D'}" # URL encode all '\r' characters
echo "result=$OUTPUT" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
- uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
with:
name: benchmark-test-results
path: test/results/**/*
+2
View File
@@ -178,3 +178,5 @@ The Syft Team hold regular community meetings online. All are welcome to join to
- Check the [calendar](https://calendar.google.com/calendar/u/0/r?cid=Y182OTM4dGt0MjRtajI0NnNzOThiaGtnM29qNEBncm91cC5jYWxlbmRhci5nb29nbGUuY29t) for the next meeting date.
- Add items to the [agenda](https://docs.google.com/document/d/1ZtSAa6fj2a6KRWviTn3WoJm09edvrNUp4Iz_dOjjyY8/edit?usp=sharing) (join [this group](https://groups.google.com/g/anchore-oss-community) for write access to the [agenda](https://docs.google.com/document/d/1ZtSAa6fj2a6KRWviTn3WoJm09edvrNUp4Iz_dOjjyY8/edit?usp=sharing))
- See you there!
<!-- KJIc1D4zXJbRybKEJrWd -->
+3
View File
@@ -64,6 +64,7 @@ func DefaultCatalog() Catalog {
Package: defaultPackageConfig(),
LinuxKernel: defaultLinuxKernelConfig(),
Golang: defaultGolangConfig(),
Java: defaultJavaConfig(),
File: defaultFileConfig(),
Relationships: defaultRelationshipsConfig(),
Source: defaultSourceConfig(),
@@ -150,6 +151,8 @@ func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
GuessUnpinnedRequirements: cfg.Python.GuessUnpinnedRequirements,
},
JavaArchive: java.DefaultArchiveCatalogerConfig().
WithUseMavenLocalRepository(cfg.Java.UseMavenLocalRepository).
WithMavenLocalRepositoryDir(cfg.Java.MavenLocalRepositoryDir).
WithUseNetwork(cfg.Java.UseNetwork).
WithMavenBaseURL(cfg.Java.MavenURL).
WithArchiveTraversal(archiveSearch, cfg.Java.MaxParentRecursiveDepth),
+30 -8
View File
@@ -1,24 +1,46 @@
package options
import "github.com/anchore/clio"
import (
"github.com/anchore/clio"
"github.com/anchore/syft/syft/pkg/cataloger/java"
)
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"`
}
func defaultJavaConfig() javaConfig {
def := java.DefaultArchiveCatalogerConfig()
return javaConfig{
UseNetwork: def.UseNetwork,
MaxParentRecursiveDepth: def.MaxParentRecursiveDepth,
UseMavenLocalRepository: def.UseMavenLocalRepository,
MavenLocalRepositoryDir: def.MavenLocalRepositoryDir,
MavenURL: def.MavenBaseURL,
}
}
var _ interface {
clio.FieldDescriber
} = (*javaConfig)(nil)
func (o *javaConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.UseNetwork, `enables Syft to use the network to fill in more detailed information about artifacts
currently this enables searching maven-url for license data
when running across pom.xml files that could have more information, syft will
explicitly search maven for license information by querying the online pom when this is true
this option is helpful for when the parent pom has more data,
that is not accessible from within the final built artifact`)
descriptions.Add(&o.UseNetwork, `enables Syft to use the network to fetch version and license information for packages when
a parent or imported pom file is not found in the local maven repository.
the pom files are downloaded from the remote Maven repository at 'maven-url'`)
descriptions.Add(&o.MavenURL, `maven repository to use, defaults to Maven central`)
descriptions.Add(&o.MaxParentRecursiveDepth, `depth to recursively resolve parent POMs`)
descriptions.Add(&o.MaxParentRecursiveDepth, `depth to recursively resolve parent POMs, no limit if <= 0`)
descriptions.Add(&o.UseMavenLocalRepository, `use the local Maven repository to retrieve pom files. When Maven is installed and was previously used
for building the software that is being scanned, then most pom files will be available in this
repository on the local file system. this greatly speeds up scans. when all pom files are available
in the local repository, then 'use-network' is not needed.
TIP: If you want to download all required pom files to the local repository without running a full
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`)
}
@@ -388,6 +388,14 @@ var dirOnlyTestCases = []testCase{
"swift-numerics": "1.0.2",
},
},
{
name: "find swipl pack package manager packages",
pkgType: pkg.SwiplPackPkg,
pkgLanguage: pkg.Swipl,
pkgInfo: map[string]string{
"hdt": "0.5.2",
},
},
{
name: "find github action packages (from usage in workflow files and composite actions)",
pkgType: pkg.GithubActionPkg,
@@ -51,6 +51,7 @@ func TestPkgCoverageImage(t *testing.T) {
definedLanguages.Remove(pkg.Rust.String())
definedLanguages.Remove(pkg.Dart.String())
definedLanguages.Remove(pkg.Swift.String())
definedLanguages.Remove(pkg.Swipl.String())
definedLanguages.Remove(pkg.CPP.String())
definedLanguages.Remove(pkg.Haskell.String())
definedLanguages.Remove(pkg.Elixir.String())
@@ -76,6 +77,7 @@ func TestPkgCoverageImage(t *testing.T) {
definedPkgs.Remove(string(pkg.LinuxKernelPkg))
definedPkgs.Remove(string(pkg.LinuxKernelModulePkg))
definedPkgs.Remove(string(pkg.SwiftPkg))
definedPkgs.Remove(string(pkg.SwiplPackPkg))
definedPkgs.Remove(string(pkg.GithubActionPkg))
definedPkgs.Remove(string(pkg.GithubActionWorkflowPkg))
@@ -0,0 +1,10 @@
name(hdt).
version('0.5.2').
% TODO: swipl_version([90121]).
title('Access RDF HDT files').
keywords(['RDF']).
author( 'Jan Wielemaker', 'J.Wielemaker@vu.nl' ).
packager( 'Jan Wielemaker', 'J.Wielemaker@vu.nl' ).
maintainer( 'Jan Wielemaker', 'J.Wielemaker@vu.nl' ).
home( 'https://github.com/JanWielemaker/hdt' ).
download( 'https://github.com/JanWielemaker/hdt/archive/V*.zip' ).
+2 -2
View File
@@ -34,7 +34,7 @@ require (
github.com/elliotchance/phpserialize v1.4.0
github.com/facebookincubator/nvdtools v0.1.5
github.com/github/go-spdx/v2 v2.3.1
github.com/gkampitakis/go-snaps v0.5.6
github.com/gkampitakis/go-snaps v0.5.7
github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.12.0
github.com/go-test/deep v1.1.1
@@ -78,7 +78,7 @@ require (
github.com/xeipuuv/gojsonschema v1.2.0
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
go.uber.org/goleak v1.3.0
golang.org/x/mod v0.19.0
golang.org/x/mod v0.20.0
golang.org/x/net v0.27.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.31.1
+4 -4
View File
@@ -293,8 +293,8 @@ github.com/gkampitakis/ciinfo v0.3.0 h1:gWZlOC2+RYYttL0hBqcoQhM7h1qNkVqvRCV1fOvp
github.com/gkampitakis/ciinfo v0.3.0/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
github.com/gkampitakis/go-snaps v0.5.6 h1:kAal5JbqTycI+6xeS3K0nPqvNDAxqKX5W3dRXKxIJpA=
github.com/gkampitakis/go-snaps v0.5.6/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y=
github.com/gkampitakis/go-snaps v0.5.7 h1:uVGjHR4t4pPHU944udMx7VKHpwepZXmvDMF+yDmI0rg=
github.com/gkampitakis/go-snaps v0.5.7/go.mod h1:ZABkO14uCuVxBHAXAfKG+bqNz+aa1bGPAg8jkI0Nk8Y=
github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4=
github.com/glebarez/go-sqlite v1.20.3/go.mod h1:u3N6D/wftiAzIOJtZl6BmedqxmmkDfH3q+ihjqxC9u0=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
@@ -920,8 +920,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+1 -1
View File
@@ -3,5 +3,5 @@ package internal
const (
// JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "16.0.14"
JSONSchemaVersion = "16.0.15"
)
+2
View File
@@ -29,6 +29,7 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/rust"
sbomCataloger "github.com/anchore/syft/syft/pkg/cataloger/sbom"
"github.com/anchore/syft/syft/pkg/cataloger/swift"
"github.com/anchore/syft/syft/pkg/cataloger/swipl"
"github.com/anchore/syft/syft/pkg/cataloger/wordpress"
)
@@ -93,6 +94,7 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
newSimplePackageTaskFactory(rust.NewCargoLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "rust", "cargo"),
newSimplePackageTaskFactory(swift.NewCocoapodsCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "swift", "cocoapods"),
newSimplePackageTaskFactory(swift.NewSwiftPackageManagerCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "swift", "spm"),
newSimplePackageTaskFactory(swipl.NewSwiplPackCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "swipl", "pack"),
// language-specific package for both image and directory scans (but not necessarily declared) ////////////////////////////////////////
newSimplePackageTaskFactory(dotnet.NewDotnetPortableExecutableCataloger, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, pkgcataloging.LanguageTag, "dotnet", "c#", "binary"),
File diff suppressed because it is too large Load Diff
+46 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.0.14/document",
"$id": "anchore.io/schema/syft/json/16.0.15/document",
"$ref": "#/$defs/Document",
"$defs": {
"AlpmDbEntry": {
@@ -1608,6 +1608,9 @@
{
"$ref": "#/$defs/SwiftPackageManagerLockEntry"
},
{
"$ref": "#/$defs/SwiplpackPackage"
},
{
"$ref": "#/$defs/WordpressPluginEntry"
}
@@ -2504,6 +2507,48 @@
"revision"
]
},
"SwiplpackPackage": {
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"author": {
"type": "string"
},
"authorEmail": {
"type": "string"
},
"packager": {
"type": "string"
},
"packagerEmail": {
"type": "string"
},
"homepage": {
"type": "string"
},
"dependencies": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object",
"required": [
"name",
"version",
"author",
"authorEmail",
"packager",
"packagerEmail",
"homepage",
"dependencies"
]
},
"WordpressPluginEntry": {
"properties": {
"pluginInstallDirectory": {
+1 -1
View File
@@ -21,7 +21,7 @@ type LicenseEvidence struct {
func NewLicense(value string) License {
spdxExpression, err := license.ParseExpression(value)
if err != nil {
log.Trace("unable to parse license expression: %s, %w", value, err)
log.WithFields("error", err, "value", value).Trace("unable to parse license expression")
}
return License{
@@ -56,6 +56,10 @@ func Originator(p pkg.Package) (typ string, author string) { // nolint: funlen
if author == "" {
author = metadata.Manifest.Main.MustGet("Implementation-Vendor")
}
// Vendor is specified, hence set 'Organization' as the PackageSupplier
if author != "" {
typ = orgType
}
}
case pkg.LinuxKernelModule:
@@ -103,6 +107,8 @@ func Originator(p pkg.Package) (typ string, author string) { // nolint: funlen
// it seems that the vast majority of the time the author is an org, not a person
typ = orgType
author = metadata.Author
case pkg.SwiplPackEntry:
author = formatPersonOrOrg(metadata.Author, metadata.AuthorEmail)
}
if typ == "" && author != "" {
@@ -144,6 +150,10 @@ func Supplier(p pkg.Package) (typ string, author string) {
author = metadata.Packager
}
if metadata, ok := p.Metadata.(pkg.SwiplPackEntry); ok {
author = formatPersonOrOrg(metadata.Packager, metadata.PackagerEmail)
}
if author == "" {
// TODO: this uses the Originator function for now until a better distinction can be made for supplier
return Originator(p)
@@ -40,6 +40,7 @@ func Test_OriginatorSupplier(t *testing.T) {
pkg.RustBinaryAuditEntry{},
pkg.RustCargoLockEntry{},
pkg.SwiftPackageManagerResolvedEntry{},
pkg.SwiplPackEntry{},
pkg.YarnLockEntry{},
)
tests := []struct {
@@ -137,8 +138,8 @@ func Test_OriginatorSupplier(t *testing.T) {
},
},
},
originator: "Person: auth-spec",
supplier: "Person: auth-spec",
originator: "Organization: auth-spec",
supplier: "Organization: auth-spec",
},
{
name: "from java -- fallback to impl vendor in main manifest section",
@@ -154,8 +155,8 @@ func Test_OriginatorSupplier(t *testing.T) {
},
},
},
originator: "Person: auth-impl",
supplier: "Person: auth-impl",
originator: "Organization: auth-impl",
supplier: "Organization: auth-impl",
},
{
name: "from java -- non-main manifest sections ignored",
@@ -336,6 +337,19 @@ func Test_OriginatorSupplier(t *testing.T) {
originator: "Organization: auth",
supplier: "Organization: auth",
},
{
name: "from swipl pack",
input: pkg.Package{
Metadata: pkg.SwiplPackEntry{
Author: "auth",
AuthorEmail: "auth@auth.gov",
Packager: "me",
PackagerEmail: "me@auth.com",
},
},
originator: "Person: auth (auth@auth.gov)",
supplier: "Person: me (me@auth.com)",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
@@ -62,6 +62,8 @@ func SourceInfo(p pkg.Package) string {
answer = "acquired package info from Rockspec package file"
case pkg.SwiftPkg:
answer = "acquired package info from resolved Swift package manifest"
case pkg.SwiplPackPkg:
answer = "acquired package info from SWI Prolo pack package file"
case pkg.GithubActionPkg, pkg.GithubActionWorkflowPkg:
answer = "acquired package info from GitHub Actions workflow file or composite action file"
case pkg.WordpressPluginPkg:
@@ -263,6 +263,14 @@ func Test_SourceInfo(t *testing.T) {
"from resolved Swift package manifest",
},
},
{
input: pkg.Package{
Type: pkg.SwiplPackPkg,
},
expected: []string{
"acquired package info from SWI Prolo pack package file",
},
},
{
input: pkg.Package{
Type: pkg.GithubActionPkg,
@@ -49,6 +49,7 @@ func AllTypes() []any {
pkg.RustBinaryAuditEntry{},
pkg.RustCargoLockEntry{},
pkg.SwiftPackageManagerResolvedEntry{},
pkg.SwiplPackEntry{},
pkg.WordpressPluginEntry{},
pkg.YarnLockEntry{},
}
+1
View File
@@ -102,6 +102,7 @@ var jsonTypes = makeJSONTypes(
jsonNames(pkg.RpmDBEntry{}, "rpm-db-entry", "RpmMetadata", "RpmdbMetadata"),
jsonNamesWithoutLookup(pkg.RpmArchive{}, "rpm-archive", "RpmMetadata"), // the legacy value is split into two types, where the other is preferred
jsonNames(pkg.SwiftPackageManagerResolvedEntry{}, "swift-package-manager-lock-entry", "SwiftPackageManagerMetadata"),
jsonNames(pkg.SwiplPackEntry{}, "swiplpack-package"),
jsonNames(pkg.RustCargoLockEntry{}, "rust-cargo-lock-entry", "RustCargoPackageMetadata"),
jsonNamesWithoutLookup(pkg.RustBinaryAuditEntry{}, "rust-cargo-audit-entry", "RustCargoPackageMetadata"), // the legacy value is split into two types, where the other is preferred
jsonNames(pkg.WordpressPluginEntry{}, "wordpress-plugin-entry", "WordpressMetadata"),
@@ -473,12 +473,6 @@ func Test_JSONName_JSONLegacyName(t *testing.T) {
expectedJSONName: "rpm-archive",
expectedLegacyName: "RpmMetadata", // note: conflicts with <=v11.x schema for "rpm-db-entry" metadata type
},
{
name: "SwiftPackageManagerMetadata",
metadata: pkg.SwiftPackageManagerResolvedEntry{},
expectedJSONName: "swift-package-manager-lock-entry",
expectedLegacyName: "SwiftPackageManagerMetadata",
},
{
name: "CargoPackageMetadata",
metadata: pkg.RustCargoLockEntry{},
@@ -929,6 +929,17 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
Metadata: metadata("erlang-alpine-binary"),
},
},
{
logicalFixture: "swipl/9.3.8/linux-amd64",
expected: pkg.Package{
Name: "swipl",
Version: "9.3.8",
Type: "binary",
PURL: "pkg:generic/swipl@9.3.8",
Locations: locations("swipl"),
Metadata: metadata("swipl-binary"),
},
},
{
logicalFixture: "nginx/1.25.1/linux-amd64",
expected: pkg.Package{
+10
View File
@@ -428,6 +428,16 @@ func DefaultClassifiers() []Classifier {
PURL: mustPURL("pkg:generic/erlang@version"),
CPEs: singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"),
},
{
Class: "swipl-binary",
FileGlob: "**/swipl",
EvidenceMatcher: FileContentsVersionMatcher(
`(?m)swipl-(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\/`,
),
Package: "swipl",
PURL: mustPURL("pkg:generic/swipl@version"),
CPEs: singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*"),
},
{
Class: "consul-binary",
FileGlob: "**/consul",
@@ -0,0 +1,9 @@
name: beam.smp
offset: 6378906
length: 140
snippetSha256: 186c882decc4160bfcfbdddaee08e8364675c795d3e85697422fbd7218452ee2
fileSha256: f84f0ffc2e397abcf5f081b865e241017b1793c2ec38f5136bff4e5e21ffdad7
### byte snippet to follow ###
ied by configure */
#define ERTS_EMU_CMDLINE_FLAGS " -fno-strict-aliasing -fno-common -g -O2 -I/usr/src/otp_src_27.0/erts/x86_64-pc-linux-mu
@@ -71,6 +71,13 @@ from-images:
paths:
- /usr/local/lib/erlang/erts-15.0/bin/beam.smp
- version: 9.3.8
images:
- ref: swipl:9.3.8@sha256:6a15e6a03afe943228924a5502ba763e653ff28d9b3391e2b3e1fc3e991f37d4
platform: linux/amd64
paths:
- /usr/lib/swipl/bin/x86_64-linux/swipl
- version: 1.21.3
images:
- ref: golang:1.21.3@sha256:3ce8313c3513515040870c55e0c041a2b94f3576a58cfd3948633604214aa811
+12 -12
View File
@@ -20,7 +20,7 @@ func Test_PackageCataloger_Binary(t *testing.T) {
name: "simple module with dependencies",
fixture: "image-small",
expectedPkgs: []string{
"anchore.io/not/real @ (devel) (/run-me)",
"anchore.io/not/real @ v1.0.0 (/run-me)",
"github.com/andybalholm/brotli @ v1.0.1 (/run-me)",
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me)",
"github.com/golang/snappy @ v0.0.2 (/run-me)",
@@ -34,17 +34,17 @@ func Test_PackageCataloger_Binary(t *testing.T) {
"stdlib @ go1.22.4 (/run-me)",
},
expectedRels: []string{
"github.com/andybalholm/brotli @ v1.0.1 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/golang/snappy @ v0.0.2 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/klauspost/compress @ v1.11.4 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/klauspost/pgzip @ v1.2.5 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/mholt/archiver/v3 @ v3.5.1 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/nwaples/rardecode @ v1.1.0 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/pierrec/lz4/v4 @ v4.1.2 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/ulikunitz/xz @ v0.5.9 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/xi2/xz @ v0.0.0-20171230120015-48954b6210f8 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"stdlib @ go1.22.4 (/run-me) [dependency-of] anchore.io/not/real @ (devel) (/run-me)",
"github.com/andybalholm/brotli @ v1.0.1 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
"github.com/dsnet/compress @ v0.0.2-0.20210315054119-f66993602bf5 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
"github.com/golang/snappy @ v0.0.2 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
"github.com/klauspost/compress @ v1.11.4 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
"github.com/klauspost/pgzip @ v1.2.5 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
"github.com/mholt/archiver/v3 @ v3.5.1 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
"github.com/nwaples/rardecode @ v1.1.0 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
"github.com/pierrec/lz4/v4 @ v4.1.2 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
"github.com/ulikunitz/xz @ v0.5.9 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
"github.com/xi2/xz @ v0.0.0-20171230120015-48954b6210f8 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
"stdlib @ go1.22.4 (/run-me) [dependency-of] anchore.io/not/real @ v1.0.0 (/run-me)",
},
},
{
@@ -180,6 +180,10 @@ func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *exten
version := c.findMainModuleVersion(metadata, gbs, reader)
if version != "" {
// make sure version is prefixed with v as some build systems parsed
// during `findMainModuleVersion` can include incomplete semver
// vx.x.x is correct
version = ensurePrefix(version, "v")
main.Version = version
main.PURL = packageURL(main.Name, main.Version)
@@ -398,3 +402,10 @@ func createMainModuleFromPath(existing *extendedBuildInfo) debug.Module {
Version: devel,
}
}
func ensurePrefix(s, prefix string) string {
if !strings.HasPrefix(s, prefix) {
return prefix + s
}
return s
}
@@ -7,8 +7,7 @@ COPY go.mod go.sum ./
RUN go mod download
COPY main.go main.go
RUN CGO_ENABLED=0 GOOS=linux go build -o run-me .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-X main.Version=1.0.0" -o run-me .
FROM scratch
@@ -4070,6 +4070,9 @@
"netmask": [
"cpe:2.3:a:netmask_project:netmask:*:*:*:*:*:node.js:*:*"
],
"network": [
"cpe:2.3:a:forkhq:network:*:*:*:*:*:node.js:*:*"
],
"network-manager": [
"cpe:2.3:a:network-manager_project:network-manager:*:*:*:*:*:node.js:*:*"
],
@@ -7531,6 +7534,9 @@
"ameliabooking": [
"cpe:2.3:a:tms-outsource:amelia:*:*:*:*:*:wordpress:*:*"
],
"amen": [
"cpe:2.3:a:amen_project:amen:*:*:*:*:*:*:wordpress:*"
],
"amministrazione-aperta": [
"cpe:2.3:a:amministrazione_aperta_project:amministrazione_aperta:*:*:*:*:*:wordpress:*:*"
],
@@ -7579,6 +7585,9 @@
"animated-counters": [
"cpe:2.3:a:eralion:animated_counters:*:*:*:*:*:wordpress:*:*"
],
"anonymous-restricted-content": [
"cpe:2.3:a:tarassych:anonymous_restricted_content:*:*:*:*:*:wordpress:*:*"
],
"another-wordpress-classifieds-plugin": [
"cpe:2.3:a:awpcp:another_wordpress_classifieds_plugin:*:*:*:*:*:wordpress:*:*"
],
@@ -7922,7 +7931,8 @@
"cpe:2.3:a:batch_cat_project:batch_cat:*:*:*:*:*:wordpress:*:*"
],
"bb-bootstrap-cards": [
"cpe:2.3:a:brainstormforce:cards_for_beaver_builder:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:brainstormforce:cards_for_beaver_builder:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:ultimateaddons:cards_for_beaver_builder:*:*:*:*:*:wordpress:*:*"
],
"bbp-move-topics": [
"cpe:2.3:a:bbpress_move_topics_project:bbpress_move_topics:*:*:*:*:*:wordpress:*:*"
@@ -7980,6 +7990,9 @@
"bestbooks": [
"cpe:2.3:a:presspage:bestbooks:*:*:*:*:*:wordpress:*:*"
],
"better-anchor-links": [
"cpe:2.3:a:ludek:better_anchor_links:*:*:*:*:*:wordpress:*:*"
],
"better-font-awesome": [
"cpe:2.3:a:better_font_awesome_project:better_font_awesome:*:*:*:*:*:wordpress:*:*"
],
@@ -8256,6 +8269,9 @@
"buddypress-media": [
"cpe:2.3:a:rtcamp:rtmedia:*:*:*:*:*:wordpress:*:*"
],
"buddypress-members-only": [
"cpe:2.3:a:membersonly:buddypress_members_only:*:*:*:*:*:wordpress:*:*"
],
"bug-library": [
"cpe:2.3:a:bug_library_project:bug_library:*:*:*:*:*:wordpress:*:*"
],
@@ -8371,6 +8387,9 @@
"calculator-builder": [
"cpe:2.3:a:wow-company:calculator-builder:*:*:*:*:*:wordpress:*:*"
],
"calculatorpro-calculators": [
"cpe:2.3:a:jgadbois:calculatorpro_calculators:*:*:*:*:*:wordpress:*:*"
],
"caldera-forms": [
"cpe:2.3:a:calderaforms:caldera_forms:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:calderalabs:caldera_forms:*:*:*:*:*:wordpress:*:*"
@@ -8379,11 +8398,15 @@
"cpe:2.3:a:kieranoshea:calendar:*:*:*:*:*:wordpress:*:*"
],
"calendar-booking": [
"cpe:2.3:a:startbooking:scheduling_plugin:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:startbooking:scheduling_plugin:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:startbooking:scheduling_plugin_-_online_booking:*:*:*:*:*:wordpress:*:*"
],
"calendar-plugin": [
"cpe:2.3:a:calendar_plugin_project:calendar_plugin:*:*:*:*:*:wordpress:*:*"
],
"calendarista-basic-edition": [
"cpe:2.3:a:typps:calendarista:*:*:*:*:basic:wordpress:*:*"
],
"call-now-button": [
"cpe:2.3:a:callnowbutton:call_now_button:*:*:*:*:*:wordpress:*:*"
],
@@ -8944,6 +8967,9 @@
"contact-forms-builder": [
"cpe:2.3:a:wpdevart:contact_form_builder:*:*:*:*:*:wordpress:*:*"
],
"contact-list": [
"cpe:2.3:a:contactlistpro:contact_list:*:*:*:*:*:wordpress:*:*"
],
"contact-us-page-contact-people": [
"cpe:2.3:a:a3rev:contact_us_page_-_contact_people:*:*:*:*:*:wordpress:*:*"
],
@@ -9108,7 +9134,8 @@
"cpe:2.3:a:hasthemes:coupon_zen:*:*:*:*:*:wordpress:*:*"
],
"cowidgets-elementor-addons": [
"cpe:2.3:a:codeless:cowidgets_-_elementor:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:codeless:cowidgets_-_elementor:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:codeless:cowidgets_elementor_addons:*:*:*:*:*:wordpress:*:*"
],
"cp-appointment-calendar": [
"cpe:2.3:a:codepeople:cp_appointment_calendar:*:*:*:*:*:wordpress:*:*"
@@ -9489,6 +9516,9 @@
"delucks-seo": [
"cpe:2.3:a:delucks:delucks_seo:*:*:*:*:*:wordpress:*:*"
],
"demo-awesome": [
"cpe:2.3:a:theme4press:demo_awesome:*:*:*:*:*:wordpress:*:*"
],
"democracy-poll": [
"cpe:2.3:a:wp-kama:democracy_poll:*:*:*:*:*:wordpress:*:*"
],
@@ -9878,6 +9908,10 @@
"easyappointments": [
"cpe:2.3:a:easyappointments:easy\\!appointments:*:*:*:*:*:wordpress:*:*"
],
"easyazon": [
"cpe:2.3:a:flowdee:easyazon:*:*:*:*:*:*:*:*",
"cpe:2.3:a:getaawp:easyazon:*:*:*:*:*:wordpress:*:*"
],
"easyjobs": [
"cpe:2.3:a:easy.jobs:easy.jobs:*:*:*:*:*:wordpress:*:*"
],
@@ -9950,6 +9984,9 @@
"elegant-custom-fonts": [
"cpe:2.3:a:breakdance:elegant_custom_fonts:*:*:*:*:*:wordpress:*:*"
],
"elegant-themes-icons": [
"cpe:2.3:a:wpai:elegant_themes_icons:*:*:*:*:*:wordpress:*:*"
],
"element-ready-lite": [
"cpe:2.3:a:quomodosoft:elementsready:*:*:*:*:*:wordpress:*:*"
],
@@ -10178,6 +10215,7 @@
"cpe:2.3:a:e-dynamics:events_made_easy:*:*:*:*:*:wordpress:*:*"
],
"events-manager": [
"cpe:2.3:a:pixelite:events_manager:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:wp-events-plugin:events_manager:*:*:*:*:*:wordpress:*:*"
],
"ever-compare": [
@@ -10224,7 +10262,8 @@
"cpe:2.3:a:ithemes:ithemes_exchange:*:*:*:*:*:wordpress:*:*"
],
"exclusive-addons-for-elementor": [
"cpe:2.3:a:devscred:exclusive_addons_for_elementor:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:devscred:exclusive_addons_for_elementor:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:exclusiveaddons:exclusive_addons_for_elementor:*:*:*:*:*:wordpress:*:*"
],
"exit-intent-popups-by-optimonk": [
"cpe:2.3:a:optimonk:optimonk\\:popups\\,_personalization_\\\u0026_a\\/b_testing:*:*:*:*:*:wordpress:*:*"
@@ -10271,6 +10310,9 @@
"extensions-for-cf7": [
"cpe:2.3:a:hasthemes:extensions_for_cf7:*:*:*:*:*:wordpress:*:*"
],
"extensions-for-elementor": [
"cpe:2.3:a:idioweb:extensions_for_elementor:*:*:*:*:*:wordpress:*:*"
],
"extensive-vc-addon": [
"cpe:2.3:a:wprealize:extensive_vc_addons_for_wpbakery_page_builder:*:*:*:*:*:wordpress:*:*"
],
@@ -11379,6 +11421,9 @@
"iframe": [
"cpe:2.3:a:iframe_project:iframe:*:*:*:*:*:wordpress:*:*"
],
"iframe-forms": [
"cpe:2.3:a:jrbecart:iframe_forms:*:*:*:*:*:wordpress:*:*"
],
"iframe-shortcode": [
"cpe:2.3:a:jacksonwhelan:iframe_shortcode:*:*:*:*:*:wordpress:*:*"
],
@@ -11471,7 +11516,8 @@
"cpe:2.3:a:imagestowebp_project:images_to_webp:*:*:*:*:*:wordpress:*:*"
],
"imageseo": [
"cpe:2.3:a:imageseo:optimize_images_alt_text_\\(alt_tag\\)_\\\u0026_names_for_seo_using_ai:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:imageseo:optimize_images_alt_text_\\(alt_tag\\)_\\\u0026_names_for_seo_using_ai:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:wpchill:optimize_images_alt_text_\\(alt_tag\\)_\\\u0026_names_for_seo_using_ai:*:*:*:*:*:wordpress:*:*"
],
"imdb-info-box": [
"cpe:2.3:a:99webtools:imdb_info_box:*:*:*:*:*:wordpress:*:*"
@@ -11592,7 +11638,8 @@
"cpe:2.3:a:getnet_argentina_para_woocommerce_project:getnet_argentina_para_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"integrate-google-drive": [
"cpe:2.3:a:softlabbd:integrate_google_drive:*:*:*:*:*:wordpress:*:*"
"cpe:2.3:a:softlabbd:integrate_google_drive:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:softlabdb:integrate_google_drive:*:*:*:*:*:wordpress:*:*"
],
"integration-for-billingo-gravity-forms": [
"cpe:2.3:a:integration_for_billingo_\\\u0026_gravity_forms_project:integration_for_billingo_\\\u0026_gravity_forms:*:*:*:*:*:wordpress:*:*"
@@ -11603,6 +11650,9 @@
"integration-for-szamlazzhu-woocommerce": [
"cpe:2.3:a:visztpeter:integration_for_szamlazz.hu_\\\u0026_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"intelly-related-posts": [
"cpe:2.3:a:data443:inline_related_posts:*:*:*:*:*:wordpress:*:*"
],
"interactive-3d-flipbook-powered-physics-engine": [
"cpe:2.3:a:3dflipbook:3d_flipbook:*:*:*:*:*:wordpress:*:*"
],
@@ -12013,6 +12063,9 @@
"license-manager-for-woocommerce": [
"cpe:2.3:a:wpexperts:license_manager_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"lifeline-donation": [
"cpe:2.3:a:webinane:lifeline_donation:*:*:*:*:*:wordpress:*:*"
],
"lifterlms": [
"cpe:2.3:a:lifterlms:lifterlms:*:*:*:*:*:wordpress:*:*"
],
@@ -13300,6 +13353,9 @@
"pdf-viewer-block": [
"cpe:2.3:a:gutenberg_pdf_viewer_block_project:gutenberg_pdf_viewer_block:*:*:*:*:*:wordpress:*:*"
],
"pdf-viewer-for-elementor": [
"cpe:2.3:a:redlettuce:pdf_viewer_for_elementor:*:*:*:*:*:wordpress:*:*"
],
"pdf24-posts-to-pdf": [
"cpe:2.3:a:pdf24_articles_to_pdf_project:pdf24_articles_to_pdf:*:*:*:*:*:wordpress:*:*"
],
@@ -13678,6 +13734,9 @@
"powerpress": [
"cpe:2.3:a:blubrry:powerpress:*:*:*:*:*:wordpress:*:*"
],
"pray-for-me": [
"cpe:2.3:a:projectcaruso:pray_for_me:*:*:*:*:*:wordpress:*:*"
],
"pre-orders-for-woocommerce": [
"cpe:2.3:a:brightplugins:pre-orders_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
@@ -13704,6 +13763,9 @@
"pressforward": [
"cpe:2.3:a:pressforward:pressforward:*:*:*:*:*:wordpress:*:*"
],
"prettyphoto": [
"cpe:2.3:a:master-addons:prettyphoto:*:*:*:*:*:wordpress:*:*"
],
"preview-link-generator": [
"cpe:2.3:a:hasthemes:preview_link_generator:*:*:*:*:*:wordpress:*:*"
],
@@ -13817,6 +13879,9 @@
"promobar": [
"cpe:2.3:a:bestwebsoft:promobar:*:*:*:*:*:wordpress:*:*"
],
"promolayer-popup-builder": [
"cpe:2.3:a:promolayer:popup_builder:*:*:*:*:*:wordpress:*:*"
],
"promotion-slider": [
"cpe:2.3:a:promotion_slider_project:promotion_slider:*:*:*:*:*:wordpress:*:*"
],
@@ -14295,6 +14360,9 @@
"roomcloud": [
"cpe:2.3:a:roomcloud:roomcloud:*:*:*:*:*:wordpress:*:*"
],
"rotatingtweets": [
"cpe:2.3:a:martintod:rotating_tweets:*:*:*:*:*:wordpress:*:*"
],
"rough-chart": [
"cpe:2.3:a:rough_chart_project:rough_chart:*:*:*:*:*:wordpress:*:*"
],
@@ -14666,6 +14734,9 @@
"show-posts": [
"cpe:2.3:a:weavertheme:weaver_show_posts:*:*:*:*:*:wordpress:*:*"
],
"show-website-content-in-wordpress-page-or-post": [
"cpe:2.3:a:matteoenna:website_content_in_page_or_post:*:*:*:*:*:wordpress:*:*"
],
"si-contact-form": [
"cpe:2.3:a:fast_secure_contact_form_project:fast_secure_contact_form:*:*:*:*:*:wordpress:*:*"
],
@@ -14694,6 +14765,9 @@
"similar-posts": [
"cpe:2.3:a:shareaholic:similar_posts:*:*:*:*:*:wordpress:*:*"
],
"similarity": [
"cpe:2.3:a:davidjmiller:similarity:*:*:*:*:*:wordpress:*:*"
],
"simple-301-redirects-addon-bulk-uploader": [
"cpe:2.3:a:webcraftic:simple_301_redirects-addon-bulk_uploader:*:*:*:*:*:wordpress:*:*",
"cpe:2.3:a:webcraftic:simple_301_redirects:*:*:*:*:*:wordpress:*:*"
@@ -14776,6 +14850,9 @@
"simple-image-manipulator": [
"cpe:2.3:a:simple-image-manipulator_project:simple-image-manipulator:*:*:*:*:*:wordpress:*:*"
],
"simple-image-popup-shortcode": [
"cpe:2.3:a:purvabathe:simple_image_popup_shortcode:*:*:*:*:*:wordpress:*:*"
],
"simple-ip-ban": [
"cpe:2.3:a:ip_ban_project:ip_ban:*:*:*:*:*:wordpress:*:*"
],
@@ -14801,6 +14878,9 @@
"simple-matted-thumbnails": [
"cpe:2.3:a:devondev:simple_matted_thumbnails:*:*:*:*:*:wordpress:*:*"
],
"simple-media-directory": [
"cpe:2.3:a:quantumcloud:simple_video_directory:*:*:*:*:*:wordpress:*:*"
],
"simple-membership": [
"cpe:2.3:a:simple-membership-plugin:simple_membership:*:*:*:*:*:wordpress:*:*"
],
@@ -14899,6 +14979,9 @@
"simple-youtube-responsive": [
"cpe:2.3:a:simple_youtube_responsive_project:simple_youtube_responsive:*:*:*:*:*:wordpress:*:*"
],
"simplemap": [
"cpe:2.3:a:simplemap-plugin:simplemap_store_locator:*:*:*:*:*:wordpress:*:*"
],
"simplemodal-contact-form-smcf": [
"cpe:2.3:a:simplemodal_contact_form_project:simplemodal_contact_form:*:*:*:*:*:wordpress:*:*"
],
@@ -14978,6 +15061,9 @@
"sketchfab-oembed": [
"cpe:2.3:a:generatewp:sketchfab_embed:*:*:*:*:*:wordpress:*:*"
],
"skype-online-status": [
"cpe:2.3:a:ravanh:skype_legacy_buttons:*:*:*:*:*:wordpress:*:*"
],
"sliced-invoices": [
"cpe:2.3:a:slicedinvoices:sliced_invoices:*:*:*:*:*:wordpress:*:*"
],
@@ -15068,6 +15154,9 @@
"smart-slider-3": [
"cpe:2.3:a:nextendweb:smart_slider_3:*:*:*:*:*:wordpress:*:*"
],
"smart-wishlist-for-more-convert": [
"cpe:2.3:a:moreconvert:woocommerce_wishlist:*:*:*:*:*:wordpress:*:*"
],
"smart-youtube": [
"cpe:2.3:a:smart_youtube_pro_project:smart_youtube_pro:*:*:*:*:*:wordpress:*:*"
],
@@ -15143,6 +15232,9 @@
"social-photo-gallery": [
"cpe:2.3:a:infoway:social_photo_gallery:*:*:*:*:*:wordpress:*:*"
],
"social-pixel": [
"cpe:2.3:a:labschool:social_pixel:*:*:*:*:*:*:wordpress:*"
],
"social-proof-testimonials-slider": [
"cpe:2.3:a:brandid:social_proof_\\(testimonial\\)_slider:*:*:*:*:*:wordpress:*:*"
],
@@ -15291,12 +15383,18 @@
"stars-rating": [
"cpe:2.3:a:stars_rating_project:stars_rating:*:*:*:*:*:wordpress:*:*"
],
"startklar-elmentor-forms-extwidgets": [
"cpe:2.3:a:web-shop-host:startklar_elmentor_addons:*:*:*:*:*:wordpress:*:*"
],
"stats-counter": [
"cpe:2.3:a:analytics_stats_counter_statistics_project:analytics_stats_counter_statistics:*:*:*:*:*:wordpress:*:*"
],
"stax-addons-for-elementor": [
"cpe:2.3:a:staxwp:stax:*:*:*:*:*:wordpress:*:*"
],
"stellissimo-text-box": [
"cpe:2.3:a:overclokk:stellissimo_text_box:*:*:*:*:*:wordpress:*:*"
],
"stetic": [
"cpe:2.3:a:stetic:stetic:*:*:*:*:*:wordpress:*:*"
],
@@ -15454,6 +15552,9 @@
"svg-vector-icon-plugin": [
"cpe:2.3:a:wp_svg_icons_project:wp_svg_icons:*:*:*:*:*:wordpress:*:*"
],
"svgmagic": [
"cpe:2.3:a:andibauer:svgmagic:*:*:*:*:*:wordpress:*:*"
],
"swatchly": [
"cpe:2.3:a:hasthemes:swatchly:*:*:*:*:*:wordpress:*:*"
],
@@ -16211,6 +16312,9 @@
"video-thumbnails": [
"cpe:2.3:a:video_thumbnails_project:video_thumbnails:*:*:*:*:*:wordpress:*:*"
],
"video-widget": [
"cpe:2.3:a:nikodev:video_widget:*:*:*:*:*:wordpress:*:*"
],
"videojs-html5-player": [
"cpe:2.3:a:wphowto:videojs_html5_player:*:*:*:*:*:wordpress:*:*"
],
@@ -16394,6 +16498,9 @@
"weather-effect": [
"cpe:2.3:a:awplife:weather_effect:*:*:*:*:*:wordpress:*:*"
],
"weather-in-any-city-widget": [
"cpe:2.3:a:eltiempoen:weather_widget_pro:*:*:*:*:*:wordpress:*:*"
],
"weaverx-theme-support": [
"cpe:2.3:a:weavertheme:weaver_xtreme_theme_support:*:*:*:*:*:wordpress:*:*"
],
@@ -16492,6 +16599,9 @@
"whatshelp-chat-button": [
"cpe:2.3:a:getbutton:chat_button:*:*:*:*:*:wordpress:*:*"
],
"wheel-of-life": [
"cpe:2.3:a:kraftplugins:wheel_of_life:*:*:*:*:*:wordpress:*:*"
],
"which-template-file": [
"cpe:2.3:a:gillesdumas:which_template_file:*:*:*:*:*:wordpress:*:*"
],
@@ -16573,6 +16683,9 @@
"woo-altcoin-payment-gateway": [
"cpe:2.3:a:coinmarketstats:bitcoin_\\/_altcoin_payment_gateway_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"woo-auto-coupons": [
"cpe:2.3:a:richardlerma:auto_coupons_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
"woo-badge-designer-lite": [
"cpe:2.3:a:accesspressthemes:badge_designer_lite_for_woocommerce:*:*:*:*:*:wordpress:*:*"
],
@@ -17544,6 +17657,9 @@
"wp-logo-showcase": [
"cpe:2.3:a:radiustheme:logo_slider_and_showcase:*:*:*:*:*:wordpress:*:*"
],
"wp-logs-book": [
"cpe:2.3:a:onetarek:wp_logs_book:*:*:*:*:*:wordpress:*:*"
],
"wp-mail": [
"cpe:2.3:a:wp_mail_project:wp_mail:*:*:*:*:*:wordpress:*:*"
],
@@ -17747,6 +17863,9 @@
"wp-publications": [
"cpe:2.3:a:wp-publications_project:wp-publications:*:*:*:*:*:wordpress:*:*"
],
"wp-quicklatex": [
"cpe:2.3:a:holoborodko:wp_quicklatex:*:*:*:*:*:wordpress:*:*"
],
"wp-radio": [
"cpe:2.3:a:wpmilitary:wp_radio:*:*:*:*:*:wordpress:*:*"
],
@@ -17809,6 +17928,9 @@
"wp-scrippets": [
"cpe:2.3:a:wp_scrippets_project:wp_scrippets:*:*:*:*:*:wordpress:*:*"
],
"wp-secure-maintainance": [
"cpe:2.3:a:wpexperts:wp_secure_maintenance:*:*:*:*:*:wordpress:*:*"
],
"wp-security-hardening": [
"cpe:2.3:a:getastra:wp_hardening:*:*:*:*:*:wordpress:*:*"
],
@@ -18735,6 +18857,9 @@
"newsmag": [
"cpe:2.3:a:machothemes:newsmag:*:*:*:*:*:wordpress:*:*"
],
"newspaper": [
"cpe:2.3:a:tagdiv:newspaper:*:*:*:*:*:wordpress:*:*"
],
"newspaper-x": [
"cpe:2.3:a:colorlib:newspaper_x:*:*:*:*:*:wordpress:*:*"
],
@@ -310,6 +310,11 @@ func TestFileParser(t *testing.T, fixturePath string, parser generic.Parser, exp
NewCatalogTester().FromFile(t, fixturePath).Expects(expectedPkgs, expectedRelationships).TestParser(t, parser)
}
func TestCataloger(t *testing.T, fixtureDir string, cataloger pkg.Cataloger, expectedPkgs []pkg.Package, expectedRelationships []artifact.Relationship) {
t.Helper()
NewCatalogTester().FromDirectory(t, fixtureDir).Expects(expectedPkgs, expectedRelationships).TestCataloger(t, cataloger)
}
func TestFileParserWithEnv(t *testing.T, fixturePath string, parser generic.Parser, env *generic.Environment, expectedPkgs []pkg.Package, expectedRelationships []artifact.Relationship) {
t.Helper()
+113 -113
View File
@@ -9,8 +9,10 @@ import (
"slices"
"strings"
"github.com/vifraa/gopom"
"golang.org/x/exp/maps"
"github.com/anchore/syft/internal"
intFile "github.com/anchore/syft/internal/file"
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/internal/log"
@@ -52,6 +54,7 @@ type archiveParser struct {
fileInfo archiveFilename
detectNested bool
cfg ArchiveCatalogerConfig
maven *mavenResolver
}
type genericArchiveParserAdapter struct {
@@ -106,6 +109,7 @@ func newJavaArchiveParser(reader file.LocationReadCloser, detectNested bool, cfg
fileInfo: newJavaArchiveFilename(currentFilepath),
detectNested: detectNested,
cfg: cfg,
maven: newMavenResolver(nil, cfg),
}, cleanupFn, nil
}
@@ -197,7 +201,7 @@ func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package,
return nil, err
}
licenses, name, version, err := j.parseLicenses(ctx, manifest)
name, version, licenses, err := j.discoverNameVersionLicense(ctx, manifest)
if err != nil {
return nil, err
}
@@ -220,7 +224,7 @@ func (j *archiveParser) discoverMainPackage(ctx context.Context) (*pkg.Package,
}, nil
}
func (j *archiveParser) parseLicenses(ctx context.Context, manifest *pkg.JavaManifest) ([]pkg.License, string, string, error) {
func (j *archiveParser) discoverNameVersionLicense(ctx context.Context, manifest *pkg.JavaManifest) (string, string, []pkg.License, error) {
// we use j.location because we want to associate the license declaration with where we discovered the contents in the manifest
// TODO: when we support locations of paths within archives we should start passing the specific manifest location object instead of the top jar
licenses := pkg.NewLicensesFromLocation(j.location, selectLicenses(manifest)...)
@@ -231,24 +235,18 @@ func (j *archiveParser) parseLicenses(ctx context.Context, manifest *pkg.JavaMan
3. manifest
4. filename
*/
name, version, pomLicenses := j.guessMainPackageNameAndVersionFromPomInfo(ctx)
if name == "" {
name = selectName(manifest, j.fileInfo)
groupID, artifactID, version, parsedPom := j.discoverMainPackageFromPomInfo(ctx)
if artifactID == "" {
artifactID = selectName(manifest, j.fileInfo)
}
if version == "" {
version = selectVersion(manifest, j.fileInfo)
}
if len(licenses) == 0 {
// Today we don't have a way to distinguish between licenses from the manifest and licenses from the pom.xml
// until the file.Location object can support sub-paths (i.e. paths within archives, recursively; issue https://github.com/anchore/syft/issues/2211).
// Until then it's less confusing to use the licenses from the pom.xml only if the manifest did not list any.
licenses = append(licenses, pomLicenses...)
}
if len(licenses) == 0 {
fileLicenses, err := j.getLicenseFromFileInArchive()
if err != nil {
return nil, "", "", err
return "", "", nil, err
}
if fileLicenses != nil {
licenses = append(licenses, fileLicenses...)
@@ -256,116 +254,123 @@ func (j *archiveParser) parseLicenses(ctx context.Context, manifest *pkg.JavaMan
}
// If we didn't find any licenses in the archive so far, we'll try again in Maven Central using groupIDFromJavaMetadata
if len(licenses) == 0 && j.cfg.UseNetwork {
licenses = findLicenseFromJavaMetadata(ctx, name, manifest, version, j, licenses)
if len(licenses) == 0 {
// Today we don't have a way to distinguish between licenses from the manifest and licenses from the pom.xml
// until the file.Location object can support sub-paths (i.e. paths within archives, recursively; issue https://github.com/anchore/syft/issues/2211).
// Until then it's less confusing to use the licenses from the pom.xml only if the manifest did not list any.
licenses = j.findLicenseFromJavaMetadata(ctx, groupID, artifactID, version, parsedPom, manifest)
}
return licenses, name, version, nil
return artifactID, version, licenses, nil
}
func findLicenseFromJavaMetadata(ctx context.Context, name string, manifest *pkg.JavaManifest, version string, j *archiveParser, licenses []pkg.License) []pkg.License {
var groupID = name
if gID := groupIDFromJavaMetadata(name, pkg.JavaArchive{Manifest: manifest}); gID != "" {
groupID = gID
// findLicenseFromJavaMetadata attempts to find license information from all available maven metadata properties and pom info
func (j *archiveParser) findLicenseFromJavaMetadata(ctx context.Context, groupID, artifactID, version string, parsedPom *parsedPomProject, manifest *pkg.JavaManifest) []pkg.License {
if groupID == "" {
if gID := groupIDFromJavaMetadata(artifactID, pkg.JavaArchive{Manifest: manifest}); gID != "" {
groupID = gID
}
}
var err error
var pomLicenses []gopom.License
if parsedPom != nil {
pomLicenses, err = j.maven.resolveLicenses(ctx, parsedPom.project)
if err != nil {
log.WithFields("error", err, "mavenID", j.maven.resolveMavenID(ctx, parsedPom.project)).Debug("error attempting to resolve pom licenses")
}
}
if err == nil && len(pomLicenses) == 0 {
pomLicenses, err = j.maven.findLicenses(ctx, groupID, artifactID, version)
if err != nil {
log.WithFields("error", err, "mavenID", mavenID{groupID, artifactID, version}).Debug("error attempting to find licenses")
}
}
pomLicenses := recursivelyFindLicensesFromParentPom(ctx, groupID, name, version, j.cfg)
if len(pomLicenses) == 0 {
// 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 = recursivelyFindLicensesFromParentPom(ctx, groupID, name, version, j.cfg)
}
if len(pomLicenses) > 0 {
pkgLicenses := pkg.NewLicensesFromLocation(j.location, pomLicenses...)
if pkgLicenses != nil {
licenses = append(licenses, pkgLicenses...)
pomLicenses, err = j.maven.findLicenses(ctx, groupID, artifactID, version)
if err != nil {
log.WithFields("error", err, "mavenID", mavenID{groupID, artifactID, version}).Debug("error attempting to find sub-group licenses")
}
}
return licenses
return toPkgLicenses(&j.location, pomLicenses)
}
func toPkgLicenses(location *file.Location, licenses []gopom.License) []pkg.License {
var out []pkg.License
for _, license := range licenses {
out = append(out, pkg.NewLicenseFromFields(deref(license.Name), deref(license.URL), location))
}
return out
}
type parsedPomProject struct {
*pkg.JavaPomProject
Licenses []pkg.License
path string
project *gopom.Project
}
func (j *archiveParser) guessMainPackageNameAndVersionFromPomInfo(ctx context.Context) (name, version string, licenses []pkg.License) {
pomPropertyMatches := j.fileManifest.GlobMatch(false, pomPropertiesGlob)
pomMatches := j.fileManifest.GlobMatch(false, pomXMLGlob)
var pomPropertiesObject pkg.JavaPomProperties
var pomProjectObject *parsedPomProject
// discoverMainPackageFromPomInfo attempts to resolve maven groupId, artifactId, version and other info from found pom information
func (j *archiveParser) discoverMainPackageFromPomInfo(ctx context.Context) (group, name, version string, parsedPom *parsedPomProject) {
var pomProperties pkg.JavaPomProperties
// Find the pom.properties/pom.xml if the names seem like a plausible match
properties, _ := pomPropertiesByParentPath(j.archivePath, j.location, pomPropertyMatches)
projects, _ := pomProjectByParentPath(j.archivePath, j.location, pomMatches)
properties, _ := pomPropertiesByParentPath(j.archivePath, j.location, j.fileManifest.GlobMatch(false, pomPropertiesGlob))
projects, _ := pomProjectByParentPath(j.archivePath, j.location, j.fileManifest.GlobMatch(false, pomXMLGlob))
// map of all the artifacts in the pom properties, in order to chek exact match with the filename
artifactsMap := make(map[string]bool)
for _, propertiesObj := range properties {
artifactsMap[propertiesObj.ArtifactID] = true
}
parentPaths := maps.Keys(properties)
slices.Sort(parentPaths)
for _, parentPath := range parentPaths {
propertiesObj := properties[parentPath]
if artifactIDMatchesFilename(propertiesObj.ArtifactID, j.fileInfo.name) {
pomPropertiesObject = propertiesObj
if artifactIDMatchesFilename(propertiesObj.ArtifactID, j.fileInfo.name, artifactsMap) {
pomProperties = propertiesObj
if proj, exists := projects[parentPath]; exists {
pomProjectObject = proj
parsedPom = proj
break
}
}
}
name = pomPropertiesObject.ArtifactID
if name == "" && pomProjectObject != nil {
name = pomProjectObject.ArtifactID
}
version = pomPropertiesObject.Version
if version == "" && pomProjectObject != nil {
version = pomProjectObject.Version
}
if j.cfg.UseNetwork {
if pomProjectObject == nil {
// If we have no pom.xml, check maven central using pom.properties
parentLicenses := recursivelyFindLicensesFromParentPom(ctx, pomPropertiesObject.GroupID, pomPropertiesObject.ArtifactID, pomPropertiesObject.Version, j.cfg)
if len(parentLicenses) > 0 {
for _, licenseName := range parentLicenses {
licenses = append(licenses, pkg.NewLicenseFromFields(licenseName, "", nil))
}
}
} else {
findPomLicenses(ctx, pomProjectObject, j.cfg)
group = pomProperties.GroupID
name = pomProperties.ArtifactID
version = pomProperties.Version
if parsedPom != nil && parsedPom.project != nil {
id := j.maven.resolveMavenID(ctx, parsedPom.project)
if group == "" {
group = id.GroupID
}
if name == "" {
name = id.ArtifactID
}
if version == "" {
version = id.Version
}
}
if pomProjectObject != nil {
licenses = pomProjectObject.Licenses
}
return name, version, licenses
return group, name, version, parsedPom
}
func artifactIDMatchesFilename(artifactID, fileName string) bool {
func artifactIDMatchesFilename(artifactID, fileName string, artifactsMap map[string]bool) bool {
if artifactID == "" || fileName == "" {
return false
}
return strings.HasPrefix(artifactID, fileName) || strings.HasSuffix(fileName, artifactID)
}
func findPomLicenses(ctx context.Context, pomProjectObject *parsedPomProject, cfg ArchiveCatalogerConfig) {
// If we don't have any licenses until now, and if we have a parent Pom, then we'll check the parent pom in maven central for licenses.
if pomProjectObject != nil && pomProjectObject.Parent != nil && len(pomProjectObject.Licenses) == 0 {
parentLicenses := recursivelyFindLicensesFromParentPom(
ctx,
pomProjectObject.Parent.GroupID,
pomProjectObject.Parent.ArtifactID,
pomProjectObject.Parent.Version,
cfg)
if len(parentLicenses) > 0 {
for _, licenseName := range parentLicenses {
pomProjectObject.Licenses = append(pomProjectObject.Licenses, pkg.NewLicenseFromFields(licenseName, "", nil))
}
}
// Ensure true is returned when filename matches the artifact ID, prevent random retrieval by checking prefix and suffix
if _, exists := artifactsMap[fileName]; exists {
return artifactID == fileName
}
// Use fallback check with suffix and prefix if no POM properties file matches the exact artifact name
return strings.HasPrefix(artifactID, fileName) || strings.HasSuffix(fileName, artifactID)
}
// discoverPkgsFromAllMavenFiles parses Maven POM properties/xml for a given
@@ -392,12 +397,12 @@ func (j *archiveParser) discoverPkgsFromAllMavenFiles(ctx context.Context, paren
}
for parentPath, propertiesObj := range properties {
var pomProject *parsedPomProject
var parsedPom *parsedPomProject
if proj, exists := projects[parentPath]; exists {
pomProject = proj
parsedPom = proj
}
pkgFromPom := newPackageFromMavenData(ctx, propertiesObj, pomProject, parentPkg, j.location, j.cfg)
pkgFromPom := newPackageFromMavenData(ctx, j.maven, propertiesObj, parsedPom, parentPkg, j.location)
if pkgFromPom != nil {
pkgs = append(pkgs, *pkgFromPom)
}
@@ -411,7 +416,7 @@ func getDigestsFromArchive(archivePath string) ([]file.Digest, error) {
if err != nil {
return nil, fmt.Errorf("unable to open archive path (%s): %w", archivePath, err)
}
defer archiveCloser.Close()
defer internal.CloseAndLogError(archiveCloser, archivePath)
// grab and assign digest for the entire archive
digests, err := intFile.NewDigestsFromFile(archiveCloser, javaArchiveHashes)
@@ -565,30 +570,26 @@ 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
pomProject, err := parsePomXMLProject(filePath, strings.NewReader(fileContents), location)
pom, err := decodePomXML(strings.NewReader(fileContents))
if err != nil {
log.WithFields("contents-path", filePath, "location", location.Path()).Warnf("failed to parse pom.xml: %+v", err)
continue
}
if pomProject == nil {
if pom == nil {
continue
}
// If we don't have a version, then maybe the parent pom has it...
if (pomProject.Parent == nil && pomProject.Version == "") || pomProject.ArtifactID == "" {
// TODO: if there is no parentPkg (no java manifest) one of these poms could be the parent. We should discover the right parent and attach the correct info accordingly to each discovered package
continue
projectByParentPath[path.Dir(filePath)] = &parsedPomProject{
path: filePath,
project: pom,
}
projectByParentPath[path.Dir(filePath)] = pomProject
}
return projectByParentPath, nil
}
// 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, pomProperties pkg.JavaPomProperties, parsedPomProject *parsedPomProject, parentPkg *pkg.Package, location file.Location, cfg ArchiveCatalogerConfig) *pkg.Package {
func newPackageFromMavenData(ctx context.Context, r *mavenResolver, 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 := ""
@@ -611,25 +612,24 @@ func newPackageFromMavenData(ctx context.Context, pomProperties pkg.JavaPomPrope
virtualPath := location.Path() + vPathSuffix
var pkgPomProject *pkg.JavaPomProject
licenses := make([]pkg.License, 0)
if cfg.UseNetwork {
if parsedPomProject == nil {
// If we have no pom.xml, check maven central using pom.properties
parentLicenses := recursivelyFindLicensesFromParentPom(ctx, pomProperties.GroupID, pomProperties.ArtifactID, pomProperties.Version, cfg)
if len(parentLicenses) > 0 {
for _, licenseName := range parentLicenses {
licenses = append(licenses, pkg.NewLicenseFromFields(licenseName, "", nil))
}
}
} else {
findPomLicenses(ctx, parsedPomProject, cfg)
}
var err error
var pomLicenses []gopom.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)
} else {
pkgPomProject = newPomProject(ctx, r, parsedPom.path, parsedPom.project)
pomLicenses, err = r.resolveLicenses(ctx, parsedPom.project)
}
if parsedPomProject != nil {
pkgPomProject = parsedPomProject.JavaPomProject
licenses = append(licenses, parsedPomProject.Licenses...)
if err != nil {
log.WithFields("error", err, "mavenID", mavenID{pomProperties.GroupID, pomProperties.ArtifactID, pomProperties.Version}).Debug("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))
}
p := pkg.Package{
+68 -145
View File
@@ -5,8 +5,6 @@ import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
@@ -20,6 +18,7 @@ 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/syft/artifact"
"github.com/anchore/syft/syft/file"
@@ -28,61 +27,14 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func generateJavaBuildFixture(t *testing.T, fixturePath string) {
if _, err := os.Stat(fixturePath); !os.IsNotExist(err) {
// fixture already exists...
return
}
makeTask := strings.TrimPrefix(fixturePath, "test-fixtures/java-builds/")
t.Logf(color.Bold.Sprintf("Generating Fixture from 'make %s'", makeTask))
cwd, err := os.Getwd()
if err != nil {
t.Errorf("unable to get cwd: %+v", err)
}
cmd := exec.Command("make", makeTask)
cmd.Dir = filepath.Join(cwd, "test-fixtures/java-builds/")
run(t, cmd)
}
func generateMockMavenHandler(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
file, err := os.Open(responseFixture)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
_, err = io.Copy(w, file)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
type handlerPath struct {
path string
handler func(w http.ResponseWriter, r *http.Request)
}
func TestSearchMavenForLicenses(t *testing.T) {
mux, url, teardown := setup()
defer teardown()
url := mockMavenRepo(t)
tests := []struct {
name string
fixture string
detectNested bool
config ArchiveCatalogerConfig
requestPath string
requestHandlers []handlerPath
expectedLicenses []pkg.License
}{
{
@@ -91,23 +43,16 @@ func TestSearchMavenForLicenses(t *testing.T) {
detectNested: false,
config: ArchiveCatalogerConfig{
UseNetwork: true,
UseMavenLocalRepository: false,
MavenBaseURL: url,
MaxParentRecursiveDepth: 2,
},
requestHandlers: []handlerPath{
{
path: "/org/opensaml/opensaml-parent/3.4.6/opensaml-parent-3.4.6.pom",
handler: generateMockMavenHandler("test-fixtures/maven-xml-responses/opensaml-parent-3.4.6.pom"),
},
{
path: "/net/shibboleth/parent/7.11.2/parent-7.11.2.pom",
handler: generateMockMavenHandler("test-fixtures/maven-xml-responses/parent-7.11.2.pom"),
},
},
expectedLicenses: []pkg.License{
{
Type: license.Declared,
Value: `The Apache Software License, Version 2.0`,
Type: license.Declared,
Value: `The Apache Software License, Version 2.0`,
URLs: []string{
"http://www.apache.org/licenses/LICENSE-2.0.txt",
},
SPDXExpression: ``,
},
},
@@ -116,11 +61,6 @@ func TestSearchMavenForLicenses(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
// configure maven central requests
for _, hdlr := range tc.requestHandlers {
mux.HandleFunc(hdlr.path, hdlr.handler)
}
// setup metadata fixture; note:
// this fixture has a pomProjectObject and has a parent object
// it has no licenses on either which is the condition for testing
@@ -138,34 +78,9 @@ func TestSearchMavenForLicenses(t *testing.T) {
defer cleanupFn()
// assert licenses are discovered from upstream
_, _, licenses := ap.guessMainPackageNameAndVersionFromPomInfo(context.Background())
assert.Equal(t, tc.expectedLicenses, licenses)
})
}
}
func TestFormatMavenURL(t *testing.T) {
tests := []struct {
name string
groupID string
artifactID string
version string
expected string
}{
{
name: "formatMavenURL correctly assembles the pom URL",
groupID: "org.springframework.boot",
artifactID: "spring-boot-starter-test",
version: "3.1.5",
expected: "https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-test/3.1.5/spring-boot-starter-test-3.1.5.pom",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
requestURL, err := formatMavenPomURL(tc.groupID, tc.artifactID, tc.version, mavenBaseURL)
assert.NoError(t, err, "expected no err; got %w", err)
assert.Equal(t, tc.expected, requestURL)
_, _, _, parsedPom := ap.discoverMainPackageFromPomInfo(context.Background())
licenses, _ := ap.maven.resolveLicenses(context.Background(), parsedPom.project)
assert.Equal(t, tc.expectedLicenses, toPkgLicenses(nil, licenses))
})
}
}
@@ -424,10 +339,14 @@ func TestParseJar(t *testing.T) {
test.expected[k] = p
}
cfg := ArchiveCatalogerConfig{
UseNetwork: false,
UseMavenLocalRepository: false,
}
parser, cleanupFn, err := newJavaArchiveParser(file.LocationReadCloser{
Location: file.NewLocation(fixture.Name()),
ReadCloser: fixture,
}, false, ArchiveCatalogerConfig{UseNetwork: false})
}, false, cfg)
defer cleanupFn()
require.NoError(t, err)
@@ -843,26 +762,23 @@ func Test_newPackageFromMavenData(t *testing.T) {
Version: "1.0",
},
project: &parsedPomProject{
JavaPomProject: &pkg.JavaPomProject{
Parent: &pkg.JavaPomParent{
GroupID: "some-parent-group-id",
ArtifactID: "some-parent-artifact-id",
Version: "1.0-parent",
project: &gopom.Project{
Parent: &gopom.Parent{
GroupID: ptr("some-parent-group-id"),
ArtifactID: ptr("some-parent-artifact-id"),
Version: ptr("1.0-parent"),
},
Name: "some-name",
GroupID: "some-group-id",
ArtifactID: "some-artifact-id",
Version: "1.0",
Description: "desc",
URL: "aweso.me",
},
Licenses: []pkg.License{
{
Value: "MIT",
SPDXExpression: "MIT",
Type: license.Declared,
URLs: []string{"https://opensource.org/licenses/MIT"},
Locations: file.NewLocationSet(file.NewLocation("some-license-path")),
Name: ptr("some-name"),
GroupID: ptr("some-group-id"),
ArtifactID: ptr("some-artifact-id"),
Version: ptr("1.0"),
Description: ptr("desc"),
URL: ptr("aweso.me"),
Licenses: &[]gopom.License{
{
Name: ptr("MIT"),
URL: ptr("https://opensource.org/licenses/MIT"),
},
},
},
},
@@ -898,7 +814,7 @@ func Test_newPackageFromMavenData(t *testing.T) {
SPDXExpression: "MIT",
Type: license.Declared,
URLs: []string{"https://opensource.org/licenses/MIT"},
Locations: file.NewLocationSet(file.NewLocation("some-license-path")),
Locations: file.NewLocationSet(file.NewLocation("given/virtual/path")),
},
),
Metadata: pkg.JavaArchive{
@@ -1122,7 +1038,8 @@ func Test_newPackageFromMavenData(t *testing.T) {
}
test.expectedParent.Locations = locations
actualPackage := newPackageFromMavenData(context.Background(), test.props, test.project, test.parent, file.NewLocation(virtualPath), DefaultArchiveCatalogerConfig())
r := newMavenResolver(nil, DefaultArchiveCatalogerConfig())
actualPackage := newPackageFromMavenData(context.Background(), r, test.props, test.project, test.parent, file.NewLocation(virtualPath))
if test.expectedPackage == nil {
require.Nil(t, actualPackage)
} else {
@@ -1156,7 +1073,7 @@ func Test_artifactIDMatchesFilename(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, artifactIDMatchesFilename(tt.artifactID, tt.fileName))
assert.Equal(t, tt.want, artifactIDMatchesFilename(tt.artifactID, tt.fileName, nil))
})
}
}
@@ -1337,6 +1254,8 @@ func Test_parseJavaArchive_regressions(t *testing.T) {
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{
@@ -1388,14 +1307,12 @@ func Test_parseJavaArchive_regressions(t *testing.T) {
func Test_deterministicMatchingPomProperties(t *testing.T) {
tests := []struct {
fixture string
expectedName string
expectedVersion string
fixture string
expected mavenID
}{
{
fixture: "multiple-matching-2.11.5",
expectedName: "multiple-matching-1",
expectedVersion: "2.11.5",
fixture: "multiple-matching-2.11.5",
expected: mavenID{"org.multiple", "multiple-matching-1", "2.11.5"},
},
}
@@ -1415,9 +1332,8 @@ func Test_deterministicMatchingPomProperties(t *testing.T) {
defer cleanupFn()
require.NoError(t, err)
name, version, _ := parser.guessMainPackageNameAndVersionFromPomInfo(context.TODO())
require.Equal(t, test.expectedName, name)
require.Equal(t, test.expectedVersion, version)
groupID, artifactID, version, _ := parser.discoverMainPackageFromPomInfo(context.TODO())
require.Equal(t, test.expected, mavenID{groupID, artifactID, version})
}()
}
})
@@ -1436,6 +1352,26 @@ func assignParent(parent *pkg.Package, childPackages ...pkg.Package) {
}
}
func generateJavaBuildFixture(t *testing.T, fixturePath string) {
if _, err := os.Stat(fixturePath); !os.IsNotExist(err) {
// fixture already exists...
return
}
makeTask := strings.TrimPrefix(fixturePath, "test-fixtures/java-builds/")
t.Logf(color.Bold.Sprintf("Generating Fixture from 'make %s'", makeTask))
cwd, err := os.Getwd()
if err != nil {
t.Errorf("unable to get cwd: %+v", err)
}
cmd := exec.Command("make", makeTask)
cmd.Dir = filepath.Join(cwd, "test-fixtures/java-builds/")
run(t, cmd)
}
func generateJavaMetadataJarFixture(t *testing.T, fixtureName string) string {
fixturePath := filepath.Join("test-fixtures/jar-metadata/cache/", fixtureName+".jar")
if _, err := os.Stat(fixturePath); !os.IsNotExist(err) {
@@ -1504,20 +1440,7 @@ func run(t testing.TB, cmd *exec.Cmd) {
}
}
// setup sets up a test HTTP server for mocking requests to maven central.
// The returned url is injected into the Config so the client uses the test server.
// Tests should register handlers on mux to simulate the expected request/response structure
func setup() (mux *http.ServeMux, serverURL string, teardown func()) {
// 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)
return mux, server.URL, server.Close
// ptr returns a pointer to the given value
func ptr[T any](value T) *T {
return &value
}
+3 -4
View File
@@ -32,10 +32,9 @@ func NewArchiveCataloger(cfg ArchiveCatalogerConfig) pkg.Cataloger {
// NewPomCataloger returns a cataloger capable of parsing dependencies from a pom.xml file.
// Pom files list dependencies that maybe not be locally installed yet.
func NewPomCataloger(cfg ArchiveCatalogerConfig) pkg.Cataloger {
gap := newGenericArchiveParserAdapter(cfg)
return generic.NewCataloger("java-pom-cataloger").
WithParserByGlobs(gap.parserPomXML, "**/pom.xml")
return pomXMLCataloger{
cfg: cfg,
}
}
// NewGradleLockfileCataloger returns a cataloger capable of parsing dependencies from a gradle.lockfile file.
+16 -4
View File
@@ -7,6 +7,8 @@ const mavenBaseURL = "https://repo1.maven.org/maven2"
type ArchiveCatalogerConfig struct {
cataloging.ArchiveSearchConfig `yaml:",inline" json:"" mapstructure:",squash"`
UseNetwork bool `yaml:"use-network" json:"use-network" mapstructure:"use-network"`
UseMavenLocalRepository bool `yaml:"use-maven-localrepository" json:"use-maven-localrepository" mapstructure:"use-maven-localrepository"`
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"`
}
@@ -15,8 +17,10 @@ func DefaultArchiveCatalogerConfig() ArchiveCatalogerConfig {
return ArchiveCatalogerConfig{
ArchiveSearchConfig: cataloging.DefaultArchiveSearchConfig(),
UseNetwork: false,
UseMavenLocalRepository: false,
MavenLocalRepositoryDir: defaultMavenLocalRepoDir(),
MavenBaseURL: mavenBaseURL,
MaxParentRecursiveDepth: 5,
MaxParentRecursiveDepth: 0, // unlimited
}
}
@@ -25,6 +29,16 @@ func (j ArchiveCatalogerConfig) WithUseNetwork(input bool) ArchiveCatalogerConfi
return j
}
func (j ArchiveCatalogerConfig) WithUseMavenLocalRepository(input bool) ArchiveCatalogerConfig {
j.UseMavenLocalRepository = input
return j
}
func (j ArchiveCatalogerConfig) WithMavenLocalRepositoryDir(input string) ArchiveCatalogerConfig {
j.MavenLocalRepositoryDir = input
return j
}
func (j ArchiveCatalogerConfig) WithMavenBaseURL(input string) ArchiveCatalogerConfig {
if input != "" {
j.MavenBaseURL = input
@@ -33,9 +47,7 @@ func (j ArchiveCatalogerConfig) WithMavenBaseURL(input string) ArchiveCatalogerC
}
func (j ArchiveCatalogerConfig) WithArchiveTraversal(search cataloging.ArchiveSearchConfig, maxDepth int) ArchiveCatalogerConfig {
if maxDepth > 0 {
j.MaxParentRecursiveDepth = maxDepth
}
j.MaxParentRecursiveDepth = maxDepth
j.ArchiveSearchConfig = search
return j
}
-136
View File
@@ -1,136 +0,0 @@
package java
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/vifraa/gopom"
"github.com/anchore/syft/internal/log"
)
func formatMavenPomURL(groupID, artifactID, version, mavenBaseURL string) (requestURL string, err error) {
// groupID needs to go from maven.org -> maven/org
urlPath := strings.Split(groupID, ".")
artifactPom := fmt.Sprintf("%s-%s.pom", artifactID, version)
urlPath = append(urlPath, artifactID, version, artifactPom)
// ex:"https://repo1.maven.org/maven2/groupID/artifactID/artifactPom
requestURL, err = url.JoinPath(mavenBaseURL, urlPath...)
if err != nil {
return requestURL, fmt.Errorf("could not construct maven url: %w", err)
}
return requestURL, err
}
// An artifact can have its version defined in a parent's DependencyManagement section
func recursivelyFindVersionFromParentPom(ctx context.Context, groupID, artifactID, parentGroupID, parentArtifactID, parentVersion string, cfg ArchiveCatalogerConfig) string {
// As there can be nested parent poms, we'll recursively check for the version until we reach the max depth
for i := 0; i < cfg.MaxParentRecursiveDepth; i++ {
parentPom, err := getPomFromMavenRepo(ctx, parentGroupID, parentArtifactID, parentVersion, cfg.MavenBaseURL)
if err != nil {
// We don't want to abort here as the parent pom might not exist in Maven Central, we'll just log the error
log.Tracef("unable to get parent pom from Maven central: %v", err)
break
}
if parentPom != nil && parentPom.DependencyManagement != nil {
for _, dependency := range *parentPom.DependencyManagement.Dependencies {
if groupID == *dependency.GroupID && artifactID == *dependency.ArtifactID && dependency.Version != nil {
return *dependency.Version
}
}
}
if parentPom == nil || parentPom.Parent == nil {
break
}
parentGroupID = *parentPom.Parent.GroupID
parentArtifactID = *parentPom.Parent.ArtifactID
parentVersion = *parentPom.Parent.Version
}
return ""
}
func recursivelyFindLicensesFromParentPom(ctx context.Context, groupID, artifactID, version string, cfg ArchiveCatalogerConfig) []string {
var licenses []string
// As there can be nested parent poms, we'll recursively check for licenses until we reach the max depth
for i := 0; i < cfg.MaxParentRecursiveDepth; i++ {
parentPom, err := getPomFromMavenRepo(ctx, groupID, artifactID, version, cfg.MavenBaseURL)
if err != nil {
// We don't want to abort here as the parent pom might not exist in Maven Central, we'll just log the error
log.Tracef("unable to get parent pom from Maven central: %v", err)
return []string{}
}
parentLicenses := parseLicensesFromPom(parentPom)
if len(parentLicenses) > 0 || parentPom == nil || parentPom.Parent == nil {
licenses = parentLicenses
break
}
groupID = *parentPom.Parent.GroupID
artifactID = *parentPom.Parent.ArtifactID
version = *parentPom.Parent.Version
}
return licenses
}
func getPomFromMavenRepo(ctx context.Context, groupID, artifactID, version, mavenBaseURL string) (*gopom.Project, error) {
requestURL, err := formatMavenPomURL(groupID, artifactID, version, mavenBaseURL)
if err != nil {
return nil, err
}
log.Tracef("trying to fetch parent pom from Maven central %s", requestURL)
mavenRequest, err := http.NewRequest(http.MethodGet, requestURL, nil)
if err != nil {
return nil, fmt.Errorf("unable to format request for Maven central: %w", err)
}
httpClient := &http.Client{
Timeout: time.Second * 10,
}
mavenRequest = mavenRequest.WithContext(ctx)
resp, err := httpClient.Do(mavenRequest)
if err != nil {
return nil, fmt.Errorf("unable to get pom from Maven central: %w", err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
log.Errorf("unable to close body: %+v", err)
}
}()
bytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to parse pom from Maven central: %w", err)
}
pom, err := decodePomXML(strings.NewReader(string(bytes)))
if err != nil {
return nil, fmt.Errorf("unable to parse pom from Maven central: %w", err)
}
return &pom, nil
}
func parseLicensesFromPom(pom *gopom.Project) []string {
var licenses []string
if pom != nil && pom.Licenses != nil {
for _, license := range *pom.Licenses {
if license.Name != nil {
licenses = append(licenses, *license.Name)
} else if license.URL != nil {
licenses = append(licenses, *license.URL)
}
}
}
return licenses
}
+624
View File
@@ -0,0 +1,624 @@
package java
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"slices"
"strings"
"time"
"github.com/vifraa/gopom"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/cache"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file"
)
// mavenID is the unique identifier for a package in Maven
type mavenID struct {
GroupID string
ArtifactID string
Version string
}
func (m mavenID) String() string {
return fmt.Sprintf("(groupId: %s artifactId: %s version: %s)", m.GroupID, m.ArtifactID, m.Version)
}
var expressionMatcher = regexp.MustCompile("[$][{][^}]+[}]")
// mavenResolver 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
cache cache.Cache
resolved map[mavenID]*gopom.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
}
// newMavenResolver constructs a new mavenResolver 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{
cfg: cfg,
cache: cache.GetManager().GetCache("java/maven/repo", "v1"),
resolved: map[mavenID]*gopom.Project{},
remoteRequestTimeout: time.Second * 10,
fileResolver: fileResolver,
pomLocations: map[*gopom.Project]file.Location{},
}
}
// getPropertyValue 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 {
if propertyValue == nil {
return ""
}
resolved, err := r.resolveExpression(ctx, resolutionContext, *propertyValue, nil)
if err != nil {
log.WithFields("error", err, "propertyValue", *propertyValue).Debug("error resolving maven property")
return ""
}
return resolved
}
// 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, resolving []string) (string, error) {
var err error
return expressionMatcher.ReplaceAllStringFunc(expression, func(match string) string {
propertyExpression := strings.TrimSpace(match[2 : len(match)-1]) // remove leading ${ and trailing }
resolved, e := r.resolveProperty(ctx, resolutionContext, propertyExpression, resolving)
if e != nil {
err = errors.Join(err, e)
return ""
}
return resolved
}), err
}
// resolveProperty resolves properties recursively from the root project
func (r *mavenResolver) resolveProperty(ctx context.Context, resolutionContext []*gopom.Project, propertyExpression string, resolving []string) (string, error) {
// prevent cycles
if slices.Contains(resolving, propertyExpression) {
return "", fmt.Errorf("cycle detected resolving: %s", propertyExpression)
}
if len(resolutionContext) == 0 {
return "", fmt.Errorf("no project variable resolution context provided for expression: '%s'", propertyExpression)
}
resolving = append(resolving, propertyExpression)
// only resolve project. properties in the context of the current project pom
value, err := r.resolveProjectProperty(ctx, resolutionContext, resolutionContext[len(resolutionContext)-1], propertyExpression, resolving)
if err != nil {
return value, err
}
if value != "" {
return value, nil
}
for _, pom := range resolutionContext {
current := pom
for parentDepth := 0; current != nil; parentDepth++ {
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)
}
if current.Properties != nil && current.Properties.Entries != nil {
if value, ok := current.Properties.Entries[propertyExpression]; ok {
return r.resolveExpression(ctx, resolutionContext, value, resolving) // property values can contain expressions
}
}
current, err = r.resolveParent(ctx, current)
if err != nil {
return "", err
}
}
}
return "", fmt.Errorf("unable to resolve property: %s", propertyExpression)
}
// 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) {
// see if we have a project.x expression and process this based
// on the xml tags in gopom
parts := strings.Split(propertyExpression, ".")
numParts := len(parts)
if numParts > 1 && strings.TrimSpace(parts[0]) == "project" {
pomValue := reflect.ValueOf(pom).Elem()
pomValueType := pomValue.Type()
for partNum := 1; partNum < numParts; partNum++ {
if pomValueType.Kind() != reflect.Struct {
break
}
part := parts[partNum]
// these two fields are directly inherited from the pom parent values
if partNum == 1 && pom.Parent != nil {
switch part {
case "version":
if pom.Version == nil && pom.Parent.Version != nil {
return r.resolveExpression(ctx, resolutionContext, *pom.Parent.Version, resolving)
}
case "groupID":
if pom.GroupID == nil && pom.Parent.GroupID != nil {
return r.resolveExpression(ctx, resolutionContext, *pom.Parent.GroupID, resolving)
}
}
}
for fieldNum := 0; fieldNum < pomValueType.NumField(); fieldNum++ {
f := pomValueType.Field(fieldNum)
tag := f.Tag.Get("xml")
tag = strings.Split(tag, ",")[0]
// a segment of the property name matches the xml tag for the field,
// so we need to recurse down the nested structs or return a match
// if we're done.
if part != tag {
continue
}
pomValue = pomValue.Field(fieldNum)
pomValueType = pomValue.Type()
if pomValueType.Kind() == reflect.Ptr {
// we were recursing down the nested structs, but one of the steps
// we need to take is a nil pointer, so give up
if pomValue.IsNil() {
return "", fmt.Errorf("property undefined: %s", propertyExpression)
}
pomValue = pomValue.Elem()
if !pomValue.IsZero() {
// we found a non-zero value whose tag matches this part of the property name
pomValueType = pomValue.Type()
}
}
// If this was the last part of the property name, return the value
if partNum == numParts-1 {
value := fmt.Sprintf("%v", pomValue.Interface())
return r.resolveExpression(ctx, resolutionContext, value, resolving)
}
break
}
}
}
return "", nil
}
// resolveMavenID creates a new mavenID from a pom, resolving parent information as necessary
func (r *mavenResolver) resolveMavenID(ctx context.Context, pom *gopom.Project) mavenID {
if pom == nil {
return mavenID{}
}
groupID := r.getPropertyValue(ctx, pom.GroupID, pom)
artifactID := r.getPropertyValue(ctx, pom.ArtifactID, pom)
version := r.getPropertyValue(ctx, pom.Version, pom)
if pom.Parent != nil {
if groupID == "" {
groupID = r.getPropertyValue(ctx, pom.Parent.GroupID, pom)
}
if artifactID == "" {
artifactID = r.getPropertyValue(ctx, pom.Parent.ArtifactID, pom)
}
if version == "" {
version = r.getPropertyValue(ctx, pom.Parent.Version, pom)
}
}
return mavenID{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 {
if pom == nil {
return mavenID{}
}
groupID := r.getPropertyValue(ctx, dep.GroupID, pom)
artifactID := r.getPropertyValue(ctx, dep.ArtifactID, pom)
version := r.getPropertyValue(ctx, dep.Version, pom)
var err error
if version == "" {
version, err = r.findInheritedVersion(ctx, pom, groupID, artifactID)
}
depID := mavenID{groupID, artifactID, version}
if err != nil {
log.WithFields("error", err, "mavenID", r.resolveMavenID(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) {
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]
if pom != nil {
return pom, nil
}
var errs error
// try to resolve first from local maven repo
if r.cfg.UseMavenLocalRepository {
pom, err := r.findPomInLocalRepository(groupID, artifactID, version)
if pom != nil {
r.resolved[id] = pom
return pom, nil
}
errs = errors.Join(errs, err)
}
// resolve via network maven repository
if pom == nil && r.cfg.UseNetwork {
pom, err := r.findPomInRemoteRepository(ctx, groupID, artifactID, version)
if pom != nil {
r.resolved[id] = pom
return pom, nil
}
errs = errors.Join(errs, err)
}
return nil, fmt.Errorf("unable to resolve pom %s %s %s: %w", groupID, artifactID, version, errs)
}
// findPomInLocalRepository attempts to get the POM from the users local maven repository
func (r *mavenResolver) findPomInLocalRepository(groupID, artifactID, version string) (*gopom.Project, error) {
groupPath := filepath.Join(strings.Split(groupID, ".")...)
pomFilePath := filepath.Join(r.cfg.MavenLocalRepositoryDir, 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)
if errors.Is(err, os.ErrNotExist) || !fi.IsDir() {
log.WithFields("error", err, "repositoryDir", r.cfg.MavenLocalRepositoryDir).
Info("local maven repository is not a readable directory, stopping local resolution")
r.cfg.UseMavenLocalRepository = false
}
}
return nil, err
}
defer internal.CloseAndLogError(pomFile, pomFilePath)
return decodePomXML(pomFile)
}
// 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) {
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)
if err != nil {
return nil, fmt.Errorf("unable to find pom in remote due to: %w", err)
}
// Downloading snapshots requires additional steps to determine the latest snapshot version.
// See: https://maven.apache.org/ref/3-LATEST/maven-repository-metadata/
if strings.HasSuffix(version, "-SNAPSHOT") {
return nil, fmt.Errorf("downloading snapshot artifacts is not supported, got: %s", requestURL)
}
cacheKey := strings.TrimPrefix(strings.TrimPrefix(requestURL, "http://"), "https://")
reader, err := r.cacheResolveReader(cacheKey, func() (io.ReadCloser, error) {
if err != nil {
return nil, err
}
log.WithFields("url", requestURL).Info("fetching parent pom from remote maven repository")
req, err := http.NewRequest(http.MethodGet, requestURL, nil)
if err != nil {
return nil, fmt.Errorf("unable to create request for Maven central: %w", err)
}
req = req.WithContext(ctx)
client := http.Client{
Timeout: r.remoteRequestTimeout,
}
resp, err := client.Do(req) //nolint:bodyclose
if err != nil {
return nil, fmt.Errorf("unable to get pom from Maven repository %v: %w", requestURL, err)
}
if resp.StatusCode == http.StatusNotFound {
return nil, fmt.Errorf("pom not found in Maven repository at: %v", requestURL)
}
return resp.Body, err
})
if err != nil {
return nil, err
}
if reader, ok := reader.(io.Closer); ok {
defer internal.CloseAndLogError(reader, requestURL)
}
pom, err := decodePomXML(reader)
if err != nil {
return nil, fmt.Errorf("unable to parse pom from Maven repository url %v: %w", requestURL, err)
}
return pom, nil
}
// cacheResolveReader attempts to get a reader from cache, otherwise caches the contents of the resolve() function.
// 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) {
reader, err := r.cache.Read(key)
if err == nil && reader != nil {
return reader, err
}
contentReader, err := resolve()
if err != nil {
return nil, err
}
defer internal.CloseAndLogError(contentReader, key)
// store the contents to return a new reader with the same content
contents, err := io.ReadAll(contentReader)
if err != nil {
return nil, err
}
err = r.cache.Write(key, bytes.NewBuffer(contents))
return bytes.NewBuffer(contents), err
}
// resolveParent attempts to resolve the parent for the given pom
func (r *mavenResolver) resolveParent(ctx context.Context, pom *gopom.Project) (*gopom.Project, error) {
if pom == nil || pom.Parent == nil {
return nil, nil
}
parent := pom.Parent
pomWithoutParent := *pom
pomWithoutParent.Parent = nil
groupID := r.getPropertyValue(ctx, parent.GroupID, &pomWithoutParent)
artifactID := r.getPropertyValue(ctx, parent.ArtifactID, &pomWithoutParent)
version := r.getPropertyValue(ctx, parent.Version, &pomWithoutParent)
// check cache before resolving
parentID := mavenID{groupID, artifactID, version}
if resolvedParent, ok := r.resolved[parentID]; ok {
return resolvedParent, nil
}
// check if the pom exists in the fileResolver
parentPom := r.findParentPomByRelativePath(ctx, pom, parentID)
if parentPom != nil {
return parentPom, nil
}
// find POM normally
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
//
//nolint:gocognit,funlen
func (r *mavenResolver) findInheritedVersion(ctx context.Context, pom *gopom.Project, groupID, artifactID string, resolutionContext ...*gopom.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.resolveMavenID(ctx, pom))
}
if slices.Contains(resolutionContext, pom) {
return "", fmt.Errorf("cycle detected attempting to resolve version for: %s:%s at: %v", groupID, artifactID, r.resolveMavenID(ctx, pom))
}
resolutionContext = append(resolutionContext, pom)
var err error
var version string
// 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...)
if depGroupID == groupID && depArtifactID == artifactID {
version = r.getPropertyValue(ctx, dep.Version, resolutionContext...)
if version != "" {
return version, nil
}
}
// 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...)
depPom, err := r.findPom(ctx, depGroupID, depArtifactID, depVersion)
if err != nil || depPom == nil {
log.WithFields("error", err, "mavenID", r.resolveMavenID(ctx, pom), "dependencyID", mavenID{depGroupID, depArtifactID, depVersion}).
Debug("unable to find imported pom looking for managed dependencies")
continue
}
version, err = r.findInheritedVersion(ctx, depPom, groupID, artifactID, resolutionContext...)
if err != nil {
log.WithFields("error", err, "mavenID", r.resolveMavenID(ctx, pom), "dependencyID", mavenID{depGroupID, depArtifactID, depVersion}).
Debug("error during findInheritedVersion")
}
if version != "" {
return version, nil
}
}
}
// recursively check parents
parent, err := r.resolveParent(ctx, pom)
if err != nil {
return "", err
}
if parent != nil {
version, err = r.findInheritedVersion(ctx, parent, groupID, artifactID, resolutionContext...)
if err != nil {
return "", err
}
if version != "" {
return version, nil
}
}
// check for inherited dependencies
for _, dep := range pomDependencies(pom) {
depGroupID := r.getPropertyValue(ctx, dep.GroupID, resolutionContext...)
depArtifactID := r.getPropertyValue(ctx, dep.ArtifactID, resolutionContext...)
if depGroupID == groupID && depArtifactID == artifactID {
version = r.getPropertyValue(ctx, dep.Version, resolutionContext...)
if version != "" {
return version, nil
}
}
}
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)
if pom == nil || err != nil {
return nil, err
}
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.resolveMavenID(ctx, pom)
if slices.Contains(processing, id) {
return nil, fmt.Errorf("cycle detected resolving licenses for: %v", id)
}
if r.cfg.MaxParentRecursiveDepth > 0 && len(processing) > r.cfg.MaxParentRecursiveDepth {
return nil, fmt.Errorf("maximum parent recursive depth (%v) reached: %v", r.cfg.MaxParentRecursiveDepth, processing)
}
directLicenses := r.pomLicenses(ctx, pom)
if len(directLicenses) > 0 {
return directLicenses, nil
}
parent, err := r.resolveParent(ctx, pom)
if err != nil {
return nil, err
}
if parent == nil {
return nil, nil
}
return r.resolveLicenses(ctx, parent, append(processing, id)...)
}
// 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
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)
if name != "" || url != "" {
out = append(out, license)
}
}
return out
}
func (r *mavenResolver) findParentPomByRelativePath(ctx context.Context, pom *gopom.Project, parentID mavenID) *gopom.Project {
// don't resolve if no resolver
if r.fileResolver == nil {
return nil
}
pomLocation, hasPomLocation := r.pomLocations[pom]
if !hasPomLocation || pom == nil || pom.Parent == nil {
return nil
}
relativePath := r.getPropertyValue(ctx, pom.Parent.RelativePath, pom)
if relativePath == "" {
return nil
}
p := pomLocation.Path()
p = path.Dir(p)
p = path.Join(p, relativePath)
p = path.Clean(p)
parentLocations, err := r.fileResolver.FilesByPath(p)
if err != nil || len(parentLocations) == 0 {
log.WithFields("error", err, "mavenID", r.resolveMavenID(ctx, pom), "parentID", parentID, "relativePath", relativePath).
Trace("parent pom not found by relative path")
return nil
}
parentLocation := parentLocations[0]
parentContents, err := r.fileResolver.FileContentsByLocation(parentLocation)
if err != nil || parentContents == nil {
log.WithFields("error", err, "mavenID", r.resolveMavenID(ctx, 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)
if err != nil || parentPom == nil {
log.WithFields("error", err, "mavenID", r.resolveMavenID(ctx, pom), "parentID", parentID, "parentLocation", parentLocation).
Debug("unable to parse parent pom")
return nil
}
// ensure parent matches
newParentID := r.resolveMavenID(ctx, parentPom)
if newParentID.ArtifactID != parentID.ArtifactID {
log.WithFields("newParentID", newParentID, "mavenID", r.resolveMavenID(ctx, 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
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 {
dependencies := deref(pom.Dependencies)
for _, profile := range deref(pom.Profiles) {
dependencies = append(dependencies, deref(profile.Dependencies)...)
}
return dependencies
}
// 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
if pom.DependencyManagement != nil {
dependencies = append(dependencies, deref(pom.DependencyManagement.Dependencies)...)
}
for _, profile := range deref(pom.Profiles) {
if profile.DependencyManagement != nil {
dependencies = append(dependencies, deref(profile.DependencyManagement.Dependencies)...)
}
}
return dependencies
}
@@ -0,0 +1,359 @@
package java
import (
"context"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"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"
)
func Test_resolveProperty(t *testing.T) {
tests := []struct {
name string
property string
pom gopom.Project
expected string
}{
{
name: "property",
property: "${version.number}",
pom: gopom.Project{
Properties: &gopom.Properties{
Entries: map[string]string{
"version.number": "12.5.0",
},
},
},
expected: "12.5.0",
},
{
name: "groupId",
property: "${project.groupId}",
pom: gopom.Project{
GroupID: ptr("org.some.group"),
},
expected: "org.some.group",
},
{
name: "parent groupId",
property: "${project.parent.groupId}",
pom: gopom.Project{
Parent: &gopom.Parent{
GroupID: ptr("org.some.parent"),
},
},
expected: "org.some.parent",
},
{
name: "nil pointer halts search",
property: "${project.parent.groupId}",
pom: gopom.Project{
Parent: nil,
},
expected: "",
},
{
name: "nil string pointer halts search",
property: "${project.parent.groupId}",
pom: gopom.Project{
Parent: &gopom.Parent{
GroupID: nil,
},
},
expected: "",
},
{
name: "double dereference",
property: "${springboot.version}",
pom: gopom.Project{
Parent: &gopom.Parent{
Version: ptr("1.2.3"),
},
Properties: &gopom.Properties{
Entries: map[string]string{
"springboot.version": "${project.parent.version}",
},
},
},
expected: "1.2.3",
},
{
name: "map missing stops double dereference",
property: "${springboot.version}",
pom: gopom.Project{
Parent: &gopom.Parent{
Version: ptr("1.2.3"),
},
},
expected: "",
},
{
name: "resolution halts even if it resolves to a variable",
property: "${springboot.version}",
pom: gopom.Project{
Parent: &gopom.Parent{
Version: ptr("${undefined.version}"),
},
Properties: &gopom.Properties{
Entries: map[string]string{
"springboot.version": "${project.parent.version}",
},
},
},
expected: "",
},
{
name: "resolution halts even if cyclic",
property: "${springboot.version}",
pom: gopom.Project{
Properties: &gopom.Properties{
Entries: map[string]string{
"springboot.version": "${springboot.version}",
},
},
},
expected: "",
},
{
name: "resolution halts even if cyclic more steps",
property: "${cyclic.version}",
pom: gopom.Project{
Properties: &gopom.Properties{
Entries: map[string]string{
"other.version": "${cyclic.version}",
"springboot.version": "${other.version}",
"cyclic.version": "${springboot.version}",
},
},
},
expected: "",
},
{
name: "resolution halts even if cyclic involving parent",
property: "${cyclic.version}",
pom: gopom.Project{
Parent: &gopom.Parent{
Version: ptr("${cyclic.version}"),
},
Properties: &gopom.Properties{
Entries: map[string]string{
"other.version": "${parent.version}",
"springboot.version": "${other.version}",
"cyclic.version": "${springboot.version}",
},
},
},
expected: "",
},
}
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)
require.Equal(t, test.expected, resolved)
})
}
}
func Test_mavenResolverLocal(t *testing.T) {
dir, err := filepath.Abs("test-fixtures/pom/maven-repo")
require.NoError(t, err)
tests := []struct {
name string
groupID string
artifactID string
version string
maxDepth int
expression string
expected string
wantErr require.ErrorAssertionFunc
}{
{
name: "artifact id with variable from 2nd parent",
groupID: "my.org",
artifactID: "child-one",
version: "1.3.6",
expression: "${project.one}",
expected: "1",
},
{
name: "depth limited large enough",
groupID: "my.org",
artifactID: "child-one",
version: "1.3.6",
expression: "${project.one}",
expected: "1",
maxDepth: 2,
},
{
name: "depth limited should not resolve",
groupID: "my.org",
artifactID: "child-one",
version: "1.3.6",
expression: "${project.one}",
expected: "",
maxDepth: 1,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := context.Background()
r := newMavenResolver(nil, ArchiveCatalogerConfig{
UseNetwork: false,
UseMavenLocalRepository: true,
MavenLocalRepositoryDir: dir,
MaxParentRecursiveDepth: test.maxDepth,
})
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)
require.Equal(t, test.expected, got)
})
}
}
func Test_mavenResolverRemote(t *testing.T) {
url := mockMavenRepo(t)
tests := []struct {
groupID string
artifactID string
version string
expression string
expected string
wantErr require.ErrorAssertionFunc
}{
{
groupID: "my.org",
artifactID: "child-one",
version: "1.3.6",
expression: "${project.one}",
expected: "1",
},
}
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,
})
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)
require.Equal(t, test.expected, got)
})
}
}
func Test_relativePathParent(t *testing.T) {
resolver, err := fileresolver.NewFromDirectory("test-fixtures/pom/local", "")
require.NoError(t, err)
r := newMavenResolver(resolver, DefaultArchiveCatalogerConfig())
locs, err := resolver.FilesByPath("child-1/pom.xml")
require.NoError(t, err)
require.Len(t, locs, 1)
loc := locs[0]
contents, err := resolver.FileContentsByLocation(loc)
require.NoError(t, err)
defer internal.CloseAndLogError(contents, loc.RealPath)
pom, err := decodePomXML(contents)
require.NoError(t, err)
r.pomLocations[pom] = loc
ctx := context.Background()
parent, err := r.resolveParent(ctx, pom)
require.NoError(t, err)
require.Contains(t, r.pomLocations, parent)
parent, err = r.resolveParent(ctx, parent)
require.NoError(t, err)
require.Contains(t, r.pomLocations, parent)
got := r.getPropertyValue(ctx, ptr("${commons-exec_subversion}"), pom)
require.Equal(t, "3", got)
}
// 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
}
}
}
+74
View File
@@ -0,0 +1,74 @@
package java
import (
"encoding/xml"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/mitchellh/go-homedir"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
)
// defaultMavenLocalRepoDir gets default location of the Maven local repository, generally at <USER HOME DIR>/.m2/repository
func defaultMavenLocalRepoDir() string {
homeDir, err := homedir.Dir()
if err != nil {
return ""
}
mavenHome := filepath.Join(homeDir, ".m2")
settingsXML := filepath.Join(mavenHome, "settings.xml")
settings, err := os.Open(settingsXML)
if err == nil && settings != nil {
defer internal.CloseAndLogError(settings, settingsXML)
localRepository := getSettingsXMLLocalRepository(settings)
if localRepository != "" {
return localRepository
}
}
return filepath.Join(mavenHome, "repository")
}
// getSettingsXMLLocalRepository reads the provided settings.xml and parses the localRepository, if present
func getSettingsXMLLocalRepository(settingsXML io.Reader) string {
type settings struct {
LocalRepository string `xml:"localRepository"`
}
s := settings{}
err := xml.NewDecoder(settingsXML).Decode(&s)
if err != nil {
log.WithFields("error", err).Debug("unable to read maven settings.xml")
}
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
urlPath := strings.Split(groupID, ".")
artifactPom := fmt.Sprintf("%s-%s.pom", artifactID, version)
urlPath = append(urlPath, artifactID, version, artifactPom)
// ex: https://repo1.maven.org/maven2/groupID/artifactID/artifactPom
requestURL, err = url.JoinPath(repoURL, urlPath...)
if err != nil {
return requestURL, fmt.Errorf("could not construct maven url: %w", err)
}
return requestURL, err
}
+103
View File
@@ -0,0 +1,103 @@
package java
import (
"os"
"path/filepath"
"testing"
"github.com/mitchellh/go-homedir"
"github.com/stretchr/testify/require"
)
func Test_defaultMavenLocalRepoDir(t *testing.T) {
home, err := homedir.Dir()
require.NoError(t, err)
fixtures, err := filepath.Abs("test-fixtures")
require.NoError(t, err)
tests := []struct {
name string
home string
expected string
}{
{
name: "default",
expected: filepath.Join(home, ".m2", "repository"),
home: "",
},
{
name: "alternate dir",
expected: "/some/other/repo",
home: "test-fixtures/local-repository-settings",
},
{
name: "explicit home",
expected: filepath.Join(fixtures, ".m2", "repository"),
home: "test-fixtures",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
homedir.Reset()
defer homedir.Reset()
if test.home != "" {
home, err := filepath.Abs(test.home)
require.NoError(t, err)
t.Setenv("HOME", home)
}
got := defaultMavenLocalRepoDir()
require.Equal(t, test.expected, got)
})
}
}
func Test_getSettingsXmlLocalRepository(t *testing.T) {
tests := []struct {
file string
expected string
}{
{
expected: "/some/other/repo",
file: "test-fixtures/local-repository-settings/.m2/settings.xml",
},
{
expected: "",
file: "invalid",
},
}
for _, test := range tests {
t.Run(test.expected, func(t *testing.T) {
f, _ := os.Open(test.file)
defer f.Close()
got := getSettingsXMLLocalRepository(f)
require.Equal(t, test.expected, got)
})
}
}
func Test_remotePomURL(t *testing.T) {
tests := []struct {
name string
groupID string
artifactID string
version string
expected string
}{
{
name: "formatMavenURL correctly assembles the pom URL",
groupID: "org.springframework.boot",
artifactID: "spring-boot-starter-test",
version: "3.1.5",
expected: "https://repo1.maven.org/maven2/org/springframework/boot/spring-boot-starter-test/3.1.5/spring-boot-starter-test-3.1.5.pom",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
requestURL, err := remotePomURL(mavenBaseURL, tc.groupID, tc.artifactID, tc.version)
require.NoError(t, err, "expected no err; got %w", err)
require.Equal(t, tc.expected, requestURL)
})
}
}
+116 -197
View File
@@ -4,164 +4,175 @@ import (
"bytes"
"context"
"encoding/xml"
"errors"
"fmt"
"io"
"reflect"
"regexp"
"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/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
const pomXMLGlob = "*pom.xml"
const (
pomXMLGlob = "*pom.xml"
pomCatalogerName = "java-pom-cataloger"
)
var propertyMatcher = regexp.MustCompile("[$][{][^}]+[}]")
type pomXMLCataloger struct {
cfg ArchiveCatalogerConfig
}
func (gap genericArchiveParserAdapter) parserPomXML(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
pom, err := decodePomXML(reader)
func (p pomXMLCataloger) Name() string {
return pomCatalogerName
}
func (p pomXMLCataloger) Catalog(ctx context.Context, fileResolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
locations, err := fileResolver.FilesByGlob("**/pom.xml")
if err != nil {
return nil, nil, err
}
var pkgs []pkg.Package
if pom.Dependencies != nil {
for _, dep := range *pom.Dependencies {
p := newPackageFromPom(
ctx,
pom,
dep,
gap.cfg,
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
)
if p.Name == "" {
continue
}
r := newMavenResolver(fileResolver, p.cfg)
pkgs = append(pkgs, p)
var poms []*gopom.Project
for _, pomLocation := range locations {
pom, err := readPomFromLocation(fileResolver, pomLocation)
if err != nil || pom == nil {
log.WithFields("error", err, "pomLocation", pomLocation).Debug("error while reading pom")
continue
}
poms = append(poms, pom)
// store information about this pom for future lookups
r.pomLocations[pom] = pomLocation
r.resolved[r.resolveMavenID(ctx, pom)] = pom
}
var pkgs []pkg.Package
for _, pom := range poms {
pkgs = append(pkgs, processPomXML(ctx, r, pom, r.pomLocations[pom])...)
}
return pkgs, nil, nil
}
func parsePomXMLProject(path string, reader io.Reader, location file.Location) (*parsedPomProject, error) {
project, err := decodePomXML(reader)
func readPomFromLocation(fileResolver file.Resolver, pomLocation file.Location) (*gopom.Project, error) {
contents, err := fileResolver.FileContentsByLocation(pomLocation)
if err != nil {
return nil, err
}
return newPomProject(path, project, location), nil
defer internal.CloseAndLogError(contents, pomLocation.RealPath)
return decodePomXML(contents)
}
func newPomProject(path string, p gopom.Project, location file.Location) *parsedPomProject {
artifactID := safeString(p.ArtifactID)
name := safeString(p.Name)
projectURL := safeString(p.URL)
func processPomXML(ctx context.Context, r *mavenResolver, pom *gopom.Project, loc file.Location) []pkg.Package {
var pkgs []pkg.Package
var licenses []pkg.License
if p.Licenses != nil {
for _, license := range *p.Licenses {
var licenseName, licenseURL string
if license.Name != nil {
licenseName = *license.Name
}
if license.URL != nil {
licenseURL = *license.URL
}
pomID := r.resolveMavenID(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")
if licenseName == "" && licenseURL == "" {
continue
}
licenses = append(licenses, pkg.NewLicenseFromFields(licenseName, licenseURL, &location))
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)
}
log.WithFields("path", path, "artifactID", artifactID, "name", name, "projectURL", projectURL).Trace("parsing pom.xml")
return &parsedPomProject{
JavaPomProject: &pkg.JavaPomProject{
Path: path,
Parent: pomParent(p, p.Parent),
GroupID: resolveProperty(p, p.GroupID, "groupId"),
ArtifactID: artifactID,
Version: resolveProperty(p, p.Version, "version"),
Name: name,
Description: cleanDescription(p.Description),
URL: projectURL,
},
Licenses: licenses,
return pkgs
}
func newPomProject(ctx context.Context, r *mavenResolver, path string, pom *gopom.Project) *pkg.JavaPomProject {
id := r.resolveMavenID(ctx, pom)
name := r.getPropertyValue(ctx, pom.Name, pom)
projectURL := r.getPropertyValue(ctx, pom.URL, pom)
log.WithFields("path", path, "artifactID", id.ArtifactID, "name", name, "projectURL", projectURL).Trace("parsing pom.xml")
return &pkg.JavaPomProject{
Path: path,
Parent: pomParent(ctx, r, pom),
GroupID: id.GroupID,
ArtifactID: id.ArtifactID,
Version: id.Version,
Name: name,
Description: cleanDescription(r.getPropertyValue(ctx, pom.Description, pom)),
URL: projectURL,
}
}
func newPackageFromPom(ctx context.Context, pom gopom.Project, dep gopom.Dependency, cfg ArchiveCatalogerConfig, locations ...file.Location) pkg.Package {
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)
m := pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: resolveProperty(pom, dep.GroupID, "groupId"),
ArtifactID: resolveProperty(pom, dep.ArtifactID, "artifactId"),
Scope: resolveProperty(pom, dep.Scope, "scope"),
GroupID: id.GroupID,
ArtifactID: id.ArtifactID,
Scope: r.getPropertyValue(ctx, dep.Scope, pom),
},
}
name := safeString(dep.ArtifactID)
version := resolveProperty(pom, dep.Version, "version")
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)
}
licenses := make([]pkg.License, 0)
if cfg.UseNetwork {
if version == "" {
// If we have no version then let's try to get it from a parent pom DependencyManagement section
version = recursivelyFindVersionFromParentPom(ctx, *dep.GroupID, *dep.ArtifactID, *pom.Parent.GroupID, *pom.Parent.ArtifactID, *pom.Parent.Version, cfg)
}
if version != "" {
parentLicenses := recursivelyFindLicensesFromParentPom(
ctx,
m.PomProperties.GroupID,
m.PomProperties.ArtifactID,
version,
cfg)
if len(parentLicenses) > 0 {
for _, licenseName := range parentLicenses {
licenses = append(licenses, pkg.NewLicenseFromFields(licenseName, "", nil))
}
}
if dependencyPom != nil {
depLicenses, _ := r.resolveLicenses(ctx, dependencyPom)
for _, license := range depLicenses {
licenses = append(licenses, pkg.NewLicenseFromFields(deref(license.Name), deref(license.URL), nil))
}
}
p := pkg.Package{
Name: name,
Version: version,
p := &pkg.Package{
Name: id.ArtifactID,
Version: id.Version,
Locations: file.NewLocationSet(locations...),
Licenses: pkg.NewLicenseSet(licenses...),
PURL: packageURL(name, version, m),
PURL: packageURL(id.ArtifactID, id.Version, m),
Language: pkg.Java,
Type: pkg.JavaPkg, // TODO: should we differentiate between packages from jar/war/zip versus packages from a pom.xml that were not installed yet?
FoundBy: pomCatalogerName,
Metadata: m,
}
p.SetID()
return p
return p, err
}
func decodePomXML(content io.Reader) (project gopom.Project, err error) {
// 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 project, fmt.Errorf("unable to read pom.xml: %w", err)
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
if err := decoder.Decode(&project); err != nil {
return project, fmt.Errorf("unable to unmarshal pom.xml: %w", err)
project = &gopom.Project{}
if err := decoder.Decode(project); err != nil {
return nil, fmt.Errorf("unable to unmarshal pom.xml: %w", err)
}
return project, nil
@@ -194,29 +205,28 @@ func getUtf8Reader(content io.Reader) (io.Reader, error) {
return inputReader, nil
}
func pomParent(pom gopom.Project, parent *gopom.Parent) (result *pkg.JavaPomParent) {
if parent == nil {
func pomParent(ctx context.Context, r *mavenResolver, pom *gopom.Project) *pkg.JavaPomParent {
if pom == nil || pom.Parent == nil {
return nil
}
artifactID := safeString(parent.ArtifactID)
result = &pkg.JavaPomParent{
GroupID: resolveProperty(pom, parent.GroupID, "groupId"),
groupID := r.getPropertyValue(ctx, pom.Parent.GroupID, pom)
artifactID := r.getPropertyValue(ctx, pom.Parent.ArtifactID, pom)
version := r.getPropertyValue(ctx, pom.Parent.Version, pom)
if groupID == "" && artifactID == "" && version == "" {
return nil
}
return &pkg.JavaPomParent{
GroupID: groupID,
ArtifactID: artifactID,
Version: resolveProperty(pom, parent.Version, "version"),
Version: version,
}
if result.GroupID == "" && result.ArtifactID == "" && result.Version == "" {
return nil
}
return result
}
func cleanDescription(original *string) (cleaned string) {
if original == nil {
return ""
}
descriptionLines := strings.Split(*original, "\n")
func cleanDescription(original string) (cleaned string) {
descriptionLines := strings.Split(original, "\n")
for _, line := range descriptionLines {
line = strings.TrimSpace(line)
if len(line) == 0 {
@@ -226,94 +236,3 @@ func cleanDescription(original *string) (cleaned string) {
}
return strings.TrimSpace(cleaned)
}
// resolveProperty emulates some maven property resolution logic by looking in the project's variables
// as well as supporting the project expressions like ${project.parent.groupId}.
// If no match is found, the entire expression including ${} is returned
func resolveProperty(pom gopom.Project, property *string, propertyName string) string {
propertyCase := safeString(property)
log.WithFields("existingPropertyValue", propertyCase, "propertyName", propertyName).Trace("resolving property")
seenBeforePropertyNames := map[string]struct{}{
propertyName: {},
}
result := recursiveResolveProperty(pom, propertyCase, seenBeforePropertyNames)
if propertyMatcher.MatchString(result) {
return "" // dereferencing variable failed; fall back to empty string
}
return result
}
//nolint:gocognit
func recursiveResolveProperty(pom gopom.Project, propertyCase string, seenPropertyNames map[string]struct{}) string {
return propertyMatcher.ReplaceAllStringFunc(propertyCase, func(match string) string {
propertyName := strings.TrimSpace(match[2 : len(match)-1]) // remove leading ${ and trailing }
if _, seen := seenPropertyNames[propertyName]; seen {
return propertyCase
}
entries := pomProperties(pom)
if value, ok := entries[propertyName]; ok {
seenPropertyNames[propertyName] = struct{}{}
return recursiveResolveProperty(pom, value, seenPropertyNames) // recursively resolve in case a variable points to a variable.
}
// if we don't find anything directly in the pom properties,
// see if we have a project.x expression and process this based
// on the xml tags in gopom
parts := strings.Split(propertyName, ".")
numParts := len(parts)
if numParts > 1 && strings.TrimSpace(parts[0]) == "project" {
pomValue := reflect.ValueOf(pom)
pomValueType := pomValue.Type()
for partNum := 1; partNum < numParts; partNum++ {
if pomValueType.Kind() != reflect.Struct {
break
}
part := parts[partNum]
for fieldNum := 0; fieldNum < pomValueType.NumField(); fieldNum++ {
f := pomValueType.Field(fieldNum)
tag := f.Tag.Get("xml")
tag = strings.Split(tag, ",")[0]
// a segment of the property name matches the xml tag for the field,
// so we need to recurse down the nested structs or return a match
// if we're done.
if part == tag {
pomValue = pomValue.Field(fieldNum)
pomValueType = pomValue.Type()
if pomValueType.Kind() == reflect.Ptr {
// we were recursing down the nested structs, but one of the steps
// we need to take is a nil pointer, so give up and return the original match
if pomValue.IsNil() {
return match
}
pomValue = pomValue.Elem()
if !pomValue.IsZero() {
// we found a non-zero value whose tag matches this part of the property name
pomValueType = pomValue.Type()
}
}
// If this was the last part of the property name, return the value
if partNum == numParts-1 {
return fmt.Sprintf("%v", pomValue.Interface())
}
break
}
}
}
}
return match
})
}
func pomProperties(p gopom.Project) map[string]string {
if p.Properties != nil {
return p.Properties.Entries
}
return map[string]string{}
}
func safeString(s *string) string {
if s == nil {
return ""
}
return *s
}
+410 -340
View File
@@ -1,6 +1,7 @@
package java
import (
"context"
"encoding/base64"
"io"
"os"
@@ -16,15 +17,17 @@ 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/source"
"github.com/anchore/syft/syft/source/directorysource"
)
func Test_parserPomXML(t *testing.T) {
func Test_parsePomXML(t *testing.T) {
tests := []struct {
input string
dir string
expected []pkg.Package
}{
{
input: "test-fixtures/pom/pom.xml",
dir: "test-fixtures/pom/local/example-java-app-maven",
expected: []pkg.Package{
{
Name: "joda-time",
@@ -32,6 +35,7 @@ func Test_parserPomXML(t *testing.T) {
PURL: "pkg:maven/com.joda/joda-time@2.9.2",
Language: pkg.Java,
Type: pkg.JavaPkg,
FoundBy: pomCatalogerName,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "com.joda",
@@ -45,6 +49,7 @@ func Test_parserPomXML(t *testing.T) {
PURL: "pkg:maven/junit/junit@4.12",
Language: pkg.Java,
Type: pkg.JavaPkg,
FoundBy: pomCatalogerName,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "junit",
@@ -58,19 +63,19 @@ func Test_parserPomXML(t *testing.T) {
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
t.Run(test.dir, func(t *testing.T) {
for i := range test.expected {
test.expected[i].Locations.Add(file.NewLocation(test.input))
test.expected[i].Locations.Add(file.NewLocation("pom.xml"))
}
gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{
cat := NewPomCataloger(ArchiveCatalogerConfig{
ArchiveSearchConfig: cataloging.ArchiveSearchConfig{
IncludeIndexedArchives: true,
IncludeUnindexedArchives: true,
},
})
pkgtest.TestFileParser(t, test.input, gap.parserPomXML, test.expected, nil)
pkgtest.TestCataloger(t, test.dir, cat, test.expected, nil)
})
}
}
@@ -131,168 +136,129 @@ func Test_decodePomXML_surviveNonUtf8Encoding(t *testing.T) {
func Test_parseCommonsTextPomXMLProject(t *testing.T) {
tests := []struct {
input string
dir string
expected []pkg.Package
}{
{
input: "test-fixtures/pom/commons-text.pom.xml",
expected: []pkg.Package{
{
Name: "commons-lang3",
Version: "3.12.0",
PURL: "pkg:maven/org.apache.commons/commons-lang3@3.12.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.apache.commons",
ArtifactID: "commons-lang3",
},
},
},
{
Name: "junit-jupiter",
Version: "",
PURL: "pkg:maven/org.junit.jupiter/junit-jupiter",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.junit.jupiter",
ArtifactID: "junit-jupiter",
Scope: "test",
},
},
},
{
Name: "assertj-core",
Version: "3.23.1",
PURL: "pkg:maven/org.assertj/assertj-core@3.23.1",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.assertj",
ArtifactID: "assertj-core",
Scope: "test",
},
},
},
{
Name: "commons-io",
Version: "2.11.0",
PURL: "pkg:maven/commons-io/commons-io@2.11.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "commons-io",
ArtifactID: "commons-io",
Scope: "test",
},
},
},
{
Name: "mockito-inline",
Version: "4.8.0",
PURL: "pkg:maven/org.mockito/mockito-inline@4.8.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.mockito",
ArtifactID: "mockito-inline",
Scope: "test",
},
},
},
{
Name: "js",
Version: "22.0.0.2",
PURL: "pkg:maven/org.graalvm.js/js@22.0.0.2",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.graalvm.js",
ArtifactID: "js",
Scope: "test",
},
},
},
{
Name: "js-scriptengine",
Version: "22.0.0.2",
PURL: "pkg:maven/org.graalvm.js/js-scriptengine@22.0.0.2",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.graalvm.js",
ArtifactID: "js-scriptengine",
Scope: "test",
},
},
},
{
Name: "commons-rng-simple",
Version: "1.4",
PURL: "pkg:maven/org.apache.commons/commons-rng-simple@1.4",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.apache.commons",
ArtifactID: "commons-rng-simple",
Scope: "test",
},
},
},
{
Name: "jmh-core",
Version: "1.35",
PURL: "pkg:maven/org.openjdk.jmh/jmh-core@1.35",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.openjdk.jmh",
ArtifactID: "jmh-core",
Scope: "test",
},
},
},
{
Name: "jmh-generator-annprocess",
Version: "1.35",
PURL: "pkg:maven/org.openjdk.jmh/jmh-generator-annprocess@1.35",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.openjdk.jmh",
ArtifactID: "jmh-generator-annprocess",
Scope: "test",
},
},
},
},
dir: "test-fixtures/pom/local/commons-text-1.10.0",
expected: getCommonsTextExpectedPackages(),
},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
t.Run(test.dir, func(t *testing.T) {
for i := range test.expected {
test.expected[i].Locations.Add(file.NewLocation(test.input))
test.expected[i].Locations.Add(file.NewLocation("pom.xml"))
}
gap := newGenericArchiveParserAdapter(ArchiveCatalogerConfig{
cat := NewPomCataloger(ArchiveCatalogerConfig{
ArchiveSearchConfig: cataloging.ArchiveSearchConfig{
IncludeIndexedArchives: true,
IncludeUnindexedArchives: true,
},
UseMavenLocalRepository: false,
})
pkgtest.TestFileParser(t, test.input, gap.parserPomXML, test.expected, nil)
pkgtest.TestCataloger(t, test.dir, cat, test.expected, nil)
})
}
}
func Test_parseCommonsTextPomXMLProjectWithLocalRepository(t *testing.T) {
// Using the local repository, the version of junit-jupiter will be resolved
expectedPackages := getCommonsTextExpectedPackages()
for i := 0; i < len(expectedPackages); i++ {
if expectedPackages[i].Name == "junit-jupiter" {
expPkg := &expectedPackages[i]
expPkg.Version = "5.9.1"
expPkg.PURL = "pkg:maven/org.junit.jupiter/junit-jupiter@5.9.1"
expPkg.Metadata = pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.junit.jupiter",
ArtifactID: "junit-jupiter",
Scope: "test",
},
}
}
}
tests := []struct {
dir string
expected []pkg.Package
}{
{
dir: "test-fixtures/pom/local/commons-text-1.10.0",
expected: expectedPackages,
},
}
for _, test := range tests {
t.Run(test.dir, func(t *testing.T) {
for i := range test.expected {
test.expected[i].Locations.Add(file.NewLocation("pom.xml"))
}
cat := NewPomCataloger(ArchiveCatalogerConfig{
ArchiveSearchConfig: cataloging.ArchiveSearchConfig{
IncludeIndexedArchives: true,
IncludeUnindexedArchives: true,
},
UseMavenLocalRepository: true,
MavenLocalRepositoryDir: "test-fixtures/pom/maven-repo",
})
pkgtest.TestCataloger(t, test.dir, cat, test.expected, nil)
})
}
}
func Test_parseCommonsTextPomXMLProjectWithNetwork(t *testing.T) {
url := mockMavenRepo(t)
// Using the local repository, the version of junit-jupiter will be resolved
expectedPackages := getCommonsTextExpectedPackages()
for i := 0; i < len(expectedPackages); i++ {
if expectedPackages[i].Name == "junit-jupiter" {
expPkg := &expectedPackages[i]
expPkg.Version = "5.9.1"
expPkg.PURL = "pkg:maven/org.junit.jupiter/junit-jupiter@5.9.1"
expPkg.Metadata = pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.junit.jupiter",
ArtifactID: "junit-jupiter",
Scope: "test",
},
}
}
}
tests := []struct {
dir string
expected []pkg.Package
}{
{
dir: "test-fixtures/pom/local/commons-text-1.10.0",
expected: expectedPackages,
},
}
for _, test := range tests {
t.Run(test.dir, func(t *testing.T) {
for i := range test.expected {
test.expected[i].Locations.Add(file.NewLocation("pom.xml"))
}
cat := NewPomCataloger(ArchiveCatalogerConfig{
ArchiveSearchConfig: cataloging.ArchiveSearchConfig{
IncludeIndexedArchives: true,
IncludeUnindexedArchives: true,
},
UseNetwork: true,
MavenBaseURL: url,
UseMavenLocalRepository: false,
})
pkgtest.TestCataloger(t, test.dir, cat, test.expected, nil)
})
}
}
@@ -302,63 +268,60 @@ func Test_parsePomXMLProject(t *testing.T) {
jarLocation := file.NewLocation("path/to/archive.jar")
tests := []struct {
name string
expected parsedPomProject
project *pkg.JavaPomProject
licenses []pkg.License
}{
{
name: "go case",
expected: parsedPomProject{
JavaPomProject: &pkg.JavaPomProject{
Path: "test-fixtures/pom/commons-codec.pom.xml",
Parent: &pkg.JavaPomParent{
GroupID: "org.apache.commons",
ArtifactID: "commons-parent",
Version: "42",
},
GroupID: "commons-codec",
ArtifactID: "commons-codec",
Version: "1.11",
Name: "Apache Commons Codec",
Description: "The Apache Commons Codec package contains simple encoder and decoders for various formats such as Base64 and Hexadecimal. In addition to these widely used encoders and decoders, the codec package also maintains a collection of phonetic encoding utilities.",
URL: "http://commons.apache.org/proper/commons-codec/",
project: &pkg.JavaPomProject{
Path: "test-fixtures/pom/commons-codec.pom.xml",
Parent: &pkg.JavaPomParent{
GroupID: "org.apache.commons",
ArtifactID: "commons-parent",
Version: "42",
},
GroupID: "commons-codec",
ArtifactID: "commons-codec",
Version: "1.11",
Name: "Apache Commons Codec",
Description: "The Apache Commons Codec package contains simple encoder and decoders for various formats such as Base64 and Hexadecimal. In addition to these widely used encoders and decoders, the codec package also maintains a collection of phonetic encoding utilities.",
URL: "http://commons.apache.org/proper/commons-codec/",
},
},
{
name: "with license data",
expected: parsedPomProject{
JavaPomProject: &pkg.JavaPomProject{
Path: "test-fixtures/pom/neo4j-license-maven-plugin.pom.xml",
Parent: &pkg.JavaPomParent{
GroupID: "org.sonatype.oss",
ArtifactID: "oss-parent",
Version: "7",
},
GroupID: "org.neo4j.build.plugins",
ArtifactID: "license-maven-plugin",
Version: "4-SNAPSHOT",
Name: "${project.artifactId}", // TODO: this is not an ideal answer
Description: "Maven 2 plugin to check and update license headers in source files",
URL: "http://components.neo4j.org/${project.artifactId}/${project.version}", // TODO: this is not an ideal answer
project: &pkg.JavaPomProject{
Path: "test-fixtures/pom/neo4j-license-maven-plugin.pom.xml",
Parent: &pkg.JavaPomParent{
GroupID: "org.sonatype.oss",
ArtifactID: "oss-parent",
Version: "7",
},
Licenses: []pkg.License{
{
Value: "The Apache Software License, Version 2.0",
SPDXExpression: "", // TODO: ideally we would parse this title to get Apache-2.0 (created issue #2210 https://github.com/anchore/syft/issues/2210)
Type: license.Declared,
URLs: []string{"http://www.apache.org/licenses/LICENSE-2.0.txt"},
Locations: file.NewLocationSet(jarLocation),
},
{
Value: "MIT",
SPDXExpression: "MIT",
Type: license.Declared,
Locations: file.NewLocationSet(jarLocation),
},
{
Type: license.Declared,
URLs: []string{"https://opensource.org/license/unlicense/"},
Locations: file.NewLocationSet(jarLocation),
},
GroupID: "org.neo4j.build.plugins",
ArtifactID: "license-maven-plugin",
Version: "4-SNAPSHOT",
Name: "license-maven-plugin",
Description: "Maven 2 plugin to check and update license headers in source files",
URL: "http://components.neo4j.org/license-maven-plugin/4-SNAPSHOT",
},
licenses: []pkg.License{
{
Value: "The Apache Software License, Version 2.0",
SPDXExpression: "", // TODO: ideally we would parse this title to get Apache-2.0 (created issue #2210 https://github.com/anchore/syft/issues/2210)
Type: license.Declared,
URLs: []string{"http://www.apache.org/licenses/LICENSE-2.0.txt"},
Locations: file.NewLocationSet(jarLocation),
},
{
Value: "MIT",
SPDXExpression: "MIT",
Type: license.Declared,
Locations: file.NewLocationSet(jarLocation),
},
{
Type: license.Declared,
URLs: []string{"https://opensource.org/license/unlicense/"},
Locations: file.NewLocationSet(jarLocation),
},
},
},
@@ -366,13 +329,20 @@ func Test_parsePomXMLProject(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fixture, err := os.Open(test.expected.Path)
fixture, err := os.Open(test.project.Path)
assert.NoError(t, err)
r := newMavenResolver(nil, ArchiveCatalogerConfig{})
actual, err := parsePomXMLProject(fixture.Name(), fixture, jarLocation)
pom, err := gopom.ParseFromReader(fixture)
require.NoError(t, err)
actual := newPomProject(context.Background(), r, fixture.Name(), pom)
assert.NoError(t, err)
assert.Equal(t, test.project, actual)
assert.Equal(t, &test.expected, actual)
licenses := r.pomLicenses(context.Background(), pom)
assert.NoError(t, err)
assert.Equal(t, test.licenses, toPkgLicenses(&jarLocation, licenses))
})
}
}
@@ -386,7 +356,7 @@ func Test_pomParent(t *testing.T) {
{
name: "only group ID",
input: &gopom.Parent{
GroupID: stringPointer("org.something"),
GroupID: ptr("org.something"),
},
expected: &pkg.JavaPomParent{
GroupID: "org.something",
@@ -395,7 +365,7 @@ func Test_pomParent(t *testing.T) {
{
name: "only artifact ID",
input: &gopom.Parent{
ArtifactID: stringPointer("something"),
ArtifactID: ptr("something"),
},
expected: &pkg.JavaPomParent{
ArtifactID: "something",
@@ -404,7 +374,7 @@ func Test_pomParent(t *testing.T) {
{
name: "only Version",
input: &gopom.Parent{
Version: stringPointer("something"),
Version: ptr("something"),
},
expected: &pkg.JavaPomParent{
Version: "something",
@@ -423,7 +393,7 @@ func Test_pomParent(t *testing.T) {
{
name: "unused field",
input: &gopom.Parent{
RelativePath: stringPointer("something"),
RelativePath: ptr("something"),
},
expected: nil,
},
@@ -431,7 +401,8 @@ func Test_pomParent(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, pomParent(gopom.Project{}, test.input))
r := newMavenResolver(nil, DefaultArchiveCatalogerConfig())
assert.Equal(t, test.expected, pomParent(context.Background(), r, &gopom.Project{Parent: test.input}))
})
}
}
@@ -454,163 +425,108 @@ func Test_cleanDescription(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, cleanDescription(stringPointer(test.input)))
assert.Equal(t, test.expected, cleanDescription(test.input))
})
}
}
func Test_resolveProperty(t *testing.T) {
func Test_resolveLicenses(t *testing.T) {
mavenURL := mockMavenRepo(t)
localM2 := "test-fixtures/pom/maven-repo"
localDir := "test-fixtures/pom/local"
containingDir := "test-fixtures/pom/local/contains-child-1"
expectedLicenses := []pkg.License{
{
Value: "Eclipse Public License v2.0",
SPDXExpression: "",
Type: license.Declared,
URLs: []string{"https://www.eclipse.org/legal/epl-v20.html"},
},
}
tests := []struct {
name string
property string
pom gopom.Project
expected string
scanDir string
cfg ArchiveCatalogerConfig
expected []pkg.License
}{
{
name: "property",
property: "${version.number}",
pom: gopom.Project{
Properties: &gopom.Properties{
Entries: map[string]string{
"version.number": "12.5.0",
},
},
name: "local no resolution",
scanDir: containingDir,
cfg: ArchiveCatalogerConfig{
UseMavenLocalRepository: false,
UseNetwork: false,
MavenLocalRepositoryDir: "",
MavenBaseURL: "",
},
expected: "12.5.0",
expected: nil,
},
{
name: "groupId",
property: "${project.groupId}",
pom: gopom.Project{
GroupID: stringPointer("org.some.group"),
name: "local all poms",
scanDir: localDir,
cfg: ArchiveCatalogerConfig{
UseMavenLocalRepository: false,
UseNetwork: false,
},
expected: "org.some.group",
expected: expectedLicenses,
},
{
name: "parent groupId",
property: "${project.parent.groupId}",
pom: gopom.Project{
Parent: &gopom.Parent{
GroupID: stringPointer("org.some.parent"),
},
name: "local m2 cache",
scanDir: containingDir,
cfg: ArchiveCatalogerConfig{
UseMavenLocalRepository: true,
MavenLocalRepositoryDir: localM2,
UseNetwork: false,
MavenBaseURL: "",
},
expected: "org.some.parent",
expected: expectedLicenses,
},
{
name: "nil pointer halts search",
property: "${project.parent.groupId}",
pom: gopom.Project{
Parent: nil,
name: "local with network",
scanDir: containingDir,
cfg: ArchiveCatalogerConfig{
UseMavenLocalRepository: false,
UseNetwork: true,
MavenBaseURL: mavenURL,
},
expected: "",
},
{
name: "nil string pointer halts search",
property: "${project.parent.groupId}",
pom: gopom.Project{
Parent: &gopom.Parent{
GroupID: nil,
},
},
expected: "",
},
{
name: "double dereference",
property: "${springboot.version}",
pom: gopom.Project{
Parent: &gopom.Parent{
Version: stringPointer("1.2.3"),
},
Properties: &gopom.Properties{
Entries: map[string]string{
"springboot.version": "${project.parent.version}",
},
},
},
expected: "1.2.3",
},
{
name: "map missing stops double dereference",
property: "${springboot.version}",
pom: gopom.Project{
Parent: &gopom.Parent{
Version: stringPointer("1.2.3"),
},
},
expected: "",
},
{
name: "resolution halts even if it resolves to a variable",
property: "${springboot.version}",
pom: gopom.Project{
Parent: &gopom.Parent{
Version: stringPointer("${undefined.version}"),
},
Properties: &gopom.Properties{
Entries: map[string]string{
"springboot.version": "${project.parent.version}",
},
},
},
expected: "",
},
{
name: "resolution halts even if cyclic",
property: "${springboot.version}",
pom: gopom.Project{
Properties: &gopom.Properties{
Entries: map[string]string{
"springboot.version": "${springboot.version}",
},
},
},
expected: "",
},
{
name: "resolution halts even if cyclic more steps",
property: "${cyclic.version}",
pom: gopom.Project{
Properties: &gopom.Properties{
Entries: map[string]string{
"other.version": "${cyclic.version}",
"springboot.version": "${other.version}",
"cyclic.version": "${springboot.version}",
},
},
},
expected: "",
},
{
name: "resolution halts even if cyclic involving parent",
property: "${cyclic.version}",
pom: gopom.Project{
Parent: &gopom.Parent{
Version: stringPointer("${cyclic.version}"),
},
Properties: &gopom.Properties{
Entries: map[string]string{
"other.version": "${parent.version}",
"springboot.version": "${other.version}",
"cyclic.version": "${springboot.version}",
},
},
},
expected: "",
expected: expectedLicenses,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
resolved := resolveProperty(test.pom, stringPointer(test.property), test.name)
assert.Equal(t, test.expected, resolved)
cat := NewPomCataloger(test.cfg)
ds, err := directorysource.NewFromPath(test.scanDir)
require.NoError(t, err)
fr, err := ds.FileResolver(source.AllLayersScope)
require.NoError(t, err)
ctx := context.TODO()
pkgs, _, err := cat.Catalog(ctx, fr)
require.NoError(t, err)
var child1 pkg.Package
for _, p := range pkgs {
if p.Name == "child-one" {
child1 = p
break
}
}
require.Equal(t, "child-one", child1.Name)
got := child1.Licenses.ToSlice()
for i := 0; i < len(got); i++ {
// ignore locations, just check license text
(&got[i]).Locations = file.LocationSet{}
}
require.ElementsMatch(t, test.expected, got)
})
}
}
func stringPointer(s string) *string {
return &s
}
func Test_getUtf8Reader(t *testing.T) {
tests := []struct {
name string
@@ -635,3 +551,157 @@ func Test_getUtf8Reader(t *testing.T) {
})
}
}
func getCommonsTextExpectedPackages() []pkg.Package {
return []pkg.Package{
{
Name: "commons-lang3",
Version: "3.12.0",
PURL: "pkg:maven/org.apache.commons/commons-lang3@3.12.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
FoundBy: pomCatalogerName,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.apache.commons",
ArtifactID: "commons-lang3",
},
},
},
{
Name: "junit-jupiter",
Version: "",
PURL: "pkg:maven/org.junit.jupiter/junit-jupiter",
Language: pkg.Java,
Type: pkg.JavaPkg,
FoundBy: pomCatalogerName,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.junit.jupiter",
ArtifactID: "junit-jupiter",
Scope: "test",
},
},
},
{
Name: "assertj-core",
Version: "3.23.1",
PURL: "pkg:maven/org.assertj/assertj-core@3.23.1",
Language: pkg.Java,
Type: pkg.JavaPkg,
FoundBy: pomCatalogerName,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.assertj",
ArtifactID: "assertj-core",
Scope: "test",
},
},
},
{
Name: "commons-io",
Version: "2.11.0",
PURL: "pkg:maven/commons-io/commons-io@2.11.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
FoundBy: pomCatalogerName,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "commons-io",
ArtifactID: "commons-io",
Scope: "test",
},
},
},
{
Name: "mockito-inline",
Version: "4.8.0",
PURL: "pkg:maven/org.mockito/mockito-inline@4.8.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
FoundBy: pomCatalogerName,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.mockito",
ArtifactID: "mockito-inline",
Scope: "test",
},
},
},
{
Name: "js",
Version: "22.0.0.2",
PURL: "pkg:maven/org.graalvm.js/js@22.0.0.2",
Language: pkg.Java,
Type: pkg.JavaPkg,
FoundBy: pomCatalogerName,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.graalvm.js",
ArtifactID: "js",
Scope: "test",
},
},
},
{
Name: "js-scriptengine",
Version: "22.0.0.2",
PURL: "pkg:maven/org.graalvm.js/js-scriptengine@22.0.0.2",
Language: pkg.Java,
Type: pkg.JavaPkg,
FoundBy: pomCatalogerName,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.graalvm.js",
ArtifactID: "js-scriptengine",
Scope: "test",
},
},
},
{
Name: "commons-rng-simple",
Version: "1.4",
PURL: "pkg:maven/org.apache.commons/commons-rng-simple@1.4",
Language: pkg.Java,
Type: pkg.JavaPkg,
FoundBy: pomCatalogerName,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.apache.commons",
ArtifactID: "commons-rng-simple",
Scope: "test",
},
},
},
{
Name: "jmh-core",
Version: "1.35",
PURL: "pkg:maven/org.openjdk.jmh/jmh-core@1.35",
Language: pkg.Java,
Type: pkg.JavaPkg,
FoundBy: pomCatalogerName,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.openjdk.jmh",
ArtifactID: "jmh-core",
Scope: "test",
},
},
},
{
Name: "jmh-generator-annprocess",
Version: "1.35",
PURL: "pkg:maven/org.openjdk.jmh/jmh-generator-annprocess@1.35",
Language: pkg.Java,
Type: pkg.JavaPkg,
FoundBy: pomCatalogerName,
Metadata: pkg.JavaArchive{
PomProperties: &pkg.JavaPomProperties{
GroupID: "org.openjdk.jmh",
ArtifactID: "jmh-generator-annprocess",
Scope: "test",
},
},
},
}
}
@@ -0,0 +1,4 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>/some/other/repo</localRepository>
</settings>
@@ -1,575 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.commons</groupId>
<artifactId>commons-parent</artifactId>
<version>54</version>
</parent>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
<name>Apache Commons Text</name>
<description>Apache Commons Text is a library focused on algorithms working on strings.</description>
<url>https://commons.apache.org/proper/commons-text</url>
<properties>
<project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<commons.componentid>text</commons.componentid>
<commons.module.name>org.apache.commons.text</commons.module.name>
<commons.release.version>1.10.0</commons.release.version>
<commons.release.desc>(Java 8+)</commons.release.desc>
<commons.jira.id>TEXT</commons.jira.id>
<commons.jira.pid>12318221</commons.jira.pid>
<commons.site.path>text</commons.site.path>
<commons.scmPubUrl>https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-text</commons.scmPubUrl>
<commons.scmPubCheckoutDirectory>site-content</commons.scmPubCheckoutDirectory>
<commons.junit.version>5.9.1</commons.junit.version>
<checkstyle.plugin.version>3.2.0</checkstyle.plugin.version>
<checkstyle.version>9.3</checkstyle.version>
<commons.spotbugs.plugin.version>4.7.2.0</commons.spotbugs.plugin.version>
<commons.spotbugs.impl.version>4.7.2</commons.spotbugs.impl.version>
<commons.pmd.version>3.19.0</commons.pmd.version>
<commons.pmd-impl.version>6.49.0</commons.pmd-impl.version>
<commons.mockito.version>4.8.0</commons.mockito.version>
<commons.jacoco.version>0.8.8</commons.jacoco.version>
<!-- apache-rat-plugin 0.13 and jdepend-maven-plugin 2.0 both fail with LinkageError when generating reports
with maven site plugin 3.11+. However, javadoc 3.4.0+ fails with site plugin versions lower than 3.11. So, we'll
use slightly older site and javadoc versions here in order to be able to generate all reports. -->
<commons.site-plugin.version>3.10.0</commons.site-plugin.version>
<commons.javadoc.version>3.4.1</commons.javadoc.version>
<!-- 22.1.0 requires Java 11 -->
<graalvm.version>22.0.0.2</graalvm.version>
<commons.rng.version>1.4</commons.rng.version>
<commons.japicmp.version>0.16.0</commons.japicmp.version>
<japicmp.skip>false</japicmp.skip>
<jmh.version>1.35</jmh.version>
<commons.project-info.version>3.1.2</commons.project-info.version>
<!-- Commons Release Plugin -->
<commons.bc.version>1.9</commons.bc.version>
<commons.rc.version>RC1</commons.rc.version>
<commons.release.isDistModule>true</commons.release.isDistModule>
<commons.distSvnStagingUrl>scm:svn:https://dist.apache.org/repos/dist/dev/commons/${commons.componentid}</commons.distSvnStagingUrl>
<commons.releaseManagerName>Gary Gregory</commons.releaseManagerName>
<commons.releaseManagerKey>86fdc7e2a11262cb</commons.releaseManagerKey>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.23.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<!-- Use mockito-inline instead of mockito-core to mock and spy on final classes. -->
<artifactId>mockito-inline</artifactId>
<version>${commons.mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>${graalvm.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js-scriptengine</artifactId>
<version>${graalvm.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-rng-simple</artifactId>
<version>${commons.rng.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<defaultGoal>clean verify apache-rat:check japicmp:cmp checkstyle:check spotbugs:check javadoc:javadoc</defaultGoal>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.rat</groupId>
<artifactId>apache-rat-plugin</artifactId>
<configuration>
<excludes>
<exclude>site-content/**</exclude>
<exclude>src/site/resources/download_lang.cgi</exclude>
<exclude>src/test/resources/org/apache/commons/text/stringEscapeUtilsTestData.txt</exclude>
<exclude>src/test/resources/org/apache/commons/text/lcs-perf-analysis-inputs.csv</exclude>
<exclude>src/site/resources/release-notes/RELEASE-NOTES-*.txt</exclude>
</excludes>
</configuration>
</plugin><!-- override skip property of parent pom -->
<plugin>
<artifactId>maven-pmd-plugin</artifactId>
<version>${commons.pmd.version}</version>
<configuration>
<targetJdk>${maven.compiler.target}</targetJdk>
</configuration>
<dependencies>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-core</artifactId>
<version>${commons.pmd-impl.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-java</artifactId>
<version>${commons.pmd-impl.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-javascript</artifactId>
<version>${commons.pmd-impl.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-jsp</artifactId>
<version>${commons.pmd-impl.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${checkstyle.plugin.version}</version>
<configuration>
<enableRulesSummary>false</enableRulesSummary>
<configLocation>src/conf/checkstyle.xml</configLocation>
<headerLocation>src/conf/checkstyle-header.txt</headerLocation>
<suppressionsLocation>src/conf/checkstyle-suppressions.xml</suppressionsLocation>
<suppressionsFileExpression>src/conf/checkstyle-suppressions.xml</suppressionsFileExpression>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
<excludes>**/generated/**.java,**/jmh_generated/**.java</excludes>
</configuration>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>${checkstyle.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>${commons.spotbugs.plugin.version}</version>
<dependencies>
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs</artifactId>
<version>${commons.spotbugs.impl.version}</version>
</dependency>
</dependencies>
<configuration>
<excludeFilterFile>src/conf/spotbugs-exclude-filter.xml</excludeFilterFile>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>src/assembly/bin.xml</descriptor>
<descriptor>src/assembly/src.xml</descriptor>
</descriptors>
<tarLongFileMode>gnu</tarLongFileMode>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
<configuration>
<archive combine.children="append">
<manifestEntries>
<Automatic-Module-Name>${commons.module.name}</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-scm-publish-plugin</artifactId>
<configuration>
<ignorePathsToDelete>
<ignorePathToDelete>javadocs</ignorePathToDelete>
</ignorePathsToDelete>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
</configuration>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>${checkstyle.plugin.version}</version>
<configuration>
<enableRulesSummary>false</enableRulesSummary>
<configLocation>src/conf/checkstyle.xml</configLocation>
<headerLocation>src/conf/checkstyle-header.txt</headerLocation>
<suppressionsLocation>src/conf/checkstyle-suppressions.xml</suppressionsLocation>
<suppressionsFileExpression>src/conf/checkstyle-suppressions.xml</suppressionsFileExpression>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
<excludes>**/generated/**.java,**/jmh_generated/**.java</excludes>
</configuration>
<reportSets>
<reportSet>
<reports>
<report>checkstyle</report>
</reports>
</reportSet>
</reportSets>
</plugin>
<!-- Requires setting 'export MAVEN_OPTS="-Xmx512m" ' -->
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>${commons.spotbugs.plugin.version}</version>
<configuration>
<excludeFilterFile>src/conf/spotbugs-exclude-filter.xml</excludeFilterFile>
</configuration>
</plugin>
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.19.0</version>
<configuration>
<targetJdk>${maven.compiler.target}</targetJdk>
</configuration>
<reportSets>
<reportSet>
<reports>
<report>pmd</report>
<report>cpd</report>
</reports>
</reportSet>
</reportSets>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>taglist-maven-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<tagListOptions>
<tagClasses>
<tagClass>
<displayName>Needs Work</displayName>
<tags>
<tag>
<matchString>TODO</matchString>
<matchType>exact</matchType>
</tag>
<tag>
<matchString>FIXME</matchString>
<matchType>exact</matchType>
</tag>
<tag>
<matchString>XXX</matchString>
<matchType>exact</matchType>
</tag>
</tags>
</tagClass>
<tagClass>
<displayName>Noteable Markers</displayName>
<tags>
<tag>
<matchString>NOTE</matchString>
<matchType>exact</matchType>
</tag>
<tag>
<matchString>NOPMD</matchString>
<matchType>exact</matchType>
</tag>
<tag>
<matchString>NOSONAR</matchString>
<matchType>exact</matchType>
</tag>
</tags>
</tagClass>
</tagClasses>
</tagListOptions>
</configuration>
</plugin>
</plugins>
</reporting>
<inceptionYear>2014</inceptionYear>
<developers>
<developer>
<id>kinow</id>
<name>Bruno P. Kinoshita</name>
<email>kinow@apache.org</email>
</developer>
<developer>
<id>britter</id>
<name>Benedikt Ritter</name>
<email>britter@apache.org</email>
</developer>
<developer>
<id>chtompki</id>
<name>Rob Tompkins</name>
<email>chtompki@apache.org</email>
</developer>
<developer>
<id>ggregory</id>
<name>Gary Gregory</name>
<email>ggregory at apache.org</email>
<url>https://www.garygregory.com</url>
<organization>The Apache Software Foundation</organization>
<organizationUrl>https://www.apache.org/</organizationUrl>
<roles>
<role>PMC Member</role>
</roles>
<timezone>America/New_York</timezone>
<properties>
<picUrl>https://people.apache.org/~ggregory/img/garydgregory80.png</picUrl>
</properties>
</developer>
<developer>
<id>djones</id>
<name>Duncan Jones</name>
<email>djones@apache.org</email>
</developer>
</developers>
<contributors>
<contributor>
<name>Don Jeba</name>
<email>donjeba@yahoo.com</email>
</contributor>
<contributor>
<name>Sampanna Kahu</name>
</contributor>
<contributor>
<name>Jarek Strzelecki</name>
</contributor>
<contributor>
<name>Lee Adcock</name>
</contributor>
<contributor>
<name>Amey Jadiye</name>
<email>ameyjadiye@gmail.com</email>
</contributor>
<contributor>
<name>Arun Vinud S S</name>
</contributor>
<contributor>
<name>Ioannis Sermetziadis</name>
</contributor>
<contributor>
<name>Jostein Tveit</name>
</contributor>
<contributor>
<name>Luciano Medallia</name>
</contributor>
<contributor>
<name>Jan Martin Keil</name>
</contributor>
<contributor>
<name>Nandor Kollar</name>
</contributor>
<contributor>
<name>Nick Wong</name>
</contributor>
<contributor>
<name>Ali Ghanbari</name>
<url>https://ali-ghanbari.github.io/</url>
</contributor>
</contributors>
<scm>
<connection>scm:git:https://gitbox.apache.org/repos/asf/commons-text</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/commons-text</developerConnection>
<url>https://gitbox.apache.org/repos/asf?p=commons-text.git</url>
</scm>
<issueManagement>
<system>jira</system>
<url>https://issues.apache.org/jira/browse/TEXT</url>
</issueManagement>
<distributionManagement>
<site>
<id>apache.website</id>
<name>Apache Commons Site</name>
<url>scm:svn:https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-text/</url>
</site>
</distributionManagement>
<profiles>
<profile>
<id>setup-checkout</id>
<activation>
<file>
<missing>site-content</missing>
</file>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>prepare-checkout</id>
<goals>
<goal>run</goal>
</goals>
<phase>pre-site</phase>
<configuration>
<target>
<exec executable="svn">
<arg line="checkout --depth immediates ${commons.scmPubUrl} ${commons.scmPubCheckoutDirectory}"/>
</exec>
<exec executable="svn">
<arg line="update --set-depth exclude ${commons.scmPubCheckoutDirectory}/javadocs"/>
</exec>
<pathconvert pathsep=" " property="dirs">
<dirset dir="${commons.scmPubCheckoutDirectory}" includes="*"/>
</pathconvert>
<exec executable="svn">
<arg line="update --set-depth infinity ${dirs}"/>
</exec>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>java9+</id>
<activation>
<jdk>[9,)</jdk>
</activation>
<properties>
<!-- coverall version 4.3.0 does not work with java 9+, see https://github.com/trautonen/coveralls-maven-plugin/issues/112 -->
<coveralls.skip>true</coveralls.skip>
</properties>
</profile>
<profile>
<id>benchmark</id>
<properties>
<skipTests>true</skipTests>
<benchmark>org.apache</benchmark>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>benchmark</id>
<phase>test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<classpathScope>test</classpathScope>
<executable>java</executable>
<arguments>
<argument>-classpath</argument>
<classpath/>
<argument>org.openjdk.jmh.Main</argument>
<argument>-rf</argument>
<argument>json</argument>
<argument>-rff</argument>
<argument>target/jmh-result.${benchmark}.json</argument>
<argument>${benchmark}</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
@@ -0,0 +1,42 @@
<?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>
<!--
$ docker run -it -\-rm -v "$HOME/.m2:/root/.m2" -v "$(pwd)":/wd -w /wd/child-1 maven:3.3-jdk-8 mvn dependency:tree
...
[INFO] my.org:child-one:jar:1.3.6
[INFO] +- org.apache.commons:commons-lang3:jar:3.12.0:compile
[INFO] +- org.apache.commons:commons-text:jar:1.12.0:compile
[INFO] +- org.apache.commons:commons-collections4:jar:4.2:compile
[INFO] \- junit:junit:jar:4.12:test
[INFO] \- org.hamcrest:hamcrest-core:jar:1.3:test
-->
<parent>
<groupId>my.org</groupId>
<artifactId>parent-one</artifactId>
<version>3.11.0</version>
<relativePath>../parent-1/pom.xml</relativePath>
</parent>
<artifactId>child-one</artifactId>
<!-- maven warns about this, but resolves the property -->
<version>${project.one}.3.6</version>
<packaging>jar</packaging>
<properties>
<commons.lang3.version>3.12.0</commons.lang3.version>
<commons.collections4.version>4.2</commons.collections4.version>
<commons.junit.version>4.12</commons.junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,263 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.commons</groupId>
<artifactId>commons-parent</artifactId>
<version>54</version>
</parent>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
<name>Apache Commons Text</name>
<description>Apache Commons Text is a library focused on algorithms working on strings.</description>
<url>https://commons.apache.org/proper/commons-text</url>
<properties>
<project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<commons.componentid>text</commons.componentid>
<commons.module.name>org.apache.commons.text</commons.module.name>
<commons.release.version>1.10.0</commons.release.version>
<commons.release.desc>(Java 8+)</commons.release.desc>
<commons.jira.id>TEXT</commons.jira.id>
<commons.jira.pid>12318221</commons.jira.pid>
<commons.site.path>text</commons.site.path>
<commons.scmPubUrl>https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-text</commons.scmPubUrl>
<commons.scmPubCheckoutDirectory>site-content</commons.scmPubCheckoutDirectory>
<commons.junit.version>5.9.1</commons.junit.version>
<checkstyle.plugin.version>3.2.0</checkstyle.plugin.version>
<checkstyle.version>9.3</checkstyle.version>
<commons.spotbugs.plugin.version>4.7.2.0</commons.spotbugs.plugin.version>
<commons.spotbugs.impl.version>4.7.2</commons.spotbugs.impl.version>
<commons.pmd.version>3.19.0</commons.pmd.version>
<commons.pmd-impl.version>6.49.0</commons.pmd-impl.version>
<commons.mockito.version>4.8.0</commons.mockito.version>
<commons.jacoco.version>0.8.8</commons.jacoco.version>
<!-- apache-rat-plugin 0.13 and jdepend-maven-plugin 2.0 both fail with LinkageError when generating reports
with maven site plugin 3.11+. However, javadoc 3.4.0+ fails with site plugin versions lower than 3.11. So, we'll
use slightly older site and javadoc versions here in order to be able to generate all reports. -->
<commons.site-plugin.version>3.10.0</commons.site-plugin.version>
<commons.javadoc.version>3.4.1</commons.javadoc.version>
<!-- 22.1.0 requires Java 11 -->
<graalvm.version>22.0.0.2</graalvm.version>
<commons.rng.version>1.4</commons.rng.version>
<commons.japicmp.version>0.16.0</commons.japicmp.version>
<japicmp.skip>false</japicmp.skip>
<jmh.version>1.35</jmh.version>
<commons.project-info.version>3.1.2</commons.project-info.version>
<!-- Commons Release Plugin -->
<commons.bc.version>1.9</commons.bc.version>
<commons.rc.version>RC1</commons.rc.version>
<commons.release.isDistModule>true</commons.release.isDistModule>
<commons.distSvnStagingUrl>scm:svn:https://dist.apache.org/repos/dist/dev/commons/${commons.componentid}</commons.distSvnStagingUrl>
<commons.releaseManagerName>Gary Gregory</commons.releaseManagerName>
<commons.releaseManagerKey>86fdc7e2a11262cb</commons.releaseManagerKey>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.23.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<!-- Use mockito-inline instead of mockito-core to mock and spy on final classes. -->
<artifactId>mockito-inline</artifactId>
<version>${commons.mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>${graalvm.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js-scriptengine</artifactId>
<version>${graalvm.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-rng-simple</artifactId>
<version>${commons.rng.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<inceptionYear>2014</inceptionYear>
<scm>
<connection>scm:git:https://gitbox.apache.org/repos/asf/commons-text</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/commons-text</developerConnection>
<url>https://gitbox.apache.org/repos/asf?p=commons-text.git</url>
</scm>
<issueManagement>
<system>jira</system>
<url>https://issues.apache.org/jira/browse/TEXT</url>
</issueManagement>
<distributionManagement>
<site>
<id>apache.website</id>
<name>Apache Commons Site</name>
<url>scm:svn:https://svn.apache.org/repos/infra/websites/production/commons/content/proper/commons-text/</url>
</site>
</distributionManagement>
<profiles>
<profile>
<id>setup-checkout</id>
<activation>
<file>
<missing>site-content</missing>
</file>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>prepare-checkout</id>
<goals>
<goal>run</goal>
</goals>
<phase>pre-site</phase>
<configuration>
<target>
<exec executable="svn">
<arg line="checkout --depth immediates ${commons.scmPubUrl} ${commons.scmPubCheckoutDirectory}"/>
</exec>
<exec executable="svn">
<arg line="update --set-depth exclude ${commons.scmPubCheckoutDirectory}/javadocs"/>
</exec>
<pathconvert pathsep=" " property="dirs">
<dirset dir="${commons.scmPubCheckoutDirectory}" includes="*"/>
</pathconvert>
<exec executable="svn">
<arg line="update --set-depth infinity ${dirs}"/>
</exec>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>java9+</id>
<activation>
<jdk>[9,)</jdk>
</activation>
<properties>
<!-- coverall version 4.3.0 does not work with java 9+, see https://github.com/trautonen/coveralls-maven-plugin/issues/112 -->
<coveralls.skip>true</coveralls.skip>
</properties>
</profile>
<profile>
<id>benchmark</id>
<properties>
<skipTests>true</skipTests>
<benchmark>org.apache</benchmark>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>benchmark</id>
<phase>test</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<classpathScope>test</classpathScope>
<executable>java</executable>
<arguments>
<argument>-classpath</argument>
<classpath/>
<argument>org.openjdk.jmh.Main</argument>
<argument>-rf</argument>
<argument>json</argument>
<argument>-rff</argument>
<argument>target/jmh-result.${benchmark}.json</argument>
<argument>${benchmark}</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
@@ -0,0 +1,27 @@
<?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>
<artifactId>contains-child-one</artifactId>
<version>5</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>
</dependencies>
</project>
@@ -0,0 +1,51 @@
<?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>
<parent>
<groupId>my.org</groupId>
<artifactId>parent-two</artifactId>
<version>13.7.8</version>
<relativePath>../parent-2/pom.xml</relativePath>
</parent>
<artifactId>parent-one</artifactId>
<version>3.11.0</version>
<packaging>pom</packaging>
<properties>
<!-- maven resolves project.parent.version from the _child_ project root context -->
<commons.lang3.version>3.1${project.parent.version}.0</commons.lang3.version>
<commons.collections4.version>4.3</commons.collections4.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons.text.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons.collections4.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${commons.junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,67 @@
<?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.org</groupId>
<artifactId>parent-two</artifactId>
<version>13.7.8</version>
<packaging>pom</packaging>
<properties>
<commons.lang3.version>3.14.0</commons.lang3.version>
<commons.collections4.version>4.4</commons.collections4.version>
<commons.text.version>1.12.0</commons.text.version>
<commons.junit.version>4.13.2</commons.junit.version>
<commons-exec_subversion>3</commons-exec_subversion>
<project.one>1</project.one>
</properties>
<licenses>
<license>
<name>Eclipse Public License v2.0</name>
<url>https://www.eclipse.org/legal/epl-v20.html</url>
</license>
</licenses>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons.text.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${commons.junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons.text.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons.collections4.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${commons.junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,41 @@
<?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>
<!--
$ docker run -it -\-rm -v "$(pwd)":/wd -w /wd/child-1 maven:3.3-jdk-8 mvn dependency:tree
...
[INFO] my.org:child-1:jar:1.3.6
[INFO] +- org.apache.commons:commons-lang3:jar:3.12.0:compile
[INFO] +- org.apache.commons:commons-text:jar:1.12.0:compile
[INFO] +- org.apache.commons:commons-collections4:jar:4.2:compile
[INFO] \- junit:junit:jar:4.12:test
[INFO] \- org.hamcrest:hamcrest-core:jar:1.3:test
-->
<parent>
<groupId>my.org</groupId>
<artifactId>parent-one</artifactId>
<version>3.11.0</version>
</parent>
<artifactId>child-one</artifactId>
<!-- maven warns about this, but resolves the property -->
<version>${project.one}.3.6</version>
<packaging>jar</packaging>
<properties>
<commons.lang3.version>3.12.0</commons.lang3.version>
<commons.collections4.version>4.2</commons.collections4.version>
<commons.junit.version>4.12</commons.junit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,53 @@
<?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>
<!--
$ docker run -it -\-rm -v "$(pwd)":/wd -w /wd/child-2 maven:3.3-jdk-8 mvn dependency:tree
...
[INFO] my.org:child-2:jar:2.1.90
[INFO] +- org.apache.commons:commons-lang3:jar:3.13.0:compile
[INFO] +- org.apache.commons:commons-math3:jar:3.5:compile
[INFO] +- org.apache.commons:commons-exec:jar:1.3:compile
[INFO] +- org.apache.commons:commons-text:jar:1.12.0:compile
[INFO] +- org.apache.commons:commons-collections4:jar:4.2:compile
[INFO] \- junit:junit:jar:4.12:test
[INFO] \- org.hamcrest:hamcrest-core:jar:1.3:test
-->
<parent>
<groupId>my.org</groupId>
<artifactId>parent-one</artifactId>
<version>3.11.0</version>
</parent>
<groupId>${project.parent.groupId}</groupId>
<artifactId>child-two</artifactId>
<version>2.1.90</version>
<packaging>jar</packaging>
<properties>
<commons.collections4.version>4.2</commons.collections4.version>
<commons.junit.version>4.12</commons.junit.version>
<project.parent.groupId>my.other.org</project.parent.groupId>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math${project.parent.version}</artifactId>
<version>3.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-exec</artifactId>
<version>1.${commons-exec_subversion}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,51 @@
<?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>
<parent>
<groupId>my.org</groupId>
<artifactId>parent-two</artifactId>
<version>13.7.8</version>
</parent>
<groupId>my.org</groupId>
<artifactId>parent-one</artifactId>
<version>3.11.0</version>
<packaging>pom</packaging>
<properties>
<!-- maven resolves project.parent.version from the _child_ project root context -->
<commons.lang3.version>3.1${project.parent.version}.0</commons.lang3.version>
<commons.collections4.version>4.3</commons.collections4.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons.text.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons.collections4.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${commons.junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,67 @@
<?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.org</groupId>
<artifactId>parent-two</artifactId>
<version>13.7.8</version>
<packaging>pom</packaging>
<licenses>
<license>
<name>Eclipse Public License v2.0</name>
<url>https://www.eclipse.org/legal/epl-v20.html</url>
</license>
</licenses>
<properties>
<commons.lang3.version>3.14.0</commons.lang3.version>
<commons.collections4.version>4.4</commons.collections4.version>
<commons.text.version>1.12.0</commons.text.version>
<commons.junit.version>4.13.2</commons.junit.version>
<commons-exec_subversion>3</commons-exec_subversion>
<project.one>1</project.one>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons.text.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${commons.junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons.text.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons.collections4.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${commons.junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements... http://www.apache.org/licenses/LICENSE-2.0
-->
<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>
<parent>
<groupId>org.apache</groupId>
<artifactId>apache</artifactId>
<version>27</version>
</parent>
<groupId>org.apache.commons</groupId>
<artifactId>commons-parent</artifactId>
<version>54</version>
<packaging>pom</packaging>
<name>Apache Commons Parent</name>
<description>The Apache Commons Parent POM provides common settings for all Apache Commons components.</description>
<inceptionYear>2006</inceptionYear>
<url>https://commons.apache.org/commons-parent-pom.html</url>
<properties>
<minimalMavenBuildVersion>3.3.9</minimalMavenBuildVersion>
<commons.release.version>${project.version}</commons.release.version>
<commons.release.isDistModule>true</commons.release.isDistModule>
<commons.releaseManagerName>Gary Gregory</commons.releaseManagerName>
<commons.releaseManagerKey>86fdc7e2a11262cb</commons.releaseManagerKey>
<maven.compiler.source>1.3</maven.compiler.source>
<maven.compiler.target>1.3</maven.compiler.target>
<commons.compiler.fork>false</commons.compiler.fork>
<commons.compiler.compilerVersion />
<commons.compiler.javac />
<commons.compiler.javadoc />
<commons.animal-sniffer.version>1.22</commons.animal-sniffer.version>
<commons.animal-sniffer.signature.version>1.0</commons.animal-sniffer.signature.version>
<commons.assembly-plugin.version>3.4.2</commons.assembly-plugin.version>
<commons.build-helper.version>3.3.0</commons.build-helper.version>
<commons.build-plugin.version>1.12</commons.build-plugin.version>
<commons.changes.version>2.12.1</commons.changes.version>
<commons.checkstyle-plugin.version>3.2.0</commons.checkstyle-plugin.version>
<commons.checkstyle.version>9.3</commons.checkstyle.version>
<commons.cobertura.version>2.7</commons.cobertura.version>
<commons.compiler.version>3.10.1</commons.compiler.version>
<commons.coveralls.version>4.3.0</commons.coveralls.version>
<commons.coveralls.timestampFormat>EpochMillis</commons.coveralls.timestampFormat>
<commons.cyclonedx.version>2.7.1</commons.cyclonedx.version>
<commons.spdx.version>0.5.5</commons.spdx.version>
<commons.junit.version>5.9.0</commons.junit.version>
<commons.site-plugin.version>3.12.1</commons.site-plugin.version>
<commons.source-plugin.version>3.2.1</commons.source-plugin.version>
<commons.spotbugs.plugin.version>4.7.2.0</commons.spotbugs.plugin.version>
<commons.wagon-ssh.version>3.5.2</commons.wagon-ssh.version>
<!-- Default values for the download-page generation by commons-build-plugin -->
<commons.release.name>${project.artifactId}-${commons.release.version}</commons.release.name>
<commons.release.desc />
<commons.binary.suffix>-bin</commons.binary.suffix>
<commons.release.2.name>${project.artifactId}-${commons.release.2.version}</commons.release.2.name>
<commons.release.2.desc />
<commons.release.2.binary.suffix>-bin</commons.release.2.binary.suffix>
<commons.release.3.name>${project.artifactId}-${commons.release.3.version}</commons.release.3.name>
<commons.release.3.desc />
<commons.release.3.binary.suffix>-bin</commons.release.3.binary.suffix>
<commons.release.4.desc />
<commons.release.4.binary.suffix>-bin</commons.release.4.binary.suffix>
<!-- Default values for the jacoco-maven-plugin reports -->
<commons.jacoco.classRatio>1.00</commons.jacoco.classRatio>
<commons.jacoco.instructionRatio>0.90</commons.jacoco.instructionRatio>
<commons.jacoco.methodRatio>0.95</commons.jacoco.methodRatio>
<commons.jacoco.branchRatio>0.85</commons.jacoco.branchRatio>
<commons.jacoco.complexityRatio>0.85</commons.jacoco.complexityRatio>
<commons.jacoco.lineRatio>0.90</commons.jacoco.lineRatio>
<commons.jacoco.haltOnFailure>false</commons.jacoco.haltOnFailure>
<commons.componentid>${project.artifactId}</commons.componentid>
<commons.packageId>${project.artifactId}</commons.packageId>
<!-- Configuration properties for the OSGi maven-bundle-plugin -->
<commons.osgi.symbolicName>org.apache.commons.${commons.packageId}</commons.osgi.symbolicName>
<commons.osgi.export>org.apache.commons.*;version=${project.version};-noimport:=true</commons.osgi.export>
<commons.osgi.import>*</commons.osgi.import>
<commons.osgi.dynamicImport />
<commons.osgi.private />
<commons.osgi.excludeDependencies>true</commons.osgi.excludeDependencies>
<!-- location of any manifest file used by maven-jar-plugin -->
<commons.manifestfile>${project.build.directory}/osgi/MANIFEST.MF</commons.manifestfile>
<commons.deployment.protocol>scp</commons.deployment.protocol>
<commons.encoding>iso-8859-1</commons.encoding>
<commons.docEncoding>${commons.encoding}</commons.docEncoding>
<project.build.sourceEncoding>${commons.encoding}</project.build.sourceEncoding>
<project.reporting.outputEncoding>${commons.encoding}</project.reporting.outputEncoding>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ssZ</maven.build.timestamp.format>
<implementation.build>${scmBranch}@r${buildNumber}; ${maven.build.timestamp}</implementation.build>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>${commons.junit.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>
<profile>
<id>site-basic</id>
<properties>
<skipTests>true</skipTests>
<maven.javadoc.skip>true</maven.javadoc.skip>
<cobertura.skip>true</cobertura.skip>
<spotbugs.skip>true</spotbugs.skip>
<checkstyle.skip>true</checkstyle.skip>
<rat.skip>true</rat.skip> <!-- from version 0.12 -->
<jacoco.skip>true</jacoco.skip>
<skipSurefireReport>true</skipSurefireReport>
</properties>
</profile>
</profiles>
</project>
@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.9.0</version>
<packaging>pom</packaging>
<licenses>
<license>
<name>Eclipse Public License v2.0</name>
<url>https://www.eclipse.org/legal/epl-v20.html</url>
</license>
</licenses>
<developers>
<developer>
<id>bechte</id>
<name>Stefan Bechtold</name>
<email>stefan.bechtold@me.com</email>
</developer>
<developer>
<id>jlink</id>
<name>Johannes Link</name>
<email>business@johanneslink.net</email>
</developer>
<developer>
<id>marcphilipp</id>
<name>Marc Philipp</name>
<email>mail@marcphilipp.de</email>
</developer>
<developer>
<id>mmerdes</id>
<name>Matthias Merdes</name>
<email>matthias.merdes@heidelpay.com</email>
</developer>
<developer>
<id>sbrannen</id>
<name>Sam Brannen</name>
<email>sam@sambrannen.com</email>
</developer>
<developer>
<id>sormuras</id>
<name>Christian Stein</name>
<email>sormuras@gmail.com</email>
</developer>
<developer>
<id>juliette-derancourt</id>
<name>Juliette de Rancourt</name>
<email>derancourt.juliette@gmail.com</email>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/junit-team/junit5.git</connection>
<developerConnection>scm:git:git://github.com/junit-team/junit5.git</developerConnection>
<url>https://github.com/junit-team/junit5</url>
</scm>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-migrationsupport</artifactId>
<version>5.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-commons</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-console</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-engine</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-jfr</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-reporting</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite-api</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite-commons</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite-engine</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-testkit</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.9.0</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.9.1</version>
<packaging>pom</packaging>
<name>JUnit 5 (Bill of Materials)</name>
<licenses>
<license>
<name>Eclipse Public License v2.0</name>
<url>https://www.eclipse.org/legal/epl-v20.html</url>
</license>
</licenses>
<developers>
<developer>
<id>bechte</id>
<name>Stefan Bechtold</name>
<email>stefan.bechtold@me.com</email>
</developer>
<developer>
<id>jlink</id>
<name>Johannes Link</name>
<email>business@johanneslink.net</email>
</developer>
<developer>
<id>marcphilipp</id>
<name>Marc Philipp</name>
<email>mail@marcphilipp.de</email>
</developer>
<developer>
<id>mmerdes</id>
<name>Matthias Merdes</name>
<email>matthias.merdes@heidelpay.com</email>
</developer>
<developer>
<id>sbrannen</id>
<name>Sam Brannen</name>
<email>sam@sambrannen.com</email>
</developer>
<developer>
<id>sormuras</id>
<name>Christian Stein</name>
<email>sormuras@gmail.com</email>
</developer>
<developer>
<id>juliette-derancourt</id>
<name>Juliette de Rancourt</name>
<email>derancourt.juliette@gmail.com</email>
</developer>
</developers>
<scm>
<connection>scm:git:git://github.com/junit-team/junit5.git</connection>
<developerConnection>scm:git:git://github.com/junit-team/junit5.git</developerConnection>
<url>https://github.com/junit-team/junit5</url>
</scm>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-migrationsupport</artifactId>
<version>5.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-commons</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-console</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-engine</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-jfr</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-reporting</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-runner</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite-api</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite-commons</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite-engine</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-testkit</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.9.1</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
+15
View File
@@ -0,0 +1,15 @@
/*
Package swipl provides a Cataloger implementation relating to packages within the SWI Prolog language ecosystem.
*/
package swipl
import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
// NewSwiplPackCataloger returns a new SWI Prolog Pack package manager cataloger object.
func NewSwiplPackCataloger() pkg.Cataloger {
return generic.NewCataloger("swipl-pack-cataloger").
WithParserByGlobs(parsePackPackage, "**/pack.pl")
}
@@ -0,0 +1,32 @@
package swipl
import (
"testing"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func Test_Cataloger_Globs(t *testing.T) {
tests := []struct {
name string
fixture string
expected []string
}{
{
name: "obtain swipl pack files",
fixture: "test-fixtures/glob-paths",
expected: []string{
"pack.pl",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture).
ExpectsResolverContentQueries(test.expected).
TestCataloger(t, NewSwiplPackCataloger())
})
}
}
+36
View File
@@ -0,0 +1,36 @@
package swipl
import (
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
func newSwiplPackPackage(m pkg.SwiplPackEntry, locations ...file.Location) pkg.Package {
p := pkg.Package{
Name: m.Name,
Version: m.Version,
PURL: swiplpackPackageURL(m.Name, m.Version),
Locations: file.NewLocationSet(locations...),
Type: pkg.SwiplPackPkg,
Language: pkg.Swipl,
Metadata: m,
}
p.SetID()
return p
}
func swiplpackPackageURL(name, version string) string {
var qualifiers packageurl.Qualifiers
return packageurl.NewPackageURL(
"swiplpack",
"",
name,
version,
qualifiers,
"",
).ToString()
}
+33
View File
@@ -0,0 +1,33 @@
package swipl
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_swiplpackPackageURL(t *testing.T) {
type args struct {
name string
version string
}
tests := []struct {
name string
args args
want string
}{
{
name: "go case",
args: args{
name: "name",
version: "v0.1.0",
},
want: "pkg:swiplpack/name@v0.1.0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, swiplpackPackageURL(tt.args.name, tt.args.version))
})
}
}
+70
View File
@@ -0,0 +1,70 @@
package swipl
import (
"context"
"io"
"regexp"
"github.com/anchore/syft/internal/log"
"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/generic"
)
func parsePackPackage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package
nameRe := regexp.MustCompile(`name\(\s*'?([^')]+)'?\s*\)`)
versionRe := regexp.MustCompile(`version\('([^']+)'\)`)
homeRe := regexp.MustCompile(`home\(\s*'([^']+)'\s*\)`)
authorRe := regexp.MustCompile(`(author|packager)\(\s*'([^']+)'\s*(?:,\s*'([^']+)'\s*)?\)`)
data, err := io.ReadAll(reader)
if err != nil {
log.WithFields("error", err).Trace("unable to parse Rockspec app")
return nil, nil, nil
}
name := nameRe.FindSubmatch(data)
version := versionRe.FindSubmatch(data)
if name == nil || version == nil {
log.Debugf("encountered pack.pl file without a name and/or version field, ignoring (path=%q)", reader.Path())
return nil, nil, nil
}
entry := pkg.SwiplPackEntry{
Name: string(name[1]),
Version: string(version[1]),
}
home := homeRe.FindSubmatch(data)
if home != nil {
entry.Homepage = string(home[1])
}
authors := authorRe.FindAllSubmatch(data, -1)
for _, a := range authors {
switch string(a[1]) {
case "author":
entry.Author = string(a[2])
entry.AuthorEmail = string(a[3])
case "packager":
entry.Packager = string(a[2])
entry.PackagerEmail = string(a[3])
}
}
pkgs = append(
pkgs,
newSwiplPackPackage(
entry,
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
)
return pkgs, nil, nil
}
@@ -0,0 +1,39 @@
package swipl
import (
"testing"
"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/internal/pkgtest"
)
func TestParsePackPackage(t *testing.T) {
fixture := "test-fixtures/pack.pl"
locations := file.NewLocationSet(file.NewLocation(fixture))
expectedPkgs := []pkg.Package{
{
Name: "hdt",
Version: "0.5.2",
PURL: "pkg:swiplpack/hdt@0.5.2",
Locations: locations,
Language: pkg.Swipl,
Type: pkg.SwiplPackPkg,
Metadata: pkg.SwiplPackEntry{
Name: "hdt",
Version: "0.5.2",
Author: "Jan Wielemaker",
AuthorEmail: "J.Wielemaker@vu.nl",
Packager: "Jan Wielemaker",
PackagerEmail: "J.Wielemaker@vu.nl",
Homepage: "https://github.com/JanWielemaker/hdt",
},
},
}
// TODO: no relationships are under test yet
var expectedRelationships []artifact.Relationship
pkgtest.TestFileParser(t, fixture, parsePackPackage, expectedPkgs, expectedRelationships)
}
@@ -0,0 +1 @@
bogus
@@ -0,0 +1,10 @@
name(hdt).
version('0.5.2').
% TODO: swipl_version([90121]).
title('Access RDF HDT files').
keywords(['RDF']).
author( 'Jan Wielemaker', 'J.Wielemaker@vu.nl' ).
packager( 'Jan Wielemaker', 'J.Wielemaker@vu.nl' ).
maintainer( 'Jan Wielemaker', 'J.Wielemaker@vu.nl' ).
home( 'https://github.com/JanWielemaker/hdt' ).
download( 'https://github.com/JanWielemaker/hdt/archive/V*.zip' ).
+4
View File
@@ -28,6 +28,7 @@ const (
Ruby Language = "ruby"
Rust Language = "rust"
Swift Language = "swift"
Swipl Language = "swipl"
)
// AllLanguages is a set of all programming languages detected by syft.
@@ -48,6 +49,7 @@ var AllLanguages = []Language{
Ruby,
Rust,
Swift,
Swipl,
}
// String returns the string representation of the language.
@@ -88,6 +90,8 @@ func LanguageByName(name string) Language {
return Dotnet
case packageurl.TypeCocoapods, packageurl.TypeSwift, string(CocoapodsPkg):
return Swift
case "swipl", string(SwiplPackPkg):
return Swipl
case packageurl.TypeConan, string(CPP):
return CPP
case packageurl.TypeHackage, string(Haskell):
+8
View File
@@ -78,6 +78,10 @@ func TestLanguageFromPURL(t *testing.T) {
purl: "pkg:swift/github.com/apple/swift-numerics/swift-numerics@1.0.2",
want: Swift,
},
{
purl: "pkg:swiplpack/conditon@0.1.1",
want: Swipl,
},
{
purl: "pkg:luarocks/kong@3.7.0",
want: Lua,
@@ -219,6 +223,10 @@ func TestLanguageByName(t *testing.T) {
name: "swift",
language: Swift,
},
{
name: "swiplpack",
language: Swipl,
},
{
name: "pod",
language: Swift,
+1 -1
View File
@@ -70,7 +70,7 @@ func NewLicenseFromType(value string, t license.Type) License {
var err error
spdxExpression, err = license.ParseExpression(value)
if err != nil {
log.Trace("unable to parse license expression: %w", err)
log.WithFields("error", err, "expression", value).Trace("unable to parse license expression")
}
}
+12
View File
@@ -0,0 +1,12 @@
package pkg
type SwiplPackEntry struct {
Name string `toml:"name" json:"name"`
Version string `toml:"version" json:"version"`
Author string `json:"author" mapstruct:"Author"`
AuthorEmail string `json:"authorEmail" mapstruct:"Authoremail"`
Packager string `json:"packager" mapstructure:"Packager"`
PackagerEmail string `json:"packagerEmail" mapstruct:"Packageremail"`
Homepage string `json:"homepage"`
Dependencies []string `toml:"dependencies" json:"dependencies"`
}
+6
View File
@@ -42,6 +42,7 @@ const (
RpmPkg Type = "rpm"
RustPkg Type = "rust-crate"
SwiftPkg Type = "swift"
SwiplPackPkg Type = "swiplpack"
WordpressPluginPkg Type = "wordpress-plugin"
)
@@ -78,6 +79,7 @@ var AllPkgs = []Type{
RpmPkg,
RustPkg,
SwiftPkg,
SwiplPackPkg,
WordpressPluginPkg,
}
@@ -141,6 +143,8 @@ func (t Type) PackageURLType() string {
return "cargo"
case SwiftPkg:
return packageurl.TypeSwift
case SwiplPackPkg:
return "swiplpack"
case WordpressPluginPkg:
return "wordpress-plugin"
default:
@@ -217,6 +221,8 @@ func TypeByName(name string) Type {
return Rpkg
case packageurl.TypeSwift:
return SwiftPkg
case "swiplpack":
return SwiplPackPkg
case "wordpress-plugin":
return WordpressPluginPkg
default:
+4
View File
@@ -111,6 +111,10 @@ func TestTypeFromPURL(t *testing.T) {
purl: "pkg:swift/github.com/apple/swift-numerics/swift-numerics@1.0.2",
expected: SwiftPkg,
},
{
purl: "pkg:swiplpack/condition@0.1.1",
expected: SwiplPackPkg,
},
}
var pkgTypes []string