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  }