Skip to content

Commit b53451d

Browse files
authored
feature: csharp(nuget) - props file support added (#2)
1 parent 58a02e6 commit b53451d

File tree

10 files changed

+305
-0
lines changed

10 files changed

+305
-0
lines changed

pkg/nuget/packagesprops/parse.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package packagesprops
2+
3+
import (
4+
"encoding/xml"
5+
"strings"
6+
7+
"golang.org/x/xerrors"
8+
9+
dio "github.com/aquasecurity/go-dep-parser/pkg/io"
10+
ftypes "github.com/aquasecurity/go-dep-parser/pkg/types"
11+
"github.com/aquasecurity/go-dep-parser/pkg/utils"
12+
)
13+
14+
type Pkg struct {
15+
Version string `xml:"Version,attr"`
16+
UpdatePackageName string `xml:"Update,attr"`
17+
IncludePackageName string `xml:"Include,attr"`
18+
}
19+
20+
// https://github.com/dotnet/roslyn-tools/blob/b4c5220f5dfc4278847b6d38eff91cc1188f8066/src/RoslynInsertionTool/RoslynInsertionTool/CoreXT.cs#L150
21+
type itemGroup struct {
22+
PackageReferenceEntry []Pkg `xml:"PackageReference"`
23+
PackageVersionEntry []Pkg `xml:"PackageVersion"`
24+
}
25+
26+
type project struct {
27+
XMLName xml.Name `xml:"Project"`
28+
ItemGroups []itemGroup `xml:"ItemGroup"`
29+
}
30+
31+
type Parser struct{}
32+
33+
func NewParser() *Parser {
34+
return &Parser{}
35+
}
36+
37+
func (p Pkg) Package() ftypes.Library {
38+
// Update attribute is considered legacy, so preferring Include
39+
name := p.UpdatePackageName
40+
if p.IncludePackageName != "" {
41+
name = p.IncludePackageName
42+
}
43+
44+
name = strings.TrimSpace(name)
45+
version := strings.TrimSpace(p.Version)
46+
return ftypes.Library{
47+
ID: utils.ID(ftypes.NuGet, name, version),
48+
Name: name,
49+
Version: version,
50+
}
51+
}
52+
53+
func shouldSkipPkg(pkg ftypes.Library) bool {
54+
if pkg.Name == "" || pkg.Version == "" {
55+
return true
56+
}
57+
// *packages.props files don't contain variable resolution information.
58+
// So we need to skip them.
59+
if isVariable(pkg.Name) || isVariable(pkg.Version) {
60+
return true
61+
}
62+
return false
63+
}
64+
65+
func isVariable(s string) bool {
66+
return strings.HasPrefix(s, "$(") && strings.HasSuffix(s, ")")
67+
}
68+
69+
func (p *Parser) Parse(r dio.ReadSeekerAt) ([]ftypes.Library, []ftypes.Dependency, error) {
70+
var configData project
71+
if err := xml.NewDecoder(r).Decode(&configData); err != nil {
72+
return nil, nil, xerrors.Errorf("failed to decode '*.packages.props' file: %w", err)
73+
}
74+
75+
var pkgs []ftypes.Library
76+
for _, item := range configData.ItemGroups {
77+
for _, pkg := range append(item.PackageReferenceEntry, item.PackageVersionEntry...) {
78+
pkg := pkg.Package()
79+
if !shouldSkipPkg(pkg) {
80+
pkgs = append(pkgs, pkg)
81+
}
82+
}
83+
}
84+
return utils.UniqueLibraries(pkgs), nil, nil
85+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package packagesprops_test
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
10+
config "github.com/aquasecurity/go-dep-parser/pkg/nuget/packagesprops"
11+
ftypes "github.com/aquasecurity/go-dep-parser/pkg/types"
12+
)
13+
14+
func TestParse(t *testing.T) {
15+
tests := []struct {
16+
name string // Test input file
17+
inputFile string
18+
want []ftypes.Library
19+
wantErr string
20+
}{
21+
{
22+
name: "PackagesProps",
23+
inputFile: "testdata/packages.props",
24+
want: []ftypes.Library{
25+
{Name: "Microsoft.Extensions.Configuration", Version: "2.1.1", ID: "Microsoft.Extensions.Configuration@2.1.1"},
26+
{Name: "Microsoft.Extensions.DependencyInjection.Abstractions", Version: "2.2.1", ID: "Microsoft.Extensions.DependencyInjection.Abstractions@2.2.1"},
27+
{Name: "Microsoft.Extensions.Http", Version: "3.2.1", ID: "Microsoft.Extensions.Http@3.2.1"},
28+
},
29+
},
30+
{
31+
name: "DirectoryPackagesProps",
32+
inputFile: "testdata/Directory.Packages.props",
33+
want: []ftypes.Library{
34+
{Name: "PackageOne", Version: "6.2.3", ID: "PackageOne@6.2.3"},
35+
{Name: "PackageTwo", Version: "6.0.0", ID: "PackageTwo@6.0.0"},
36+
{Name: "PackageThree", Version: "2.4.1", ID: "PackageThree@2.4.1"},
37+
},
38+
},
39+
{
40+
name: "SeveralItemGroupElements",
41+
inputFile: "testdata/several_item_groups",
42+
want: []ftypes.Library{
43+
{Name: "PackageOne", Version: "6.2.3", ID: "PackageOne@6.2.3"},
44+
{Name: "PackageTwo", Version: "6.0.0", ID: "PackageTwo@6.0.0"},
45+
{Name: "PackageThree", Version: "2.4.1", ID: "PackageThree@2.4.1"},
46+
},
47+
},
48+
{
49+
name: "VariablesAsNamesOrVersion",
50+
inputFile: "testdata/variables_and_empty",
51+
want: []ftypes.Library{
52+
{Name: "PackageFour", Version: "2.4.1", ID: "PackageFour@2.4.1"},
53+
},
54+
},
55+
{
56+
name: "NoItemGroupInXMLStructure",
57+
inputFile: "testdata/no_item_group.props",
58+
want: []ftypes.Library(nil),
59+
},
60+
{
61+
name: "NoProject",
62+
inputFile: "testdata/no_project.props",
63+
wantErr: "failed to decode '*.packages.props' file",
64+
},
65+
}
66+
for _, tt := range tests {
67+
t.Run(tt.name, func(t *testing.T) {
68+
f, err := os.Open(tt.inputFile)
69+
require.NoError(t, err)
70+
71+
got, _, err := config.NewParser().Parse(f)
72+
if tt.wantErr != "" {
73+
require.NotNil(t, err)
74+
assert.Contains(t, err.Error(), tt.wantErr)
75+
return
76+
}
77+
78+
assert.NoError(t, err)
79+
assert.Equal(t, tt.want, got)
80+
})
81+
}
82+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<Project>
2+
<ItemGroup>
3+
<PackageReference Include="PackageOne" Version=" 6.2.3" />
4+
<PackageReference Include="PackageTwo" Version="6.0.0" />
5+
<PackageReference Include="PackageThree " Version="2.4.1" />
6+
</ItemGroup>
7+
</Project>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
4+
<PackageReference Update="Microsoft.Extensions.Configuration" Version="2.1.1" />
5+
<PackageReference Update="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.1" />
6+
<PackageReference Update="Microsoft.Extensions.Http" Version="3.2.1" />
7+
<PackageReference WrongAttribute="Package1" Version="3.2.1" />
8+
<WrongEntry Update="Package2" Version="3.2.1" />
9+
<PackageReference Update="Package3" />
10+
11+
</Project>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<ItemGroup Label="Microsoft Nugets">
4+
<PackageReference Update="Microsoft.Extensions.Configuration" Version="2.1.1" />
5+
<PackageReference Update="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.1" />
6+
<PackageReference Update="Microsoft.Extensions.Http" Version="3.2.1" />
7+
<PackageReference WrongAttribute="Package1" Version="3.2.1" />
8+
<WrongEntry Update="Package2" Version="3.2.1" />
9+
<PackageReference Update="Package3" />
10+
</ItemGroup>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<ItemGroup Label="Microsoft Nugets">
4+
<PackageReference Update="Microsoft.Extensions.Configuration" Version="2.1.1" />
5+
<PackageReference Update="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.1" />
6+
<PackageReference Update="Microsoft.Extensions.Http" Version="3.2.1" />
7+
<PackageReference WrongAttribute="Package1" Version="3.2.1" />
8+
<WrongEntry Update="Package2" Version="3.2.1" />
9+
<PackageReference Update="Package3" />
10+
</ItemGroup>
11+
</Project>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project>
2+
<ItemGroup>
3+
<PackageReference Include="PackageOne" Version="6.2.3" />
4+
<PackageReference Include="PackageTwo" Version="6.0.0" />
5+
6+
</ItemGroup>
7+
<ItemGroup>
8+
<PackageReference Include="PackageThree" Version="2.4.1" />
9+
</ItemGroup>
10+
</Project>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project>
2+
<ItemGroup>
3+
<PackageReference Include="PackageOne" Version="$(Variable1)" />
4+
<PackageReference Include="$(variable2)" Version="6.0.0" />
5+
<PackageReference Include="" Version="2.4.1" />
6+
<PackageReference Include="package" Version="" />
7+
<PackageReference Include="PackageFour" Version="2.4.1" />
8+
</ItemGroup>
9+
</Project>

pkg/types/types.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,54 @@ import (
44
dio "github.com/aquasecurity/go-dep-parser/pkg/io"
55
)
66

7+
type (
8+
// TargetType represents the type of target
9+
TargetType string
10+
11+
// LangType is an alias of TargetType for programming languages
12+
LangType = TargetType
13+
)
14+
15+
const (
16+
Bundler LangType = "bundler"
17+
GemSpec LangType = "gemspec"
18+
Cargo LangType = "cargo"
19+
Composer LangType = "composer"
20+
Npm LangType = "npm"
21+
NuGet LangType = "nuget"
22+
DotNetCore LangType = "dotnet-core"
23+
PackagesProps LangType = "packages-props"
24+
Pip LangType = "pip"
25+
Pipenv LangType = "pipenv"
26+
Poetry LangType = "poetry"
27+
CondaPkg LangType = "conda-pkg"
28+
CondaEnv LangType = "conda-environment"
29+
PythonPkg LangType = "python-pkg"
30+
NodePkg LangType = "node-pkg"
31+
Yarn LangType = "yarn"
32+
Pnpm LangType = "pnpm"
33+
Jar LangType = "jar"
34+
Pom LangType = "pom"
35+
Gradle LangType = "gradle"
36+
GoBinary LangType = "gobinary"
37+
GoModule LangType = "gomod"
38+
JavaScript LangType = "javascript"
39+
RustBinary LangType = "rustbinary"
40+
Conan LangType = "conan"
41+
Cocoapods LangType = "cocoapods"
42+
Swift LangType = "swift"
43+
Pub LangType = "pub"
44+
Hex LangType = "hex"
45+
Bitnami LangType = "bitnami"
46+
47+
K8sUpstream LangType = "kubernetes"
48+
EKS LangType = "eks" // Amazon Elastic Kubernetes Service
49+
GKE LangType = "gke" // Google Kubernetes Engine
50+
AKS LangType = "aks" // Azure Kubernetes Service
51+
RKE LangType = "rke" // Rancher Kubernetes Engine
52+
OCP LangType = "ocp" // Red Hat OpenShift Container Platform
53+
)
54+
755
type Library struct {
856
ID string `json:",omitempty"`
957
Name string

pkg/utils/id.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package utils
2+
3+
import (
4+
"strings"
5+
6+
"github.com/aquasecurity/go-dep-parser/pkg/types"
7+
)
8+
9+
// ID returns a unique ID for the given library.
10+
// The package ID is used to construct the dependency graph.
11+
// The separator is different for each language type.
12+
func ID(ltype types.LangType, name, version string) string {
13+
if version == "" {
14+
return name
15+
}
16+
17+
sep := "@"
18+
switch ltype {
19+
case types.Conan:
20+
sep = "/"
21+
case types.GoModule, types.GoBinary:
22+
// Return a module ID according the Go way.
23+
// Format: <module_name>@v<module_version>
24+
// e.g. github.com/aquasecurity/go-dep-parser@v0.0.0-20230130190635-5e31092b0621
25+
if !strings.HasPrefix(version, "v") {
26+
version = "v" + version
27+
}
28+
case types.Jar, types.Pom, types.Gradle:
29+
sep = ":"
30+
}
31+
return name + sep + version
32+
}

0 commit comments

Comments
 (0)