diff --git a/go.mod b/go.mod index 7e5fb03d..ddacf6e7 100644 --- a/go.mod +++ b/go.mod @@ -7,15 +7,16 @@ require ( github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-retryablehttp v0.7.4 + github.com/hashicorp/go-retryablehttp v0.7.5 github.com/liamg/jfather v0.0.7 github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 github.com/samber/lo v1.38.1 github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.26.0 golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4 - golang.org/x/mod v0.13.0 - golang.org/x/net v0.17.0 + golang.org/x/mod v0.14.0 + golang.org/x/net v0.18.0 + golang.org/x/text v0.14.0 golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 gopkg.in/yaml.v3 v3.0.1 ) @@ -26,5 +27,4 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/text v0.13.0 // indirect ) diff --git a/go.sum b/go.sum index 74abe090..80823fb7 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= -github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= +github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/liamg/jfather v0.0.7 h1:Xf78zS263yfT+xr2VSo6+kyAy4ROlCacRqJG7s5jt4k= github.com/liamg/jfather v0.0.7/go.mod h1:xXBGiBoiZ6tmHhfy5Jzw8sugzajwYdi6VosIpB3/cPM= github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 h1:TLygBUBxikNJJfLwgm+Qwdgq1FtfV8Uh7bcxRyTzK8s= @@ -43,12 +43,12 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4 h1:K3x+yU+fbot38x5bQbU2QqUAVyYLEktdNH2GxZLnM3U= golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= diff --git a/pkg/java/pom/parse.go b/pkg/java/pom/parse.go index e2de5094..9e8fe900 100644 --- a/pkg/java/pom/parse.go +++ b/pkg/java/pom/parse.go @@ -510,6 +510,15 @@ func (p *parser) tryRelativePath(parentArtifact artifact, currentPath, relativeP return nil, err } + // To avoid an infinite loop or parsing the wrong parent when using relatedPath or `../pom.xml`, + // we need to compare GAV of `parentArtifact` (`parent` tag from base pom) and GAV of pom from `relativePath`. + // See `compare ArtifactIDs for base and parent pom's` test for example. + // But GroupID can be inherited from parent (`p.analyze` function is required to get the GroupID). + // Version can contain a property (`p.analyze` function is required to get the GroupID). + // So we can only match ArtifactID's. + if pom.artifact().ArtifactID != parentArtifact.ArtifactID { + return nil, xerrors.New("'parent.relativePath' points at wrong local POM") + } result, err := p.analyze(pom, analysisOptions{}) if err != nil { return nil, xerrors.Errorf("analyze error: %w", err) diff --git a/pkg/java/pom/parse_test.go b/pkg/java/pom/parse_test.go index 36fdd84e..398e9ade 100644 --- a/pkg/java/pom/parse_test.go +++ b/pkg/java/pom/parse_test.go @@ -984,6 +984,19 @@ func TestPom_Parse(t *testing.T) { }, }, }, + { + name: "compare ArtifactIDs for base and parent pom's", + inputFile: filepath.Join("testdata", "no-parent-infinity-loop", "pom.xml"), + local: true, + want: []types.Library{ + { + ID: "com.example:child:1.0.0", + Name: "com.example:child", + Version: "1.0.0", + License: "The Apache Software License, Version 2.0", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/java/pom/testdata/no-parent-infinity-loop/parent/pom.xml b/pkg/java/pom/testdata/no-parent-infinity-loop/parent/pom.xml new file mode 100644 index 00000000..0af9c80e --- /dev/null +++ b/pkg/java/pom/testdata/no-parent-infinity-loop/parent/pom.xml @@ -0,0 +1,19 @@ + + 4.0.0 + + com.example + parent + 1.0.0 + + pom + parent + Parent + + + org.example + example-api + 1.7.30 + + + diff --git a/pkg/java/pom/testdata/no-parent-infinity-loop/pom.xml b/pkg/java/pom/testdata/no-parent-infinity-loop/pom.xml new file mode 100644 index 00000000..f2c9bfb7 --- /dev/null +++ b/pkg/java/pom/testdata/no-parent-infinity-loop/pom.xml @@ -0,0 +1,17 @@ + + 4.0.0 + + child + + child + Child + + + com.example + parent + 1.0.0 + ./parent + + + diff --git a/pkg/java/pom/testdata/parent-child-properties/parent/pom.xml b/pkg/java/pom/testdata/parent-child-properties/parent/pom.xml index b51bee75..5ec3c450 100644 --- a/pkg/java/pom/testdata/parent-child-properties/parent/pom.xml +++ b/pkg/java/pom/testdata/parent-child-properties/parent/pom.xml @@ -4,7 +4,7 @@ com.example - parent + top-parent 1.0.0 ../top-parent diff --git a/pkg/nuget/packagesprops/parse.go b/pkg/nuget/packagesprops/parse.go new file mode 100644 index 00000000..995364a6 --- /dev/null +++ b/pkg/nuget/packagesprops/parse.go @@ -0,0 +1,85 @@ +package packagesprops + +import ( + "encoding/xml" + "strings" + + "golang.org/x/xerrors" + + dio "github.com/aquasecurity/go-dep-parser/pkg/io" + "github.com/aquasecurity/go-dep-parser/pkg/types" + "github.com/aquasecurity/go-dep-parser/pkg/utils" +) + +type pkg struct { + Version string `xml:"Version,attr"` + UpdatePackageName string `xml:"Update,attr"` + IncludePackageName string `xml:"Include,attr"` +} + +// https://github.com/dotnet/roslyn-tools/blob/b4c5220f5dfc4278847b6d38eff91cc1188f8066/src/RoslynInsertionTool/RoslynInsertionTool/CoreXT.cs#L150 +type itemGroup struct { + PackageReferenceEntry []pkg `xml:"PackageReference"` + PackageVersionEntry []pkg `xml:"PackageVersion"` +} + +type project struct { + XMLName xml.Name `xml:"Project"` + ItemGroups []itemGroup `xml:"ItemGroup"` +} + +type Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p pkg) library() types.Library { + // Update attribute is considered legacy, so preferring Include + name := p.UpdatePackageName + if p.IncludePackageName != "" { + name = p.IncludePackageName + } + + name = strings.TrimSpace(name) + version := strings.TrimSpace(p.Version) + return types.Library{ + ID: utils.PackageID(name, version), + Name: name, + Version: version, + } +} + +func shouldSkipLib(lib types.Library) bool { + if len(lib.Name) == 0 || len(lib.Version) == 0 { + return true + } + // *packages.props files don't contain variable resolution information. + // So we need to skip them. + if isVariable(lib.Name) || isVariable(lib.Version) { + return true + } + return false +} + +func isVariable(s string) bool { + return strings.HasPrefix(s, "$(") && strings.HasSuffix(s, ")") +} + +func (p *Parser) Parse(r dio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var configData project + if err := xml.NewDecoder(r).Decode(&configData); err != nil { + return nil, nil, xerrors.Errorf("failed to decode '*.packages.props' file: %w", err) + } + + var libs []types.Library + for _, item := range configData.ItemGroups { + for _, pkg := range append(item.PackageReferenceEntry, item.PackageVersionEntry...) { + lib := pkg.library() + if !shouldSkipLib(lib) { + libs = append(libs, lib) + } + } + } + return utils.UniqueLibraries(libs), nil, nil +} diff --git a/pkg/nuget/packagesprops/parse_test.go b/pkg/nuget/packagesprops/parse_test.go new file mode 100644 index 00000000..56318f74 --- /dev/null +++ b/pkg/nuget/packagesprops/parse_test.go @@ -0,0 +1,82 @@ +package packagesprops_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + config "github.com/aquasecurity/go-dep-parser/pkg/nuget/packagesprops" + "github.com/aquasecurity/go-dep-parser/pkg/types" +) + +func TestParse(t *testing.T) { + tests := []struct { + name string // Test input file + inputFile string + want []types.Library + wantErr string + }{ + { + name: "PackagesProps", + inputFile: "testdata/packages.props", + want: []types.Library{ + {Name: "Microsoft.Extensions.Configuration", Version: "2.1.1", ID: "Microsoft.Extensions.Configuration@2.1.1"}, + {Name: "Microsoft.Extensions.DependencyInjection.Abstractions", Version: "2.2.1", ID: "Microsoft.Extensions.DependencyInjection.Abstractions@2.2.1"}, + {Name: "Microsoft.Extensions.Http", Version: "3.2.1", ID: "Microsoft.Extensions.Http@3.2.1"}, + }, + }, + { + name: "DirectoryPackagesProps", + inputFile: "testdata/Directory.Packages.props", + want: []types.Library{ + {Name: "PackageOne", Version: "6.2.3", ID: "PackageOne@6.2.3"}, + {Name: "PackageThree", Version: "2.4.1", ID: "PackageThree@2.4.1"}, + {Name: "PackageTwo", Version: "6.0.0", ID: "PackageTwo@6.0.0"}, + }, + }, + { + name: "SeveralItemGroupElements", + inputFile: "testdata/several_item_groups", + want: []types.Library{ + {Name: "PackageOne", Version: "6.2.3", ID: "PackageOne@6.2.3"}, + {Name: "PackageThree", Version: "2.4.1", ID: "PackageThree@2.4.1"}, + {Name: "PackageTwo", Version: "6.0.0", ID: "PackageTwo@6.0.0"}, + }, + }, + { + name: "VariablesAsNamesOrVersion", + inputFile: "testdata/variables_and_empty", + want: []types.Library{ + {Name: "PackageFour", Version: "2.4.1", ID: "PackageFour@2.4.1"}, + }, + }, + { + name: "NoItemGroupInXMLStructure", + inputFile: "testdata/no_item_group.props", + want: []types.Library(nil), + }, + { + name: "NoProject", + inputFile: "testdata/no_project.props", + wantErr: "failed to decode '*.packages.props' file", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + + got, _, err := config.NewParser().Parse(f) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/nuget/packagesprops/testdata/Directory.Packages.props b/pkg/nuget/packagesprops/testdata/Directory.Packages.props new file mode 100644 index 00000000..62c085ba --- /dev/null +++ b/pkg/nuget/packagesprops/testdata/Directory.Packages.props @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/pkg/nuget/packagesprops/testdata/no_item_group.props b/pkg/nuget/packagesprops/testdata/no_item_group.props new file mode 100644 index 00000000..aa3b2813 --- /dev/null +++ b/pkg/nuget/packagesprops/testdata/no_item_group.props @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/nuget/packagesprops/testdata/no_project.props b/pkg/nuget/packagesprops/testdata/no_project.props new file mode 100644 index 00000000..041498a6 --- /dev/null +++ b/pkg/nuget/packagesprops/testdata/no_project.props @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/pkg/nuget/packagesprops/testdata/packages.props b/pkg/nuget/packagesprops/testdata/packages.props new file mode 100644 index 00000000..7ec6d02a --- /dev/null +++ b/pkg/nuget/packagesprops/testdata/packages.props @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/nuget/packagesprops/testdata/several_item_groups b/pkg/nuget/packagesprops/testdata/several_item_groups new file mode 100644 index 00000000..1575af4a --- /dev/null +++ b/pkg/nuget/packagesprops/testdata/several_item_groups @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/pkg/nuget/packagesprops/testdata/variables_and_empty b/pkg/nuget/packagesprops/testdata/variables_and_empty new file mode 100644 index 00000000..d801b1f7 --- /dev/null +++ b/pkg/nuget/packagesprops/testdata/variables_and_empty @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file