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 }