remove parent java package from json && add java manifest section parsing

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman 2020-10-28 17:14:16 -04:00
parent 62f6146c37
commit 2675891110
No known key found for this signature in database
GPG Key ID: 5CB45AE22BAB7EA7
5 changed files with 126 additions and 305 deletions

View File

@ -125,116 +125,109 @@
"type": "string" "type": "string"
}, },
"manifest": { "manifest": {
"anyOf": [ "properties": {
{ "extraFields": {
"type": "null"
},
{
"properties": { "properties": {
"extraFields": { "Archiver-Version": {
"properties": {
"Archiver-Version": {
"type": "string"
},
"Build-Jdk": {
"type": "string"
},
"Built-By": {
"type": "string"
},
"Created-By": {
"type": "string"
},
"Extension-Name": {
"type": "string"
},
"Group-Id": {
"type": "string"
},
"Hudson-Version": {
"type": "string"
},
"Jenkins-Version": {
"type": "string"
},
"Long-Name": {
"type": "string"
},
"Main-Class": {
"type": "string"
},
"Minimum-Java-Version": {
"type": "string"
},
"Plugin-Dependencies": {
"type": "string"
},
"Plugin-Developers": {
"type": "string"
},
"Plugin-License-Name": {
"type": "string"
},
"Plugin-License-Url": {
"type": "string"
},
"Plugin-ScmUrl": {
"type": "string"
},
"Plugin-Version": {
"type": "string"
},
"Short-Name": {
"type": "string"
}
},
"required": [
"Archiver-Version",
"Build-Jdk",
"Built-By",
"Created-By"
],
"type": "object"
},
"implementationTitle": {
"type": "string" "type": "string"
}, },
"implementationVendor": { "Build-Jdk": {
"type": "string" "type": "string"
}, },
"implementationVersion": { "Built-By": {
"type": "string" "type": "string"
}, },
"manifestVersion": { "Created-By": {
"type": "string" "type": "string"
}, },
"name": { "Extension-Name": {
"type": "string" "type": "string"
}, },
"specificationTitle": { "Group-Id": {
"type": "string" "type": "string"
}, },
"specificationVendor": { "Hudson-Version": {
"type": "string" "type": "string"
}, },
"specificationVersion": { "Jenkins-Version": {
"type": "string"
},
"Long-Name": {
"type": "string"
},
"Main-Class": {
"type": "string"
},
"Minimum-Java-Version": {
"type": "string"
},
"Plugin-Dependencies": {
"type": "string"
},
"Plugin-Developers": {
"type": "string"
},
"Plugin-License-Name": {
"type": "string"
},
"Plugin-License-Url": {
"type": "string"
},
"Plugin-ScmUrl": {
"type": "string"
},
"Plugin-Version": {
"type": "string"
},
"Short-Name": {
"type": "string" "type": "string"
} }
}, },
"required": [ "required": [
"extraFields", "Archiver-Version",
"implementationTitle", "Build-Jdk",
"implementationVendor", "Built-By",
"implementationVersion", "Created-By"
"manifestVersion",
"name",
"specificationTitle",
"specificationVendor",
"specificationVersion"
], ],
"type": "object" "type": "object"
},
"implementationTitle": {
"type": "string"
},
"implementationVendor": {
"type": "string"
},
"implementationVersion": {
"type": "string"
},
"manifestVersion": {
"type": "string"
},
"name": {
"type": "string"
},
"specificationTitle": {
"type": "string"
},
"specificationVendor": {
"type": "string"
},
"specificationVersion": {
"type": "string"
} }
] },
"required": [
"extraFields",
"implementationTitle",
"implementationVendor",
"implementationVersion",
"manifestVersion",
"name",
"specificationTitle",
"specificationVendor",
"specificationVersion"
],
"type": "object"
}, },
"name": { "name": {
"type": "string" "type": "string"
@ -245,206 +238,6 @@
"package": { "package": {
"type": "string" "type": "string"
}, },
"parentPackage": {
"anyOf": [
{
"type": "null"
},
{
"properties": {
"foundBy": {
"type": "string"
},
"language": {
"type": "integer"
},
"licenses": {
"type": "null"
},
"manifest": {
"type": "string"
},
"metadata": {
"properties": {
"manifest": {
"properties": {
"extraFields": {
"properties": {
"Archiver-Version": {
"type": "string"
},
"Build-Jdk": {
"type": "string"
},
"Built-By": {
"type": "string"
},
"Created-By": {
"type": "string"
},
"Extension-Name": {
"type": "string"
},
"Group-Id": {
"type": "string"
},
"Hudson-Version": {
"type": "string"
},
"Jenkins-Version": {
"type": "string"
},
"Long-Name": {
"type": "string"
},
"Main-Class": {
"type": "string"
},
"Minimum-Java-Version": {
"type": "string"
},
"Plugin-Dependencies": {
"type": "string"
},
"Plugin-Developers": {
"type": "string"
},
"Plugin-License-Name": {
"type": "string"
},
"Plugin-License-Url": {
"type": "string"
},
"Plugin-ScmUrl": {
"type": "string"
},
"Plugin-Version": {
"type": "string"
},
"Short-Name": {
"type": "string"
}
},
"required": [
"Archiver-Version",
"Build-Jdk",
"Built-By",
"Created-By"
],
"type": "object"
},
"implementationTitle": {
"type": "string"
},
"implementationVendor": {
"type": "string"
},
"implementationVersion": {
"type": "string"
},
"manifestVersion": {
"type": "string"
},
"name": {
"type": "string"
},
"specificationTitle": {
"type": "string"
},
"specificationVendor": {
"type": "string"
},
"specificationVersion": {
"type": "string"
}
},
"required": [
"extraFields",
"implementationTitle",
"implementationVendor",
"implementationVersion",
"manifestVersion",
"name",
"specificationTitle",
"specificationVendor",
"specificationVersion"
],
"type": "object"
},
"parentPackage": {
"type": "null"
},
"pomProperties": {
"properties": {
"artifactId": {
"type": "string"
},
"extraFields": {
"type": "null"
},
"groupId": {
"type": "string"
},
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"artifactId",
"extraFields",
"groupId",
"name",
"path",
"version"
],
"type": "object"
},
"virtualPath": {
"type": "string"
}
},
"required": [
"manifest",
"parentPackage",
"pomProperties",
"virtualPath"
],
"type": "object"
},
"metadataType": {
"type": "string"
},
"sources": {
"type": "null"
},
"type": {
"type": "string"
},
"version": {
"type": "string"
}
},
"required": [
"foundBy",
"language",
"licenses",
"manifest",
"metadata",
"metadataType",
"sources",
"type",
"version"
],
"type": "object"
}
]
},
"platform": { "platform": {
"type": "string" "type": "string"
}, },

View File

@ -70,10 +70,14 @@ func (a archiveFilename) version() string {
} }
func (a archiveFilename) name() string { func (a archiveFilename) name() string {
// there should be only one name, if there is more or less then something is wrong for _, fieldSet := range a.fields {
if len(a.fields) != 1 { if name, ok := fieldSet["name"]; ok {
return "" // return the first name
return name
}
} }
return a.fields[0]["name"] // derive the name from the archive name (no path or extension)
basename := filepath.Base(a.raw)
return strings.TrimSuffix(basename, filepath.Ext(basename))
} }

View File

@ -12,17 +12,29 @@ import (
const manifestPath = "META-INF/MANIFEST.MF" const manifestPath = "META-INF/MANIFEST.MF"
// nolint:funlen
func parseJavaManifest(reader io.Reader) (*pkg.JavaManifest, error) { func parseJavaManifest(reader io.Reader) (*pkg.JavaManifest, error) {
var manifest pkg.JavaManifest var manifest pkg.JavaManifest
manifestMap := make(map[string]string) sections := []map[string]string{
make(map[string]string),
}
currentSection := 0
scanner := bufio.NewScanner(reader) scanner := bufio.NewScanner(reader)
var lastKey string var lastKey string
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
// ignore empty lines // empty lines denote section separators
if strings.TrimSpace(line) == "" { if strings.TrimSpace(line) == "" {
currentSection++
// we don't want to allocate a new section map that wont necessarily be used, do that once there is
// a non-empty line to process
// do not process line continuations after this
lastKey = ""
continue continue
} else if currentSection >= len(sections) {
sections = append(sections, make(map[string]string))
} }
if line[0] == ' ' { if line[0] == ' ' {
@ -30,7 +42,7 @@ func parseJavaManifest(reader io.Reader) (*pkg.JavaManifest, error) {
if lastKey == "" { if lastKey == "" {
return nil, fmt.Errorf("found continuation with no previous key (%s)", line) return nil, fmt.Errorf("found continuation with no previous key (%s)", line)
} }
manifestMap[lastKey] += strings.TrimSpace(line) sections[currentSection][lastKey] += strings.TrimSpace(line)
} else { } else {
// this is a new key-value pair // this is a new key-value pair
idx := strings.Index(line, ":") idx := strings.Index(line, ":")
@ -40,9 +52,9 @@ func parseJavaManifest(reader io.Reader) (*pkg.JavaManifest, error) {
key := strings.TrimSpace(line[0:idx]) key := strings.TrimSpace(line[0:idx])
value := strings.TrimSpace(line[idx+1:]) value := strings.TrimSpace(line[idx+1:])
manifestMap[key] = value sections[currentSection][key] = value
// keep track of key for future continuations // keep track of key for potential future continuations
lastKey = key lastKey = key
} }
} }
@ -51,10 +63,15 @@ 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(manifestMap, &manifest); err != nil { if err := mapstructure.Decode(sections[0], &manifest); err != nil {
return nil, fmt.Errorf("unable to parse java manifest: %w", err) return nil, fmt.Errorf("unable to parse java manifest: %w", err)
} }
// append on extra sections
if len(sections) > 1 {
manifest.Sections = sections[1:]
}
// clean select fields // clean select fields
if strings.Trim(manifest.ImplVersion, " ") != "" { if strings.Trim(manifest.ImplVersion, " ") != "" {
// transform versions with dates attached to just versions (e.g. "1.3 2244 October 5 2008" --> "1.3") // transform versions with dates attached to just versions (e.g. "1.3 2244 October 5 2008" --> "1.3")

View File

@ -2,10 +2,11 @@ package java
import ( import (
"encoding/json" "encoding/json"
"github.com/anchore/syft/syft/pkg"
"github.com/go-test/deep"
"os" "os"
"testing" "testing"
"github.com/anchore/syft/syft/pkg"
"github.com/go-test/deep"
) )
func TestParseJavaManifest(t *testing.T) { func TestParseJavaManifest(t *testing.T) {
@ -38,10 +39,16 @@ func TestParseJavaManifest(t *testing.T) {
ManifestVersion: "1.0", ManifestVersion: "1.0",
Extra: map[string]string{ Extra: map[string]string{
"Archiver-Version": "Plexus Archiver", "Archiver-Version": "Plexus Archiver",
"Build-Jdk": "14.0.1",
"Built-By": "?",
"Created-By": "Apache Maven 3.6.3", "Created-By": "Apache Maven 3.6.3",
"Main-Class": "hello.HelloWorld", },
Sections: []map[string]string{
{
"Built-By": "?",
},
{
"Build-Jdk": "14.0.1",
"Main-Class": "hello.HelloWorld",
},
}, },
}, },
}, },

View File

@ -7,7 +7,7 @@ type JavaMetadata struct {
VirtualPath string `json:"virtualPath"` VirtualPath string `json:"virtualPath"`
Manifest *JavaManifest `mapstructure:"Manifest" json:"manifest,omitempty"` Manifest *JavaManifest `mapstructure:"Manifest" json:"manifest,omitempty"`
PomProperties *PomProperties `mapstructure:"PomProperties" json:"pomProperties,omitempty"` PomProperties *PomProperties `mapstructure:"PomProperties" json:"pomProperties,omitempty"`
Parent *Package `json:"parentPackage,omitempty"` // TODO: should this be included in the json output? Parent *Package `json:"-"`
} }
// PomProperties represents the fields of interest extracted from a Java archive's pom.xml file. // PomProperties represents the fields of interest extracted from a Java archive's pom.xml file.
@ -31,7 +31,7 @@ type JavaManifest struct {
ImplVersion string `mapstructure:"Implementation-Version" json:"implementationVersion"` ImplVersion string `mapstructure:"Implementation-Version" json:"implementationVersion"`
ImplVendor string `mapstructure:"Implementation-Vendor" json:"implementationVendor"` ImplVendor string `mapstructure:"Implementation-Vendor" json:"implementationVendor"`
Extra map[string]string `mapstructure:",remain" json:"extraFields"` Extra map[string]string `mapstructure:",remain" json:"extraFields"`
Sections []map[string]string `json:"sections"` Sections []map[string]string `json:"sections,omitempty"`
} }
func (m JavaMetadata) PackageURL() string { func (m JavaMetadata) PackageURL() string {