diff --git a/pkg/nuget/csproj/parse.go b/pkg/nuget/csproj/parse.go new file mode 100644 index 00000000..4e7a5ff2 --- /dev/null +++ b/pkg/nuget/csproj/parse.go @@ -0,0 +1,68 @@ +package csproj + +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 Parser struct{} + +func NewParser() types.Parser { + return &Parser{} +} + +func (p *Parser) Parse(r dio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { + var cfgData csProject + if err := xml.NewDecoder(r).Decode(&cfgData); err != nil { + return nil, nil, xerrors.Errorf("failed to decode .csproj file: %w", err) + } + + libs := make([]types.Library, 0) + for _, pkg := range cfgData.Packages { + if pkg.Include == "" || isDevDependency(pkg) { + continue + } + + var versionNotFloating = strings.TrimRight(pkg.Version, ".*") + + lib := types.Library{ + Name: pkg.Include, + Version: versionNotFloating, + } + + libs = append(libs, lib) + } + + return utils.UniqueLibraries(libs), nil, nil +} + +func isDevDependency(pkg packageReference) bool { + var privateAssets = tagOrAttribute(pkg.PrivateAssetsTag, pkg.PrivateAssetsAttr) + var excludeAssets = tagOrAttribute(pkg.ExcludeAssetsTag, pkg.ExcludeAssetsAttr) + return assetListContains(privateAssets, "all") || assetListContains(excludeAssets, "all") || assetListContains(excludeAssets, "runtime") +} + +func assetListContains(assets []string, needle string) bool { + for _, v := range assets { + if strings.EqualFold(v, needle) { + return true + } + } + return false +} + +func tagOrAttribute(tag string, attr string) []string { + var strvalue = "" + if tag != "" { + strvalue = tag + } else { + strvalue = attr + } + return strings.Split(strvalue, ";") +} diff --git a/pkg/nuget/csproj/parse_test.go b/pkg/nuget/csproj/parse_test.go new file mode 100644 index 00000000..9d962bcb --- /dev/null +++ b/pkg/nuget/csproj/parse_test.go @@ -0,0 +1,64 @@ +package csproj_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/go-dep-parser/pkg/nuget/csproj" + "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: "csproj", + inputFile: "testdata/packages.csproj", + want: []types.Library{ + {Name: "Newtonsoft.Json", Version: "6.0.4"}, + {Name: "Microsoft.AspNet.WebApi", Version: "5.2.2"}, + {Name: "Floating.Version", Version: "1.2"}, + }, + }, + { + name: "with development dependency", + inputFile: "testdata/dev_dependency.csproj", + want: []types.Library{ + {Name: "PrivateAssets.Tag.None", Version: "1.0.0"}, + {Name: "PrivateAssets.Conflicting.Tag.Attribute", Version: "1.0.0"}, + {Name: "ExcludeAssets.Tag.ContentFiles", Version: "1.0.0"}, + {Name: "ExcludeAssets.Tag.None", Version: "1.0.0"}, + {Name: "ExcludeAssets.Conflicting.Tag.Attribute", Version: "1.0.0"}, + {Name: "Newtonsoft.Json", Version: "8.0.3"}, + }, + }, + { + name: "sad path", + inputFile: "testdata/malformed_xml.csproj", + wantErr: "failed to decode .csproj file: XML syntax error on line 10: unexpected EOF", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f, err := os.Open(tt.inputFile) + require.NoError(t, err) + + got, _, err := csproj.NewParser().Parse(f) + if tt.wantErr != "" { + require.NotNil(t, err) + assert.ErrorContains(t, err, tt.wantErr) + return + } + + assert.NoError(t, err) + assert.ElementsMatch(t, tt.want, got) + }) + } +} diff --git a/pkg/nuget/csproj/testdata/dev_dependency.csproj b/pkg/nuget/csproj/testdata/dev_dependency.csproj new file mode 100644 index 00000000..f70fd028 --- /dev/null +++ b/pkg/nuget/csproj/testdata/dev_dependency.csproj @@ -0,0 +1,45 @@ + + + + net46 + + + + + all + + + none + + + + None + + + + + all + + + runtime + + + contentFiles;runtime;native + + + contentFiles + + + none + + + + None + + + + + + \ No newline at end of file diff --git a/pkg/nuget/csproj/testdata/malformed_xml.csproj b/pkg/nuget/csproj/testdata/malformed_xml.csproj new file mode 100644 index 00000000..3235cf90 --- /dev/null +++ b/pkg/nuget/csproj/testdata/malformed_xml.csproj @@ -0,0 +1,10 @@ + + + + net46 + + + + all + + \ No newline at end of file diff --git a/pkg/nuget/csproj/testdata/packages.csproj b/pkg/nuget/csproj/testdata/packages.csproj new file mode 100644 index 00000000..ca2d68bb --- /dev/null +++ b/pkg/nuget/csproj/testdata/packages.csproj @@ -0,0 +1,17 @@ + + + + net45 + + + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/nuget/csproj/types.go b/pkg/nuget/csproj/types.go new file mode 100644 index 00000000..c26fc124 --- /dev/null +++ b/pkg/nuget/csproj/types.go @@ -0,0 +1,18 @@ +package csproj + +import "encoding/xml" + +type packageReference struct { + XMLName xml.Name `xml:"PackageReference"` + Version string `xml:"Version,attr"` + Include string `xml:"Include,attr"` + PrivateAssetsTag string `xml:"PrivateAssets"` + PrivateAssetsAttr string `xml:"PrivateAssets,attr"` + ExcludeAssetsTag string `xml:"ExcludeAssets"` + ExcludeAssetsAttr string `xml:"ExcludeAssets,attr"` +} + +type csProject struct { + XMLName xml.Name `xml:"Project"` + Packages []packageReference `xml:"ItemGroup>PackageReference"` +}