github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/java/parse_java_manifest_test.go (about) 1 package java 2 3 import ( 4 "encoding/json" 5 "os" 6 "testing" 7 8 "github.com/go-test/deep" 9 "github.com/stretchr/testify/assert" 10 11 "github.com/anchore/syft/syft/pkg" 12 ) 13 14 func TestParseJavaManifest(t *testing.T) { 15 tests := []struct { 16 fixture string 17 expected pkg.JavaManifest 18 }{ 19 { 20 fixture: "test-fixtures/manifest/small", 21 expected: pkg.JavaManifest{ 22 Main: pkg.KeyValues{ 23 {Key: "Manifest-Version", Value: "1.0"}, 24 }, 25 }, 26 }, 27 { 28 fixture: "test-fixtures/manifest/standard-info", 29 expected: pkg.JavaManifest{ 30 Main: pkg.KeyValues{ 31 {Key: "Manifest-Version", Value: "1.0"}, 32 {Key: "Name", Value: "the-best-name"}, 33 {Key: "Specification-Title", Value: "the-spec-title"}, 34 {Key: "Specification-Vendor", Value: "the-spec-vendor"}, 35 {Key: "Specification-Version", Value: "the-spec-version"}, 36 {Key: "Implementation-Title", Value: "the-impl-title"}, 37 {Key: "Implementation-Vendor", Value: "the-impl-vendor"}, 38 {Key: "Implementation-Version", Value: "the-impl-version"}, 39 }, 40 }, 41 }, 42 { 43 fixture: "test-fixtures/manifest/extra-info", 44 expected: pkg.JavaManifest{ 45 Main: []pkg.KeyValue{ 46 { 47 Key: "Manifest-Version", 48 Value: "1.0", 49 }, 50 { 51 Key: "Archiver-Version", 52 Value: "Plexus Archiver", 53 }, 54 { 55 Key: "Created-By", 56 Value: "Apache Maven 3.6.3", 57 }, 58 }, 59 Sections: []pkg.KeyValues{ 60 { 61 { 62 Key: "Name", 63 Value: "thing-1", 64 }, 65 { 66 Key: "Built-By", 67 Value: "?", 68 }, 69 }, 70 { 71 { 72 Key: "Build-Jdk", 73 Value: "14.0.1", 74 }, 75 { 76 Key: "Main-Class", 77 Value: "hello.HelloWorld", 78 }, 79 }, 80 }, 81 }, 82 }, 83 { 84 fixture: "test-fixtures/manifest/extra-empty-lines", 85 expected: pkg.JavaManifest{ 86 Main: pkg.KeyValues{ 87 { 88 Key: "Manifest-Version", 89 Value: "1.0", 90 }, 91 { 92 Key: "Archiver-Version", 93 Value: "Plexus Archiver", 94 }, 95 { 96 Key: "Created-By", 97 Value: "Apache Maven 3.6.3", 98 }, 99 }, 100 Sections: []pkg.KeyValues{ 101 { 102 {Key: "Name", Value: "thing-1"}, 103 {Key: "Built-By", Value: "?"}, 104 }, 105 { 106 {Key: "Name", Value: "thing-2"}, 107 {Key: "Built-By", Value: "someone!"}, 108 }, 109 { 110 {Key: "Other", Value: "things"}, 111 }, 112 { 113 {Key: "Last", Value: "item"}, 114 }, 115 }, 116 }, 117 }, 118 { 119 fixture: "test-fixtures/manifest/continuation", 120 expected: pkg.JavaManifest{ 121 Main: pkg.KeyValues{ 122 { 123 Key: "Manifest-Version", 124 Value: "1.0", 125 }, 126 { 127 Key: "Plugin-ScmUrl", 128 Value: "https://github.com/jenkinsci/plugin-pom/example-jenkins-plugin", 129 }, 130 }, 131 }, 132 }, 133 { 134 // regression test, we should always keep the full version 135 fixture: "test-fixtures/manifest/version-with-date", 136 expected: pkg.JavaManifest{ 137 Main: []pkg.KeyValue{ 138 { 139 Key: "Manifest-Version", 140 Value: "1.0", 141 }, 142 { 143 Key: "Implementation-Version", 144 Value: "1.3 2244 October 5 2005", 145 }, 146 }, 147 }, 148 }, 149 { 150 // regression test, we should not trim space and choke of empty space 151 // https://github.com/anchore/syft/issues/2179 152 fixture: "test-fixtures/manifest/leading-space", 153 expected: pkg.JavaManifest{ 154 Main: []pkg.KeyValue{ 155 { 156 Key: "Key-keykeykey", 157 Value: "initialconfig:com$ # aka not empty line", 158 }, 159 { 160 Key: "should", 161 Value: "parse", 162 }, 163 }, 164 }, 165 }, 166 } 167 168 for _, test := range tests { 169 t.Run(test.fixture, func(t *testing.T) { 170 fixture, err := os.Open(test.fixture) 171 if err != nil { 172 t.Fatalf("could not open fixture: %+v", err) 173 } 174 175 actual, err := parseJavaManifest(test.fixture, fixture) 176 if err != nil { 177 t.Fatalf("failed to parse manifest: %+v", err) 178 } 179 180 diffs := deep.Equal(actual, &test.expected) 181 if len(diffs) > 0 { 182 for _, d := range diffs { 183 t.Errorf("diff: %+v", d) 184 } 185 186 b, err := json.MarshalIndent(actual, "", " ") 187 if err != nil { 188 t.Fatalf("can't show results: %+v", err) 189 } 190 191 t.Errorf("full result: %s", string(b)) 192 } 193 }) 194 } 195 } 196 197 func TestSelectName(t *testing.T) { 198 tests := []struct { 199 desc string 200 manifest pkg.JavaManifest 201 archive archiveFilename 202 expected string 203 }{ 204 { 205 desc: "Get name from Implementation-Title", 206 archive: archiveFilename{}, 207 manifest: pkg.JavaManifest{ 208 Main: []pkg.KeyValue{ 209 { 210 Key: "Implementation-Title", 211 Value: "maven-wrapper", 212 }, 213 }, 214 }, 215 expected: "maven-wrapper", 216 }, 217 { 218 desc: "Implementation-Title does not override name from filename", 219 manifest: pkg.JavaManifest{ 220 Main: []pkg.KeyValue{ 221 { 222 Key: "Name", 223 Value: "foo", 224 }, 225 { 226 Key: "Implementation-Title", 227 Value: "maven-wrapper", 228 }, 229 }, 230 }, 231 archive: newJavaArchiveFilename("/something/omg.jar"), 232 expected: "omg", 233 }, 234 { 235 desc: "Use the artifact ID baked by the Apache Maven Bundle Plugin", 236 manifest: pkg.JavaManifest{ 237 Main: pkg.KeyValues{ 238 {Key: "Created-By", Value: "Apache Maven Bundle Plugin"}, 239 {Key: "Bundle-SymbolicName", Value: "com.atlassian.gadgets.atlassian-gadgets-api"}, 240 {Key: "Name", Value: "foo"}, 241 {Key: "Implementation-Title", Value: "maven-wrapper"}, 242 }, 243 }, 244 archive: newJavaArchiveFilename("/something/omg.jar"), 245 expected: "atlassian-gadgets-api", 246 }, 247 { 248 // example: pkg:maven/org.apache.servicemix.bundles/org.apache.servicemix.bundles.spring-beans@5.3.26_1 249 desc: "Apache Maven Bundle Plugin might bake a version in the created-by field", 250 manifest: pkg.JavaManifest{ 251 Main: pkg.KeyValues{ 252 {Key: "Created-By", Value: "Apache Maven Bundle Plugin 5.1.6"}, 253 {Key: "Bundle-SymbolicName", Value: "com.atlassian.gadgets.atlassian-gadgets-api"}, 254 {Key: "Name", Value: "foo"}, 255 {Key: "Implementation-Title", Value: "maven-wrapper"}, 256 }, 257 }, 258 archive: newJavaArchiveFilename("/something/omg.jar"), 259 expected: "atlassian-gadgets-api", 260 }, 261 { 262 desc: "Filename looks like a groupid + artifact id", 263 manifest: pkg.JavaManifest{ 264 Main: []pkg.KeyValue{ 265 { 266 Key: "Name", 267 Value: "foo", 268 }, 269 { 270 Key: "Implementation-Title", 271 Value: "maven-wrapper", 272 }, 273 }, 274 }, 275 archive: newJavaArchiveFilename("/something/com.atlassian.gadgets.atlassian-gadgets-api.jar"), 276 expected: "atlassian-gadgets-api", 277 }, 278 { 279 desc: "Filename has period that is not groupid + artifact id", 280 manifest: pkg.JavaManifest{}, 281 archive: newJavaArchiveFilename("/something/http4s-crypto_2.12-0.1.0.jar"), 282 expected: "http4s-crypto_2.12", 283 }, 284 { 285 desc: "Filename has period that is not groupid + artifact id, kafka", 286 manifest: pkg.JavaManifest{}, 287 archive: newJavaArchiveFilename("/something//kafka_2.13-3.2.2.jar"), 288 expected: "kafka_2.13", // see https://mvnrepository.com/artifact/org.apache.kafka/kafka_2.13/3.2.2 289 }, 290 { 291 desc: "Skip stripping groupId prefix from archive filename for org.eclipse", 292 manifest: pkg.JavaManifest{ 293 Main: []pkg.KeyValue{ 294 { 295 Key: "Automatic-Module-Name", 296 Value: "org.eclipse.ant.core", 297 }, 298 }, 299 }, 300 archive: newJavaArchiveFilename("/something/org.eclipse.ant.core-3.7.0.jar"), 301 expected: "org.eclipse.ant.core", 302 }, 303 { 304 // example: pkg:maven/com.google.oauth-client/google-oauth-client@1.25.0 305 desc: "skip Apache Maven Bundle Plugin logic if symbolic name is same as vendor id", 306 manifest: pkg.JavaManifest{ 307 Main: pkg.KeyValues{ 308 {Key: "Bundle-DocURL", Value: "http://www.google.com/"}, 309 {Key: "Bundle-License", Value: "http://www.apache.org/licenses/LICENSE-2.0.txt"}, 310 {Key: "Bundle-ManifestVersion", Value: "2"}, 311 {Key: "Bundle-Name", Value: "Google OAuth Client Library for Java"}, 312 {Key: "Bundle-RequiredExecutionEnvironment", Value: "JavaSE-1.6"}, 313 {Key: "Bundle-SymbolicName", Value: "com.google.oauth-client"}, 314 {Key: "Bundle-Vendor", Value: "Google"}, 315 {Key: "Bundle-Version", Value: "1.25.0"}, 316 {Key: "Created-By", Value: "Apache Maven Bundle Plugin"}, 317 {Key: "Export-Package", Value: "com.google.api.client.auth.openidconnect;uses:=\"com.google.api.client.auth.oauth2,com.google.api.client.json,com.google.api.client.json.webtoken,com.google.api.client.util\";version=\"1.25.0\",com.google.api.client.auth.oauth;uses:=\"com.google.api.client.http,com.google.api.client.util\";version=\"1.25.0\",com.google.api.client.auth.oauth2;uses:=\"com.google.api.client.http,com.google.api.client.json,com.google.api.client.util,com.google.api.client.util.store\";version=\"1.25.0\""}, 318 {Key: "Implementation-Title", Value: "Google OAuth Client Library for Java"}, 319 {Key: "Implementation-Vendor", Value: "Google"}, 320 {Key: "Implementation-Vendor-Id", Value: "com.google.oauth-client"}, 321 {Key: "Implementation-Version", Value: "1.25.0"}, 322 }, 323 }, 324 archive: newJavaArchiveFilename("/something/google-oauth-client-1.25.0.jar"), 325 expected: "google-oauth-client", 326 }, 327 } 328 329 for _, test := range tests { 330 t.Run(test.desc, func(t *testing.T) { 331 result := selectName(&test.manifest, test.archive) 332 333 if result != test.expected { 334 t.Errorf("mismatch in names: '%s' != '%s'", result, test.expected) 335 } 336 }) 337 } 338 } 339 340 func TestSelectVersion(t *testing.T) { 341 tests := []struct { 342 name string 343 manifest pkg.JavaManifest 344 archive archiveFilename 345 expected string 346 }{ 347 { 348 name: "Get name from Implementation-Version", 349 archive: archiveFilename{}, 350 manifest: pkg.JavaManifest{ 351 Main: []pkg.KeyValue{ 352 { 353 Key: "Implementation-Version", 354 Value: "1.8.2", 355 }, 356 }, 357 }, 358 expected: "1.8.2", 359 }, 360 { 361 name: "Implementation-Version takes precedence over Specification-Version", 362 manifest: pkg.JavaManifest{ 363 Main: []pkg.KeyValue{ 364 { 365 Key: "Implementation-Version", 366 Value: "1.8.2", 367 }, 368 { 369 Key: "Specification-Version", 370 Value: "1.0", 371 }, 372 }, 373 }, 374 expected: "1.8.2", 375 }, 376 { 377 name: "Implementation-Version found outside the main section", 378 manifest: pkg.JavaManifest{ 379 Main: pkg.KeyValues{ 380 {Key: "Manifest-Version", Value: "1.0"}, 381 {Key: "Ant-Version", Value: "Apache Ant 1.8.2"}, 382 {Key: "Created-By", Value: "1.5.0_22-b03 (Sun Microsystems Inc.)"}, 383 }, 384 Sections: []pkg.KeyValues{ 385 { 386 {Key: "Name", Value: "org/apache/tools/ant/taskdefs/optional/"}, 387 {Key: "Implementation-Version", Value: "1.8.2"}, 388 }, 389 }, 390 }, 391 expected: "1.8.2", 392 }, 393 { 394 name: "Implementation-Version takes precedence over Specification-Version in subsequent section", 395 manifest: pkg.JavaManifest{ 396 Main: pkg.KeyValues{ 397 {Key: "Manifest-Version", Value: "1.0"}, 398 {Key: "Ant-Version", Value: "Apache Ant 1.8.2"}, 399 {Key: "Created-By", Value: "1.5.0_22-b03 (Sun Microsystems Inc.)"}, 400 {Key: "Specification-Version", Value: "2.0"}, 401 }, 402 Sections: []pkg.KeyValues{ 403 { 404 {Key: "Name", Value: "org/apache/tools/ant/taskdefs/optional/"}, 405 {Key: "Specification-Version", Value: "1.8"}, 406 }, 407 { 408 {Key: "Name", Value: "some-other-section"}, 409 {Key: "Implementation-Version", Value: "1.8.2"}, 410 }, 411 }, 412 }, 413 414 expected: "1.8.2", 415 }, 416 { 417 name: "Implementation-Version takes precedence over Specification-Version in subsequent section", 418 manifest: pkg.JavaManifest{ 419 Main: []pkg.KeyValue{ 420 { 421 Key: "Manifest-Version", 422 Value: "1.0", 423 }, 424 { 425 Key: "Ant-Version", 426 Value: "Apache Ant 1.8.2", 427 }, 428 { 429 Key: "Created-By", 430 Value: "1.5.0_22-b03 (Sun Microsystems Inc.)", 431 }, 432 }, 433 Sections: []pkg.KeyValues{ 434 { 435 { 436 Key: "Name", 437 Value: "some-other-section", 438 }, 439 { 440 Key: "Bundle-Version", 441 Value: "1.11.28", 442 }, 443 }, 444 }, 445 }, 446 expected: "1.11.28", 447 }, 448 } 449 450 for _, test := range tests { 451 t.Run(test.name, func(t *testing.T) { 452 result := selectVersion(&test.manifest, test.archive) 453 454 assert.Equal(t, test.expected, result) 455 }) 456 } 457 }