mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 10:36:45 +01:00
enable more flexible java manifest structure (closer to the spec)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
a4f22e65fc
commit
a5cba13ddf
@ -126,7 +126,7 @@
|
|||||||
},
|
},
|
||||||
"manifest": {
|
"manifest": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"extraFields": {
|
"main": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"Archiver-Version": {
|
"Archiver-Version": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -149,6 +149,12 @@
|
|||||||
"Hudson-Version": {
|
"Hudson-Version": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"Implementation-Title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Implementation-Version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"Jenkins-Version": {
|
"Jenkins-Version": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -158,6 +164,9 @@
|
|||||||
"Main-Class": {
|
"Main-Class": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"Manifest-Version": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"Minimum-Java-Version": {
|
"Minimum-Java-Version": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -181,32 +190,23 @@
|
|||||||
},
|
},
|
||||||
"Short-Name": {
|
"Short-Name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Specification-Title": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"Archiver-Version",
|
"Archiver-Version",
|
||||||
"Build-Jdk",
|
"Build-Jdk",
|
||||||
"Built-By",
|
"Built-By",
|
||||||
"Created-By"
|
"Created-By",
|
||||||
|
"Manifest-Version"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
|
||||||
"implementationTitle": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"implementationVersion": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"manifestVersion": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"specificationTitle": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"extraFields",
|
"main"
|
||||||
"manifestVersion"
|
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -136,7 +136,7 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
|||||||
|
|
||||||
// parse the manifest file into a rich object
|
// parse the manifest file into a rich object
|
||||||
manifestContents := contents[manifestMatches[0]]
|
manifestContents := contents[manifestMatches[0]]
|
||||||
manifest, err := parseJavaManifest(strings.NewReader(manifestContents))
|
manifest, err := parseJavaManifest(j.archivePath, strings.NewReader(manifestContents))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse java manifest (%s): %w", j.virtualPath, err)
|
return nil, fmt.Errorf("failed to parse java manifest (%s): %w", j.virtualPath, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,18 +89,19 @@ func TestSelectName(t *testing.T) {
|
|||||||
desc: "name from Implementation-Title",
|
desc: "name from Implementation-Title",
|
||||||
archive: archiveFilename{},
|
archive: archiveFilename{},
|
||||||
manifest: pkg.JavaManifest{
|
manifest: pkg.JavaManifest{
|
||||||
Name: "",
|
Main: map[string]string{
|
||||||
SpecTitle: "",
|
"Implementation-Title": "maven-wrapper",
|
||||||
ImplTitle: "maven-wrapper",
|
},
|
||||||
},
|
},
|
||||||
expected: "maven-wrapper",
|
expected: "maven-wrapper",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Implementation-Title does not override",
|
desc: "Implementation-Title does not override",
|
||||||
manifest: pkg.JavaManifest{
|
manifest: pkg.JavaManifest{
|
||||||
Name: "Foo",
|
Main: map[string]string{
|
||||||
SpecTitle: "",
|
"Name": "foo",
|
||||||
ImplTitle: "maven-wrapper",
|
"Implementation-Title": "maven-wrapper",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
archive: archiveFilename{
|
archive: archiveFilename{
|
||||||
fields: []map[string]string{
|
fields: []map[string]string{
|
||||||
@ -145,11 +146,12 @@ func TestParseJar(t *testing.T) {
|
|||||||
Metadata: pkg.JavaMetadata{
|
Metadata: pkg.JavaMetadata{
|
||||||
VirtualPath: "test-fixtures/java-builds/packages/example-jenkins-plugin.hpi",
|
VirtualPath: "test-fixtures/java-builds/packages/example-jenkins-plugin.hpi",
|
||||||
Manifest: &pkg.JavaManifest{
|
Manifest: &pkg.JavaManifest{
|
||||||
ManifestVersion: "1.0",
|
Main: map[string]string{
|
||||||
SpecTitle: "The Jenkins Plugins Parent POM Project",
|
"Manifest-Version": "1.0",
|
||||||
ImplTitle: "example-jenkins-plugin",
|
"Specification-Title": "The Jenkins Plugins Parent POM Project",
|
||||||
ImplVersion: "1.0-SNAPSHOT",
|
"Implementation-Title": "example-jenkins-plugin",
|
||||||
Extra: map[string]string{
|
"Implementation-Version": "1.0-SNAPSHOT",
|
||||||
|
// extra fields...
|
||||||
"Archiver-Version": "Plexus Archiver",
|
"Archiver-Version": "Plexus Archiver",
|
||||||
"Plugin-License-Url": "https://opensource.org/licenses/MIT",
|
"Plugin-License-Url": "https://opensource.org/licenses/MIT",
|
||||||
"Plugin-License-Name": "MIT License",
|
"Plugin-License-Name": "MIT License",
|
||||||
@ -191,7 +193,9 @@ func TestParseJar(t *testing.T) {
|
|||||||
Metadata: pkg.JavaMetadata{
|
Metadata: pkg.JavaMetadata{
|
||||||
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar",
|
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar",
|
||||||
Manifest: &pkg.JavaManifest{
|
Manifest: &pkg.JavaManifest{
|
||||||
ManifestVersion: "1.0",
|
Main: map[string]string{
|
||||||
|
"Manifest-Version": "1.0",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -212,8 +216,9 @@ func TestParseJar(t *testing.T) {
|
|||||||
Metadata: pkg.JavaMetadata{
|
Metadata: pkg.JavaMetadata{
|
||||||
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar",
|
VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar",
|
||||||
Manifest: &pkg.JavaManifest{
|
Manifest: &pkg.JavaManifest{
|
||||||
ManifestVersion: "1.0",
|
Main: map[string]string{
|
||||||
Extra: map[string]string{
|
"Manifest-Version": "1.0",
|
||||||
|
// extra fields...
|
||||||
"Archiver-Version": "Plexus Archiver",
|
"Archiver-Version": "Plexus Archiver",
|
||||||
"Created-By": "Apache Maven 3.6.3",
|
"Created-By": "Apache Maven 3.6.3",
|
||||||
"Built-By": "?",
|
"Built-By": "?",
|
||||||
@ -305,11 +310,11 @@ func TestParseJar(t *testing.T) {
|
|||||||
metadata := a.Metadata.(pkg.JavaMetadata)
|
metadata := a.Metadata.(pkg.JavaMetadata)
|
||||||
metadata.Parent = nil
|
metadata.Parent = nil
|
||||||
|
|
||||||
// ignore select fields
|
// ignore select fields (only works for the main section)
|
||||||
for _, field := range test.ignoreExtras {
|
for _, field := range test.ignoreExtras {
|
||||||
if metadata.Manifest != nil && metadata.Manifest.Extra != nil {
|
if metadata.Manifest != nil && metadata.Manifest.Main != nil {
|
||||||
if _, ok := metadata.Manifest.Extra[field]; ok {
|
if _, ok := metadata.Manifest.Main[field]; ok {
|
||||||
delete(metadata.Manifest.Extra, field)
|
delete(metadata.Manifest.Main, field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,16 +4,20 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/mitchellh/mapstructure"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const manifestGlob = "/META-INF/MANIFEST.MF"
|
const manifestGlob = "/META-INF/MANIFEST.MF"
|
||||||
|
|
||||||
// nolint:funlen
|
// nolint:funlen
|
||||||
func parseJavaManifest(reader io.Reader) (*pkg.JavaManifest, error) {
|
// parseJavaManifest takes MANIFEST.MF file content and returns sections of parsed key/value pairs.
|
||||||
|
// For more information: https://docs.oracle.com/en/java/javase/11/docs/specs/jar/jar.html#jar-manifest
|
||||||
|
func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error) {
|
||||||
var manifest pkg.JavaManifest
|
var manifest pkg.JavaManifest
|
||||||
sections := []map[string]string{
|
sections := []map[string]string{
|
||||||
make(map[string]string),
|
make(map[string]string),
|
||||||
@ -63,13 +67,24 @@ func parseJavaManifest(reader io.Reader) (*pkg.JavaManifest, error) {
|
|||||||
return nil, fmt.Errorf("unable to read java manifest: %w", err)
|
return nil, fmt.Errorf("unable to read java manifest: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mapstructure.Decode(sections[0], &manifest); err != nil {
|
if len(sections) > 0 {
|
||||||
return nil, fmt.Errorf("unable to parse java manifest: %w", err)
|
manifest.Main = sections[0]
|
||||||
}
|
if len(sections) > 1 {
|
||||||
|
manifest.Sections = make(map[string]map[string]string)
|
||||||
// append on extra sections
|
for i, s := range sections[1:] {
|
||||||
if len(sections) > 1 {
|
name, ok := s["Name"]
|
||||||
manifest.Sections = sections[1:]
|
if !ok {
|
||||||
|
// per the manifest spec (https://docs.oracle.com/en/java/javase/11/docs/specs/jar/jar.html#jar-manifest)
|
||||||
|
// this should never happen. If it does we want to know about it, but not necessarily stop
|
||||||
|
// cataloging entirely... for this reason we only log.
|
||||||
|
log.Errorf("java manifest section found without a name: %s", path)
|
||||||
|
name = strconv.Itoa(i)
|
||||||
|
} else {
|
||||||
|
delete(s, "Name")
|
||||||
|
}
|
||||||
|
manifest.Sections[name] = s
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &manifest, nil
|
return &manifest, nil
|
||||||
@ -80,24 +95,21 @@ func selectName(manifest *pkg.JavaManifest, filenameObj archiveFilename) string
|
|||||||
switch {
|
switch {
|
||||||
case filenameObj.name() != "":
|
case filenameObj.name() != "":
|
||||||
name = filenameObj.name()
|
name = filenameObj.name()
|
||||||
case manifest.Name != "":
|
case manifest.Main["Name"] != "":
|
||||||
// Manifest original spec...
|
// Manifest original spec...
|
||||||
name = manifest.Name
|
name = manifest.Main["Name"]
|
||||||
case manifest.Extra["Bundle-Name"] != "":
|
case manifest.Main["Bundle-Name"] != "":
|
||||||
// BND tooling...
|
// BND tooling...
|
||||||
name = manifest.Extra["Bundle-Name"]
|
name = manifest.Main["Bundle-Name"]
|
||||||
case manifest.Extra["Short-Name"] != "":
|
case manifest.Main["Short-Name"] != "":
|
||||||
// Jenkins...
|
// Jenkins...
|
||||||
name = manifest.Extra["Short-Name"]
|
name = manifest.Main["Short-Name"]
|
||||||
case manifest.Extra["Extension-Name"] != "":
|
case manifest.Main["Extension-Name"] != "":
|
||||||
// Jenkins...
|
// Jenkins...
|
||||||
name = manifest.Extra["Extension-Name"]
|
name = manifest.Main["Extension-Name"]
|
||||||
}
|
case manifest.Main["Implementation-Title"] != "":
|
||||||
|
// last ditch effort...
|
||||||
// in situations where we hit this point and no name was
|
name = manifest.Main["Implementation-Title"]
|
||||||
// determined, look at the Implementation-Title
|
|
||||||
if name == "" && manifest.ImplTitle != "" {
|
|
||||||
name = manifest.ImplTitle
|
|
||||||
}
|
}
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
@ -105,14 +117,14 @@ func selectName(manifest *pkg.JavaManifest, filenameObj archiveFilename) string
|
|||||||
func selectVersion(manifest *pkg.JavaManifest, filenameObj archiveFilename) string {
|
func selectVersion(manifest *pkg.JavaManifest, filenameObj archiveFilename) string {
|
||||||
var version string
|
var version string
|
||||||
switch {
|
switch {
|
||||||
case manifest.ImplVersion != "":
|
case manifest.Main["Implementation-Version"] != "":
|
||||||
version = manifest.ImplVersion
|
version = manifest.Main["Implementation-Version"]
|
||||||
case filenameObj.version() != "":
|
case filenameObj.version() != "":
|
||||||
version = filenameObj.version()
|
version = filenameObj.version()
|
||||||
case manifest.SpecVersion != "":
|
case manifest.Main["Specification-Version"] != "":
|
||||||
version = manifest.SpecVersion
|
version = manifest.Main["Specification-Version"]
|
||||||
case manifest.Extra["Plugin-Version"] != "":
|
case manifest.Main["Plugin-Version"] != "":
|
||||||
version = manifest.Extra["Plugin-Version"]
|
version = manifest.Main["Plugin-Version"]
|
||||||
}
|
}
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,35 +17,39 @@ func TestParseJavaManifest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
fixture: "test-fixtures/manifest/small",
|
fixture: "test-fixtures/manifest/small",
|
||||||
expected: pkg.JavaManifest{
|
expected: pkg.JavaManifest{
|
||||||
ManifestVersion: "1.0",
|
Main: map[string]string{
|
||||||
|
"Manifest-Version": "1.0",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fixture: "test-fixtures/manifest/standard-info",
|
fixture: "test-fixtures/manifest/standard-info",
|
||||||
expected: pkg.JavaManifest{
|
expected: pkg.JavaManifest{
|
||||||
ManifestVersion: "1.0",
|
Main: map[string]string{
|
||||||
Name: "the-best-name",
|
"Name": "the-best-name",
|
||||||
SpecTitle: "the-spec-title",
|
"Manifest-Version": "1.0",
|
||||||
SpecVersion: "the-spec-version",
|
"Specification-Title": "the-spec-title",
|
||||||
SpecVendor: "the-spec-vendor",
|
"Specification-Version": "the-spec-version",
|
||||||
ImplTitle: "the-impl-title",
|
"Specification-Vendor": "the-spec-vendor",
|
||||||
ImplVersion: "the-impl-version",
|
"Implementation-Title": "the-impl-title",
|
||||||
ImplVendor: "the-impl-vendor",
|
"Implementation-Version": "the-impl-version",
|
||||||
|
"Implementation-Vendor": "the-impl-vendor",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fixture: "test-fixtures/manifest/extra-info",
|
fixture: "test-fixtures/manifest/extra-info",
|
||||||
expected: pkg.JavaManifest{
|
expected: pkg.JavaManifest{
|
||||||
ManifestVersion: "1.0",
|
Main: map[string]string{
|
||||||
Extra: map[string]string{
|
"Manifest-Version": "1.0",
|
||||||
"Archiver-Version": "Plexus Archiver",
|
"Archiver-Version": "Plexus Archiver",
|
||||||
"Created-By": "Apache Maven 3.6.3",
|
"Created-By": "Apache Maven 3.6.3",
|
||||||
},
|
},
|
||||||
Sections: []map[string]string{
|
Sections: map[string]map[string]string{
|
||||||
{
|
"thing-1": {
|
||||||
"Built-By": "?",
|
"Built-By": "?",
|
||||||
},
|
},
|
||||||
{
|
"2": {
|
||||||
"Build-Jdk": "14.0.1",
|
"Build-Jdk": "14.0.1",
|
||||||
"Main-Class": "hello.HelloWorld",
|
"Main-Class": "hello.HelloWorld",
|
||||||
},
|
},
|
||||||
@ -55,9 +59,9 @@ func TestParseJavaManifest(t *testing.T) {
|
|||||||
{
|
{
|
||||||
fixture: "test-fixtures/manifest/continuation",
|
fixture: "test-fixtures/manifest/continuation",
|
||||||
expected: pkg.JavaManifest{
|
expected: pkg.JavaManifest{
|
||||||
ManifestVersion: "1.0",
|
Main: map[string]string{
|
||||||
Extra: map[string]string{
|
"Manifest-Version": "1.0",
|
||||||
"Plugin-ScmUrl": "https://github.com/jenkinsci/plugin-pom/example-jenkins-plugin",
|
"Plugin-ScmUrl": "https://github.com/jenkinsci/plugin-pom/example-jenkins-plugin",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -65,8 +69,10 @@ func TestParseJavaManifest(t *testing.T) {
|
|||||||
// regression test, we should always keep the full version
|
// regression test, we should always keep the full version
|
||||||
fixture: "test-fixtures/manifest/version-with-date",
|
fixture: "test-fixtures/manifest/version-with-date",
|
||||||
expected: pkg.JavaManifest{
|
expected: pkg.JavaManifest{
|
||||||
ManifestVersion: "1.0",
|
Main: map[string]string{
|
||||||
ImplVersion: "1.3 2244 October 5 2005",
|
"Manifest-Version": "1.0",
|
||||||
|
"Implementation-Version": "1.3 2244 October 5 2005",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -78,7 +84,7 @@ func TestParseJavaManifest(t *testing.T) {
|
|||||||
t.Fatalf("could not open fixture: %+v", err)
|
t.Fatalf("could not open fixture: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual, err := parseJavaManifest(fixture)
|
actual, err := parseJavaManifest(test.fixture, fixture)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to parse manifest: %+v", err)
|
t.Fatalf("failed to parse manifest: %+v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ Manifest-Version: 1.0
|
|||||||
Archiver-Version: Plexus Archiver
|
Archiver-Version: Plexus Archiver
|
||||||
Created-By: Apache Maven 3.6.3
|
Created-By: Apache Maven 3.6.3
|
||||||
|
|
||||||
|
Name: thing-1
|
||||||
Built-By: ?
|
Built-By: ?
|
||||||
|
|
||||||
Build-Jdk: 14.0.1
|
Build-Jdk: 14.0.1
|
||||||
|
|||||||
@ -22,16 +22,8 @@ type PomProperties struct {
|
|||||||
|
|
||||||
// JavaManifest represents the fields of interest extracted from a Java archive's META-INF/MANIFEST.MF file.
|
// JavaManifest represents the fields of interest extracted from a Java archive's META-INF/MANIFEST.MF file.
|
||||||
type JavaManifest struct {
|
type JavaManifest struct {
|
||||||
Name string `mapstructure:"Name" json:"name,omitempty"`
|
Main map[string]string `json:"main,omitempty"`
|
||||||
ManifestVersion string `mapstructure:"Manifest-Version" json:"manifestVersion,omitempty"`
|
Sections map[string]map[string]string `json:"sections,omitempty"`
|
||||||
SpecTitle string `mapstructure:"Specification-Title" json:"specificationTitle,omitempty"`
|
|
||||||
SpecVersion string `mapstructure:"Specification-Version" json:"specificationVersion,omitempty"`
|
|
||||||
SpecVendor string `mapstructure:"Specification-Vendor" json:"specificationVendor,omitempty"`
|
|
||||||
ImplTitle string `mapstructure:"Implementation-Title" json:"implementationTitle,omitempty"`
|
|
||||||
ImplVersion string `mapstructure:"Implementation-Version" json:"implementationVersion,omitempty"`
|
|
||||||
ImplVendor string `mapstructure:"Implementation-Vendor" json:"implementationVendor,omitempty"`
|
|
||||||
Extra map[string]string `mapstructure:",remain" json:"extraFields,omitempty"`
|
|
||||||
Sections []map[string]string `json:"sections,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m JavaMetadata) PackageURL() string {
|
func (m JavaMetadata) PackageURL() string {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user