github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/dotnet/package_test.go (about)

     1  package dotnet
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  
     9  	"github.com/anchore/syft/syft/cpe"
    10  	"github.com/anchore/syft/syft/file"
    11  	"github.com/anchore/syft/syft/pkg"
    12  	"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    13  )
    14  
    15  func Test_getDepsJSONFilePrefix(t *testing.T) {
    16  	tests := []struct {
    17  		name string
    18  		path string
    19  		want string
    20  	}{
    21  		{
    22  			name: "windows-style full path",
    23  			path: `C:\Code\Projects\My-Project\My.Rest.Project.deps.json`,
    24  			want: "My.Rest.Project",
    25  		},
    26  		{
    27  			name: "leading backslash",
    28  			path: `\My.Project.deps.json`,
    29  			want: "My.Project",
    30  		},
    31  		{
    32  			name: "unix-style path with lots of prefixes",
    33  			path: "/my/cool/project/cool-project.deps.json",
    34  			want: "cool-project",
    35  		},
    36  		{
    37  			name: "unix-style relative path",
    38  			path: "cool-project/my-dotnet-project.deps.json",
    39  			want: "my-dotnet-project",
    40  		},
    41  	}
    42  	for _, tt := range tests {
    43  		t.Run(tt.name, func(t *testing.T) {
    44  			assert.Equalf(t, tt.want, getDepsJSONFilePrefix(tt.path), "getDepsJSONFilePrefix(%v)", tt.path)
    45  		})
    46  	}
    47  }
    48  
    49  func Test_NewDotnetBinaryPackage(t *testing.T) {
    50  	tests := []struct {
    51  		name             string
    52  		versionResources map[string]string
    53  		expectedPackage  pkg.Package
    54  	}{
    55  		{
    56  			name: "dotnet package with extra version info",
    57  			versionResources: map[string]string{
    58  				"InternalName":     "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll",
    59  				"FileVersion":      "3.14.40721.0918    xxxfffdddjjjj",
    60  				"FileDescription":  "Active Directory Authentication Library",
    61  				"ProductName":      "Active Directory Authentication Library",
    62  				"Comments":         "",
    63  				"CompanyName":      "Microsoft Corporation",
    64  				"LegalTrademarks":  "",
    65  				"LegalCopyright":   "Copyright (c) Microsoft Corporation. All rights reserved.",
    66  				"OriginalFilename": "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll",
    67  				"ProductVersion":   "c61f043686a544863efc014114c42e844f905336",
    68  				"Assembly Version": "3.14.2.11",
    69  			},
    70  			expectedPackage: pkg.Package{
    71  				Name:    "Active Directory Authentication Library",
    72  				Version: "3.14.40721.0918",
    73  				Metadata: pkg.DotnetPortableExecutableEntry{
    74  					AssemblyVersion: "3.14.2.11",
    75  					LegalCopyright:  "Copyright (c) Microsoft Corporation. All rights reserved.",
    76  					InternalName:    "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll",
    77  					CompanyName:     "Microsoft Corporation",
    78  					ProductName:     "Active Directory Authentication Library",
    79  					ProductVersion:  "c61f043686a544863efc014114c42e844f905336",
    80  				},
    81  			},
    82  		},
    83  		{
    84  			// show we can do a best effort to make a package from bad data
    85  			name: "dotnet package with malformed field and extended version",
    86  			versionResources: map[string]string{
    87  				"CompanyName":      "Microsoft Corporation",
    88  				"FileDescription":  "äbFile\xa0\xa1Versi on",
    89  				"FileVersion":      "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db",
    90  				"InternalName":     "äbFileVersion",
    91  				"LegalCopyright":   "© Microsoft Corporation.  All rights reserved.",
    92  				"OriginalFilename": "TProductName",
    93  				"ProductName":      "Microsoft® .NET Framework",
    94  				"ProductVersion":   "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db",
    95  			},
    96  			expectedPackage: pkg.Package{
    97  				Name:    "äbFileVersi on",
    98  				Version: "4.6.25512.01",
    99  				PURL:    "pkg:nuget/%C3%A4bFileVersi%20on@4.6.25512.01",
   100  				Metadata: pkg.DotnetPortableExecutableEntry{
   101  					LegalCopyright: "© Microsoft Corporation.  All rights reserved.",
   102  					InternalName:   "äb\x01FileVersion",
   103  					CompanyName:    "Microsoft Corporation",
   104  					ProductName:    "Microsoft® .NET Framework",
   105  					ProductVersion: "4.6.25512.01 built by: dlab-DDVSOWINAGE016. Commit Hash: d0d5c7b49271cadb6d97de26d8e623e98abdc8db",
   106  				},
   107  			},
   108  		},
   109  		{
   110  			name: "System.Data.Linq.dll",
   111  			versionResources: map[string]string{
   112  				"CompanyName":      "Microsoft Corporation",
   113  				"FileDescription":  "System.Data.Linq.dll",
   114  				"FileVersion":      "4.7.3190.0 built by: NET472REL1LAST_C",
   115  				"InternalName":     "System.Data.Linq.dll",
   116  				"LegalCopyright":   "© Microsoft Corporation.  All rights reserved.",
   117  				"OriginalFilename": "System.Data.Linq.dll",
   118  				"ProductName":      "Microsoft® .NET Framework",
   119  				"ProductVersion":   "4.7.3190.0",
   120  			},
   121  			expectedPackage: pkg.Package{
   122  				Name:    "System.Data.Linq.dll",
   123  				Version: "4.7.3190.0",
   124  			},
   125  		},
   126  		{
   127  			name: "curl",
   128  			versionResources: map[string]string{
   129  				"CompanyName":      "curl, https://curl.se/",
   130  				"FileDescription":  "The curl executable",
   131  				"FileVersion":      "8.4.0",
   132  				"InternalName":     "curl",
   133  				"LegalCopyright":   "© Daniel Stenberg, <daniel@haxx.se>.",
   134  				"OriginalFilename": "curl.exe",
   135  				"ProductName":      "The curl executable",
   136  				"ProductVersion":   "8.4.0",
   137  			},
   138  			expectedPackage: pkg.Package{
   139  				Name:    "The curl executable",
   140  				Version: "8.4.0",
   141  			},
   142  		},
   143  		{
   144  			name: "Prometheus",
   145  			versionResources: map[string]string{
   146  				"AssemblyVersion":  "8.0.0.0",
   147  				"CompanyName":      "",
   148  				"FileDescription":  "",
   149  				"FileVersion":      "8.0.1",
   150  				"InternalName":     "Prometheus.AspNetCore.dll",
   151  				"OriginalFilename": "Prometheus.AspNetCore.dll",
   152  				"ProductName":      "",
   153  				"ProductVersion":   "8.0.1",
   154  			},
   155  			expectedPackage: pkg.Package{
   156  				Name:    "Prometheus.AspNetCore.dll",
   157  				Version: "8.0.1",
   158  			},
   159  		},
   160  		{
   161  			name: "Hidden Input",
   162  			versionResources: map[string]string{
   163  				"FileDescription":  "Reads from stdin without leaking info to the terminal and outputs back to stdout",
   164  				"FileVersion":      "1, 0, 0, 0",
   165  				"InternalName":     "hiddeninput",
   166  				"LegalCopyright":   "Jordi Boggiano - 2012",
   167  				"OriginalFilename": "hiddeninput.exe",
   168  				"ProductName":      "Hidden Input",
   169  				"ProductVersion":   "1, 0, 0, 0",
   170  			},
   171  			expectedPackage: pkg.Package{
   172  				Name:    "Hidden Input",
   173  				Version: "1, 0, 0, 0",
   174  			},
   175  		},
   176  		{
   177  			name: "SQLite3",
   178  			versionResources: map[string]string{
   179  				"CompanyName":     "SQLite Development Team",
   180  				"FileDescription": "SQLite is a software library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine.",
   181  				"FileVersion":     "3.23.2",
   182  				"InternalName":    "sqlite3",
   183  				"LegalCopyright":  "http://www.sqlite.org/copyright.html",
   184  				"ProductName":     "SQLite",
   185  				"ProductVersion":  "3.23.2",
   186  			},
   187  			expectedPackage: pkg.Package{
   188  				Name:    "SQLite",
   189  				Version: "3.23.2",
   190  			},
   191  		},
   192  		{
   193  			name: "Brave Browser",
   194  			versionResources: map[string]string{
   195  				"CompanyName":      "Brave Software, Inc.",
   196  				"FileDescription":  "Brave Browser",
   197  				"FileVersion":      "80.1.7.92",
   198  				"InternalName":     "chrome_exe",
   199  				"LegalCopyright":   "Copyright 2016 The Brave Authors. All rights reserved.",
   200  				"OriginalFilename": "chrome.exe",
   201  				"ProductName":      "Brave Browser",
   202  				"ProductVersion":   "80.1.7.92",
   203  			},
   204  			expectedPackage: pkg.Package{
   205  				Name:    "Brave Browser",
   206  				Version: "80.1.7.92",
   207  			},
   208  		},
   209  		{
   210  			name: "Better product version",
   211  			versionResources: map[string]string{
   212  				"FileDescription": "Better version",
   213  				"FileVersion":     "80.1.7",
   214  				"ProductVersion":  "80.1.7.92",
   215  			},
   216  			expectedPackage: pkg.Package{
   217  				Name:    "Better version",
   218  				Version: "80.1.7.92",
   219  			},
   220  		},
   221  		{
   222  			name: "Better file version",
   223  			versionResources: map[string]string{
   224  				"FileDescription": "Better version",
   225  				"FileVersion":     "80.1.7.92",
   226  				"ProductVersion":  "80.1.7",
   227  			},
   228  			expectedPackage: pkg.Package{
   229  				Name:    "Better version",
   230  				Version: "80.1.7.92",
   231  			},
   232  		},
   233  		{
   234  			name: "Higher semantic version Product Version",
   235  			versionResources: map[string]string{
   236  				"FileDescription": "Higher semantic version Product Version",
   237  				"FileVersion":     "3.0.0.0",
   238  				"ProductVersion":  "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
   239  			},
   240  			expectedPackage: pkg.Package{
   241  				Name:    "Higher semantic version Product Version",
   242  				Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
   243  			},
   244  		},
   245  		{
   246  			name: "Higher semantic version File Version",
   247  			versionResources: map[string]string{
   248  				"FileDescription": "Higher semantic version File Version",
   249  				"FileVersion":     "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
   250  				"ProductVersion":  "3.0.0",
   251  			},
   252  			expectedPackage: pkg.Package{
   253  				Name:    "Higher semantic version File Version",
   254  				Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
   255  			},
   256  		},
   257  		{
   258  			name: "Invalid semantic version File Version",
   259  			versionResources: map[string]string{
   260  				"FileDescription": "Invalid semantic version File Version",
   261  				"FileVersion":     "A",
   262  				"ProductVersion":  "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
   263  			},
   264  			expectedPackage: pkg.Package{
   265  				Name:    "Invalid semantic version File Version",
   266  				Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
   267  			},
   268  		},
   269  		{
   270  			name: "Invalid semantic version File Version",
   271  			versionResources: map[string]string{
   272  				"FileDescription": "Invalid semantic version File Version",
   273  				"FileVersion":     "A",
   274  				"ProductVersion":  "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
   275  			},
   276  			expectedPackage: pkg.Package{
   277  				Name:    "Invalid semantic version File Version",
   278  				Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
   279  			},
   280  		},
   281  		{
   282  			name: "Invalid semantic version Product Version",
   283  			versionResources: map[string]string{
   284  				"FileDescription": "Invalid semantic version Product Version",
   285  				"FileVersion":     "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
   286  				"ProductVersion":  "A",
   287  			},
   288  			expectedPackage: pkg.Package{
   289  				Name:    "Invalid semantic version Product Version",
   290  				Version: "3.0.1+b86b61bf676163639795b163d8d753b20aad6207",
   291  			},
   292  		},
   293  		{
   294  			name: "Semantically equal falls through, chooses File Version with more components",
   295  			versionResources: map[string]string{
   296  				"FileDescription": "Semantically equal falls through, chooses File Version with more components",
   297  				"FileVersion":     "3.0.0.0",
   298  				"ProductVersion":  "3.0.0",
   299  			},
   300  			expectedPackage: pkg.Package{
   301  				Name:    "Semantically equal falls through, chooses File Version with more components",
   302  				Version: "3.0.0.0",
   303  			},
   304  		},
   305  	}
   306  
   307  	for _, tc := range tests {
   308  		t.Run(tc.name, func(t *testing.T) {
   309  			location := file.NewLocation("")
   310  			got := newDotnetBinaryPackage(tc.versionResources, location)
   311  
   312  			// ignore certain metadata
   313  			if tc.expectedPackage.Metadata == nil {
   314  				got.Metadata = nil
   315  			}
   316  			// set known defaults
   317  			if tc.expectedPackage.Type == "" {
   318  				tc.expectedPackage.Type = pkg.DotnetPkg
   319  			}
   320  			if tc.expectedPackage.Language == "" {
   321  				tc.expectedPackage.Language = pkg.Dotnet
   322  			}
   323  			if tc.expectedPackage.PURL == "" {
   324  				tc.expectedPackage.PURL = binaryPackageURL(tc.expectedPackage.Name, tc.expectedPackage.Version)
   325  			}
   326  			tc.expectedPackage.Locations = file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
   327  
   328  			pkgtest.AssertPackagesEqual(t, tc.expectedPackage, got)
   329  		})
   330  	}
   331  }
   332  
   333  func Test_extractVersion(t *testing.T) {
   334  	tests := []struct {
   335  		input    string
   336  		expected string
   337  	}{
   338  		{
   339  			input:    "1, 0, 0, 0",
   340  			expected: "1, 0, 0, 0",
   341  		},
   342  		{
   343  			input:    "Release 73",
   344  			expected: "Release 73",
   345  		},
   346  		{
   347  			input:    "4.7.4076.0 built by: NET472REL1LAST_B",
   348  			expected: "4.7.4076.0",
   349  		},
   350  	}
   351  
   352  	for _, test := range tests {
   353  		t.Run(test.input, func(t *testing.T) {
   354  			got := extractVersionFromResourcesValue(test.input)
   355  			assert.Equal(t, test.expected, got)
   356  		})
   357  	}
   358  }
   359  
   360  func Test_spaceNormalize(t *testing.T) {
   361  	tests := []struct {
   362  		input    string
   363  		expected string
   364  	}{
   365  		{
   366  			expected: "some spaces apart",
   367  			input:    " some 	spaces\n\t\t \n\rapart\n",
   368  		},
   369  		{
   370  			expected: "söme ¡nvalid characters",
   371  			input:    "\rsöme \u0001¡nvalid\t characters\n",
   372  		},
   373  	}
   374  
   375  	for _, test := range tests {
   376  		t.Run(test.expected, func(t *testing.T) {
   377  			got := spaceNormalize(test.input)
   378  			assert.Equal(t, test.expected, got)
   379  		})
   380  	}
   381  }
   382  
   383  func TestRuntimeCPEs(t *testing.T) {
   384  	tests := []struct {
   385  		name     string
   386  		version  string
   387  		expected []cpe.CPE
   388  	}{
   389  		{
   390  			name:    ".NET Core 1.0",
   391  			version: "1.0",
   392  			expected: []cpe.CPE{
   393  				{
   394  					Attributes: cpe.Attributes{
   395  						Part:    "a",
   396  						Vendor:  "microsoft",
   397  						Product: "dotnet_core",
   398  						Version: "1.0",
   399  					},
   400  					Source: cpe.DeclaredSource,
   401  				},
   402  			},
   403  		},
   404  		{
   405  			name:    ".NET Core 2.1",
   406  			version: "2.1",
   407  			expected: []cpe.CPE{
   408  				{
   409  					Attributes: cpe.Attributes{
   410  						Part:    "a",
   411  						Vendor:  "microsoft",
   412  						Product: "dotnet_core",
   413  						Version: "2.1",
   414  					},
   415  					Source: cpe.DeclaredSource,
   416  				},
   417  			},
   418  		},
   419  		{
   420  			name:    ".NET Core 3.1",
   421  			version: "3.1",
   422  			expected: []cpe.CPE{
   423  				{
   424  					Attributes: cpe.Attributes{
   425  						Part:    "a",
   426  						Vendor:  "microsoft",
   427  						Product: "dotnet_core",
   428  						Version: "3.1",
   429  					},
   430  					Source: cpe.DeclaredSource,
   431  				},
   432  			},
   433  		},
   434  		{
   435  			name:    ".NET Core 4.9 (hypothetical)",
   436  			version: "4.9",
   437  			expected: []cpe.CPE{
   438  				{
   439  					Attributes: cpe.Attributes{
   440  						Part:    "a",
   441  						Vendor:  "microsoft",
   442  						Product: "dotnet_core",
   443  						Version: "4.9",
   444  					},
   445  					Source: cpe.DeclaredSource,
   446  				},
   447  			},
   448  		},
   449  		{
   450  			name:    ".NET 5.0",
   451  			version: "5.0",
   452  			expected: []cpe.CPE{
   453  				{
   454  					Attributes: cpe.Attributes{
   455  						Part:    "a",
   456  						Vendor:  "microsoft",
   457  						Product: "dotnet",
   458  						Version: "5.0",
   459  					},
   460  					Source: cpe.DeclaredSource,
   461  				},
   462  			},
   463  		},
   464  		{
   465  			name:    ".NET 6.0",
   466  			version: "6.0",
   467  			expected: []cpe.CPE{
   468  				{
   469  					Attributes: cpe.Attributes{
   470  						Part:    "a",
   471  						Vendor:  "microsoft",
   472  						Product: "dotnet",
   473  						Version: "6.0",
   474  					},
   475  					Source: cpe.DeclaredSource,
   476  				},
   477  			},
   478  		},
   479  		{
   480  			name:    ".NET 8.0",
   481  			version: "8.0",
   482  			expected: []cpe.CPE{
   483  				{
   484  					Attributes: cpe.Attributes{
   485  						Part:    "a",
   486  						Vendor:  "microsoft",
   487  						Product: "dotnet",
   488  						Version: "8.0",
   489  					},
   490  					Source: cpe.DeclaredSource,
   491  				},
   492  			},
   493  		},
   494  		{
   495  			name:    ".NET 10.0 (future version)",
   496  			version: "10.0",
   497  			expected: []cpe.CPE{
   498  				{
   499  					Attributes: cpe.Attributes{
   500  						Part:    "a",
   501  						Vendor:  "microsoft",
   502  						Product: "dotnet",
   503  						Version: "10.0",
   504  					},
   505  					Source: cpe.DeclaredSource,
   506  				},
   507  			},
   508  		},
   509  		{
   510  			name:    "Patch version should not be included",
   511  			version: "6.0.21",
   512  			expected: []cpe.CPE{
   513  				{
   514  					Attributes: cpe.Attributes{
   515  						Part:    "a",
   516  						Vendor:  "microsoft",
   517  						Product: "dotnet",
   518  						Version: "6.0",
   519  					},
   520  					Source: cpe.DeclaredSource,
   521  				},
   522  			},
   523  		},
   524  		{
   525  			name:    "Assumed minor version",
   526  			version: "6",
   527  			expected: []cpe.CPE{
   528  				{
   529  					Attributes: cpe.Attributes{
   530  						Part:    "a",
   531  						Vendor:  "microsoft",
   532  						Product: "dotnet",
   533  						Version: "6.0",
   534  					},
   535  					Source: cpe.DeclaredSource,
   536  				},
   537  			},
   538  		},
   539  		{
   540  			name:     "Invalid version format",
   541  			version:  "invalid",
   542  			expected: nil,
   543  		},
   544  		{
   545  			name:     "Empty version",
   546  			version:  "",
   547  			expected: nil,
   548  		},
   549  	}
   550  
   551  	for _, tc := range tests {
   552  		t.Run(tc.name, func(t *testing.T) {
   553  			result := runtimeCPEs(tc.version)
   554  
   555  			if !reflect.DeepEqual(result, tc.expected) {
   556  				t.Errorf("runtimeCPEs(%q) = %+v; want %+v",
   557  					tc.version, result, tc.expected)
   558  			}
   559  		})
   560  	}
   561  }