diff --git a/Taskfile.yaml b/Taskfile.yaml
index 7261a36f225f..61312f40e21a 100644
--- a/Taskfile.yaml
+++ b/Taskfile.yaml
@@ -390,7 +390,7 @@ tasks:
generate-cpe-dictionary-index:
desc: Generate the CPE index based off of the latest available CPE dictionary
- dir: "syft/pkg/cataloger/common/cpe/dictionary"
+ dir: "syft/pkg/cataloger/internal/cpegenerate/dictionary"
cmds:
- "go generate"
diff --git a/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json b/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json
index 26060565f78e..6f4a6ca1ff4f 100644
--- a/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json
+++ b/syft/pkg/cataloger/internal/cpegenerate/dictionary/data/cpe-index.json
@@ -1031,6 +1031,19 @@
"zjjserver": "cpe:2.3:a:zjjserver_project:zjjserver:*:*:*:*:*:node.js:*:*",
"zwserver": "cpe:2.3:a:zwserver_project:zwserver:*:*:*:*:*:node.js:*:*"
},
+ "php_pear": {
+ "Archive_Tar": "cpe:2.3:a:php:pear_archive_tar:*:*:*:*:*:*:*:*",
+ "HTML_AJAX": "cpe:2.3:a:pear:html_ajax:*:*:*:*:*:*:*:*",
+ "HTML_QuickForm": "cpe:2.3:a:html_quickform_project:html_quickform:*:*:*:*:*:*:*:*",
+ "PEAR": "cpe:2.3:a:php:pear:*:*:*:*:*:*:*:*",
+ "XML_RPC": "cpe:2.3:a:php:xml_rpc:*:*:*:*:*:pear:*:*"
+ },
+ "php_pecl": {
+ "imagick": "cpe:2.3:a:php:imagick:*:*:*:*:*:*:*:*",
+ "memcached": "cpe:2.3:a:php:memcached:*:*:*:*:*:*:*:*",
+ "pecl_http": "cpe:2.3:a:php:pecl_http:*:*:*:*:*:*:*:*",
+ "xhprof": "cpe:2.3:a:php:xhprof:*:*:*:*:*:*:*:*"
+ },
"pypi": {
"0.0.1": "cpe:2.3:a:pypi:pypi:*:*:*:*:*:*:*:*",
"AAmiles": "cpe:2.3:a:pypi:aamiles:*:*:*:*:*:pypi:*:*",
diff --git a/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/generate.go b/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/generate.go
index 23868e30f35f..24746b3f0218 100644
--- a/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/generate.go
+++ b/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/generate.go
@@ -108,6 +108,10 @@ const (
prefixForPyPIPackages = "https://pypi.org/project/"
prefixForJenkinsPlugins = "https://github.com/jenkinsci/"
prefixForRustCrates = "https://crates.io/crates/"
+ prefixForPHPPear = "https://pear.php.net/"
+ prefixForPHPPearHTTP = "http://pear.php.net/"
+ prefixForPHPPecl = "https://pecl.php.net/"
+ prefixForPHPPeclHTTP = "http://pecl.php.net/"
)
// indexCPEList creates an index of CPEs by ecosystem.
@@ -141,6 +145,12 @@ func indexCPEList(list CpeList) *dictionary.Indexed {
case strings.HasPrefix(ref, prefixForRustCrates):
addEntryForRustCrate(indexed, ref, cpeItemName)
+
+ case strings.HasPrefix(ref, prefixForPHPPear), strings.HasPrefix(ref, prefixForPHPPearHTTP):
+ addEntryForPHPPearPackage(indexed, ref, cpeItemName)
+
+ case strings.HasPrefix(ref, prefixForPHPPecl), strings.HasPrefix(ref, prefixForPHPPeclHTTP):
+ addEntryForPHPPeclPackage(indexed, ref, cpeItemName)
}
}
}
@@ -228,3 +238,63 @@ func addEntryForNPMPackage(indexed *dictionary.Indexed, ref string, cpeItemName
indexed.EcosystemPackages[dictionary.EcosystemNPM][ref] = cpeItemName
}
+
+func phpExtensionPackageFromURLFragment(ref string) string {
+ if strings.HasPrefix(ref, "package/") { // package/HTML_QuickForm/download
+ ref = strings.TrimPrefix(ref, "package/")
+ components := strings.Split(ref, "/")
+
+ if len(components) < 1 {
+ return ""
+ }
+
+ ref = components[0]
+ } else if strings.Contains(ref, "?package=") { // package-changelog.php?package=xhprof&release=0.9.4
+ components := strings.Split(ref, "?package=")
+
+ if len(components) < 2 {
+ return ""
+ }
+
+ components = strings.Split(components[1], "&")
+ if len(components) < 2 {
+ return ""
+ }
+
+ ref = components[0]
+ }
+
+ return ref
+}
+
+func addEntryForPHPPearPackage(indexed *dictionary.Indexed, ref string, cpeItemName string) {
+ ref = strings.TrimPrefix(ref, prefixForPHPPear)
+ ref = strings.TrimPrefix(ref, prefixForPHPPearHTTP)
+ ref = phpExtensionPackageFromURLFragment(ref)
+
+ if ref == "" {
+ return
+ }
+
+ if _, ok := indexed.EcosystemPackages[dictionary.EcosystemPHPPear]; !ok {
+ indexed.EcosystemPackages[dictionary.EcosystemPHPPear] = make(dictionary.Packages)
+ }
+
+ indexed.EcosystemPackages[dictionary.EcosystemPHPPear][ref] = cpeItemName
+}
+
+func addEntryForPHPPeclPackage(indexed *dictionary.Indexed, ref string, cpeItemName string) {
+ ref = strings.TrimPrefix(ref, prefixForPHPPecl)
+ ref = strings.TrimPrefix(ref, prefixForPHPPeclHTTP)
+ ref = phpExtensionPackageFromURLFragment(ref)
+
+ if ref == "" {
+ return
+ }
+
+ if _, ok := indexed.EcosystemPackages[dictionary.EcosystemPHPPecl]; !ok {
+ indexed.EcosystemPackages[dictionary.EcosystemPHPPecl] = make(dictionary.Packages)
+ }
+
+ indexed.EcosystemPackages[dictionary.EcosystemPHPPecl][ref] = cpeItemName
+}
diff --git a/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/generate_test.go b/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/generate_test.go
index db13735d7427..267ffe011da5 100644
--- a/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/generate_test.go
+++ b/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/generate_test.go
@@ -151,6 +151,58 @@ func Test_addEntryFuncs(t *testing.T) {
},
},
},
+ {
+ name: "addEntryForPHPPeclPackage",
+ addEntryFunc: addEntryForPHPPeclPackage,
+ inputRef: "https://pecl.php.net/package/imagick/something/something/v4007.0",
+ inputCpeItemName: "cpe:2.3:a:php:imagick:*:*:*:*:*:*:*:*",
+ expectedIndexed: dictionary.Indexed{
+ EcosystemPackages: map[string]dictionary.Packages{
+ dictionary.EcosystemPHPPecl: {
+ "imagick": "cpe:2.3:a:php:imagick:*:*:*:*:*:*:*:*",
+ },
+ },
+ },
+ },
+ {
+ name: "addEntryForPHPPeclPackage http changelog",
+ addEntryFunc: addEntryForPHPPeclPackage,
+ inputRef: "http://pecl.php.net/package-changelog.php?package=memcached&release",
+ inputCpeItemName: "cpe:2.3:a:php:memcached:*:*:*:*:*:*:*:*",
+ expectedIndexed: dictionary.Indexed{
+ EcosystemPackages: map[string]dictionary.Packages{
+ dictionary.EcosystemPHPPecl: {
+ "memcached": "cpe:2.3:a:php:memcached:*:*:*:*:*:*:*:*",
+ },
+ },
+ },
+ },
+ {
+ name: "addEntryForPHPPearPackage",
+ addEntryFunc: addEntryForPHPPearPackage,
+ inputRef: "https://pear.php.net/package/PEAR/download",
+ inputCpeItemName: "cpe:2.3:a:php:pear:*:*:*:*:*:*:*:*",
+ expectedIndexed: dictionary.Indexed{
+ EcosystemPackages: map[string]dictionary.Packages{
+ dictionary.EcosystemPHPPear: {
+ "PEAR": "cpe:2.3:a:php:pear:*:*:*:*:*:*:*:*",
+ },
+ },
+ },
+ },
+ {
+ name: "addEntryForPHPPearPackage http changelog",
+ addEntryFunc: addEntryForPHPPearPackage,
+ inputRef: "http://pear.php.net/package-changelog.php?package=abcdefg&release",
+ inputCpeItemName: "cpe:2.3:a:php:abcdefg:*:*:*:*:*:*:*:*",
+ expectedIndexed: dictionary.Indexed{
+ EcosystemPackages: map[string]dictionary.Packages{
+ dictionary.EcosystemPHPPear: {
+ "abcdefg": "cpe:2.3:a:php:abcdefg:*:*:*:*:*:*:*:*",
+ },
+ },
+ },
+ },
}
for _, tt := range tests {
diff --git a/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/testdata/expected-cpe-index.json b/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/testdata/expected-cpe-index.json
index c6f95615961d..bf34cee57b3d 100644
--- a/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/testdata/expected-cpe-index.json
+++ b/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/testdata/expected-cpe-index.json
@@ -13,6 +13,16 @@
"unicode": "cpe:2.3:a:unicode_project:unicode:*:*:*:*:*:node.js:*:*",
"unicorn-list": "cpe:2.3:a:unicorn-list_project:unicorn-list:*:*:*:*:*:node.js:*:*"
},
+ "php_pear": {
+ "HTML_QuickForm": "cpe:2.3:a:html_quickform_project:html_quickform:*:*:*:*:*:*:*:*",
+ "PEAR": "cpe:2.3:a:php:pear:*:*:*:*:*:*:*:*",
+ "XML_RPC": "cpe:2.3:a:php:xml_rpc:*:*:*:*:*:pear:*:*"
+ },
+ "php_pecl": {
+ "imagick": "cpe:2.3:a:php:imagick:*:*:*:*:*:*:*:*",
+ "memcached": "cpe:2.3:a:php:memcached:*:*:*:*:*:*:*:*",
+ "xhprof": "cpe:2.3:a:php:xhprof:*:*:*:*:*:*:*:*"
+ },
"rubygems": {
"openssl": "cpe:2.3:a:ruby-lang:openssl:*:*:*:*:*:*:*:*"
},
diff --git a/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/testdata/official-cpe-dictionary_v2.3.xml b/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/testdata/official-cpe-dictionary_v2.3.xml
index 5cd48842ee76..ac5cb0c8f300 100644
--- a/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/testdata/official-cpe-dictionary_v2.3.xml
+++ b/syft/pkg/cataloger/internal/cpegenerate/dictionary/index-generator/testdata/official-cpe-dictionary_v2.3.xml
@@ -24873,4 +24873,114 @@
+
+ PHP XHProf 0.9.1
+
+ product changelog
+
+
+
+
+ PHP XHProf 0.9.2
+
+ product changelog
+
+
+
+
+ PHP XHProf 0.9.3
+
+ product changelog
+
+
+
+
+ PHP memecached 0.1.0
+
+ Change Log
+
+
+
+
+ PHP memecached 0.1.2
+
+ Change Log
+
+
+
+
+ PHP memecached 0.1.3
+
+ Change Log
+
+
+
+
+ PHP Imagick 3.3.0
+
+ Version
+
+
+
+
+ PHP Imagick 3.4.0
+
+ Version
+
+
+
+
+ PHP XML_RPC 1.4.4 for Pear
+
+ Version
+ Product
+
+
+
+
+ PHP XML_RPC 1.4.5 for Pear
+
+ Version
+ Product
+
+
+
+
+ PHP XML_RPC 1.4.6 for Pear
+
+ Version
+ Product
+
+
+
+
+ PHP PEAR 1.0
+
+ Change Log
+
+
+
+
+ PHP PEAR 1.0 Beta 1
+
+ Change Log
+
+
+
+
+ HTML_QuickForm project HTML_QuickForm 2.0
+
+ Project
+ Version
+
+
+
+
+ HTML_QuickForm project HTML_QuickForm 2.1
+
+ Project
+ Version
+
+
+
diff --git a/syft/pkg/cataloger/internal/cpegenerate/dictionary/types.go b/syft/pkg/cataloger/internal/cpegenerate/dictionary/types.go
index a8a5f8f7fdb8..7977d9e6ae7d 100644
--- a/syft/pkg/cataloger/internal/cpegenerate/dictionary/types.go
+++ b/syft/pkg/cataloger/internal/cpegenerate/dictionary/types.go
@@ -4,6 +4,8 @@ const (
EcosystemNPM = "npm"
EcosystemRubyGems = "rubygems"
EcosystemPyPI = "pypi"
+ EcosystemPHPPear = "php_pear"
+ EcosystemPHPPecl = "php_pecl"
EcosystemJenkinsPlugins = "jenkins_plugins"
EcosystemRustCrates = "rust_crates"
)
diff --git a/syft/pkg/cataloger/internal/cpegenerate/generate.go b/syft/pkg/cataloger/internal/cpegenerate/generate.go
index 7cefdda201ac..45161e9d42fa 100644
--- a/syft/pkg/cataloger/internal/cpegenerate/generate.go
+++ b/syft/pkg/cataloger/internal/cpegenerate/generate.go
@@ -86,6 +86,9 @@ func FromDictionaryFind(p pkg.Package) (cpe.CPE, bool) {
case pkg.RustPkg:
cpeString, ok = dict.EcosystemPackages[dictionary.EcosystemRustCrates][p.Name]
+ case pkg.PhpPeclPkg:
+ cpeString, ok = dict.EcosystemPackages[dictionary.EcosystemPHPPecl][p.Name]
+
default:
// The dictionary doesn't support this package type yet.
return cpe.CPE{}, false