feat: Add Wordpress cataloger (#2218)

* Closes #1911 Wordpress cataloger

Signed-off-by: disc <a.hacicheant@gmail.com>

* Fixed a few unit tests and static analizer notices

Signed-off-by: disc <a.hacicheant@gmail.com>

* Updated `README.md`

Signed-off-by: disc <a.hacicheant@gmail.com>

* Fixed `golangci-lint` notices
Added integration test for `wordpress-plugin`

Signed-off-by: disc <a.hacicheant@gmail.com>

* Fixed `gosimports` notices

Signed-off-by: disc <a.hacicheant@gmail.com>

* Updated `json schema` version

Signed-off-by: disc <a.hacicheant@gmail.com>

* Fixed CLI tests, increased expected package count

Signed-off-by: disc <a.hacicheant@gmail.com>

* Read first 4Kb of a plugins file's content

Signed-off-by: disc <a.hacicheant@gmail.com>

* replace JSON schema version

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* change wording on source info for wordpress packages

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* Minor changes after a huge refactoring

Signed-off-by: disc <a.hacicheant@gmail.com>

* Removed unused files

Signed-off-by: disc <a.hacicheant@gmail.com>

* Updated schema

Signed-off-by: disc <a.hacicheant@gmail.com>

* Fixed integration tests

Signed-off-by: disc <a.hacicheant@gmail.com>

* fix integration tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* Renamed `metadata.Name` to `metadata.PluginInstallDirectory`

Signed-off-by: disc <a.hacicheant@gmail.com>

* rename fields to be compliant with json conventions

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: disc <a.hacicheant@gmail.com>
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alexandr Hacicheant 2024-02-14 18:03:25 +02:00 committed by GitHub
parent 98b700e83c
commit 96ee2db875
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 4852 additions and 9 deletions

View File

@ -54,6 +54,7 @@ For commercial support options with Syft or Grype, please [contact Anchore](http
- Ruby (gem)
- Rust (cargo.lock)
- Swift (cocoapods, swift-package-manager)
- Wordpress plugins
## Installation

View File

@ -452,4 +452,12 @@ var commonTestCases = []testCase{
"glibc": "2.34-210",
},
},
{
name: "find wordpress plugins",
pkgType: pkg.WordpressPluginPkg,
pkgLanguage: pkg.PHP,
pkgInfo: map[string]string{
"Akismet Anti-spam: Spam Protection": "5.3",
},
},
}

View File

@ -123,7 +123,6 @@ func TestPkgCoverageImage(t *testing.T) {
}
t.Fatalf("unexpected package count: %d!=%d", pkgCount, len(c.pkgInfo))
}
})
}
@ -211,7 +210,6 @@ func TestPkgCoverageDirectory(t *testing.T) {
}
t.Fatalf("unexpected package count: %d!=%d", actualPkgCount, len(test.pkgInfo))
}
})
}
@ -249,7 +247,6 @@ func TestPkgCoverageImage_HasEvidence(t *testing.T) {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type)
for _, l := range a.Locations.ToSlice() {
@ -259,7 +256,6 @@ func TestPkgCoverageImage_HasEvidence(t *testing.T) {
}
}
}
})
}
@ -279,7 +275,6 @@ func TestPkgCoverageDirectory_HasEvidence(t *testing.T) {
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
for a := range sbom.Artifacts.Packages.Enumerate(c.pkgType) {
assert.NotEmpty(t, a.Locations.ToSlice(), "package %q has no locations (type=%q)", a.Name, a.Type)
for _, l := range a.Locations.ToSlice() {
@ -289,7 +284,6 @@ func TestPkgCoverageDirectory_HasEvidence(t *testing.T) {
}
}
}
})
}

View File

@ -0,0 +1,17 @@
<?php
/**
* @package Akismet
*/
/*
Plugin Name:Akismet Anti-spam: Spam Protection
Plugin URI: https://akismet.com/
Description: Used by millions, Akismet is quite possibly the best way in the world to <strong>protect your blog from spam</strong>. Akismet Anti-spam keeps your site protected even while you sleep. To get started: activate the Akismet plugin and then go to your Akismet Settings page to set up your API key.
Version: 5.3
Requires at least: 5.8
Requires PHP: 5.6.20
Author: Automattic - Anti-spam Team
Author URI: https://automattic.com/wordpress-plugins/
License: GPLv2 or later
Text Domain: akismet
*/
// rest of plugin's code ...

View File

@ -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 = "16.0.3"
JSONSchemaVersion = "16.0.4"
)

View File

@ -28,6 +28,7 @@ import (
"github.com/anchore/syft/syft/pkg/cataloger/rust"
sbomCataloger "github.com/anchore/syft/syft/pkg/cataloger/sbom"
"github.com/anchore/syft/syft/pkg/cataloger/swift"
"github.com/anchore/syft/syft/pkg/cataloger/wordpress"
)
//nolint:funlen
@ -125,5 +126,6 @@ func DefaultPackageTaskFactories() PackageTaskFactories {
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.InstalledTag, pkgcataloging.ImageTag, "linux", "kernel",
),
newSimplePackageTaskFactory(sbomCataloger.NewCataloger, "sbom"), // note: not evidence of installed packages
newSimplePackageTaskFactory(wordpress.NewWordpressPluginCataloger, pkgcataloging.DirectoryTag, pkgcataloging.ImageTag, "wordpress"),
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2242,6 +2242,12 @@
},
"type": "array"
},
"cpes": {
"items": {
"$ref": "#/$defs/CPE"
},
"type": "array"
},
"licenses": {
"items": {
"$ref": "#/$defs/License"

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.0.3/document",
"$id": "anchore.io/schema/syft/json/16.0.4/document",
"$ref": "#/$defs/Document",
"$defs": {
"AlpmDbEntry": {
@ -1493,6 +1493,9 @@
},
{
"$ref": "#/$defs/SwiftPackageManagerLockEntry"
},
{
"$ref": "#/$defs/WordpressPluginEntry"
}
]
}
@ -2268,6 +2271,23 @@
"revision"
]
},
"WordpressPluginEntry": {
"properties": {
"pluginInstallDirectory": {
"type": "string"
},
"author": {
"type": "string"
},
"authorUri": {
"type": "string"
}
},
"type": "object",
"required": [
"pluginInstallDirectory"
]
},
"cpes": {
"items": {
"$ref": "#/$defs/CPE"

View File

@ -60,6 +60,8 @@ func SourceInfo(p pkg.Package) string {
answer = "acquired package info from resolved Swift package manifest"
case pkg.GithubActionPkg, pkg.GithubActionWorkflowPkg:
answer = "acquired package info from GitHub Actions workflow file or composite action file"
case pkg.WordpressPluginPkg:
answer = "acquired package info from found wordpress plugin PHP source files"
default:
answer = "acquired package info from the following paths"
}

View File

@ -263,6 +263,14 @@ func Test_SourceInfo(t *testing.T) {
"from GitHub Actions workflow file or composite action file",
},
},
{
input: pkg.Package{
Type: pkg.WordpressPluginPkg,
},
expected: []string{
"acquired package info from found wordpress plugin PHP source files",
},
},
}
var pkgTypes []pkg.Type
for _, test := range tests {

View File

@ -46,6 +46,7 @@ func AllTypes() []any {
pkg.RustBinaryAuditEntry{},
pkg.RustCargoLockEntry{},
pkg.SwiftPackageManagerResolvedEntry{},
pkg.WordpressPluginEntry{},
pkg.YarnLockEntry{},
}
}

View File

@ -102,6 +102,7 @@ var jsonTypes = makeJSONTypes(
jsonNames(pkg.SwiftPackageManagerResolvedEntry{}, "swift-package-manager-lock-entry", "SwiftPackageManagerMetadata"),
jsonNames(pkg.RustCargoLockEntry{}, "rust-cargo-lock-entry", "RustCargoPackageMetadata"),
jsonNamesWithoutLookup(pkg.RustBinaryAuditEntry{}, "rust-cargo-audit-entry", "RustCargoPackageMetadata"), // the legacy value is split into two types, where the other is preferred
jsonNames(pkg.WordpressPluginEntry{}, "wordpress-plugin-entry", "WordpressMetadata"),
)
func expandLegacyNameVariants(names ...string) []string {

View File

@ -145,6 +145,7 @@ func FromPackageAttributes(p pkg.Package) []cpe.CPE {
return result
}
//nolint:funlen
func candidateVendors(p pkg.Package) []string {
// in ecosystems where the packaging metadata does not have a clear field to indicate a vendor (or a field that
// could be interpreted indirectly as such) the project name tends to be a common stand in. Examples of this
@ -184,6 +185,9 @@ func candidateVendors(p pkg.Package) []string {
vendors.union(candidateVendorsForAPK(p))
case pkg.NpmPackage:
vendors.union(candidateVendorsForJavascript(p))
case pkg.WordpressPluginEntry:
vendors.clear()
vendors.union(candidateVendorsForWordpressPlugin(p))
}
// We should no longer be generating vendor candidates with these values ["" and "*"]
@ -243,6 +247,11 @@ func candidateProducts(p pkg.Package) []string {
products.union(candidateProductsForAPK(p))
}
if _, hasWordpressMetadata := p.Metadata.(pkg.WordpressPluginEntry); hasWordpressMetadata {
products.clear()
products.union(candidateProductsForWordpressPlugin(p))
}
// it is never OK to have candidates with these values ["" and "*"] (since CPEs will match any other value)
products.removeByValue("")
products.removeByValue("*")

View File

@ -0,0 +1,56 @@
package cpegenerate
import (
"fmt"
"regexp"
"strings"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/pkg"
)
var (
vendorFromURLRegexp = regexp.MustCompile(`^https?://(www.)?(?P<vendor>.+)\.\w/?`)
)
func candidateVendorsForWordpressPlugin(p pkg.Package) fieldCandidateSet {
metadata, ok := p.Metadata.(pkg.WordpressPluginEntry)
if !ok {
return nil
}
vendors := newFieldCandidateSet()
if metadata.AuthorURI != "" {
matchMap := internal.MatchNamedCaptureGroups(vendorFromURLRegexp, metadata.AuthorURI)
if vendor, ok := matchMap["vendor"]; ok && vendor != "" {
vendors.addValue(vendor)
}
} else {
// add plugin_name + _project as a vendor if no Author URI found
vendors.addValue(fmt.Sprintf("%s_project", normalizeWordpressPluginName(p.Name)))
}
return vendors
}
func candidateProductsForWordpressPlugin(p pkg.Package) fieldCandidateSet {
metadata, ok := p.Metadata.(pkg.WordpressPluginEntry)
if !ok {
return nil
}
products := newFieldCandidateSet()
products.addValue(normalizeWordpressPluginName(p.Name))
products.addValue(normalizeWordpressPluginName(metadata.PluginInstallDirectory))
return products
}
func normalizeWordpressPluginName(name string) string {
name = strings.TrimSpace(strings.ToLower(name))
for _, value := range []string{" "} {
name = strings.ReplaceAll(name, value, "_")
}
return name
}

View File

@ -0,0 +1,135 @@
package cpegenerate
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/pkg"
)
func Test_candidateVendorsForWordpressPlugin(t *testing.T) {
tests := []struct {
name string
pkg pkg.Package
expected []string
}{
{
name: "Akismet Anti-spam: Spam Protection",
pkg: pkg.Package{
Name: "Akismet Anti-spam: Spam Protection",
Metadata: pkg.WordpressPluginEntry{
PluginInstallDirectory: "akismet",
Author: "Automattic - Anti-spam Team",
AuthorURI: "https://automattic.com/wordpress-plugins/",
},
},
expected: []string{"automattic"},
},
{
name: "All-in-One WP Migration",
pkg: pkg.Package{
Name: "All-in-One WP Migration",
Metadata: pkg.WordpressPluginEntry{
PluginInstallDirectory: "all-in-one-wp-migration",
AuthorURI: "https://servmask.com",
},
},
expected: []string{"servmask"},
},
{
name: "Booking Ultra Pro Appointments Booking Calendar",
pkg: pkg.Package{
Name: "Booking Ultra Pro Appointments Booking Calendar",
Metadata: pkg.WordpressPluginEntry{
PluginInstallDirectory: "booking-ultra-pro",
Author: "Booking Ultra Pro",
AuthorURI: "https://bookingultrapro.com/",
},
},
expected: []string{"bookingultrapro"},
},
{
name: "Coming Soon Chop Chop",
pkg: pkg.Package{
Metadata: pkg.WordpressPluginEntry{
PluginInstallDirectory: "cc-coming-soon",
Author: "Chop-Chop.org",
AuthorURI: "https://www.chop-chop.org",
},
},
expected: []string{"chop-chop"},
},
{
name: "Access Code Feeder",
pkg: pkg.Package{
Name: "Access Code Feeder",
Metadata: pkg.WordpressPluginEntry{
PluginInstallDirectory: "access-code-feeder",
},
},
// When a plugin as no `Author URI` use plugin_name + _project as a vendor
expected: []string{"access_code_feeder_project"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := candidateVendorsForWordpressPlugin(test.pkg).uniqueValues()
assert.ElementsMatch(t, test.expected, actual, "different vendors")
})
}
}
func Test_candidateProductsWordpressPlugin(t *testing.T) {
tests := []struct {
name string
pkg pkg.Package
expected []string
}{
{
name: "All-in-One WP Migration",
pkg: pkg.Package{
Name: "All-in-One WP Migration",
Metadata: pkg.WordpressPluginEntry{
PluginInstallDirectory: "all-in-one-wp-migration",
},
},
expected: []string{"all-in-one_wp_migration", "all-in-one-wp-migration"},
},
{
name: "Akismet Anti-spam: Spam Protection",
pkg: pkg.Package{
Name: "Akismet Anti-spam: Spam Protection",
Metadata: pkg.WordpressPluginEntry{
PluginInstallDirectory: "akismet",
},
},
expected: []string{"akismet_anti-spam:_spam_protection", "akismet"},
},
{
name: "Access Code Feeder",
pkg: pkg.Package{
Name: "Access Code Feeder",
Metadata: pkg.WordpressPluginEntry{
PluginInstallDirectory: "access-code-feeder",
},
},
expected: []string{"access_code_feeder", "access-code-feeder"},
},
{
name: "CampTix Event Ticketing",
pkg: pkg.Package{
Name: "CampTix Event Ticketing",
Metadata: pkg.WordpressPluginEntry{
PluginInstallDirectory: "camptix",
},
},
expected: []string{"camptix_event_ticketing", "camptix"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.ElementsMatch(t, test.expected, candidateProductsForWordpressPlugin(test.pkg).uniqueValues(), "different products")
})
}
}

View File

@ -0,0 +1,16 @@
package wordpress
import (
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
const (
catalogerName = "wordpress-plugins-cataloger"
wordpressPluginsGlob = "**/wp-content/plugins/*/*.php"
)
func NewWordpressPluginCataloger() pkg.Cataloger {
return generic.NewCataloger(catalogerName).
WithParserByGlobs(parseWordpressPluginFiles, wordpressPluginsGlob)
}

View File

@ -0,0 +1,33 @@
package wordpress
import (
"testing"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func Test_WordpressPlugin_Globs(t *testing.T) {
tests := []struct {
name string
fixture string
expected []string
}{
{
name: "obtain wordpress plugin files",
fixture: "test-fixtures/glob-paths",
expected: []string{
"wp-content/plugins/akismet/akismet.php",
"wp-content/plugins/all-in-one-wp-migration/all-in-one-wp-migration.php",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
pkgtest.NewCatalogTester().
FromDirectory(t, test.fixture).
ExpectsResolverContentQueries(test.expected).
TestCataloger(t, NewWordpressPluginCataloger())
})
}
}

View File

@ -0,0 +1,31 @@
package wordpress
import (
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
)
func newWordpressPluginPackage(name, version string, m pluginData, location file.Location) pkg.Package {
meta := pkg.WordpressPluginEntry{
PluginInstallDirectory: m.PluginInstallDirectory,
Author: m.Author,
AuthorURI: m.AuthorURI,
}
p := pkg.Package{
Name: name,
Version: version,
Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
Language: pkg.PHP,
Type: pkg.WordpressPluginPkg,
Metadata: meta,
}
if len(m.Licenses) > 0 {
p.Licenses = pkg.NewLicenseSet(pkg.NewLicense(m.Licenses[0]))
}
p.SetID()
return p
}

View File

@ -0,0 +1,98 @@
package wordpress
import (
"context"
"fmt"
"path/filepath"
"regexp"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
const contentBufferSize = 4096
var patterns = map[string]*regexp.Regexp{
// match example: "Plugin Name: WP Migration" ---> WP Migration
"name": regexp.MustCompile(`(?i)plugin name:\s*(?P<name>.+)`),
// match example: "Version: 5.3" ---> 5.3
"version": regexp.MustCompile(`(?i)version:\s*(?P<version>[\d.]+)`),
// match example: "License: GPLv3" ---> GPLv3
"license": regexp.MustCompile(`(?i)license:\s*(?P<license>\w+)`),
// match example: "Author: MonsterInsights" ---> MonsterInsights
"author": regexp.MustCompile(`(?i)author:\s*(?P<author>.+)`),
// match example: "Author URI: https://servmask.com/" ---> https://servmask.com/
"author_uri": regexp.MustCompile(`(?i)author uri:\s*(?P<author_uri>.+)`),
}
type pluginData struct {
Licenses []string `mapstructure:"licenses" json:"licenses,omitempty"`
pkg.WordpressPluginEntry `mapstructure:",squash" json:",inline"`
}
func parseWordpressPluginFiles(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package
var fields = make(map[string]interface{})
buffer := make([]byte, contentBufferSize)
_, err := reader.Read(buffer)
if err != nil {
return nil, nil, fmt.Errorf("failed to read %s file: %w", reader.Location.Path(), err)
}
fileContent := string(buffer)
for field, pattern := range patterns {
matchMap := internal.MatchNamedCaptureGroups(pattern, fileContent)
if value := matchMap[field]; value != "" {
fields[field] = value
}
}
name, nameOk := fields["name"]
version, versionOk := fields["version"]
// get a plugin name from a plugin's directory name
pluginInstallDirectory := filepath.Base(filepath.Dir(reader.RealPath))
if nameOk && name != "" && versionOk && version != "" {
var metadata pluginData
metadata.PluginInstallDirectory = pluginInstallDirectory
author, authorOk := fields["author"]
if authorOk && author != "" {
metadata.Author = author.(string)
}
authorURI, authorURIOk := fields["author_uri"]
if authorURIOk && authorURI != "" {
metadata.AuthorURI = authorURI.(string)
}
license, licenseOk := fields["license"]
if licenseOk && license != "" {
licenses := make([]string, 0)
licenses = append(licenses, license.(string))
metadata.Licenses = licenses
}
pkgs = append(
pkgs,
newWordpressPluginPackage(
name.(string),
version.(string),
metadata,
reader.Location,
),
)
}
return pkgs, nil, nil
}

View File

@ -0,0 +1,32 @@
package wordpress
import (
"testing"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func TestParseWordpressPluginFiles(t *testing.T) {
fixture := "test-fixtures/glob-paths/wp-content/plugins/akismet/akismet.php"
locations := file.NewLocationSet(file.NewLocation(fixture))
var expectedPkg = pkg.Package{
Name: "Akismet Anti-spam: Spam Protection",
Version: "5.3",
Locations: locations,
Type: pkg.WordpressPluginPkg,
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromLocations("GPLv2"),
),
Language: pkg.PHP,
Metadata: pkg.WordpressPluginEntry{
PluginInstallDirectory: "akismet",
Author: "Automattic - Anti-spam Team",
AuthorURI: "https://automattic.com/wordpress-plugins/",
},
}
pkgtest.TestFileParser(t, fixture, parseWordpressPluginFiles, []pkg.Package{expectedPkg}, nil)
}

View File

@ -0,0 +1,2 @@
<?php
// stub file

View File

@ -0,0 +1,17 @@
<?php
/**
* @package Akismet
*/
/*
Plugin Name:Akismet Anti-spam: Spam Protection
Plugin URI: https://akismet.com/
Description: Used by millions, Akismet is quite possibly the best way in the world to <strong>protect your blog from spam</strong>. Akismet Anti-spam keeps your site protected even while you sleep. To get started: activate the Akismet plugin and then go to your Akismet Settings page to set up your API key.
Version: 5.3
Requires at least: 5.8
Requires PHP: 5.6.20
Author: Automattic - Anti-spam Team
Author URI: https://automattic.com/wordpress-plugins/
License: GPLv2 or later
Text Domain: akismet
*/
// rest of plugin's code ...

View File

@ -0,0 +1,10 @@
<?php
/**
* Plugin Name: All-in-One WP Migration
* Plugin URI: https://servmask.com/
* Description: Migration tool for all your blog data. Import or Export your blog content with a single click.
* Author: ServMask
* Author URI: https://servmask.com/
* Version: 7.78
*/
// rest of plugin's code ...

View File

@ -0,0 +1,2 @@
<?php
// stub file

View File

@ -40,6 +40,7 @@ const (
RpmPkg Type = "rpm"
RustPkg Type = "rust-crate"
SwiftPkg Type = "swift"
WordpressPluginPkg Type = "wordpress-plugin"
)
// AllPkgs represents all supported package types
@ -73,6 +74,7 @@ var AllPkgs = []Type{
RpmPkg,
RustPkg,
SwiftPkg,
WordpressPluginPkg,
}
// PackageURLType returns the PURL package type for the current package.
@ -131,6 +133,8 @@ func (t Type) PackageURLType() string {
return "cargo"
case SwiftPkg:
return packageurl.TypeSwift
case WordpressPluginPkg:
return "wordpress-plugin"
default:
// TODO: should this be a "generic" purl type instead?
return ""
@ -201,6 +205,8 @@ func TypeByName(name string) Type {
return Rpkg
case packageurl.TypeSwift:
return SwiftPkg
case "wordpress-plugin":
return WordpressPluginPkg
default:
return UnknownPkg
}

View File

@ -119,6 +119,7 @@ func TestTypeFromPURL(t *testing.T) {
expectedTypes.Remove(string(BinaryPkg))
expectedTypes.Remove(string(LinuxKernelModulePkg))
expectedTypes.Remove(string(GithubActionPkg), string(GithubActionWorkflowPkg))
expectedTypes.Remove(string(WordpressPluginPkg))
for _, test := range tests {
t.Run(string(test.expected), func(t *testing.T) {

8
syft/pkg/wordpress.go Normal file
View File

@ -0,0 +1,8 @@
package pkg
// WordpressPluginEntry represents all metadata parsed from the wordpress plugin file
type WordpressPluginEntry struct {
PluginInstallDirectory string `mapstructure:"pluginInstallDirectory" json:"pluginInstallDirectory"`
Author string `mapstructure:"author" json:"author,omitempty"`
AuthorURI string `mapstructure:"authorUri" json:"authorUri,omitempty"`
}

View File

@ -9,7 +9,7 @@ import (
const (
// this is the number of packages that should be found in the image-pkg-coverage fixture image
// when analyzed with the squashed scope.
coverageImageSquashedPackageCount = 27
coverageImageSquashedPackageCount = 28
)
func TestPackagesCmdFlags(t *testing.T) {