From 7c0f3378d855304c8ef4cacbf1660e8a19322172 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 26 Jan 2024 21:45:06 -0500 Subject: [PATCH] translate maps to sequences in pkg metadata Signed-off-by: Alex Goodman --- internal/constants.go | 2 +- schema/json/schema-15.0.0.json | 2147 +++++++++++++++++ .../packagemetadata/discover_type_names.go | 38 +- syft/pkg/cataloger/cpp/parse_conanlock.go | 9 +- .../pkg/cataloger/cpp/parse_conanlock_test.go | 347 +-- syft/pkg/cataloger/golang/package.go | 2 +- syft/pkg/cataloger/golang/parse_go_binary.go | 15 +- .../cataloger/golang/parse_go_binary_test.go | 192 +- .../internal/cpegenerate/generate_test.go | 27 +- .../cataloger/internal/cpegenerate/java.go | 14 +- .../internal/cpegenerate/java_test.go | 177 +- .../pkg/cataloger/java/archive_parser_test.go | 245 +- syft/pkg/cataloger/java/package_url_test.go | 35 +- .../pkg/cataloger/java/parse_java_manifest.go | 61 +- .../java/parse_java_manifest_test.go | 312 ++- syft/pkg/conan.go | 18 +- syft/pkg/golang.go | 12 +- syft/pkg/java.go | 13 +- syft/pkg/key_value.go | 28 + 19 files changed, 3152 insertions(+), 542 deletions(-) create mode 100644 schema/json/schema-15.0.0.json create mode 100644 syft/pkg/key_value.go diff --git a/internal/constants.go b/internal/constants.go index e16f6d9cfc4..e52ca7f8901 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -3,5 +3,5 @@ package internal const ( // JSONSchemaVersion is the current schema version output by the JSON encoder // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. - JSONSchemaVersion = "14.0.0" + JSONSchemaVersion = "15.0.0" ) diff --git a/schema/json/schema-15.0.0.json b/schema/json/schema-15.0.0.json new file mode 100644 index 00000000000..63e0f110d8b --- /dev/null +++ b/schema/json/schema-15.0.0.json @@ -0,0 +1,2147 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "anchore.io/schema/syft/json/15.0.0/document", + "$ref": "#/$defs/Document", + "$defs": { + "AlpmDbEntry": { + "properties": { + "basepackage": { + "type": "string" + }, + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "packager": { + "type": "string" + }, + "url": { + "type": "string" + }, + "validation": { + "type": "string" + }, + "reason": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "backup": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "basepackage", + "package", + "version", + "description", + "architecture", + "size", + "packager", + "url", + "validation", + "reason", + "files", + "backup" + ] + }, + "AlpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "gid": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "size": { + "type": "string" + }, + "link": { + "type": "string" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object" + }, + "ApkDbEntry": { + "properties": { + "package": { + "type": "string" + }, + "originPackage": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "version": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "installedSize": { + "type": "integer" + }, + "pullDependencies": { + "items": { + "type": "string" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "pullChecksum": { + "type": "string" + }, + "gitCommitOfApkPort": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/ApkFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "provides", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ] + }, + "ApkFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "ownerUid": { + "type": "string" + }, + "ownerGid": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "BinarySignature": { + "properties": { + "matches": { + "items": { + "$ref": "#/$defs/ClassifierMatch" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "matches" + ] + }, + "CConanFileEntry": { + "properties": { + "ref": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanInfoEntry": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "CConanLockEntry": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "build_requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "py_requires": { + "items": { + "type": "string" + }, + "type": "array" + }, + "options": { + "$ref": "#/$defs/KeyValues" + }, + "path": { + "type": "string" + }, + "context": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "ClassifierMatch": { + "properties": { + "classifier": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Location" + } + }, + "type": "object", + "required": [ + "classifier", + "location" + ] + }, + "CocoaPodfileLockEntry": { + "properties": { + "checksum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "checksum" + ] + }, + "Coordinates": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "DartPubspecLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hosted_url": { + "type": "string" + }, + "vcs_url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Descriptor": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "configuration": true + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Digest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "Document": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/Package" + }, + "type": "array" + }, + "artifactRelationships": { + "items": { + "$ref": "#/$defs/Relationship" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/File" + }, + "type": "array" + }, + "source": { + "$ref": "#/$defs/Source" + }, + "distro": { + "$ref": "#/$defs/LinuxRelease" + }, + "descriptor": { + "$ref": "#/$defs/Descriptor" + }, + "schema": { + "$ref": "#/$defs/Schema" + } + }, + "type": "object", + "required": [ + "artifacts", + "artifactRelationships", + "source", + "distro", + "descriptor", + "schema" + ] + }, + "DotnetDepsEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sha512": { + "type": "string" + }, + "hashPath": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ] + }, + "DotnetPortableExecutableEntry": { + "properties": { + "assemblyVersion": { + "type": "string" + }, + "legalCopyright": { + "type": "string" + }, + "comments": { + "type": "string" + }, + "internalName": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "productVersion": { + "type": "string" + } + }, + "type": "object", + "required": [ + "assemblyVersion", + "legalCopyright", + "companyName", + "productName", + "productVersion" + ] + }, + "DpkgDbEntry": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "preDepends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "DpkgFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "isConfigFile": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "path", + "isConfigFile" + ] + }, + "ElixirMixLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "ErlangRebarLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "File": { + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Coordinates" + }, + "metadata": { + "$ref": "#/$defs/FileMetadataEntry" + }, + "contents": { + "type": "string" + }, + "digests": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + }, + "licenses": { + "items": { + "$ref": "#/$defs/FileLicense" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "id", + "location" + ] + }, + "FileLicense": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "evidence": { + "$ref": "#/$defs/FileLicenseEvidence" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type" + ] + }, + "FileLicenseEvidence": { + "properties": { + "confidence": { + "type": "integer" + }, + "offset": { + "type": "integer" + }, + "extent": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "confidence", + "offset", + "extent" + ] + }, + "FileMetadataEntry": { + "properties": { + "mode": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "linkDestination": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "groupID": { + "type": "integer" + }, + "mimeType": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType", + "size" + ] + }, + "GoModuleBuildinfoEntry": { + "properties": { + "goBuildSettings": { + "$ref": "#/$defs/KeyValues" + }, + "goCompiledVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "h1Digest": { + "type": "string" + }, + "mainModule": { + "type": "string" + }, + "goCryptoSettings": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "goCompiledVersion", + "architecture" + ] + }, + "GoModuleEntry": { + "properties": { + "h1Digest": { + "type": "string" + } + }, + "type": "object" + }, + "HaskellHackageStackEntry": { + "properties": { + "pkgHash": { + "type": "string" + } + }, + "type": "object" + }, + "HaskellHackageStackLockEntry": { + "properties": { + "pkgHash": { + "type": "string" + }, + "snapshotURL": { + "type": "string" + } + }, + "type": "object" + }, + "IDLikes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "JavaArchive": { + "properties": { + "virtualPath": { + "type": "string" + }, + "manifest": { + "$ref": "#/$defs/JavaManifest" + }, + "pomProperties": { + "$ref": "#/$defs/JavaPomProperties" + }, + "pomProject": { + "$ref": "#/$defs/JavaPomProject" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "virtualPath" + ] + }, + "JavaManifest": { + "properties": { + "main": { + "$ref": "#/$defs/KeyValues" + }, + "sections": { + "items": { + "$ref": "#/$defs/KeyValues" + }, + "type": "array" + } + }, + "type": "object" + }, + "JavaPomParent": { + "properties": { + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object", + "required": [ + "groupId", + "artifactId", + "version" + ] + }, + "JavaPomProject": { + "properties": { + "path": { + "type": "string" + }, + "parent": { + "$ref": "#/$defs/JavaPomParent" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "groupId", + "artifactId", + "version", + "name" + ] + }, + "JavaPomProperties": { + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version" + ] + }, + "JavascriptNpmPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "homepage", + "description", + "url", + "private" + ] + }, + "JavascriptNpmPackageLockEntry": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "KeyValue": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "key", + "value" + ] + }, + "KeyValues": { + "items": { + "$ref": "#/$defs/KeyValue" + }, + "type": "array" + }, + "License": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type", + "urls", + "locations" + ] + }, + "LinuxKernelArchive": { + "properties": { + "name": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extendedVersion": { + "type": "string" + }, + "buildTime": { + "type": "string" + }, + "author": { + "type": "string" + }, + "format": { + "type": "string" + }, + "rwRootFS": { + "type": "boolean" + }, + "swapDevice": { + "type": "integer" + }, + "rootDevice": { + "type": "integer" + }, + "videoMode": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "architecture", + "version" + ] + }, + "LinuxKernelModule": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "path": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "license": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "versionMagic": { + "type": "string" + }, + "parameters": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/LinuxKernelModuleParameter" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "LinuxKernelModuleParameter": { + "properties": { + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "LinuxRelease": { + "properties": { + "prettyName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "idLike": { + "$ref": "#/$defs/IDLikes" + }, + "version": { + "type": "string" + }, + "versionID": { + "type": "string" + }, + "versionCodename": { + "type": "string" + }, + "buildID": { + "type": "string" + }, + "imageID": { + "type": "string" + }, + "imageVersion": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "variantID": { + "type": "string" + }, + "homeURL": { + "type": "string" + }, + "supportURL": { + "type": "string" + }, + "bugReportURL": { + "type": "string" + }, + "privacyPolicyURL": { + "type": "string" + }, + "cpeName": { + "type": "string" + }, + "supportEnd": { + "type": "string" + } + }, + "type": "object" + }, + "Location": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + }, + "accessPath": { + "type": "string" + }, + "annotations": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "accessPath" + ] + }, + "MicrosoftKbPatch": { + "properties": { + "product_id": { + "type": "string" + }, + "kb": { + "type": "string" + } + }, + "type": "object", + "required": [ + "product_id", + "kb" + ] + }, + "NixStoreEntry": { + "properties": { + "outputHash": { + "type": "string" + }, + "output": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "outputHash", + "files" + ] + }, + "Package": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "licenses": { + "$ref": "#/$defs/licenses" + }, + "language": { + "type": "string" + }, + "cpes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AlpmDbEntry" + }, + { + "$ref": "#/$defs/ApkDbEntry" + }, + { + "$ref": "#/$defs/BinarySignature" + }, + { + "$ref": "#/$defs/CConanFileEntry" + }, + { + "$ref": "#/$defs/CConanInfoEntry" + }, + { + "$ref": "#/$defs/CConanLockEntry" + }, + { + "$ref": "#/$defs/CocoaPodfileLockEntry" + }, + { + "$ref": "#/$defs/DartPubspecLockEntry" + }, + { + "$ref": "#/$defs/DotnetDepsEntry" + }, + { + "$ref": "#/$defs/DotnetPortableExecutableEntry" + }, + { + "$ref": "#/$defs/DpkgDbEntry" + }, + { + "$ref": "#/$defs/ElixirMixLockEntry" + }, + { + "$ref": "#/$defs/ErlangRebarLockEntry" + }, + { + "$ref": "#/$defs/GoModuleBuildinfoEntry" + }, + { + "$ref": "#/$defs/GoModuleEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackEntry" + }, + { + "$ref": "#/$defs/HaskellHackageStackLockEntry" + }, + { + "$ref": "#/$defs/JavaArchive" + }, + { + "$ref": "#/$defs/JavascriptNpmPackage" + }, + { + "$ref": "#/$defs/JavascriptNpmPackageLockEntry" + }, + { + "$ref": "#/$defs/LinuxKernelArchive" + }, + { + "$ref": "#/$defs/LinuxKernelModule" + }, + { + "$ref": "#/$defs/MicrosoftKbPatch" + }, + { + "$ref": "#/$defs/NixStoreEntry" + }, + { + "$ref": "#/$defs/PhpComposerInstalledEntry" + }, + { + "$ref": "#/$defs/PhpComposerLockEntry" + }, + { + "$ref": "#/$defs/PortageDbEntry" + }, + { + "$ref": "#/$defs/PythonPackage" + }, + { + "$ref": "#/$defs/PythonPipRequirementsEntry" + }, + { + "$ref": "#/$defs/PythonPipfileLockEntry" + }, + { + "$ref": "#/$defs/RDescription" + }, + { + "$ref": "#/$defs/RpmArchive" + }, + { + "$ref": "#/$defs/RpmDbEntry" + }, + { + "$ref": "#/$defs/RubyGemspec" + }, + { + "$ref": "#/$defs/RustCargoAuditEntry" + }, + { + "$ref": "#/$defs/RustCargoLockEntry" + }, + { + "$ref": "#/$defs/SwiftPackageManagerLockEntry" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "language", + "cpes", + "purl" + ] + }, + "PhpComposerAuthors": { + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "PhpComposerExternalReference": { + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type", + "url", + "reference" + ] + }, + "PhpComposerInstalledEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PhpComposerLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PortageDbEntry": { + "properties": { + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/PortageFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "installedSize", + "files" + ] + }, + "PortageFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonDirectURLOriginInfo": { + "properties": { + "url": { + "type": "string" + }, + "commitId": { + "type": "string" + }, + "vcs": { + "type": "string" + } + }, + "type": "object", + "required": [ + "url" + ] + }, + "PythonFileDigest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "PythonFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/PythonFileDigest" + }, + "size": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonPackage": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/PythonFileRecord" + }, + "type": "array" + }, + "sitePackagesRootPath": { + "type": "string" + }, + "topLevelPackages": { + "items": { + "type": "string" + }, + "type": "array" + }, + "directUrlOrigin": { + "$ref": "#/$defs/PythonDirectURLOriginInfo" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ] + }, + "PythonPipRequirementsEntry": { + "properties": { + "name": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + }, + "versionConstraint": { + "type": "string" + }, + "url": { + "type": "string" + }, + "markers": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "versionConstraint" + ] + }, + "PythonPipfileLockEntry": { + "properties": { + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "index": { + "type": "string" + } + }, + "type": "object", + "required": [ + "hashes", + "index" + ] + }, + "RDescription": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "url": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "built": { + "type": "string" + }, + "needsCompilation": { + "type": "boolean" + }, + "imports": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "suggests": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "Relationship": { + "properties": { + "parent": { + "type": "string" + }, + "child": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "parent", + "child", + "type" + ] + }, + "RpmArchive": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ] + }, + "RpmDbEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "files" + ] + }, + "RpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "userName": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "flags": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "mode", + "size", + "digest", + "userName", + "groupName", + "flags" + ] + }, + "RubyGemspec": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "RustCargoAuditEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source" + ] + }, + "RustCargoLockEntry": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + }, + "checksum": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "checksum", + "dependencies" + ] + }, + "Schema": { + "properties": { + "version": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "version", + "url" + ] + }, + "Source": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "metadata" + ] + }, + "SwiftPackageManagerLockEntry": { + "properties": { + "revision": { + "type": "string" + } + }, + "type": "object", + "required": [ + "revision" + ] + }, + "licenses": { + "items": { + "$ref": "#/$defs/License" + }, + "type": "array" + } + } +} diff --git a/syft/internal/packagemetadata/discover_type_names.go b/syft/internal/packagemetadata/discover_type_names.go index 00d3c626db2..ca9b2c9ddc5 100644 --- a/syft/internal/packagemetadata/discover_type_names.go +++ b/syft/internal/packagemetadata/discover_type_names.go @@ -68,6 +68,10 @@ func findMetadataDefinitionNames(paths ...string) ([]string, error) { // any definition that is used within another struct should not be considered a top-level metadata definition names.Remove(usedNames.List()...) + // remove known exceptions, that is, types exported in the pkg Package that are not used + // in a metadata type but are not metadata types themselves. + names.Remove("Licenses", "KeyValue") + strNames := names.List() sort.Strings(strNames) @@ -114,7 +118,11 @@ func findMetadataDefinitionNamesInFile(path string) ([]string, []string, error) structType := extractStructType(spec.Type) if structType == nil { - continue + // maybe this is a slice of structs? This is useful (say type KeyValues is []KeyValue) + structType = extractSliceOfStructType(spec.Type) + if structType == nil { + continue + } } metadataDefinitions = append(metadataDefinitions, name) @@ -124,6 +132,34 @@ func findMetadataDefinitionNamesInFile(path string) ([]string, []string, error) return metadataDefinitions, usedTypeNames, nil } +func extractSliceOfStructType(exp ast.Expr) *ast.StructType { + var structType *ast.StructType + switch ty := exp.(type) { + case *ast.ArrayType: + // this is a standard definition: + // type FooMetadata []BarMetadata + structType = extractStructType(ty.Elt) + case *ast.Ident: + if ty.Obj == nil { + return nil + } + + // this might be a type created from another type: + // type FooMetadata BarMetadata + // ... but we need to check that the other type definition is a struct type + typeSpec, ok := ty.Obj.Decl.(*ast.TypeSpec) + if !ok { + return nil + } + nestedStructType, ok := typeSpec.Type.(*ast.StructType) + if !ok { + return nil + } + structType = nestedStructType + } + return structType +} + func extractStructType(exp ast.Expr) *ast.StructType { var structType *ast.StructType switch ty := exp.(type) { diff --git a/syft/pkg/cataloger/cpp/parse_conanlock.go b/syft/pkg/cataloger/cpp/parse_conanlock.go index c8228341db9..ad2499c1d2e 100644 --- a/syft/pkg/cataloger/cpp/parse_conanlock.go +++ b/syft/pkg/cataloger/cpp/parse_conanlock.go @@ -87,8 +87,8 @@ func parseConanlock(_ context.Context, _ file.Resolver, _ *generic.Environment, return pkgs, relationships, nil } -func parseOptions(options string) map[string]string { - o := make(map[string]string) +func parseOptions(options string) []pkg.KeyValue { + o := make([]pkg.KeyValue, 0) if len(options) == 0 { return nil } @@ -97,7 +97,10 @@ func parseOptions(options string) map[string]string { for _, kvp := range kvps { kv := strings.Split(kvp, "=") if len(kv) == 2 { - o[kv[0]] = kv[1] + o = append(o, pkg.KeyValue{ + Key: kv[0], + Value: kv[1], + }) } } diff --git a/syft/pkg/cataloger/cpp/parse_conanlock_test.go b/syft/pkg/cataloger/cpp/parse_conanlock_test.go index a068978884f..c972f3e18ca 100644 --- a/syft/pkg/cataloger/cpp/parse_conanlock_test.go +++ b/syft/pkg/cataloger/cpp/parse_conanlock_test.go @@ -21,82 +21,82 @@ func TestParseConanlock(t *testing.T) { Type: pkg.ConanPkg, Metadata: pkg.ConanLockEntry{ Ref: "mfast/1.2.2@my_user/my_channel#c6f6387c9b99780f0ee05e25f99d0f39", - Options: map[string]string{ - "fPIC": "True", - "shared": "False", - "with_sqlite3": "False", - "boost:addr2line_location": "/usr/bin/addr2line", - "boost:asio_no_deprecated": "False", - "boost:buildid": "None", - "boost:bzip2": "True", - "boost:debug_level": "0", - "boost:diagnostic_definitions": "False", - "boost:error_code_header_only": "False", - "boost:extra_b2_flags": "None", - "boost:fPIC": "True", - "boost:filesystem_no_deprecated": "False", - "boost:header_only": "False", - "boost:i18n_backend": "deprecated", - "boost:i18n_backend_iconv": "libc", - "boost:i18n_backend_icu": "False", - "boost:layout": "system", - "boost:lzma": "False", - "boost:magic_autolink": "False", - "boost:multithreading": "True", - "boost:namespace": "boost", - "boost:namespace_alias": "False", - "boost:numa": "True", - "boost:pch": "True", - "boost:python_executable": "None", - "boost:python_version": "None", - "boost:segmented_stacks": "False", - "boost:shared": "False", - "boost:system_no_deprecated": "False", - "boost:system_use_utf8": "False", - "boost:visibility": "hidden", - "boost:with_stacktrace_backtrace": "True", - "boost:without_atomic": "False", - "boost:without_chrono": "False", - "boost:without_container": "False", - "boost:without_context": "False", - "boost:without_contract": "False", - "boost:without_coroutine": "False", - "boost:without_date_time": "False", - "boost:without_exception": "False", - "boost:without_fiber": "False", - "boost:without_filesystem": "False", - "boost:without_graph": "False", - "boost:without_graph_parallel": "True", - "boost:without_iostreams": "False", - "boost:without_json": "False", - "boost:without_locale": "False", - "boost:without_log": "False", - "boost:without_math": "False", - "boost:without_mpi": "True", - "boost:without_nowide": "False", - "boost:without_program_options": "False", - "boost:without_python": "True", - "boost:without_random": "False", - "boost:without_regex": "False", - "boost:without_serialization": "False", - "boost:without_stacktrace": "False", - "boost:without_system": "False", - "boost:without_test": "False", - "boost:without_thread": "False", - "boost:without_timer": "False", - "boost:without_type_erasure": "False", - "boost:without_wave": "False", - "boost:zlib": "True", - "boost:zstd": "False", - "bzip2:build_executable": "True", - "bzip2:fPIC": "True", - "bzip2:shared": "False", - "libbacktrace:fPIC": "True", - "libbacktrace:shared": "False", - "tinyxml2:fPIC": "True", - "tinyxml2:shared": "False", - "zlib:fPIC": "True", - "zlib:shared": "False", + Options: pkg.KeyValues{ + {Key: "fPIC", Value: "True"}, + {Key: "shared", Value: "False"}, + {Key: "with_sqlite3", Value: "False"}, + {Key: "boost:addr2line_location", Value: "/usr/bin/addr2line"}, + {Key: "boost:asio_no_deprecated", Value: "False"}, + {Key: "boost:buildid", Value: "None"}, + {Key: "boost:bzip2", Value: "True"}, + {Key: "boost:debug_level", Value: "0"}, + {Key: "boost:diagnostic_definitions", Value: "False"}, + {Key: "boost:error_code_header_only", Value: "False"}, + {Key: "boost:extra_b2_flags", Value: "None"}, + {Key: "boost:fPIC", Value: "True"}, + {Key: "boost:filesystem_no_deprecated", Value: "False"}, + {Key: "boost:header_only", Value: "False"}, + {Key: "boost:i18n_backend", Value: "deprecated"}, + {Key: "boost:i18n_backend_iconv", Value: "libc"}, + {Key: "boost:i18n_backend_icu", Value: "False"}, + {Key: "boost:layout", Value: "system"}, + {Key: "boost:lzma", Value: "False"}, + {Key: "boost:magic_autolink", Value: "False"}, + {Key: "boost:multithreading", Value: "True"}, + {Key: "boost:namespace", Value: "boost"}, + {Key: "boost:namespace_alias", Value: "False"}, + {Key: "boost:numa", Value: "True"}, + {Key: "boost:pch", Value: "True"}, + {Key: "boost:python_executable", Value: "None"}, + {Key: "boost:python_version", Value: "None"}, + {Key: "boost:segmented_stacks", Value: "False"}, + {Key: "boost:shared", Value: "False"}, + {Key: "boost:system_no_deprecated", Value: "False"}, + {Key: "boost:system_use_utf8", Value: "False"}, + {Key: "boost:visibility", Value: "hidden"}, + {Key: "boost:with_stacktrace_backtrace", Value: "True"}, + {Key: "boost:without_atomic", Value: "False"}, + {Key: "boost:without_chrono", Value: "False"}, + {Key: "boost:without_container", Value: "False"}, + {Key: "boost:without_context", Value: "False"}, + {Key: "boost:without_contract", Value: "False"}, + {Key: "boost:without_coroutine", Value: "False"}, + {Key: "boost:without_date_time", Value: "False"}, + {Key: "boost:without_exception", Value: "False"}, + {Key: "boost:without_fiber", Value: "False"}, + {Key: "boost:without_filesystem", Value: "False"}, + {Key: "boost:without_graph", Value: "False"}, + {Key: "boost:without_graph_parallel", Value: "True"}, + {Key: "boost:without_iostreams", Value: "False"}, + {Key: "boost:without_json", Value: "False"}, + {Key: "boost:without_locale", Value: "False"}, + {Key: "boost:without_log", Value: "False"}, + {Key: "boost:without_math", Value: "False"}, + {Key: "boost:without_mpi", Value: "True"}, + {Key: "boost:without_nowide", Value: "False"}, + {Key: "boost:without_program_options", Value: "False"}, + {Key: "boost:without_python", Value: "True"}, + {Key: "boost:without_random", Value: "False"}, + {Key: "boost:without_regex", Value: "False"}, + {Key: "boost:without_serialization", Value: "False"}, + {Key: "boost:without_stacktrace", Value: "False"}, + {Key: "boost:without_system", Value: "False"}, + {Key: "boost:without_test", Value: "False"}, + {Key: "boost:without_thread", Value: "False"}, + {Key: "boost:without_timer", Value: "False"}, + {Key: "boost:without_type_erasure", Value: "False"}, + {Key: "boost:without_wave", Value: "False"}, + {Key: "boost:zlib", Value: "True"}, + {Key: "boost:zstd", Value: "False"}, + {Key: "bzip2:build_executable", Value: "True"}, + {Key: "bzip2:fPIC", Value: "True"}, + {Key: "bzip2:shared", Value: "False"}, + {Key: "libbacktrace:fPIC", Value: "True"}, + {Key: "libbacktrace:shared", Value: "False"}, + {Key: "tinyxml2:fPIC", Value: "True"}, + {Key: "tinyxml2:shared", Value: "False"}, + {Key: "zlib:fPIC", Value: "True"}, + {Key: "zlib:shared", Value: "False"}, }, Context: "host", PackageID: "9d1f076b471417647c2022a78d5e2c1f834289ac", @@ -112,77 +112,77 @@ func TestParseConanlock(t *testing.T) { Type: pkg.ConanPkg, Metadata: pkg.ConanLockEntry{ Ref: "boost/1.75.0#a9c318f067216f900900e044e7af4ab1", - Options: map[string]string{ - "addr2line_location": "/usr/bin/addr2line", - "asio_no_deprecated": "False", - "buildid": "None", - "bzip2": "True", - "debug_level": "0", - "diagnostic_definitions": "False", - "error_code_header_only": "False", - "extra_b2_flags": "None", - "fPIC": "True", - "filesystem_no_deprecated": "False", - "header_only": "False", - "i18n_backend": "deprecated", - "i18n_backend_iconv": "libc", - "i18n_backend_icu": "False", - "layout": "system", - "lzma": "False", - "magic_autolink": "False", - "multithreading": "True", - "namespace": "boost", - "namespace_alias": "False", - "numa": "True", - "pch": "True", - "python_executable": "None", - "python_version": "None", - "segmented_stacks": "False", - "shared": "False", - "system_no_deprecated": "False", - "system_use_utf8": "False", - "visibility": "hidden", - "with_stacktrace_backtrace": "True", - "without_atomic": "False", - "without_chrono": "False", - "without_container": "False", - "without_context": "False", - "without_contract": "False", - "without_coroutine": "False", - "without_date_time": "False", - "without_exception": "False", - "without_fiber": "False", - "without_filesystem": "False", - "without_graph": "False", - "without_graph_parallel": "True", - "without_iostreams": "False", - "without_json": "False", - "without_locale": "False", - "without_log": "False", - "without_math": "False", - "without_mpi": "True", - "without_nowide": "False", - "without_program_options": "False", - "without_python": "True", - "without_random": "False", - "without_regex": "False", - "without_serialization": "False", - "without_stacktrace": "False", - "without_system": "False", - "without_test": "False", - "without_thread": "False", - "without_timer": "False", - "without_type_erasure": "False", - "without_wave": "False", - "zlib": "True", - "zstd": "False", - "bzip2:build_executable": "True", - "bzip2:fPIC": "True", - "bzip2:shared": "False", - "libbacktrace:fPIC": "True", - "libbacktrace:shared": "False", - "zlib:fPIC": "True", - "zlib:shared": "False", + Options: pkg.KeyValues{ + {Key: "addr2line_location", Value: "/usr/bin/addr2line"}, + {Key: "asio_no_deprecated", Value: "False"}, + {Key: "buildid", Value: "None"}, + {Key: "bzip2", Value: "True"}, + {Key: "debug_level", Value: "0"}, + {Key: "diagnostic_definitions", Value: "False"}, + {Key: "error_code_header_only", Value: "False"}, + {Key: "extra_b2_flags", Value: "None"}, + {Key: "fPIC", Value: "True"}, + {Key: "filesystem_no_deprecated", Value: "False"}, + {Key: "header_only", Value: "False"}, + {Key: "i18n_backend", Value: "deprecated"}, + {Key: "i18n_backend_iconv", Value: "libc"}, + {Key: "i18n_backend_icu", Value: "False"}, + {Key: "layout", Value: "system"}, + {Key: "lzma", Value: "False"}, + {Key: "magic_autolink", Value: "False"}, + {Key: "multithreading", Value: "True"}, + {Key: "namespace", Value: "boost"}, + {Key: "namespace_alias", Value: "False"}, + {Key: "numa", Value: "True"}, + {Key: "pch", Value: "True"}, + {Key: "python_executable", Value: "None"}, + {Key: "python_version", Value: "None"}, + {Key: "segmented_stacks", Value: "False"}, + {Key: "shared", Value: "False"}, + {Key: "system_no_deprecated", Value: "False"}, + {Key: "system_use_utf8", Value: "False"}, + {Key: "visibility", Value: "hidden"}, + {Key: "with_stacktrace_backtrace", Value: "True"}, + {Key: "without_atomic", Value: "False"}, + {Key: "without_chrono", Value: "False"}, + {Key: "without_container", Value: "False"}, + {Key: "without_context", Value: "False"}, + {Key: "without_contract", Value: "False"}, + {Key: "without_coroutine", Value: "False"}, + {Key: "without_date_time", Value: "False"}, + {Key: "without_exception", Value: "False"}, + {Key: "without_fiber", Value: "False"}, + {Key: "without_filesystem", Value: "False"}, + {Key: "without_graph", Value: "False"}, + {Key: "without_graph_parallel", Value: "True"}, + {Key: "without_iostreams", Value: "False"}, + {Key: "without_json", Value: "False"}, + {Key: "without_locale", Value: "False"}, + {Key: "without_log", Value: "False"}, + {Key: "without_math", Value: "False"}, + {Key: "without_mpi", Value: "True"}, + {Key: "without_nowide", Value: "False"}, + {Key: "without_program_options", Value: "False"}, + {Key: "without_python", Value: "True"}, + {Key: "without_random", Value: "False"}, + {Key: "without_regex", Value: "False"}, + {Key: "without_serialization", Value: "False"}, + {Key: "without_stacktrace", Value: "False"}, + {Key: "without_system", Value: "False"}, + {Key: "without_test", Value: "False"}, + {Key: "without_thread", Value: "False"}, + {Key: "without_timer", Value: "False"}, + {Key: "without_type_erasure", Value: "False"}, + {Key: "without_wave", Value: "False"}, + {Key: "zlib", Value: "True"}, + {Key: "zstd", Value: "False"}, + {Key: "bzip2:build_executable", Value: "True"}, + {Key: "bzip2:fPIC", Value: "True"}, + {Key: "bzip2:shared", Value: "False"}, + {Key: "libbacktrace:fPIC", Value: "True"}, + {Key: "libbacktrace:shared", Value: "False"}, + {Key: "zlib:fPIC", Value: "True"}, + {Key: "zlib:shared", Value: "False"}, }, Context: "host", PackageID: "dc8aedd23a0f0a773a5fcdcfe1ae3e89c4205978", @@ -198,9 +198,15 @@ func TestParseConanlock(t *testing.T) { Type: pkg.ConanPkg, Metadata: pkg.ConanLockEntry{ Ref: "zlib/1.2.12#c67ce17f2e96b972d42393ce50a76a1a", - Options: map[string]string{ - "fPIC": "True", - "shared": "False", + Options: pkg.KeyValues{ + { + Key: "fPIC", + Value: "True", + }, + { + Key: "shared", + Value: "False", + }, }, Context: "host", PackageID: "dfbe50feef7f3c6223a476cd5aeadb687084a646", @@ -216,10 +222,19 @@ func TestParseConanlock(t *testing.T) { Type: pkg.ConanPkg, Metadata: pkg.ConanLockEntry{ Ref: "bzip2/1.0.8#62a8031289639043797cf53fa876d0ef", - Options: map[string]string{ - "build_executable": "True", - "fPIC": "True", - "shared": "False", + Options: []pkg.KeyValue{ + { + Key: "build_executable", + Value: "True", + }, + { + Key: "fPIC", + Value: "True", + }, + { + Key: "shared", + Value: "False", + }, }, Context: "host", PackageID: "c32092bf4d4bb47cf962af898e02823f499b017e", @@ -235,9 +250,15 @@ func TestParseConanlock(t *testing.T) { Type: pkg.ConanPkg, Metadata: pkg.ConanLockEntry{ Ref: "libbacktrace/cci.20210118#76e40b760e0bcd602d46db56b22820ab", - Options: map[string]string{ - "fPIC": "True", - "shared": "False", + Options: []pkg.KeyValue{ + { + Key: "fPIC", + Value: "True", + }, + { + Key: "shared", + Value: "False", + }, }, Context: "host", PackageID: "dfbe50feef7f3c6223a476cd5aeadb687084a646", @@ -253,9 +274,15 @@ func TestParseConanlock(t *testing.T) { Type: pkg.ConanPkg, Metadata: pkg.ConanLockEntry{ Ref: "tinyxml2/9.0.0#9f13a36ebfc222cd55fe531a0a8d94d1", - Options: map[string]string{ - "fPIC": "True", - "shared": "False", + Options: []pkg.KeyValue{ + { + Key: "fPIC", + Value: "True", + }, + { + Key: "shared", + Value: "False", + }, }, Context: "host", // intentionally remove to test missing PackageID and Prev diff --git a/syft/pkg/cataloger/golang/package.go b/syft/pkg/cataloger/golang/package.go index 90ce0aa3e05..51598ff71e4 100644 --- a/syft/pkg/cataloger/golang/package.go +++ b/syft/pkg/cataloger/golang/package.go @@ -10,7 +10,7 @@ import ( "github.com/anchore/syft/syft/pkg" ) -func (c *goBinaryCataloger) newGoBinaryPackage(resolver file.Resolver, dep *debug.Module, mainModule, goVersion, architecture string, buildSettings map[string]string, cryptoSettings []string, locations ...file.Location) pkg.Package { +func (c *goBinaryCataloger) newGoBinaryPackage(resolver file.Resolver, dep *debug.Module, mainModule, goVersion, architecture string, buildSettings pkg.KeyValues, cryptoSettings []string, locations ...file.Location) pkg.Package { if dep.Replace != nil { dep = dep.Replace } diff --git a/syft/pkg/cataloger/golang/parse_go_binary.go b/syft/pkg/cataloger/golang/parse_go_binary.go index bce75ac6ffd..c316b79ea8c 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary.go +++ b/syft/pkg/cataloger/golang/parse_go_binary.go @@ -84,8 +84,8 @@ func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *exten return main } - version, hasVersion := gbs["vcs.revision"] - timestamp, hasTimestamp := gbs["vcs.time"] + version, hasVersion := gbs.Get("vcs.revision") + timestamp, hasTimestamp := gbs.Get("vcs.time") var ldflags string if metadata, ok := main.Metadata.(pkg.GolangBinaryBuildinfoEntry); ok { @@ -95,7 +95,7 @@ func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *exten // there is a matching vcs tag to match that could be referenced. This assumption could // be incorrect in terms of the go.mod contents, but is not incorrect in terms of the logical // version of the package. - ldflags = metadata.BuildSettings["-ldflags"] + ldflags, _ = metadata.BuildSettings.Get("-ldflags") } majorVersion, fullVersion := extractVersionFromLDFlags(ldflags) @@ -207,10 +207,13 @@ func getGOARCHFromBin(r io.ReaderAt) (string, error) { return arch, nil } -func getBuildSettings(settings []debug.BuildSetting) map[string]string { - m := make(map[string]string) +func getBuildSettings(settings []debug.BuildSetting) pkg.KeyValues { + m := make(pkg.KeyValues, 0) for _, s := range settings { - m[s.Key] = s.Value + m = append(m, pkg.KeyValue{ + Key: s.Key, + Value: s.Value, + }) } return m } diff --git a/syft/pkg/cataloger/golang/parse_go_binary_test.go b/syft/pkg/cataloger/golang/parse_go_binary_test.go index 533d0f33a3f..a59758b7e5b 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary_test.go +++ b/syft/pkg/cataloger/golang/parse_go_binary_test.go @@ -124,10 +124,20 @@ func TestBuildGoPkgInfo(t *testing.T) { goCompiledVersion = "1.18" archDetails = "amd64" ) - defaultBuildSettings := map[string]string{ - "GOARCH": "amd64", - "GOOS": "darwin", - "GOAMD64": "v1", + + defaultBuildSettings := []pkg.KeyValue{ + { + Key: "GOARCH", + Value: "amd64", + }, + { + Key: "GOOS", + Value: "darwin", + }, + { + Key: "GOAMD64", + Value: "v1", + }, } unmodifiedMain := pkg.Package{ @@ -275,10 +285,19 @@ func TestBuildGoPkgInfo(t *testing.T) { GoCompiledVersion: goCompiledVersion, Architecture: archDetails, H1Digest: "", - BuildSettings: map[string]string{ - "GOAMD64": "v1", - "GOARCH": "amd64", - "GOOS": "darwin", + BuildSettings: []pkg.KeyValue{ + { + Key: "GOARCH", + Value: archDetails, + }, + { + Key: "GOOS", + Value: "darwin", + }, + { + Key: "GOAMD64", + Value: "v1", + }, }, MainModule: "github.com/a/b/c", GoCryptoSettings: []string{"boringcrypto + fips"}, @@ -339,13 +358,31 @@ func TestBuildGoPkgInfo(t *testing.T) { Metadata: pkg.GolangBinaryBuildinfoEntry{ GoCompiledVersion: goCompiledVersion, Architecture: archDetails, - BuildSettings: map[string]string{ - "GOARCH": archDetails, - "GOOS": "darwin", - "GOAMD64": "v1", - "vcs.revision": "41bc6bb410352845f22766e27dd48ba93aa825a4", - "vcs.time": "2022-10-14T19:54:57Z", - "-ldflags": `build -ldflags="-w -s -extldflags '-static' -X blah=foobar`, + BuildSettings: []pkg.KeyValue{ + { + Key: "GOARCH", + Value: archDetails, + }, + { + Key: "GOOS", + Value: "darwin", + }, + { + Key: "GOAMD64", + Value: "v1", + }, + { + Key: "vcs.revision", + Value: "41bc6bb410352845f22766e27dd48ba93aa825a4", + }, + { + Key: "vcs.time", + Value: "2022-10-14T19:54:57Z", + }, + { + Key: "-ldflags", + Value: `build -ldflags="-w -s -extldflags '-static' -X blah=foobar`, + }, }, MainModule: "github.com/anchore/syft", }, @@ -388,13 +425,31 @@ func TestBuildGoPkgInfo(t *testing.T) { Metadata: pkg.GolangBinaryBuildinfoEntry{ GoCompiledVersion: goCompiledVersion, Architecture: archDetails, - BuildSettings: map[string]string{ - "GOARCH": archDetails, - "GOOS": "darwin", - "GOAMD64": "v1", - "vcs.revision": "41bc6bb410352845f22766e27dd48ba93aa825a4", - "vcs.time": "2022-10-14T19:54:57Z", - "-ldflags": `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`, + BuildSettings: []pkg.KeyValue{ + { + Key: "GOARCH", + Value: archDetails, + }, + { + Key: "GOOS", + Value: "darwin", + }, + { + Key: "GOAMD64", + Value: "v1", + }, + { + Key: "vcs.revision", + Value: "41bc6bb410352845f22766e27dd48ba93aa825a4", + }, + { + Key: "vcs.time", + Value: "2022-10-14T19:54:57Z", + }, + { + Key: "-ldflags", + Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`, + }, }, MainModule: "github.com/anchore/syft", }, @@ -435,11 +490,23 @@ func TestBuildGoPkgInfo(t *testing.T) { Metadata: pkg.GolangBinaryBuildinfoEntry{ GoCompiledVersion: goCompiledVersion, Architecture: archDetails, - BuildSettings: map[string]string{ - "GOARCH": archDetails, - "GOOS": "darwin", - "GOAMD64": "v1", - "-ldflags": `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`, + BuildSettings: []pkg.KeyValue{ + { + Key: "GOARCH", + Value: archDetails, + }, + { + Key: "GOOS", + Value: "darwin", + }, + { + Key: "GOAMD64", + Value: "v1", + }, + { + Key: "-ldflags", + Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`, + }, }, MainModule: "github.com/anchore/syft", }, @@ -480,11 +547,23 @@ func TestBuildGoPkgInfo(t *testing.T) { Metadata: pkg.GolangBinaryBuildinfoEntry{ GoCompiledVersion: goCompiledVersion, Architecture: archDetails, - BuildSettings: map[string]string{ - "GOARCH": archDetails, - "GOOS": "darwin", - "GOAMD64": "v1", - "-ldflags": `build -ldflags="-w -s -extldflags '-static' -X main.version=0.79.0`, + BuildSettings: []pkg.KeyValue{ + { + Key: "GOARCH", + Value: archDetails, + }, + { + Key: "GOOS", + Value: "darwin", + }, + { + Key: "GOAMD64", + Value: "v1", + }, + { + Key: "-ldflags", + Value: `build -ldflags="-w -s -extldflags '-static' -X main.version=0.79.0`, + }, }, MainModule: "github.com/anchore/syft", }, @@ -525,11 +604,23 @@ func TestBuildGoPkgInfo(t *testing.T) { Metadata: pkg.GolangBinaryBuildinfoEntry{ GoCompiledVersion: goCompiledVersion, Architecture: archDetails, - BuildSettings: map[string]string{ - "GOARCH": archDetails, - "GOOS": "darwin", - "GOAMD64": "v1", - "-ldflags": `build -ldflags="-w -s -extldflags '-static' -X main.Version=0.79.0`, + BuildSettings: []pkg.KeyValue{ + { + Key: "GOARCH", + Value: archDetails, + }, + { + Key: "GOOS", + Value: "darwin", + }, + { + Key: "GOAMD64", + Value: "v1", + }, + { + Key: "-ldflags", + Value: `build -ldflags="-w -s -extldflags '-static' -X main.Version=0.79.0`, + }, }, MainModule: "github.com/anchore/syft", }, @@ -571,12 +662,27 @@ func TestBuildGoPkgInfo(t *testing.T) { Metadata: pkg.GolangBinaryBuildinfoEntry{ GoCompiledVersion: goCompiledVersion, Architecture: archDetails, - BuildSettings: map[string]string{ - "GOARCH": archDetails, - "GOOS": "darwin", - "GOAMD64": "v1", - "vcs.revision": "41bc6bb410352845f22766e27dd48ba93aa825a4", - "vcs.time": "2022-10-14T19:54:57Z", + BuildSettings: []pkg.KeyValue{ + { + Key: "GOARCH", + Value: archDetails, + }, + { + Key: "GOOS", + Value: "darwin", + }, + { + Key: "GOAMD64", + Value: "v1", + }, + { + Key: "vcs.revision", + Value: "41bc6bb410352845f22766e27dd48ba93aa825a4", + }, + { + Key: "vcs.time", + Value: "2022-10-14T19:54:57Z", + }, }, MainModule: "github.com/anchore/syft", }, diff --git a/syft/pkg/cataloger/internal/cpegenerate/generate_test.go b/syft/pkg/cataloger/internal/cpegenerate/generate_test.go index faa70af95f4..04b8c82bb88 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/generate_test.go +++ b/syft/pkg/cataloger/internal/cpegenerate/generate_test.go @@ -13,6 +13,17 @@ import ( "github.com/anchore/syft/syft/pkg" ) +func keyValues(m map[string]string) []pkg.KeyValue { + var kvs []pkg.KeyValue + for k, v := range m { + kvs = append(kvs, pkg.KeyValue{ + Key: k, + Value: v, + }) + } + return kvs +} + func TestGeneratePackageCPEs(t *testing.T) { tests := []struct { name string @@ -201,7 +212,7 @@ func TestGeneratePackageCPEs(t *testing.T) { Type: pkg.JavaPkg, Metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - Main: map[string]string{ + Main: keyValues(map[string]string{ "Ant-Version": "Apache Ant 1.6.5", "Built-By": "tatu", "Created-By": "1.4.2_03-b02 (Sun Microsystems Inc.)", @@ -212,7 +223,7 @@ func TestGeneratePackageCPEs(t *testing.T) { "Specification-Title": "StAX 1.0 API", "Specification-Vendor": "http://jcp.org/en/jsr/detail?id=173", "Specification-Version": "1.0", - }, + }), }, }, }, @@ -253,7 +264,7 @@ func TestGeneratePackageCPEs(t *testing.T) { Metadata: pkg.JavaArchive{ VirtualPath: "/opt/jboss/keycloak/modules/system/layers/base/org/apache/cxf/impl/main/cxf-rt-bindings-xml-3.3.10.jar", Manifest: &pkg.JavaManifest{ - Main: map[string]string{ + Main: keyValues(map[string]string{ "Automatic-Module-Name": "org.apache.cxf.binding.xml", "Bnd-LastModified": "1615836524860", "Build-Jdk": "1.8.0_261", @@ -278,7 +289,7 @@ func TestGeneratePackageCPEs(t *testing.T) { "Specification-Vendor": "The Apache Software Foundation", "Specification-Version": "3.3.10", "Tool": "Bnd-4.2.0.201903051501", - }, + }), }, PomProperties: &pkg.JavaPomProperties{ Path: "META-INF/maven/org.apache.cxf/cxf-rt-bindings-xml/pom.properties", @@ -558,7 +569,7 @@ func TestGeneratePackageCPEs(t *testing.T) { FoundBy: "java-cataloger", Metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - Main: map[string]string{ + Main: keyValues(map[string]string{ "Extension-Name": "handlebars", "Group-Id": "org.jenkins-ci.ui", "Hudson-Version": "2.204", @@ -566,7 +577,7 @@ func TestGeneratePackageCPEs(t *testing.T) { "Implementation-Version": "3.0.8", "Plugin-Version": "3.0.8", "Short-Name": "handlebars", - }, + }), }, PomProperties: &pkg.JavaPomProperties{ GroupID: "org.jenkins-ci.ui", @@ -594,10 +605,10 @@ func TestGeneratePackageCPEs(t *testing.T) { Language: pkg.Java, Metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - Main: map[string]string{ + Main: keyValues(map[string]string{ "Extension-Name": "active-directory", "Group-Id": "org.jenkins-ci.plugins", - }, + }), }, PomProperties: &pkg.JavaPomProperties{ GroupID: "org.jenkins-ci.plugins", diff --git a/syft/pkg/cataloger/internal/cpegenerate/java.go b/syft/pkg/cataloger/internal/cpegenerate/java.go index 7bd324beceb..7de85c9e641 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/java.go +++ b/syft/pkg/cataloger/internal/cpegenerate/java.go @@ -66,7 +66,7 @@ func vendorsFromJavaManifestNames(p pkg.Package) fieldCandidateSet { for _, name := range javaManifestNameFields { if metadata.Manifest.Main != nil { - if value, exists := metadata.Manifest.Main[name]; exists { + if value, exists := metadata.Manifest.Main.Get(name); exists { if !startsWithTopLevelDomain(value) { vendors.add(fieldCandidate{ value: normalizePersonName(value), @@ -75,12 +75,12 @@ func vendorsFromJavaManifestNames(p pkg.Package) fieldCandidateSet { } } } - if metadata.Manifest.NamedSections != nil { - for _, section := range metadata.Manifest.NamedSections { + if metadata.Manifest.Sections != nil { + for _, section := range metadata.Manifest.Sections { if section == nil { continue } - if value, exists := section[name]; exists { + if value, exists := section.Get(name); exists { if !startsWithTopLevelDomain(value) { vendors.add(fieldCandidate{ value: normalizePersonName(value), @@ -275,13 +275,13 @@ func GetManifestFieldGroupIDs(manifest *pkg.JavaManifest, fields []string) (grou } for _, name := range fields { - if value, exists := manifest.Main[name]; exists { + if value, exists := manifest.Main.Get(name); exists { if startsWithTopLevelDomain(value) { groupIDs = append(groupIDs, cleanGroupID(value)) } } - for _, section := range manifest.NamedSections { - if value, exists := section[name]; exists { + for _, section := range manifest.Sections { + if value, exists := section.Get(name); exists { if startsWithTopLevelDomain(value) { groupIDs = append(groupIDs, cleanGroupID(value)) } diff --git a/syft/pkg/cataloger/internal/cpegenerate/java_test.go b/syft/pkg/cataloger/internal/cpegenerate/java_test.go index cdacd698bf9..a08c0fec9ec 100644 --- a/syft/pkg/cataloger/internal/cpegenerate/java_test.go +++ b/syft/pkg/cataloger/internal/cpegenerate/java_test.go @@ -188,8 +188,11 @@ func Test_groupIDsFromJavaPackage(t *testing.T) { pkg: pkg.Package{ Metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - Main: map[string]string{ - "Extension-Name": "io.jenkins-ci.plugin.thing", + Main: pkg.KeyValues{ + { + Key: "Extension-Name", + Value: "io.jenkins-ci.plugin.thing", + }, }, }, }, @@ -201,9 +204,16 @@ func Test_groupIDsFromJavaPackage(t *testing.T) { pkg: pkg.Package{ Metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - NamedSections: map[string]map[string]string{ - "section": { - "Extension-Name": "io.jenkins-ci.plugin.thing", + Sections: []pkg.KeyValues{ + { + { + Key: "Name", + Value: "section", + }, + { + Key: "Extension-Name", + Value: "io.jenkins-ci.plugin.thing", + }, }, }, }, @@ -216,20 +226,20 @@ func Test_groupIDsFromJavaPackage(t *testing.T) { pkg: pkg.Package{ Metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - Main: map[string]string{ + Main: []pkg.KeyValue{ // positive cases // tier 1 - "Extension-Name": "io.jenkins-ci.plugin.1", - "Specification-Vendor": "io.jenkins-ci.plugin.2", - "Implementation-Vendor": "io.jenkins-ci.plugin.3", - "Bundle-SymbolicName": "io.jenkins-ci.plugin.4", - "Implementation-Vendor-Id": "io.jenkins-ci.plugin.5", - "Implementation-Title": "io.jenkins-ci.plugin.6", - "Bundle-Activator": "io.jenkins-ci.plugin.7", + {Key: "Extension-Name", Value: "io.jenkins-ci.plugin.1"}, + {Key: "Specification-Vendor", Value: "io.jenkins-ci.plugin.2"}, + {Key: "Implementation-Vendor", Value: "io.jenkins-ci.plugin.3"}, + {Key: "Bundle-SymbolicName", Value: "io.jenkins-ci.plugin.4"}, + {Key: "Implementation-Vendor-Id", Value: "io.jenkins-ci.plugin.5"}, + {Key: "Implementation-Title", Value: "io.jenkins-ci.plugin.6"}, + {Key: "Bundle-Activator", Value: "io.jenkins-ci.plugin.7"}, // tier 2 - "Automatic-Module-Name": "io.jenkins-ci.plugin.8", - "Main-Class": "io.jenkins-ci.plugin.9", - "Package": "io.jenkins-ci.plugin.10", + {Key: "Automatic-Module-Name", Value: "io.jenkins-ci.plugin.8"}, + {Key: "Main-Class", Value: "io.jenkins-ci.plugin.9"}, + {Key: "Package", Value: "io.jenkins-ci.plugin.10"}, }, }, }, @@ -249,11 +259,11 @@ func Test_groupIDsFromJavaPackage(t *testing.T) { pkg: pkg.Package{ Metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - Main: map[string]string{ + Main: []pkg.KeyValue{ // positive cases - "Automatic-Module-Name": "io.jenkins-ci.plugin.8", - "Main-Class": "io.jenkins-ci.plugin.9", - "Package": "io.jenkins-ci.plugin.10", + {Key: "Automatic-Module-Name", Value: "io.jenkins-ci.plugin.8"}, + {Key: "Main-Class", Value: "io.jenkins-ci.plugin.9"}, + {Key: "Package", Value: "io.jenkins-ci.plugin.10"}, }, }, }, @@ -269,10 +279,10 @@ func Test_groupIDsFromJavaPackage(t *testing.T) { pkg: pkg.Package{ Metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - Main: map[string]string{ + Main: []pkg.KeyValue{ // negative cases - "Extension-Name": "not.a-group.id", - "bogus": "io.jenkins-ci.plugin.please-dont-find-me", + {Key: "Extension-Name", Value: "not.a-group.id"}, + {Key: "bogus", Value: "io.jenkins-ci.plugin.please-dont-find-me"}, }, }, }, @@ -284,21 +294,55 @@ func Test_groupIDsFromJavaPackage(t *testing.T) { pkg: pkg.Package{ Metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - NamedSections: map[string]map[string]string{ - "section": { + Sections: []pkg.KeyValues{ + { + { + Key: "Name", + Value: "section", + }, // positive cases // tier 1 - "Extension-Name": "io.jenkins-ci.plugin.1", - "Specification-Vendor": "io.jenkins-ci.plugin.2", - "Implementation-Vendor": "io.jenkins-ci.plugin.3", - "Bundle-SymbolicName": "io.jenkins-ci.plugin.4", - "Implementation-Vendor-Id": "io.jenkins-ci.plugin.5", - "Implementation-Title": "io.jenkins-ci.plugin.6", - "Bundle-Activator": "io.jenkins-ci.plugin.7", + { + Key: "Extension-Name", + Value: "io.jenkins-ci.plugin.1", + }, + { + Key: "Specification-Vendor", + Value: "io.jenkins-ci.plugin.2", + }, + { + Key: "Implementation-Vendor", + Value: "io.jenkins-ci.plugin.3", + }, + { + Key: "Bundle-SymbolicName", + Value: "io.jenkins-ci.plugin.4", + }, + { + Key: "Implementation-Vendor-Id", + Value: "io.jenkins-ci.plugin.5", + }, + { + Key: "Implementation-Title", + Value: "io.jenkins-ci.plugin.6", + }, + { + Key: "Bundle-Activator", + Value: "io.jenkins-ci.plugin.7", + }, // tier 2 - "Automatic-Module-Name": "io.jenkins-ci.plugin.8", - "Main-Class": "io.jenkins-ci.plugin.9", - "Package": "io.jenkins-ci.plugin.10", + { + Key: "Automatic-Module-Name", + Value: "io.jenkins-ci.plugin.8", + }, + { + Key: "Main-Class", + Value: "io.jenkins-ci.plugin.9", + }, + { + Key: "Package", + Value: "io.jenkins-ci.plugin.10", + }, }, }, }, @@ -319,11 +363,20 @@ func Test_groupIDsFromJavaPackage(t *testing.T) { pkg: pkg.Package{ Metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - NamedSections: map[string]map[string]string{ - "section": { - // negative cases - "Extension-Name": "not.a-group.id", - "bogus": "io.jenkins-ci.plugin.please-dont-find-me", + Sections: []pkg.KeyValues{ + { + { + Key: "Name", + Value: "section", + }, + { + Key: "Extension-Name", + Value: "not.a-group.id", + }, + { + Key: "bogus", + Value: "io.jenkins-ci.plugin.please-dont-find-me", + }, }, }, }, @@ -403,11 +456,21 @@ func Test_vendorsFromJavaManifestNames(t *testing.T) { pkg: pkg.Package{ Metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - NamedSections: map[string]map[string]string{ - "section": { + Sections: []pkg.KeyValues{ + { + { + Key: "Name", + Value: "section", + }, // positive cases - "Specification-Vendor": "Alex Goodman", - "Implementation-Vendor": "William Goodman", + { + Key: "Specification-Vendor", + Value: "Alex Goodman", + }, + { + Key: "Implementation-Vendor", + Value: "William Goodman", + }, }, }, }, @@ -420,11 +483,22 @@ func Test_vendorsFromJavaManifestNames(t *testing.T) { pkg: pkg.Package{ Metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - NamedSections: map[string]map[string]string{ - "section": { + Sections: []pkg.KeyValues{ + { + { + Key: "Name", + Value: "section", + }, // negative cases - "Specification-Vendor": "io.jenkins-ci.plugin.thing", - "Implementation-Vendor-ID": "William Goodman", + + { + Key: "Specification-Vendor", + Value: "io.jenkins-ci.plugin.thing", + }, + { + Key: "Implementation-Vendor-ID", + Value: "William Goodman", + }, }, }, }, @@ -459,8 +533,11 @@ func Test_groupIDsFromJavaManifest(t *testing.T) { { name: "spring-foo", manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Implementation-Vendor": "org.foo", + Main: []pkg.KeyValue{ + { + Key: "Implementation-Vendor", + Value: "org.foo", + }, }, }, expected: []string{"org.foo"}, diff --git a/syft/pkg/cataloger/java/archive_parser_test.go b/syft/pkg/cataloger/java/archive_parser_test.go index a81a1aa6bac..4c49d22fb53 100644 --- a/syft/pkg/cataloger/java/archive_parser_test.go +++ b/syft/pkg/cataloger/java/archive_parser_test.go @@ -14,6 +14,7 @@ import ( "syscall" "testing" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/gookit/color" "github.com/scylladb/go-set/strset" @@ -197,31 +198,31 @@ func TestParseJar(t *testing.T) { Metadata: pkg.JavaArchive{ VirtualPath: "test-fixtures/java-builds/packages/example-jenkins-plugin.hpi", Manifest: &pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", - "Specification-Title": "Example Jenkins Plugin", - "Specification-Version": "1.0", - "Implementation-Title": "Example Jenkins Plugin", - "Implementation-Version": "1.0-SNAPSHOT", + Main: pkg.KeyValues{ + {Key: "Manifest-Version", Value: "1.0"}, + {Key: "Created-By", Value: "Maven Archiver 3.6.0"}, + {Key: "Build-Jdk-Spec", Value: "18"}, + {Key: "Specification-Title", Value: "Example Jenkins Plugin"}, + {Key: "Specification-Version", Value: "1.0"}, + {Key: "Implementation-Title", Value: "Example Jenkins Plugin"}, + {Key: "Implementation-Version", Value: "1.0-SNAPSHOT"}, + {Key: "Group-Id", Value: "io.jenkins.plugins"}, + {Key: "Short-Name", Value: "example-jenkins-plugin"}, + {Key: "Long-Name", Value: "Example Jenkins Plugin"}, + {Key: "Hudson-Version", Value: "2.204"}, + {Key: "Jenkins-Version", Value: "2.204"}, + {Key: "Plugin-Dependencies", Value: "structs:1.20"}, + {Key: "Plugin-Developers", Value: ""}, + {Key: "Plugin-License-Name", Value: "MIT License"}, + {Key: "Plugin-License-Url", Value: "https://opensource.org/licenses/MIT"}, + {Key: "Plugin-ScmUrl", Value: "https://github.com/jenkinsci/plugin-pom/example-jenkins-plugin"}, // extra fields... - //"Archiver-Version": "Plexus Archiver", - "Plugin-License-Url": "https://opensource.org/licenses/MIT", - "Plugin-License-Name": "MIT License", - "Created-By": "Maven Archiver 3.6.0", - //"Built-By": "?", - //"Build-Jdk": "14.0.1", - "Build-Jdk-Spec": "18", - "Jenkins-Version": "2.204", - //"Minimum-Java-Version": "1.8", - "Plugin-Developers": "", - "Plugin-ScmUrl": "https://github.com/jenkinsci/plugin-pom/example-jenkins-plugin", - //"Extension-Name": "example-jenkins-plugin", - "Short-Name": "example-jenkins-plugin", - "Group-Id": "io.jenkins.plugins", - "Plugin-Dependencies": "structs:1.20", - //"Plugin-Version": "1.0-SNAPSHOT (private-07/09/2020 13:30-?)", - "Hudson-Version": "2.204", - "Long-Name": "Example Jenkins Plugin", + //{Key: "Minimum-Java-Version", Value: "1.8"}, + //{Key: "Archiver-Version", Value: "Plexus Archiver"}, + //{Key: "Built-By", Value: "?"}, + //{Key: "Build-Jdk", Value: "14.0.1"}, + //{Key: "Extension-Name", Value: "example-jenkins-plugin"}, + //{Key: "Plugin-Version", Value: "1.0-SNAPSHOT (private-07/09/2020 13:30-?)"}, }, }, PomProperties: &pkg.JavaPomProperties{ @@ -255,9 +256,15 @@ func TestParseJar(t *testing.T) { Metadata: pkg.JavaArchive{ VirtualPath: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar", Manifest: &pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", - "Main-Class": "hello.HelloWorld", + Main: []pkg.KeyValue{ + { + Key: "Manifest-Version", + Value: "1.0", + }, + { + Key: "Main-Class", + Value: "hello.HelloWorld", + }, }, }, }, @@ -326,14 +333,32 @@ func TestParseJar(t *testing.T) { Metadata: pkg.JavaArchive{ VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar", Manifest: &pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", + Main: []pkg.KeyValue{ + { + Key: "Manifest-Version", + Value: "1.0", + }, // extra fields... - "Archiver-Version": "Plexus Archiver", - "Created-By": "Apache Maven 3.8.6", - //"Built-By": "?", - //"Build-Jdk": "14.0.1", - "Main-Class": "hello.HelloWorld", + { + Key: "Archiver-Version", + Value: "Plexus Archiver", + }, + { + Key: "Created-By", + Value: "Apache Maven 3.8.6", + }, + //{ + // Key: "Built-By", + // Value: "?", + //}, + //{ + // Key: "Build-Jdk", + // Value: "14.0.1", + //}, + { + Key: "Main-Class", + Value: "hello.HelloWorld", + }, }, }, PomProperties: &pkg.JavaPomProperties{ @@ -455,7 +480,14 @@ func TestParseJar(t *testing.T) { // ignore select fields (only works for the main section) for _, field := range test.ignoreExtras { if metadata.Manifest != nil && metadata.Manifest.Main != nil { - delete(metadata.Manifest.Main, field) + newMain := make(pkg.KeyValues, 0) + for i, kv := range metadata.Manifest.Main { + if kv.Key == field { + continue + } + newMain = append(newMain, metadata.Manifest.Main[i]) + } + metadata.Manifest.Main = newMain } } @@ -1158,32 +1190,32 @@ func Test_parseJavaArchive_regressions(t *testing.T) { Metadata: pkg.JavaArchive{ VirtualPath: "test-fixtures/jar-metadata/cache/jackson-core-2.15.2.jar", Manifest: &pkg.JavaManifest{ - Main: map[string]string{ - "Build-Jdk-Spec": "1.8", - "Bundle-Description": "Core Jackson processing abstractions", - "Bundle-DocURL": "https://github.com/FasterXML/jackson-core", - "Bundle-License": "https://www.apache.org/licenses/LICENSE-2.0.txt", - "Bundle-ManifestVersion": "2", - "Bundle-Name": "Jackson-core", - "Bundle-SymbolicName": "com.fasterxml.jackson.core.jackson-core", - "Bundle-Vendor": "FasterXML", - "Bundle-Version": "2.15.2", - "Created-By": "Apache Maven Bundle Plugin 5.1.8", - "Export-Package": "com.fasterxml.jackson.core;version...snip", - "Implementation-Title": "Jackson-core", - "Implementation-Vendor": "FasterXML", - "Implementation-Vendor-Id": "com.fasterxml.jackson.core", - "Implementation-Version": "2.15.2", - "Import-Package": "com.fasterxml.jackson.core;version=...snip", - "Manifest-Version": "1.0", - "Multi-Release": "true", - "Require-Capability": `osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"`, - "Specification-Title": "Jackson-core", - "Specification-Vendor": "FasterXML", - "Specification-Version": "2.15.2", - "Tool": "Bnd-6.3.1.202206071316", - "X-Compile-Source-JDK": "1.8", - "X-Compile-Target-JDK": "1.8", + Main: pkg.KeyValues{ + {Key: "Manifest-Version", Value: "1.0"}, + {Key: "Bundle-License", Value: "https://www.apache.org/licenses/LICENSE-2.0.txt"}, + {Key: "Bundle-SymbolicName", Value: "com.fasterxml.jackson.core.jackson-core"}, + {Key: "Implementation-Vendor-Id", Value: "com.fasterxml.jackson.core"}, + {Key: "Specification-Title", Value: "Jackson-core"}, + {Key: "Bundle-DocURL", Value: "https://github.com/FasterXML/jackson-core"}, + {Key: "Import-Package", Value: "com.fasterxml.jackson.core;version=...snip"}, + {Key: "Require-Capability", Value: `osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"`}, + {Key: "Export-Package", Value: "com.fasterxml.jackson.core;version...snip"}, + {Key: "Bundle-Name", Value: "Jackson-core"}, + {Key: "Multi-Release", Value: "true"}, + {Key: "Build-Jdk-Spec", Value: "1.8"}, + {Key: "Bundle-Description", Value: "Core Jackson processing abstractions"}, + {Key: "Implementation-Title", Value: "Jackson-core"}, + {Key: "Implementation-Version", Value: "2.15.2"}, + {Key: "Bundle-ManifestVersion", Value: "2"}, + {Key: "Specification-Vendor", Value: "FasterXML"}, + {Key: "Bundle-Vendor", Value: "FasterXML"}, + {Key: "Tool", Value: "Bnd-6.3.1.202206071316"}, + {Key: "Implementation-Vendor", Value: "FasterXML"}, + {Key: "Bundle-Version", Value: "2.15.2"}, + {Key: "X-Compile-Target-JDK", Value: "1.8"}, + {Key: "X-Compile-Source-JDK", Value: "1.8"}, + {Key: "Created-By", Value: "Apache Maven Bundle Plugin 5.1.8"}, + {Key: "Specification-Version", Value: "2.15.2"}, }, }, // not under test @@ -1212,32 +1244,32 @@ func Test_parseJavaArchive_regressions(t *testing.T) { Metadata: pkg.JavaArchive{ VirtualPath: "test-fixtures/jar-metadata/cache/com.fasterxml.jackson.core.jackson-core-2.15.2.jar", Manifest: &pkg.JavaManifest{ - Main: map[string]string{ - "Build-Jdk-Spec": "1.8", - "Bundle-Description": "Core Jackson processing abstractions", - "Bundle-DocURL": "https://github.com/FasterXML/jackson-core", - "Bundle-License": "https://www.apache.org/licenses/LICENSE-2.0.txt", - "Bundle-ManifestVersion": "2", - "Bundle-Name": "Jackson-core", - "Bundle-SymbolicName": "com.fasterxml.jackson.core.jackson-core", - "Bundle-Vendor": "FasterXML", - "Bundle-Version": "2.15.2", - "Created-By": "Apache Maven Bundle Plugin 5.1.8", - "Export-Package": "com.fasterxml.jackson.core;version...snip", - "Implementation-Title": "Jackson-core", - "Implementation-Vendor": "FasterXML", - "Implementation-Vendor-Id": "com.fasterxml.jackson.core", - "Implementation-Version": "2.15.2", - "Import-Package": "com.fasterxml.jackson.core;version=...snip", - "Manifest-Version": "1.0", - "Multi-Release": "true", - "Require-Capability": `osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"`, - "Specification-Title": "Jackson-core", - "Specification-Vendor": "FasterXML", - "Specification-Version": "2.15.2", - "Tool": "Bnd-6.3.1.202206071316", - "X-Compile-Source-JDK": "1.8", - "X-Compile-Target-JDK": "1.8", + Main: pkg.KeyValues{ + {Key: "Manifest-Version", Value: "1.0"}, + {Key: "Bundle-License", Value: "https://www.apache.org/licenses/LICENSE-2.0.txt"}, + {Key: "Bundle-SymbolicName", Value: "com.fasterxml.jackson.core.jackson-core"}, + {Key: "Implementation-Vendor-Id", Value: "com.fasterxml.jackson.core"}, + {Key: "Specification-Title", Value: "Jackson-core"}, + {Key: "Bundle-DocURL", Value: "https://github.com/FasterXML/jackson-core"}, + {Key: "Import-Package", Value: "com.fasterxml.jackson.core;version=...snip"}, + {Key: "Require-Capability", Value: `osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"`}, + {Key: "Export-Package", Value: "com.fasterxml.jackson.core;version...snip"}, + {Key: "Bundle-Name", Value: "Jackson-core"}, + {Key: "Multi-Release", Value: "true"}, + {Key: "Build-Jdk-Spec", Value: "1.8"}, + {Key: "Bundle-Description", Value: "Core Jackson processing abstractions"}, + {Key: "Implementation-Title", Value: "Jackson-core"}, + {Key: "Implementation-Version", Value: "2.15.2"}, + {Key: "Bundle-ManifestVersion", Value: "2"}, + {Key: "Specification-Vendor", Value: "FasterXML"}, + {Key: "Bundle-Vendor", Value: "FasterXML"}, + {Key: "Tool", Value: "Bnd-6.3.1.202206071316"}, + {Key: "Implementation-Vendor", Value: "FasterXML"}, + {Key: "Bundle-Version", Value: "2.15.2"}, + {Key: "X-Compile-Target-JDK", Value: "1.8"}, + {Key: "X-Compile-Source-JDK", Value: "1.8"}, + {Key: "Created-By", Value: "Apache Maven Bundle Plugin 5.1.8"}, + {Key: "Specification-Version", Value: "2.15.2"}, }, }, // not under test @@ -1261,11 +1293,23 @@ func Test_parseJavaArchive_regressions(t *testing.T) { Metadata: pkg.JavaArchive{ VirtualPath: "test-fixtures/jar-metadata/cache/api-all-2.0.0-sources.jar", Manifest: &pkg.JavaManifest{ - Main: map[string]string{ - "Build-Jdk": "1.8.0_191", - "Built-By": "elecharny", - "Created-By": "Apache Maven 3.6.0", - "Manifest-Version": "1.0", + Main: []pkg.KeyValue{ + { + Key: "Manifest-Version", + Value: "1.0", + }, + { + Key: "Built-By", + Value: "elecharny", + }, + { + Key: "Created-By", + Value: "Apache Maven 3.6.0", + }, + { + Key: "Build-Jdk", + Value: "1.8.0_191", + }, }, }, PomProperties: &pkg.JavaPomProperties{ @@ -1314,10 +1358,25 @@ func Test_parseJavaArchive_regressions(t *testing.T) { if tt.assignParent { assignParent(&tt.expectedPkgs[0], tt.expectedPkgs[1:]...) } + for i := range tt.expectedPkgs { + tt.expectedPkgs[i].SetID() + } pkgtest.NewCatalogTester(). FromFile(t, generateJavaMetadataJarFixture(t, tt.fixtureName)). Expects(tt.expectedPkgs, tt.expectedRelationships). - WithCompareOptions(cmpopts.IgnoreFields(pkg.JavaArchive{}, "ArchiveDigests")). + WithCompareOptions( + cmpopts.IgnoreFields(pkg.JavaArchive{}, "ArchiveDigests"), + cmp.Comparer(func(x, y pkg.KeyValue) bool { + if x.Key != y.Key { + return false + } + if x.Value != y.Value { + return false + } + + return true + }), + ). TestParser(t, gap.parseJavaArchive) }) } diff --git a/syft/pkg/cataloger/java/package_url_test.go b/syft/pkg/cataloger/java/package_url_test.go index c6b156d9ab2..4153adb8363 100644 --- a/syft/pkg/cataloger/java/package_url_test.go +++ b/syft/pkg/cataloger/java/package_url_test.go @@ -24,8 +24,11 @@ func Test_packageURL(t *testing.T) { Metadata: pkg.JavaArchive{ VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar", Manifest: &pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", + Main: []pkg.KeyValue{ + { + Key: "Manifest-Version", + Value: "1.0", + }, }, }, PomProperties: &pkg.JavaPomProperties{ @@ -49,8 +52,11 @@ func Test_packageURL(t *testing.T) { Metadata: pkg.JavaArchive{ VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar", Manifest: &pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", + Main: []pkg.KeyValue{ + { + Key: "Manifest-Version", + Value: "1.0", + }, }, }, PomProperties: &pkg.JavaPomProperties{ @@ -74,8 +80,11 @@ func Test_packageURL(t *testing.T) { Metadata: pkg.JavaArchive{ VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar", Manifest: &pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", + Main: []pkg.KeyValue{ + { + Key: "Manifest-Version", + Value: "1.0", + }, }, }, PomProperties: &pkg.JavaPomProperties{ @@ -101,8 +110,11 @@ func Test_packageURL(t *testing.T) { Metadata: pkg.JavaArchive{ VirtualPath: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar", Manifest: &pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", + Main: []pkg.KeyValue{ + { + Key: "Manifest-Version", + Value: "1.0", + }, }, }, PomProperties: &pkg.JavaPomProperties{ @@ -163,8 +175,11 @@ func Test_groupIDFromJavaMetadata(t *testing.T) { name: "java manifest", metadata: pkg.JavaArchive{ Manifest: &pkg.JavaManifest{ - Main: map[string]string{ - "Implementation-Vendor": "org.anchore", + Main: []pkg.KeyValue{ + { + Key: "Implementation-Vendor", + Value: "org.anchore", + }, }, }, }, diff --git a/syft/pkg/cataloger/java/parse_java_manifest.go b/syft/pkg/cataloger/java/parse_java_manifest.go index 50088111f9c..27a42d96193 100644 --- a/syft/pkg/cataloger/java/parse_java_manifest.go +++ b/syft/pkg/cataloger/java/parse_java_manifest.go @@ -4,7 +4,6 @@ import ( "bufio" "fmt" "io" - "strconv" "strings" "github.com/anchore/syft/internal/log" @@ -19,7 +18,7 @@ const manifestGlob = "/META-INF/MANIFEST.MF" //nolint:funlen func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error) { var manifest pkg.JavaManifest - var sections []map[string]string + sections := make([]pkg.KeyValues, 0) currentSection := func() int { return len(sections) - 1 @@ -50,7 +49,9 @@ func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error) continue } - sections[currentSection()][lastKey] += strings.TrimSpace(line) + lastSection := sections[currentSection()] + + sections[currentSection()][len(lastSection)-1].Value += strings.TrimSpace(line) continue } @@ -72,10 +73,13 @@ func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error) if lastKey == "" { // we're entering a new section - sections = append(sections, make(map[string]string)) + sections = append(sections, make(pkg.KeyValues, 0)) } - sections[currentSection()][key] = value + sections[currentSection()] = append(sections[currentSection()], pkg.KeyValue{ + Key: key, + Value: value, + }) // keep track of key for potential future continuations lastKey = key @@ -88,20 +92,7 @@ func parseJavaManifest(path string, reader io.Reader) (*pkg.JavaManifest, error) if len(sections) > 0 { manifest.Main = sections[0] if len(sections) > 1 { - manifest.NamedSections = make(map[string]map[string]string) - for i, s := range sections[1:] { - name, ok := s["Name"] - 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.Debugf("java manifest section found without a name: %s", path) - name = strconv.Itoa(i) - } else { - delete(s, "Name") - } - manifest.NamedSections[name] = s - } + manifest.Sections = sections[1:] } } @@ -126,12 +117,12 @@ func extractNameFromApacheMavenBundlePlugin(manifest *pkg.JavaManifest) string { // The computed symbolic name is also stored in the $(maven-symbolicname) property in case you want to add attributes or directives to it. // if manifest != nil { - if strings.Contains(manifest.Main["Created-By"], "Apache Maven Bundle Plugin") { - if symbolicName := manifest.Main["Bundle-SymbolicName"]; symbolicName != "" { + if strings.Contains(manifest.Main.MustGet("Created-By"), "Apache Maven Bundle Plugin") { + if symbolicName := manifest.Main.MustGet("Bundle-SymbolicName"); symbolicName != "" { // It is possible that `Bundle-SymbolicName` is just the groupID (like in the case of // https://repo1.maven.org/maven2/com/google/oauth-client/google-oauth-client/1.25.0/google-oauth-client-1.25.0.jar), // so if `Implementation-Vendor-Id` is equal to `Bundle-SymbolicName`, bail on this logic - if vendorID := manifest.Main["Implementation-Vendor-Id"]; vendorID != "" && vendorID == symbolicName { + if vendorID := manifest.Main.MustGet("Implementation-Vendor-Id"); vendorID != "" && vendorID == symbolicName { return "" } @@ -184,21 +175,21 @@ func selectName(manifest *pkg.JavaManifest, filenameObj archiveFilename) string // remaining fields in the manifest is a bit of a free-for-all depending on the build tooling used and package maintainer preferences if manifest != nil { switch { - case manifest.Main["Name"] != "": + case manifest.Main.MustGet("Name") != "": // Manifest original spec... - return manifest.Main["Name"] - case manifest.Main["Bundle-Name"] != "": + return manifest.Main.MustGet("Name") + case manifest.Main.MustGet("Bundle-Name") != "": // BND tooling... TODO: this does not seem accurate (I don't see a reference in the BND tooling docs for this) - return manifest.Main["Bundle-Name"] - case manifest.Main["Short-Name"] != "": + return manifest.Main.MustGet("Bundle-Name") + case manifest.Main.MustGet("Short-Name") != "": // Jenkins... - return manifest.Main["Short-Name"] - case manifest.Main["Extension-Name"] != "": + return manifest.Main.MustGet("Short-Name") + case manifest.Main.MustGet("Extension-Name") != "": // Jenkins... - return manifest.Main["Extension-Name"] - case manifest.Main["Implementation-Title"] != "": + return manifest.Main.MustGet("Extension-Name") + case manifest.Main.MustGet("Implementation-Title") != "": // last ditch effort... - return manifest.Main["Implementation-Title"] + return manifest.Main.MustGet("Implementation-Title") } } return "" @@ -250,12 +241,12 @@ func selectLicenses(manifest *pkg.JavaManifest) []string { } func fieldValueFromManifest(manifest pkg.JavaManifest, fieldName string) string { - if value := manifest.Main[fieldName]; value != "" { + if value := manifest.Main.MustGet(fieldName); value != "" { return value } - for _, section := range manifest.NamedSections { - if value := section[fieldName]; value != "" { + for _, section := range manifest.Sections { + if value := section.MustGet(fieldName); value != "" { return value } } diff --git a/syft/pkg/cataloger/java/parse_java_manifest_test.go b/syft/pkg/cataloger/java/parse_java_manifest_test.go index 7bca35bd83f..11a51da7fe7 100644 --- a/syft/pkg/cataloger/java/parse_java_manifest_test.go +++ b/syft/pkg/cataloger/java/parse_java_manifest_test.go @@ -19,41 +19,63 @@ func TestParseJavaManifest(t *testing.T) { { fixture: "test-fixtures/manifest/small", expected: pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", + Main: pkg.KeyValues{ + {Key: "Manifest-Version", Value: "1.0"}, }, }, }, { fixture: "test-fixtures/manifest/standard-info", expected: pkg.JavaManifest{ - Main: map[string]string{ - "Name": "the-best-name", - "Manifest-Version": "1.0", - "Specification-Title": "the-spec-title", - "Specification-Version": "the-spec-version", - "Specification-Vendor": "the-spec-vendor", - "Implementation-Title": "the-impl-title", - "Implementation-Version": "the-impl-version", - "Implementation-Vendor": "the-impl-vendor", + Main: pkg.KeyValues{ + {Key: "Manifest-Version", Value: "1.0"}, + {Key: "Name", Value: "the-best-name"}, + {Key: "Specification-Title", Value: "the-spec-title"}, + {Key: "Specification-Vendor", Value: "the-spec-vendor"}, + {Key: "Specification-Version", Value: "the-spec-version"}, + {Key: "Implementation-Title", Value: "the-impl-title"}, + {Key: "Implementation-Vendor", Value: "the-impl-vendor"}, + {Key: "Implementation-Version", Value: "the-impl-version"}, }, }, }, { fixture: "test-fixtures/manifest/extra-info", expected: pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", - "Archiver-Version": "Plexus Archiver", - "Created-By": "Apache Maven 3.6.3", + Main: []pkg.KeyValue{ + { + Key: "Manifest-Version", + Value: "1.0", + }, + { + Key: "Archiver-Version", + Value: "Plexus Archiver", + }, + { + Key: "Created-By", + Value: "Apache Maven 3.6.3", + }, }, - NamedSections: map[string]map[string]string{ - "thing-1": { - "Built-By": "?", + Sections: []pkg.KeyValues{ + { + { + Key: "Name", + Value: "thing-1", + }, + { + Key: "Built-By", + Value: "?", + }, }, - "1": { - "Build-Jdk": "14.0.1", - "Main-Class": "hello.HelloWorld", + { + { + Key: "Build-Jdk", + Value: "14.0.1", + }, + { + Key: "Main-Class", + Value: "hello.HelloWorld", + }, }, }, }, @@ -61,23 +83,34 @@ func TestParseJavaManifest(t *testing.T) { { fixture: "test-fixtures/manifest/extra-empty-lines", expected: pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", - "Archiver-Version": "Plexus Archiver", - "Created-By": "Apache Maven 3.6.3", + Main: pkg.KeyValues{ + { + Key: "Manifest-Version", + Value: "1.0", + }, + { + Key: "Archiver-Version", + Value: "Plexus Archiver", + }, + { + Key: "Created-By", + Value: "Apache Maven 3.6.3", + }, }, - NamedSections: map[string]map[string]string{ - "thing-1": { - "Built-By": "?", + Sections: []pkg.KeyValues{ + { + {Key: "Name", Value: "thing-1"}, + {Key: "Built-By", Value: "?"}, }, - "thing-2": { - "Built-By": "someone!", + { + {Key: "Name", Value: "thing-2"}, + {Key: "Built-By", Value: "someone!"}, }, - "2": { - "Other": "things", + { + {Key: "Other", Value: "things"}, }, - "3": { - "Last": "item", + { + {Key: "Last", Value: "item"}, }, }, }, @@ -85,9 +118,15 @@ func TestParseJavaManifest(t *testing.T) { { fixture: "test-fixtures/manifest/continuation", expected: pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", - "Plugin-ScmUrl": "https://github.com/jenkinsci/plugin-pom/example-jenkins-plugin", + Main: pkg.KeyValues{ + { + Key: "Manifest-Version", + Value: "1.0", + }, + { + Key: "Plugin-ScmUrl", + Value: "https://github.com/jenkinsci/plugin-pom/example-jenkins-plugin", + }, }, }, }, @@ -95,9 +134,15 @@ func TestParseJavaManifest(t *testing.T) { // regression test, we should always keep the full version fixture: "test-fixtures/manifest/version-with-date", expected: pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", - "Implementation-Version": "1.3 2244 October 5 2005", + Main: []pkg.KeyValue{ + { + Key: "Manifest-Version", + Value: "1.0", + }, + { + Key: "Implementation-Version", + Value: "1.3 2244 October 5 2005", + }, }, }, }, @@ -106,9 +151,15 @@ func TestParseJavaManifest(t *testing.T) { // https://github.com/anchore/syft/issues/2179 fixture: "test-fixtures/manifest/leading-space", expected: pkg.JavaManifest{ - Main: map[string]string{ - "Key-keykeykey": "initialconfig:com$ # aka not empty line", - "should": "parse", + Main: []pkg.KeyValue{ + { + Key: "Key-keykeykey", + Value: "initialconfig:com$ # aka not empty line", + }, + { + Key: "should", + Value: "parse", + }, }, }, }, @@ -154,8 +205,11 @@ func TestSelectName(t *testing.T) { desc: "Get name from Implementation-Title", archive: archiveFilename{}, manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Implementation-Title": "maven-wrapper", + Main: []pkg.KeyValue{ + { + Key: "Implementation-Title", + Value: "maven-wrapper", + }, }, }, expected: "maven-wrapper", @@ -163,9 +217,15 @@ func TestSelectName(t *testing.T) { { desc: "Implementation-Title does not override name from filename", manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Name": "foo", - "Implementation-Title": "maven-wrapper", + Main: []pkg.KeyValue{ + { + Key: "Name", + Value: "foo", + }, + { + Key: "Implementation-Title", + Value: "maven-wrapper", + }, }, }, archive: newJavaArchiveFilename("/something/omg.jar"), @@ -174,11 +234,11 @@ func TestSelectName(t *testing.T) { { desc: "Use the artifact ID baked by the Apache Maven Bundle Plugin", manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Created-By": "Apache Maven Bundle Plugin", - "Bundle-SymbolicName": "com.atlassian.gadgets.atlassian-gadgets-api", - "Name": "foo", - "Implementation-Title": "maven-wrapper", + Main: pkg.KeyValues{ + {Key: "Created-By", Value: "Apache Maven Bundle Plugin"}, + {Key: "Bundle-SymbolicName", Value: "com.atlassian.gadgets.atlassian-gadgets-api"}, + {Key: "Name", Value: "foo"}, + {Key: "Implementation-Title", Value: "maven-wrapper"}, }, }, archive: newJavaArchiveFilename("/something/omg.jar"), @@ -188,11 +248,11 @@ func TestSelectName(t *testing.T) { // example: pkg:maven/org.apache.servicemix.bundles/org.apache.servicemix.bundles.spring-beans@5.3.26_1 desc: "Apache Maven Bundle Plugin might bake a version in the created-by field", manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Created-By": "Apache Maven Bundle Plugin 5.1.6", - "Bundle-SymbolicName": "com.atlassian.gadgets.atlassian-gadgets-api", - "Name": "foo", - "Implementation-Title": "maven-wrapper", + Main: pkg.KeyValues{ + {Key: "Created-By", Value: "Apache Maven Bundle Plugin 5.1.6"}, + {Key: "Bundle-SymbolicName", Value: "com.atlassian.gadgets.atlassian-gadgets-api"}, + {Key: "Name", Value: "foo"}, + {Key: "Implementation-Title", Value: "maven-wrapper"}, }, }, archive: newJavaArchiveFilename("/something/omg.jar"), @@ -201,9 +261,15 @@ func TestSelectName(t *testing.T) { { desc: "Filename looks like a groupid + artifact id", manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Name": "foo", - "Implementation-Title": "maven-wrapper", + Main: []pkg.KeyValue{ + { + Key: "Name", + Value: "foo", + }, + { + Key: "Implementation-Title", + Value: "maven-wrapper", + }, }, }, archive: newJavaArchiveFilename("/something/com.atlassian.gadgets.atlassian-gadgets-api.jar"), @@ -212,8 +278,11 @@ func TestSelectName(t *testing.T) { { desc: "Skip stripping groupId prefix from archive filename for org.eclipse", manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Automatic-Module-Name": "org.eclipse.ant.core", + Main: []pkg.KeyValue{ + { + Key: "Automatic-Module-Name", + Value: "org.eclipse.ant.core", + }, }, }, archive: newJavaArchiveFilename("/something/org.eclipse.ant.core-3.7.0.jar"), @@ -223,21 +292,21 @@ func TestSelectName(t *testing.T) { // example: pkg:maven/com.google.oauth-client/google-oauth-client@1.25.0 desc: "skip Apache Maven Bundle Plugin logic if symbolic name is same as vendor id", manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Bundle-DocURL": "http://www.google.com/", - "Bundle-License": "http://www.apache.org/licenses/LICENSE-2.0.txt", - "Bundle-ManifestVersion": "2", - "Bundle-Name": "Google OAuth Client Library for Java", - "Bundle-RequiredExecutionEnvironment": "JavaSE-1.6", - "Bundle-SymbolicName": "com.google.oauth-client", - "Bundle-Vendor": "Google", - "Bundle-Version": "1.25.0", - "Created-By": "Apache Maven Bundle Plugin", - "Export-Package": "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\"", - "Implementation-Title": "Google OAuth Client Library for Java", - "Implementation-Vendor": "Google", - "Implementation-Vendor-Id": "com.google.oauth-client", - "Implementation-Version": "1.25.0", + Main: pkg.KeyValues{ + {Key: "Bundle-DocURL", Value: "http://www.google.com/"}, + {Key: "Bundle-License", Value: "http://www.apache.org/licenses/LICENSE-2.0.txt"}, + {Key: "Bundle-ManifestVersion", Value: "2"}, + {Key: "Bundle-Name", Value: "Google OAuth Client Library for Java"}, + {Key: "Bundle-RequiredExecutionEnvironment", Value: "JavaSE-1.6"}, + {Key: "Bundle-SymbolicName", Value: "com.google.oauth-client"}, + {Key: "Bundle-Vendor", Value: "Google"}, + {Key: "Bundle-Version", Value: "1.25.0"}, + {Key: "Created-By", Value: "Apache Maven Bundle Plugin"}, + {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\""}, + {Key: "Implementation-Title", Value: "Google OAuth Client Library for Java"}, + {Key: "Implementation-Vendor", Value: "Google"}, + {Key: "Implementation-Vendor-Id", Value: "com.google.oauth-client"}, + {Key: "Implementation-Version", Value: "1.25.0"}, }, }, archive: newJavaArchiveFilename("/something/google-oauth-client-1.25.0.jar"), @@ -267,8 +336,11 @@ func TestSelectVersion(t *testing.T) { name: "Get name from Implementation-Version", archive: archiveFilename{}, manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Implementation-Version": "1.8.2", + Main: []pkg.KeyValue{ + { + Key: "Implementation-Version", + Value: "1.8.2", + }, }, }, expected: "1.8.2", @@ -276,9 +348,15 @@ func TestSelectVersion(t *testing.T) { { name: "Implementation-Version takes precedence over Specification-Version", manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Implementation-Version": "1.8.2", - "Specification-Version": "1.0", + Main: []pkg.KeyValue{ + { + Key: "Implementation-Version", + Value: "1.8.2", + }, + { + Key: "Specification-Version", + Value: "1.0", + }, }, }, expected: "1.8.2", @@ -286,14 +364,15 @@ func TestSelectVersion(t *testing.T) { { name: "Implementation-Version found outside the main section", manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", - "Ant-Version": "Apache Ant 1.8.2", - "Created-By": "1.5.0_22-b03 (Sun Microsystems Inc.)", + Main: pkg.KeyValues{ + {Key: "Manifest-Version", Value: "1.0"}, + {Key: "Ant-Version", Value: "Apache Ant 1.8.2"}, + {Key: "Created-By", Value: "1.5.0_22-b03 (Sun Microsystems Inc.)"}, }, - NamedSections: map[string]map[string]string{ - "org/apache/tools/ant/taskdefs/optional/": { - "Implementation-Version": "1.8.2", + Sections: []pkg.KeyValues{ + { + {Key: "Name", Value: "org/apache/tools/ant/taskdefs/optional/"}, + {Key: "Implementation-Version", Value: "1.8.2"}, }, }, }, @@ -302,34 +381,53 @@ func TestSelectVersion(t *testing.T) { { name: "Implementation-Version takes precedence over Specification-Version in subsequent section", manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", - "Ant-Version": "Apache Ant 1.8.2", - "Created-By": "1.5.0_22-b03 (Sun Microsystems Inc.)", - "Specification-Version": "2.0", + Main: pkg.KeyValues{ + {Key: "Manifest-Version", Value: "1.0"}, + {Key: "Ant-Version", Value: "Apache Ant 1.8.2"}, + {Key: "Created-By", Value: "1.5.0_22-b03 (Sun Microsystems Inc.)"}, + {Key: "Specification-Version", Value: "2.0"}, }, - NamedSections: map[string]map[string]string{ - "org/apache/tools/ant/taskdefs/optional/": { - "Specification-Version": "1.8", + Sections: []pkg.KeyValues{ + { + {Key: "Name", Value: "org/apache/tools/ant/taskdefs/optional/"}, + {Key: "Specification-Version", Value: "1.8"}, }, - "some-other-section": { - "Implementation-Version": "1.8.2", + { + {Key: "Name", Value: "some-other-section"}, + {Key: "Implementation-Version", Value: "1.8.2"}, }, }, }, + expected: "1.8.2", }, { name: "Implementation-Version takes precedence over Specification-Version in subsequent section", manifest: pkg.JavaManifest{ - Main: map[string]string{ - "Manifest-Version": "1.0", - "Ant-Version": "Apache Ant 1.8.2", - "Created-By": "1.5.0_22-b03 (Sun Microsystems Inc.)", + Main: []pkg.KeyValue{ + { + Key: "Manifest-Version", + Value: "1.0", + }, + { + Key: "Ant-Version", + Value: "Apache Ant 1.8.2", + }, + { + Key: "Created-By", + Value: "1.5.0_22-b03 (Sun Microsystems Inc.)", + }, }, - NamedSections: map[string]map[string]string{ - "some-other-section": { - "Bundle-Version": "1.11.28", + Sections: []pkg.KeyValues{ + { + { + Key: "Name", + Value: "some-other-section", + }, + { + Key: "Bundle-Version", + Value: "1.11.28", + }, }, }, }, diff --git a/syft/pkg/conan.go b/syft/pkg/conan.go index bf2b1584b4f..14cf830e00b 100644 --- a/syft/pkg/conan.go +++ b/syft/pkg/conan.go @@ -2,15 +2,15 @@ package pkg // ConanLockEntry represents a single "node" entry from a conan.lock file. type ConanLockEntry struct { - Ref string `json:"ref"` - PackageID string `json:"package_id,omitempty"` - Prev string `json:"prev,omitempty"` - Requires []string `json:"requires,omitempty"` - BuildRequires []string `json:"build_requires,omitempty"` - PythonRequires []string `json:"py_requires,omitempty"` - Options map[string]string `json:"options,omitempty"` - Path string `json:"path,omitempty"` - Context string `json:"context,omitempty"` + Ref string `json:"ref"` + PackageID string `json:"package_id,omitempty"` + Prev string `json:"prev,omitempty"` + Requires []string `json:"requires,omitempty"` + BuildRequires []string `json:"build_requires,omitempty"` + PythonRequires []string `json:"py_requires,omitempty"` + Options KeyValues `json:"options,omitempty"` + Path string `json:"path,omitempty"` + Context string `json:"context,omitempty"` } // ConanfileEntry represents a single "Requires" entry from a conanfile.txt. diff --git a/syft/pkg/golang.go b/syft/pkg/golang.go index 54e18dc9673..9f59f904b63 100644 --- a/syft/pkg/golang.go +++ b/syft/pkg/golang.go @@ -2,12 +2,12 @@ package pkg // GolangBinaryBuildinfoEntry represents all captured data for a Golang binary type GolangBinaryBuildinfoEntry struct { - BuildSettings map[string]string `json:"goBuildSettings,omitempty" cyclonedx:"goBuildSettings"` - GoCompiledVersion string `json:"goCompiledVersion" cyclonedx:"goCompiledVersion"` - Architecture string `json:"architecture" cyclonedx:"architecture"` - H1Digest string `json:"h1Digest,omitempty" cyclonedx:"h1Digest"` - MainModule string `json:"mainModule,omitempty" cyclonedx:"mainModule"` - GoCryptoSettings []string `json:"goCryptoSettings,omitempty" cyclonedx:"goCryptoSettings"` + BuildSettings KeyValues `json:"goBuildSettings,omitempty" cyclonedx:"goBuildSettings"` + GoCompiledVersion string `json:"goCompiledVersion" cyclonedx:"goCompiledVersion"` + Architecture string `json:"architecture" cyclonedx:"architecture"` + H1Digest string `json:"h1Digest,omitempty" cyclonedx:"h1Digest"` + MainModule string `json:"mainModule,omitempty" cyclonedx:"mainModule"` + GoCryptoSettings []string `json:"goCryptoSettings,omitempty" cyclonedx:"goCryptoSettings"` } // GolangModuleEntry represents all captured data for a Golang source scan with go.mod/go.sum diff --git a/syft/pkg/java.go b/syft/pkg/java.go index 33c46ee98cf..1fb5a3b5b40 100644 --- a/syft/pkg/java.go +++ b/syft/pkg/java.go @@ -66,6 +66,15 @@ func (p JavaPomProperties) PkgTypeIndicated() Type { // JavaManifest represents the fields of interest extracted from a Java archive's META-INF/MANIFEST.MF file. type JavaManifest struct { - Main map[string]string `json:"main,omitempty"` - NamedSections map[string]map[string]string `json:"namedSections,omitempty"` + Main KeyValues `json:"main,omitempty"` + Sections []KeyValues `json:"sections,omitempty"` +} + +func (m JavaManifest) Section(name string) KeyValues { + for _, section := range m.Sections { + if sectionName, ok := section.Get("Name"); ok && sectionName == name { + return section + } + } + return nil } diff --git a/syft/pkg/key_value.go b/syft/pkg/key_value.go new file mode 100644 index 00000000000..97cce0193de --- /dev/null +++ b/syft/pkg/key_value.go @@ -0,0 +1,28 @@ +package pkg + +type KeyValue struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type KeyValues []KeyValue + +func (k KeyValues) Get(key string) (string, bool) { + for _, kv := range k { + if kv.Key == key { + return kv.Value, true + } + } + + return "", false +} + +func (k KeyValues) MustGet(key string) string { + for _, kv := range k { + if kv.Key == key { + return kv.Value + } + } + + return "" +}