github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/internal/cpegenerate/generate_test.go (about)

     1  package cpegenerate
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/scylladb/go-set"
    10  	"github.com/scylladb/go-set/strset"
    11  	"github.com/stretchr/testify/assert"
    12  
    13  	"github.com/anchore/syft/syft/cpe"
    14  	"github.com/anchore/syft/syft/pkg"
    15  )
    16  
    17  func keyValues(m map[string]string) []pkg.KeyValue {
    18  	var kvs []pkg.KeyValue
    19  	for k, v := range m {
    20  		kvs = append(kvs, pkg.KeyValue{
    21  			Key:   k,
    22  			Value: v,
    23  		})
    24  	}
    25  	return kvs
    26  }
    27  
    28  func TestGeneratePackageCPEs(t *testing.T) {
    29  	tests := []struct {
    30  		name     string
    31  		p        pkg.Package
    32  		expected []string
    33  	}{
    34  		{
    35  			name: "hyphen replacement",
    36  			p: pkg.Package{
    37  				Name:     "name-part",
    38  				Version:  "3.2",
    39  				FoundBy:  "some-analyzer",
    40  				Language: pkg.Python,
    41  				Type:     pkg.DebPkg,
    42  			},
    43  			expected: []string{
    44  				"cpe:2.3:a:name-part:name-part:3.2:*:*:*:*:*:*:*",
    45  				"cpe:2.3:a:name-part:name_part:3.2:*:*:*:*:*:*:*",
    46  				"cpe:2.3:a:name-part:python-name-part:3.2:*:*:*:*:*:*:*",
    47  				"cpe:2.3:a:name-part:python_name_part:3.2:*:*:*:*:*:*:*",
    48  				"cpe:2.3:a:name:name-part:3.2:*:*:*:*:*:*:*",
    49  				"cpe:2.3:a:name:name_part:3.2:*:*:*:*:*:*:*",
    50  				"cpe:2.3:a:name:python-name-part:3.2:*:*:*:*:*:*:*",
    51  				"cpe:2.3:a:name:python_name_part:3.2:*:*:*:*:*:*:*",
    52  				"cpe:2.3:a:name_part:name-part:3.2:*:*:*:*:*:*:*",
    53  				"cpe:2.3:a:name_part:name_part:3.2:*:*:*:*:*:*:*",
    54  				"cpe:2.3:a:name_part:python-name-part:3.2:*:*:*:*:*:*:*",
    55  				"cpe:2.3:a:name_part:python_name_part:3.2:*:*:*:*:*:*:*",
    56  				"cpe:2.3:a:python-name-part:name-part:3.2:*:*:*:*:*:*:*",
    57  				"cpe:2.3:a:python-name-part:name_part:3.2:*:*:*:*:*:*:*",
    58  				"cpe:2.3:a:python-name-part:python-name-part:3.2:*:*:*:*:*:*:*",
    59  				"cpe:2.3:a:python-name-part:python_name_part:3.2:*:*:*:*:*:*:*",
    60  				"cpe:2.3:a:python-name:name-part:3.2:*:*:*:*:*:*:*",
    61  				"cpe:2.3:a:python-name:name_part:3.2:*:*:*:*:*:*:*",
    62  				"cpe:2.3:a:python-name:python-name-part:3.2:*:*:*:*:*:*:*",
    63  				"cpe:2.3:a:python-name:python_name_part:3.2:*:*:*:*:*:*:*",
    64  				"cpe:2.3:a:python:name-part:3.2:*:*:*:*:*:*:*",
    65  				"cpe:2.3:a:python:name_part:3.2:*:*:*:*:*:*:*",
    66  				"cpe:2.3:a:python:python-name-part:3.2:*:*:*:*:*:*:*",
    67  				"cpe:2.3:a:python:python_name_part:3.2:*:*:*:*:*:*:*",
    68  				"cpe:2.3:a:python_name:name-part:3.2:*:*:*:*:*:*:*",
    69  				"cpe:2.3:a:python_name:name_part:3.2:*:*:*:*:*:*:*",
    70  				"cpe:2.3:a:python_name:python-name-part:3.2:*:*:*:*:*:*:*",
    71  				"cpe:2.3:a:python_name:python_name_part:3.2:*:*:*:*:*:*:*",
    72  				"cpe:2.3:a:python_name_part:name-part:3.2:*:*:*:*:*:*:*",
    73  				"cpe:2.3:a:python_name_part:name_part:3.2:*:*:*:*:*:*:*",
    74  				"cpe:2.3:a:python_name_part:python-name-part:3.2:*:*:*:*:*:*:*",
    75  				"cpe:2.3:a:python_name_part:python_name_part:3.2:*:*:*:*:*:*:*",
    76  			},
    77  		},
    78  		{
    79  			name: "python language",
    80  			p: pkg.Package{
    81  				Name:     "name",
    82  				Version:  "3.2",
    83  				FoundBy:  "some-analyzer",
    84  				Language: pkg.Python,
    85  				Type:     pkg.DebPkg,
    86  				Metadata: pkg.PythonPackage{
    87  					Author:      "alex goodman",
    88  					AuthorEmail: "william.goodman@anchore.com",
    89  				},
    90  			},
    91  			expected: []string{
    92  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
    93  				"cpe:2.3:a:name:python-name:3.2:*:*:*:*:*:*:*",
    94  				"cpe:2.3:a:name:python_name:3.2:*:*:*:*:*:*:*",
    95  				"cpe:2.3:a:python-name:name:3.2:*:*:*:*:*:*:*",
    96  				"cpe:2.3:a:python-name:python-name:3.2:*:*:*:*:*:*:*",
    97  				"cpe:2.3:a:python-name:python_name:3.2:*:*:*:*:*:*:*",
    98  				"cpe:2.3:a:python:name:3.2:*:*:*:*:*:*:*",
    99  				"cpe:2.3:a:python:python-name:3.2:*:*:*:*:*:*:*",
   100  				"cpe:2.3:a:python:python_name:3.2:*:*:*:*:*:*:*",
   101  				"cpe:2.3:a:python_name:name:3.2:*:*:*:*:*:*:*",
   102  				"cpe:2.3:a:python_name:python-name:3.2:*:*:*:*:*:*:*",
   103  				"cpe:2.3:a:python_name:python_name:3.2:*:*:*:*:*:*:*",
   104  				"cpe:2.3:a:alex_goodman:name:3.2:*:*:*:*:*:*:*",
   105  				"cpe:2.3:a:alex_goodman:python-name:3.2:*:*:*:*:*:*:*",
   106  				"cpe:2.3:a:alex_goodman:python_name:3.2:*:*:*:*:*:*:*",
   107  				"cpe:2.3:a:william-goodman:name:3.2:*:*:*:*:*:*:*",
   108  				"cpe:2.3:a:william-goodman:python-name:3.2:*:*:*:*:*:*:*",
   109  				"cpe:2.3:a:william-goodman:python_name:3.2:*:*:*:*:*:*:*",
   110  				"cpe:2.3:a:william_goodman:name:3.2:*:*:*:*:*:*:*",
   111  				"cpe:2.3:a:william_goodman:python-name:3.2:*:*:*:*:*:*:*",
   112  				"cpe:2.3:a:william_goodman:python_name:3.2:*:*:*:*:*:*:*",
   113  				"cpe:2.3:a:alex_goodman_project:python_name:3.2:*:*:*:*:*:*:*",
   114  				"cpe:2.3:a:alex_goodman_project:name:3.2:*:*:*:*:*:*:*",
   115  				"cpe:2.3:a:alex_goodman_project:python-name:3.2:*:*:*:*:*:*:*",
   116  				"cpe:2.3:a:alex_goodmanproject:name:3.2:*:*:*:*:*:*:*",
   117  				"cpe:2.3:a:alex_goodmanproject:python-name:3.2:*:*:*:*:*:*:*",
   118  				"cpe:2.3:a:alex_goodmanproject:python_name:3.2:*:*:*:*:*:*:*",
   119  				"cpe:2.3:a:william_goodman_project:name:3.2:*:*:*:*:*:*:*",
   120  				"cpe:2.3:a:william_goodman_project:python-name:3.2:*:*:*:*:*:*:*",
   121  				"cpe:2.3:a:william_goodman_project:python_name:3.2:*:*:*:*:*:*:*",
   122  				"cpe:2.3:a:william_goodmanproject:name:3.2:*:*:*:*:*:*:*",
   123  				"cpe:2.3:a:william_goodmanproject:python-name:3.2:*:*:*:*:*:*:*",
   124  				"cpe:2.3:a:william_goodmanproject:python_name:3.2:*:*:*:*:*:*:*",
   125  			},
   126  		},
   127  		{
   128  			name: "javascript language",
   129  			p: pkg.Package{
   130  				Name:     "name",
   131  				Version:  "3.2",
   132  				FoundBy:  "some-analyzer",
   133  				Language: pkg.JavaScript,
   134  				Metadata: pkg.NpmPackage{
   135  					Author: "jon",
   136  					URL:    "https://github.com/bob/npm-name",
   137  				},
   138  			},
   139  			expected: []string{
   140  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   141  				"cpe:2.3:a:bob:name:3.2:*:*:*:*:*:*:*",
   142  			},
   143  		},
   144  		{
   145  			name: "ruby language",
   146  			p: pkg.Package{
   147  				Name:     "name",
   148  				Version:  "3.2",
   149  				FoundBy:  "some-analyzer",
   150  				Language: pkg.Ruby,
   151  				Type:     pkg.DebPkg,
   152  				Metadata: pkg.RubyGemspec{
   153  					Authors: []string{
   154  						"someones name",
   155  						"someones.elses.name@gmail.com",
   156  					},
   157  					Homepage: "https://github.com/tom/ruby-name",
   158  				},
   159  			},
   160  			expected: []string{
   161  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   162  				"cpe:2.3:a:ruby-lang:name:3.2:*:*:*:*:*:*:*",
   163  				"cpe:2.3:a:ruby:name:3.2:*:*:*:*:*:*:*",
   164  				"cpe:2.3:a:ruby_lang:name:3.2:*:*:*:*:*:*:*",
   165  				"cpe:2.3:a:someones-elses-name:name:3.2:*:*:*:*:*:*:*",
   166  				"cpe:2.3:a:someones-name:name:3.2:*:*:*:*:*:*:*",
   167  				"cpe:2.3:a:someones_elses_name:name:3.2:*:*:*:*:*:*:*",
   168  				"cpe:2.3:a:someones_name:name:3.2:*:*:*:*:*:*:*",
   169  				"cpe:2.3:a:tom:name:3.2:*:*:*:*:*:*:*",
   170  			},
   171  		},
   172  		{
   173  			name: "java language",
   174  			p: pkg.Package{
   175  				Name:     "name",
   176  				Version:  "3.2",
   177  				FoundBy:  "some-analyzer",
   178  				Language: pkg.Java,
   179  				Type:     pkg.JavaPkg,
   180  			},
   181  			expected: []string{
   182  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   183  			},
   184  		},
   185  		{
   186  			name: "java language with groupID",
   187  			p: pkg.Package{
   188  				Name:     "name",
   189  				Version:  "3.2",
   190  				FoundBy:  "some-analyzer",
   191  				Language: pkg.Java,
   192  				Type:     pkg.JavaPkg,
   193  				Metadata: pkg.JavaArchive{
   194  					PomProperties: &pkg.JavaPomProperties{
   195  						GroupID: "org.sonatype.nexus",
   196  					},
   197  				},
   198  			},
   199  			expected: []string{
   200  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   201  				"cpe:2.3:a:name:nexus:3.2:*:*:*:*:*:*:*",
   202  				"cpe:2.3:a:nexus:name:3.2:*:*:*:*:*:*:*",
   203  				"cpe:2.3:a:nexus:nexus:3.2:*:*:*:*:*:*:*",
   204  				"cpe:2.3:a:sonatype:name:3.2:*:*:*:*:*:*:*",
   205  				"cpe:2.3:a:sonatype:nexus:3.2:*:*:*:*:*:*:*",
   206  				"cpe:2.3:a:org.sonatype.nexus:name:3.2:*:*:*:*:*:*:*",
   207  				"cpe:2.3:a:org.sonatype.nexus:nexus:3.2:*:*:*:*:*:*:*",
   208  			},
   209  		},
   210  		{
   211  			name: "java with URL in metadata", // regression: https://github.com/anchore/grype/issues/417
   212  			p: pkg.Package{
   213  				Name:    "wstx-asl",
   214  				Version: "3.2.7",
   215  				Type:    pkg.JavaPkg,
   216  				Metadata: pkg.JavaArchive{
   217  					Manifest: &pkg.JavaManifest{
   218  						Main: keyValues(map[string]string{
   219  							"Ant-Version":            "Apache Ant 1.6.5",
   220  							"Built-By":               "tatu",
   221  							"Created-By":             "1.4.2_03-b02 (Sun Microsystems Inc.)",
   222  							"Implementation-Title":   "WoodSToX XML-processor",
   223  							"Implementation-Vendor":  "woodstox.codehaus.org",
   224  							"Implementation-Version": "3.2.7",
   225  							"Manifest-Version":       "1.0",
   226  							"Specification-Title":    "StAX 1.0 API",
   227  							"Specification-Vendor":   "http://jcp.org/en/jsr/detail?id=173",
   228  							"Specification-Version":  "1.0",
   229  						}),
   230  					},
   231  				},
   232  			},
   233  			expected: []string{
   234  				"cpe:2.3:a:woodstox_codehaus_org:wstx-asl:3.2.7:*:*:*:*:*:*:*",
   235  				"cpe:2.3:a:woodstox_codehaus_org:wstx_asl:3.2.7:*:*:*:*:*:*:*",
   236  				"cpe:2.3:a:woodstox-codehaus-org:wstx_asl:3.2.7:*:*:*:*:*:*:*",
   237  				"cpe:2.3:a:woodstox-codehaus-org:wstx-asl:3.2.7:*:*:*:*:*:*:*",
   238  				"cpe:2.3:a:wstx_asl:wstx-asl:3.2.7:*:*:*:*:*:*:*",
   239  				"cpe:2.3:a:wstx-asl:wstx-asl:3.2.7:*:*:*:*:*:*:*",
   240  				"cpe:2.3:a:wstx-asl:wstx_asl:3.2.7:*:*:*:*:*:*:*",
   241  				"cpe:2.3:a:wstx_asl:wstx_asl:3.2.7:*:*:*:*:*:*:*",
   242  				"cpe:2.3:a:wstx:wstx_asl:3.2.7:*:*:*:*:*:*:*",
   243  				"cpe:2.3:a:wstx:wstx-asl:3.2.7:*:*:*:*:*:*:*",
   244  			},
   245  		},
   246  		{
   247  			name: "jenkins package identified via pkg type",
   248  			p: pkg.Package{
   249  				Name:     "name",
   250  				Version:  "3.2",
   251  				FoundBy:  "some-analyzer",
   252  				Language: pkg.Java,
   253  				Type:     pkg.JenkinsPluginPkg,
   254  			},
   255  			expected: []string{
   256  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   257  			},
   258  		},
   259  		{
   260  			name: "java language - multi tier manifest fields",
   261  			p: pkg.Package{
   262  				Name:     "cxf-rt-bindings-xml",
   263  				Version:  "3.3.10",
   264  				FoundBy:  "java-cataloger",
   265  				Language: pkg.Java,
   266  				Type:     pkg.JavaPkg,
   267  				Metadata: pkg.JavaArchive{
   268  					VirtualPath: "/opt/jboss/keycloak/modules/system/layers/base/org/apache/cxf/impl/main/cxf-rt-bindings-xml-3.3.10.jar",
   269  					Manifest: &pkg.JavaManifest{
   270  						Main: keyValues(map[string]string{
   271  							"Automatic-Module-Name":    "org.apache.cxf.binding.xml",
   272  							"Bnd-LastModified":         "1615836524860",
   273  							"Build-Jdk":                "1.8.0_261",
   274  							"Built-By":                 "dkulp",
   275  							"Bundle-ActivationPolicy":  "lazy",
   276  							"Bundle-Description":       "Apache CXF Runtime XML Binding",
   277  							"Bundle-DocURL":            "http://cxf.apache.org",
   278  							"Bundle-License":           "https://www.apache.org/licenses/LICENSE-2.0.txt",
   279  							"Bundle-ManifestVersion":   "2",
   280  							"Bundle-Name":              "Apache CXF Runtime XML Binding",
   281  							"Bundle-SymbolicName":      "org.apache.cxf.cxf-rt-bindings-xml",
   282  							"Bundle-Vendor":            "The Apache Software Foundation",
   283  							"Bundle-Version":           "3.3.10",
   284  							"Created-By":               "Apache Maven Bundle Plugin",
   285  							"Export-Package":           "org.apache.cxf.binding.xml;version=\"3.3.10\",org.apache.cxf.binding.xml.wsdl11;version=\"3.3.10\",org.apache.cxf.binding.xml.interceptor;version=\"3.3.10\",org.apache.cxf.bindings.xformat;version=\"3.3.10\"",
   286  							"Implementation-Vendor":    "The Apache Software Foundation",
   287  							"Implementation-Vendor-Id": "org.apache",
   288  							"Implementation-Version":   "3.3.10",
   289  							"Import-Package":           "javax.xml.bind;version=\"[0,3)\",javax.xml.bind.annotation;version=\"[0,3)\",javax.wsdl;resolution:=optional,javax.wsdl.extensions;resolution:=optional,javax.wsdl.extensions.http;resolution:=optional,javax.xml.namespace,javax.xml.stream,org.apache.cxf;version=\"[3.3,4)\",org.apache.cxf.binding;version=\"[3.3,4)\",org.apache.cxf.binding.xml,org.apache.cxf.binding.xml.interceptor,org.apache.cxf.bindings.xformat,org.apache.cxf.common.i18n;version=\"[3.3,4)\",org.apache.cxf.common.injection;version=\"[3.3,4)\",org.apache.cxf.common.logging;version=\"[3.3,4)\",org.apache.cxf.common.util;version=\"[3.3,4)\",org.apache.cxf.endpoint;version=\"[3.3,4)\",org.apache.cxf.helpers;version=\"[3.3,4)\",org.apache.cxf.interceptor;version=\"[3.3,4)\",org.apache.cxf.message;version=\"[3.3,4)\",org.apache.cxf.service.model;version=\"[3.3,4)\",org.apache.cxf.staxutils;version=\"[3.3,4)\",org.apache.cxf.tools.common;version=\"[3.3,4)\";resolution:=optional,org.apache.cxf.tools.validator;version=\"[3.3,4)\";resolution:=optional,org.apache.cxf.transport;version=\"[3.3,4)\",org.apache.cxf.wsdl;version=\"[3.3,4)\";resolution:=optional,org.apache.cxf.wsdl.http;version=\"[3.3,4)\",org.apache.cxf.wsdl.interceptors;version=\"[3.3,4)\";resolution:=optional,org.w3c.dom",
   290  							"Manifest-Version":         "1.0",
   291  							"Require-Capability":       "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"",
   292  							"Specification-Vendor":     "The Apache Software Foundation",
   293  							"Specification-Version":    "3.3.10",
   294  							"Tool":                     "Bnd-4.2.0.201903051501",
   295  						}),
   296  					},
   297  					PomProperties: &pkg.JavaPomProperties{
   298  						Path:       "META-INF/maven/org.apache.cxf/cxf-rt-bindings-xml/pom.properties",
   299  						GroupID:    "org.apache.cxf",
   300  						ArtifactID: "cxf-rt-bindings-xml",
   301  						Version:    "3.3.10",
   302  					},
   303  				},
   304  			},
   305  			expected: []string{
   306  				"cpe:2.3:a:apache:cxf-rt-bindings-xml:3.3.10:*:*:*:*:*:*:*",
   307  				"cpe:2.3:a:apache:cxf:3.3.10:*:*:*:*:*:*:*",
   308  				"cpe:2.3:a:apache:cxf_rt_bindings_xml:3.3.10:*:*:*:*:*:*:*",
   309  			},
   310  		},
   311  		{
   312  			name: "rpm archive vendor selection",
   313  			p: pkg.Package{
   314  				Name:    "name",
   315  				Version: "3.2",
   316  				FoundBy: "some-analyzer",
   317  				Type:    pkg.RpmPkg,
   318  				Metadata: pkg.RpmArchive{
   319  					Vendor: "some-vendor",
   320  				},
   321  			},
   322  			expected: []string{
   323  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   324  				"cpe:2.3:a:some-vendor:name:3.2:*:*:*:*:*:*:*",
   325  				"cpe:2.3:a:some_vendor:name:3.2:*:*:*:*:*:*:*",
   326  			},
   327  		},
   328  		{
   329  			name: "rpm vendor selection",
   330  			p: pkg.Package{
   331  				Name:    "name",
   332  				Version: "3.2",
   333  				FoundBy: "some-analyzer",
   334  				Type:    pkg.RpmPkg,
   335  				Metadata: pkg.RpmDBEntry{
   336  					Vendor: "some-vendor",
   337  				},
   338  			},
   339  			expected: []string{
   340  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   341  				"cpe:2.3:a:some-vendor:name:3.2:*:*:*:*:*:*:*",
   342  				"cpe:2.3:a:some_vendor:name:3.2:*:*:*:*:*:*:*",
   343  			},
   344  		},
   345  		{
   346  			name: "rpm with epoch",
   347  			p: pkg.Package{
   348  				Name:    "name",
   349  				Version: "1:3.2",
   350  				FoundBy: "some-analyzer",
   351  				Type:    pkg.RpmPkg,
   352  				Metadata: pkg.RpmDBEntry{
   353  					Vendor: "some-vendor",
   354  				},
   355  			},
   356  			expected: []string{
   357  				"cpe:2.3:a:name:name:1\\:3.2:*:*:*:*:*:*:*",
   358  				"cpe:2.3:a:some-vendor:name:1\\:3.2:*:*:*:*:*:*:*",
   359  				"cpe:2.3:a:some_vendor:name:1\\:3.2:*:*:*:*:*:*:*",
   360  			},
   361  		},
   362  		{
   363  			name: "deb with epoch",
   364  			p: pkg.Package{
   365  				Name:     "name",
   366  				Version:  "1:3.2",
   367  				FoundBy:  "some-analyzer",
   368  				Type:     pkg.DebPkg,
   369  				Metadata: pkg.DpkgDBEntry{},
   370  			},
   371  			expected: []string{
   372  				"cpe:2.3:a:name:name:1\\:3.2:*:*:*:*:*:*:*",
   373  			},
   374  		},
   375  		{
   376  			name: "cloudbees jenkins package identified via groupId",
   377  			p: pkg.Package{
   378  				Name:     "name",
   379  				Version:  "3.2",
   380  				FoundBy:  "some-analyzer",
   381  				Language: pkg.Java,
   382  				Type:     pkg.JenkinsPluginPkg,
   383  				Metadata: pkg.JavaArchive{
   384  					PomProperties: &pkg.JavaPomProperties{
   385  						GroupID: "com.cloudbees.jenkins.plugins",
   386  					},
   387  				},
   388  			},
   389  			expected: []string{
   390  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   391  				"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
   392  				"cpe:2.3:a:cloudbees:name:3.2:*:*:*:*:*:*:*",
   393  				"cpe:2.3:a:com.cloudbees.jenkins.plugins:name:3.2:*:*:*:*:*:*:*",
   394  			},
   395  		},
   396  		{
   397  			name: "jenkins.io package identified via groupId prefix",
   398  			p: pkg.Package{
   399  				Name:     "name",
   400  				Version:  "3.2",
   401  				FoundBy:  "some-analyzer",
   402  				Language: pkg.Java,
   403  				Type:     pkg.JenkinsPluginPkg,
   404  				Metadata: pkg.JavaArchive{
   405  					PomProperties: &pkg.JavaPomProperties{
   406  						GroupID: "io.jenkins.plugins.name.something",
   407  					},
   408  				},
   409  			},
   410  			expected: []string{
   411  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   412  				"cpe:2.3:a:name:something:3.2:*:*:*:*:*:*:*",
   413  				"cpe:2.3:a:something:name:3.2:*:*:*:*:*:*:*",
   414  				"cpe:2.3:a:something:something:3.2:*:*:*:*:*:*:*",
   415  				"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
   416  				"cpe:2.3:a:jenkins:something:3.2:*:*:*:*:*:*:*",
   417  				"cpe:2.3:a:io.jenkins.plugins.name.something:name:3.2:*:*:*:*:*:*:*",
   418  				"cpe:2.3:a:io.jenkins.plugins.name.something:something:3.2:*:*:*:*:*:*:*",
   419  			},
   420  		},
   421  		{
   422  			name: "jenkins.io package identified via groupId",
   423  			p: pkg.Package{
   424  				Name:     "name",
   425  				Version:  "3.2",
   426  				FoundBy:  "some-analyzer",
   427  				Language: pkg.Java,
   428  				Type:     pkg.JenkinsPluginPkg,
   429  				Metadata: pkg.JavaArchive{
   430  					PomProperties: &pkg.JavaPomProperties{
   431  						GroupID: "io.jenkins.plugins",
   432  					},
   433  				},
   434  			},
   435  			expected: []string{
   436  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   437  				"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
   438  				"cpe:2.3:a:io.jenkins.plugins:name:3.2:*:*:*:*:*:*:*",
   439  			},
   440  		},
   441  		{
   442  			name: "jenkins-ci.io package identified via groupId",
   443  			p: pkg.Package{
   444  				Name:     "name",
   445  				Version:  "3.2",
   446  				FoundBy:  "some-analyzer",
   447  				Language: pkg.Java,
   448  				Type:     pkg.JenkinsPluginPkg,
   449  				Metadata: pkg.JavaArchive{
   450  					PomProperties: &pkg.JavaPomProperties{
   451  						GroupID: "io.jenkins-ci.plugins",
   452  					},
   453  				},
   454  			},
   455  			expected: []string{
   456  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   457  				"cpe:2.3:a:jenkins-ci:name:3.2:*:*:*:*:*:*:*",
   458  				"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
   459  				"cpe:2.3:a:jenkins_ci:name:3.2:*:*:*:*:*:*:*",
   460  				"cpe:2.3:a:io.jenkins-ci.plugins:name:3.2:*:*:*:*:*:*:*",
   461  			},
   462  		},
   463  		{
   464  			name: "jenkins-ci.org package identified via groupId",
   465  			p: pkg.Package{
   466  				Name:     "name",
   467  				Version:  "3.2",
   468  				FoundBy:  "some-analyzer",
   469  				Language: pkg.Java,
   470  				Type:     pkg.JenkinsPluginPkg,
   471  				Metadata: pkg.JavaArchive{
   472  					PomProperties: &pkg.JavaPomProperties{
   473  						GroupID: "org.jenkins-ci.plugins",
   474  					},
   475  				},
   476  			},
   477  			expected: []string{
   478  				"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
   479  				"cpe:2.3:a:jenkins-ci:name:3.2:*:*:*:*:*:*:*",
   480  				"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
   481  				"cpe:2.3:a:jenkins_ci:name:3.2:*:*:*:*:*:*:*",
   482  				"cpe:2.3:a:org.jenkins-ci.plugins:name:3.2:*:*:*:*:*:*:*",
   483  			},
   484  		},
   485  		{
   486  			name: "jira-atlassian filtering",
   487  			p: pkg.Package{
   488  				Name:     "jira_client_core",
   489  				Version:  "3.2",
   490  				FoundBy:  "some-analyzer",
   491  				Language: pkg.Java,
   492  				Type:     pkg.JavaPkg,
   493  				Metadata: pkg.JavaArchive{
   494  					PomProperties: &pkg.JavaPomProperties{
   495  						GroupID:    "org.atlassian.jira",
   496  						ArtifactID: "jira_client_core",
   497  					},
   498  				},
   499  			},
   500  			expected: []string{
   501  				"cpe:2.3:a:atlassian:jira-client-core:3.2:*:*:*:*:*:*:*",
   502  				"cpe:2.3:a:atlassian:jira_client_core:3.2:*:*:*:*:*:*:*",
   503  				"cpe:2.3:a:jira-client-core:jira-client-core:3.2:*:*:*:*:*:*:*",
   504  				"cpe:2.3:a:jira-client-core:jira:3.2:*:*:*:*:*:*:*",
   505  				"cpe:2.3:a:jira-client-core:jira_client_core:3.2:*:*:*:*:*:*:*",
   506  				"cpe:2.3:a:jira-client:jira-client-core:3.2:*:*:*:*:*:*:*",
   507  				"cpe:2.3:a:jira-client:jira:3.2:*:*:*:*:*:*:*",
   508  				"cpe:2.3:a:jira-client:jira_client_core:3.2:*:*:*:*:*:*:*",
   509  				"cpe:2.3:a:jira:jira-client-core:3.2:*:*:*:*:*:*:*",
   510  				"cpe:2.3:a:jira:jira_client_core:3.2:*:*:*:*:*:*:*",
   511  				"cpe:2.3:a:jira_client:jira-client-core:3.2:*:*:*:*:*:*:*",
   512  				"cpe:2.3:a:jira_client:jira:3.2:*:*:*:*:*:*:*",
   513  				"cpe:2.3:a:jira_client:jira_client_core:3.2:*:*:*:*:*:*:*",
   514  				"cpe:2.3:a:jira_client_core:jira-client-core:3.2:*:*:*:*:*:*:*",
   515  				"cpe:2.3:a:jira_client_core:jira:3.2:*:*:*:*:*:*:*",
   516  				"cpe:2.3:a:jira_client_core:jira_client_core:3.2:*:*:*:*:*:*:*",
   517  				"cpe:2.3:a:org.atlassian.jira:jira:3.2:*:*:*:*:*:*:*",
   518  				"cpe:2.3:a:org.atlassian.jira:jira_client_core:3.2:*:*:*:*:*:*:*",
   519  				"cpe:2.3:a:org.atlassian.jira:jira-client-core:3.2:*:*:*:*:*:*:*",
   520  			},
   521  		},
   522  		{
   523  			name: "jenkins filtering",
   524  			p: pkg.Package{
   525  				Name:     "cloudbees-installation-manager",
   526  				Version:  "2.89.0.33",
   527  				FoundBy:  "some-analyzer",
   528  				Language: pkg.Java,
   529  				Type:     pkg.JavaPkg,
   530  				Metadata: pkg.JavaArchive{
   531  					PomProperties: &pkg.JavaPomProperties{
   532  						GroupID:    "com.cloudbees.jenkins.modules",
   533  						ArtifactID: "cloudbees-installation-manager",
   534  					},
   535  				},
   536  			},
   537  			expected: []string{
   538  				"cpe:2.3:a:cloudbees-installation-manager:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   539  				"cpe:2.3:a:cloudbees-installation-manager:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   540  				"cpe:2.3:a:cloudbees-installation:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   541  				"cpe:2.3:a:cloudbees-installation:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   542  				"cpe:2.3:a:cloudbees:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   543  				"cpe:2.3:a:cloudbees:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   544  				"cpe:2.3:a:cloudbees_installation:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   545  				"cpe:2.3:a:cloudbees_installation:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   546  				"cpe:2.3:a:cloudbees_installation_manager:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   547  				"cpe:2.3:a:cloudbees_installation_manager:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   548  				"cpe:2.3:a:jenkins:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   549  				"cpe:2.3:a:jenkins:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   550  				"cpe:2.3:a:modules:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   551  				"cpe:2.3:a:modules:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   552  				"cpe:2.3:a:com.cloudbees.jenkins.modules:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*",
   553  				"cpe:2.3:a:com.cloudbees.jenkins.modules:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*",
   554  			},
   555  		},
   556  		{
   557  			name: "go product and vendor candidates are wired up",
   558  			p: pkg.Package{
   559  				Name:     "github.com/someone/something",
   560  				Version:  "3.2",
   561  				FoundBy:  "go-cataloger",
   562  				Language: pkg.Go,
   563  				Type:     pkg.GoModulePkg,
   564  			},
   565  			expected: []string{
   566  				"cpe:2.3:a:someone:something:3.2:*:*:*:*:*:*:*",
   567  			},
   568  		},
   569  		{
   570  			name: "go product with vendor candidates and an extra sub-item",
   571  			p: pkg.Package{
   572  				Name:     "github.com/someone/something/more",
   573  				Version:  "3.2",
   574  				FoundBy:  "go-cataloger",
   575  				Language: pkg.Go,
   576  				Type:     pkg.GoModulePkg,
   577  			},
   578  			expected: []string{
   579  				"cpe:2.3:a:someone:something\\/more:3.2:*:*:*:*:*:*:*",
   580  			},
   581  		},
   582  		{
   583  			name: "generate no CPEs for indeterminate golang package name",
   584  			p: pkg.Package{
   585  				Name:     "github.com/what",
   586  				Version:  "3.2",
   587  				FoundBy:  "go-cataloger",
   588  				Language: pkg.Go,
   589  				Type:     pkg.GoModulePkg,
   590  			},
   591  			expected: []string{},
   592  		},
   593  		{
   594  			name: "regression: handlebars within java archive",
   595  			p: pkg.Package{
   596  				Name:     "handlebars",
   597  				Version:  "3.0.8",
   598  				Type:     pkg.JavaPkg,
   599  				Language: pkg.Java,
   600  				FoundBy:  "java-cataloger",
   601  				Metadata: pkg.JavaArchive{
   602  					Manifest: &pkg.JavaManifest{
   603  						Main: keyValues(map[string]string{
   604  							"Extension-Name":         "handlebars",
   605  							"Group-Id":               "org.jenkins-ci.ui",
   606  							"Hudson-Version":         "2.204",
   607  							"Implementation-Title":   "handlebars",
   608  							"Implementation-Version": "3.0.8",
   609  							"Plugin-Version":         "3.0.8",
   610  							"Short-Name":             "handlebars",
   611  						}),
   612  					},
   613  					PomProperties: &pkg.JavaPomProperties{
   614  						GroupID:    "org.jenkins-ci.ui",
   615  						ArtifactID: "handlebars",
   616  						Version:    "3.0.8",
   617  					},
   618  				},
   619  			},
   620  			expected: []string{
   621  				"cpe:2.3:a:handlebars:handlebars:3.0.8:*:*:*:*:*:*:*",
   622  				"cpe:2.3:a:handlebarsjs:handlebars:3.0.8:*:*:*:*:*:*:*", // important!
   623  				"cpe:2.3:a:jenkins-ci:handlebars:3.0.8:*:*:*:*:*:*:*",
   624  				"cpe:2.3:a:jenkins:handlebars:3.0.8:*:*:*:*:*:*:*",
   625  				"cpe:2.3:a:jenkins_ci:handlebars:3.0.8:*:*:*:*:*:*:*",
   626  				"cpe:2.3:a:ui:handlebars:3.0.8:*:*:*:*:*:*:*",
   627  				"cpe:2.3:a:org.jenkins-ci.ui:handlebars:3.0.8:*:*:*:*:*:*:*",
   628  			},
   629  		},
   630  		{
   631  			name: "regression: jenkins plugin active-directory",
   632  			p: pkg.Package{
   633  				Name:     "active-directory",
   634  				Version:  "2.25.1",
   635  				Type:     pkg.JenkinsPluginPkg,
   636  				FoundBy:  "java-cataloger",
   637  				Language: pkg.Java,
   638  				Metadata: pkg.JavaArchive{
   639  					Manifest: &pkg.JavaManifest{
   640  						Main: keyValues(map[string]string{
   641  							"Extension-Name": "active-directory",
   642  							"Group-Id":       "org.jenkins-ci.plugins",
   643  						}),
   644  					},
   645  					PomProperties: &pkg.JavaPomProperties{
   646  						GroupID:    "org.jenkins-ci.plugins",
   647  						ArtifactID: "org.jenkins-ci.plugins",
   648  						Version:    "2.25.1",
   649  					},
   650  				},
   651  			},
   652  			expected: []string{
   653  				"cpe:2.3:a:active-directory:active-directory:2.25.1:*:*:*:*:*:*:*",
   654  				"cpe:2.3:a:active-directory:active_directory:2.25.1:*:*:*:*:*:*:*",
   655  				"cpe:2.3:a:active:active-directory:2.25.1:*:*:*:*:*:*:*",
   656  				"cpe:2.3:a:active:active_directory:2.25.1:*:*:*:*:*:*:*",
   657  				"cpe:2.3:a:active_directory:active-directory:2.25.1:*:*:*:*:*:*:*",
   658  				"cpe:2.3:a:active_directory:active_directory:2.25.1:*:*:*:*:*:*:*",
   659  				"cpe:2.3:a:jenkins-ci:active-directory:2.25.1:*:*:*:*:*:*:*",
   660  				"cpe:2.3:a:jenkins-ci:active_directory:2.25.1:*:*:*:*:*:*:*",
   661  				"cpe:2.3:a:jenkins:active-directory:2.25.1:*:*:*:*:*:*:*", // important!
   662  				"cpe:2.3:a:jenkins:active_directory:2.25.1:*:*:*:*:*:*:*", // important!
   663  				"cpe:2.3:a:jenkins_ci:active-directory:2.25.1:*:*:*:*:*:*:*",
   664  				"cpe:2.3:a:jenkins_ci:active_directory:2.25.1:*:*:*:*:*:*:*",
   665  				"cpe:2.3:a:org.jenkins-ci.plugins:active-directory:2.25.1:*:*:*:*:*:*:*",
   666  				"cpe:2.3:a:org.jenkins-ci.plugins:active_directory:2.25.1:*:*:*:*:*:*:*",
   667  			},
   668  		},
   669  		{
   670  			name: "regression: special characters in CPE should result in no generation",
   671  			p: pkg.Package{
   672  				Name:     "bundler",
   673  				Version:  "2.1.4",
   674  				Type:     pkg.GemPkg,
   675  				FoundBy:  "gem-cataloger",
   676  				Language: pkg.Ruby,
   677  				Metadata: pkg.RubyGemspec{
   678  					Name:    "bundler",
   679  					Version: "2.1.4",
   680  					Authors: []string{
   681  						"jessica lynn suttles",
   682  						"stephanie morillo",
   683  						"david rodríguez",
   684  						"andré medeiros",
   685  					},
   686  				},
   687  			},
   688  			expected: []string{
   689  				"cpe:2.3:a:bundler:bundler:2.1.4:*:*:*:*:*:*:*",
   690  				"cpe:2.3:a:ruby-lang:bundler:2.1.4:*:*:*:*:*:*:*",
   691  				"cpe:2.3:a:ruby:bundler:2.1.4:*:*:*:*:*:*:*",
   692  				"cpe:2.3:a:ruby_lang:bundler:2.1.4:*:*:*:*:*:*:*",
   693  				"cpe:2.3:a:jessica-lynn-suttles:bundler:2.1.4:*:*:*:*:*:*:*",
   694  				"cpe:2.3:a:jessica_lynn_suttles:bundler:2.1.4:*:*:*:*:*:*:*",
   695  				"cpe:2.3:a:stephanie-morillo:bundler:2.1.4:*:*:*:*:*:*:*",
   696  				"cpe:2.3:a:stephanie_morillo:bundler:2.1.4:*:*:*:*:*:*:*",
   697  			},
   698  		},
   699  		{
   700  			name: "regression: python redis shadows normal redis",
   701  			p: pkg.Package{
   702  				Name:     "redis",
   703  				Version:  "2.1.4",
   704  				Type:     pkg.PythonPkg,
   705  				FoundBy:  "some-analyzer",
   706  				Language: pkg.Python,
   707  			},
   708  			expected: []string{
   709  				"cpe:2.3:a:python-redis:python-redis:2.1.4:*:*:*:*:*:*:*",
   710  				"cpe:2.3:a:python-redis:python_redis:2.1.4:*:*:*:*:*:*:*",
   711  				"cpe:2.3:a:python-redis:redis:2.1.4:*:*:*:*:*:*:*",
   712  				"cpe:2.3:a:python:python-redis:2.1.4:*:*:*:*:*:*:*",
   713  				"cpe:2.3:a:python:python_redis:2.1.4:*:*:*:*:*:*:*",
   714  				"cpe:2.3:a:python:redis:2.1.4:*:*:*:*:*:*:*",
   715  				"cpe:2.3:a:python_redis:python-redis:2.1.4:*:*:*:*:*:*:*",
   716  				"cpe:2.3:a:python_redis:python_redis:2.1.4:*:*:*:*:*:*:*",
   717  				"cpe:2.3:a:python_redis:redis:2.1.4:*:*:*:*:*:*:*",
   718  			},
   719  		},
   720  		{
   721  			name: "regression: ruby-rake apk missing expected ruby-lang:rake CPE",
   722  			p: pkg.Package{
   723  				Name:     "ruby-rake",
   724  				Version:  "2.7.6-r0",
   725  				Type:     pkg.ApkPkg,
   726  				FoundBy:  "apk-db-analyzer",
   727  				Language: pkg.UnknownLanguage,
   728  				Metadata: pkg.ApkDBEntry{
   729  					Package:       "ruby-rake",
   730  					URL:           "https://www.ruby-lang.org/",
   731  					OriginPackage: "ruby",
   732  				},
   733  			},
   734  			expected: []string{
   735  				"cpe:2.3:a:ruby-lang:rake:2.7.6-r0:*:*:*:*:*:*:*",
   736  				"cpe:2.3:a:rake:rake:2.7.6-r0:*:*:*:*:*:*:*",
   737  				"cpe:2.3:a:rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
   738  				"cpe:2.3:a:rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
   739  				"cpe:2.3:a:ruby-lang:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
   740  				"cpe:2.3:a:ruby-lang:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
   741  				"cpe:2.3:a:ruby-rake:rake:2.7.6-r0:*:*:*:*:*:*:*",
   742  				"cpe:2.3:a:ruby-rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
   743  				"cpe:2.3:a:ruby-rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
   744  				"cpe:2.3:a:ruby:rake:2.7.6-r0:*:*:*:*:*:*:*",
   745  				"cpe:2.3:a:ruby:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
   746  				"cpe:2.3:a:ruby:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
   747  				"cpe:2.3:a:ruby_lang:rake:2.7.6-r0:*:*:*:*:*:*:*",
   748  				"cpe:2.3:a:ruby_lang:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
   749  				"cpe:2.3:a:ruby_lang:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
   750  				"cpe:2.3:a:ruby_rake:rake:2.7.6-r0:*:*:*:*:*:*:*",
   751  				"cpe:2.3:a:ruby_rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*",
   752  				"cpe:2.3:a:ruby_rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*",
   753  			},
   754  		},
   755  		{
   756  			name: "wordpress plugin",
   757  			p: pkg.Package{
   758  				Name:    "WP Coder",
   759  				Version: "2.5.1",
   760  				Type:    pkg.WordpressPluginPkg,
   761  				Metadata: pkg.WordpressPluginEntry{
   762  					PluginInstallDirectory: "wp-coder",
   763  					Author:                 "Wow-Company",
   764  					AuthorURI:              "https://wow-estore.com",
   765  				},
   766  			},
   767  			expected: []string{
   768  				"cpe:2.3:a:wow-company:wp-coder:2.5.1:*:*:*:*:wordpress:*:*",
   769  				"cpe:2.3:a:wow-company:wp_coder:2.5.1:*:*:*:*:wordpress:*:*", // this is the correct CPE relative to CVE-2021-25053
   770  				"cpe:2.3:a:wow-estore:wp-coder:2.5.1:*:*:*:*:wordpress:*:*",
   771  				"cpe:2.3:a:wow-estore:wp_coder:2.5.1:*:*:*:*:wordpress:*:*",
   772  				"cpe:2.3:a:wow:wp-coder:2.5.1:*:*:*:*:wordpress:*:*",
   773  				"cpe:2.3:a:wow:wp_coder:2.5.1:*:*:*:*:wordpress:*:*",
   774  				"cpe:2.3:a:wow_company:wp-coder:2.5.1:*:*:*:*:wordpress:*:*",
   775  				"cpe:2.3:a:wow_company:wp_coder:2.5.1:*:*:*:*:wordpress:*:*",
   776  				"cpe:2.3:a:wow_estore:wp-coder:2.5.1:*:*:*:*:wordpress:*:*",
   777  				"cpe:2.3:a:wow_estore:wp_coder:2.5.1:*:*:*:*:wordpress:*:*",
   778  			},
   779  		},
   780  		{
   781  			name: "dotnet deps.json",
   782  			p: pkg.Package{
   783  				Name:    "Something",
   784  				Version: "2.5.1",
   785  				Type:    pkg.DotnetPkg,
   786  				Metadata: pkg.DotnetDepsEntry{
   787  					Name: "Something-Else",
   788  
   789  					Executables: map[string]pkg.DotnetPortableExecutableEntry{
   790  						"1": {
   791  							AssemblyVersion: "assembly-version!",
   792  							LegalCopyright:  "copyright!",
   793  							Comments:        "comments!",
   794  							InternalName:    "internal!",
   795  							CompanyName:     "company!",
   796  							ProductName:     "product!",
   797  							ProductVersion:  "version!",
   798  						},
   799  					},
   800  				},
   801  			},
   802  			expected: []string{
   803  				"cpe:2.3:a:company\\!:product\\!:2.5.1:*:*:*:*:*:*:*",
   804  				"cpe:2.3:a:company\\!:product\\!_.net:2.5.1:*:*:*:*:*:*:*",
   805  				"cpe:2.3:a:company\\!:something_else:2.5.1:*:*:*:*:*:*:*",
   806  				"cpe:2.3:a:company\\!:something_else_.net:2.5.1:*:*:*:*:*:*:*",
   807  				"cpe:2.3:a:something_else:product\\!:2.5.1:*:*:*:*:*:*:*",
   808  				"cpe:2.3:a:something_else:product\\!_.net:2.5.1:*:*:*:*:*:*:*",
   809  				"cpe:2.3:a:something_else:something_else:2.5.1:*:*:*:*:*:*:*",
   810  				"cpe:2.3:a:something_else:something_else_.net:2.5.1:*:*:*:*:*:*:*",
   811  			},
   812  		},
   813  		{
   814  			name: "dotnet executable",
   815  			p: pkg.Package{
   816  				Name:    "Something",
   817  				Version: "2.5.1",
   818  				Type:    pkg.DotnetPkg,
   819  				Metadata: pkg.DotnetPortableExecutableEntry{
   820  					AssemblyVersion: "assembly-version!",
   821  					LegalCopyright:  "copyright!",
   822  					Comments:        "comments!",
   823  					InternalName:    "internal!",
   824  					CompanyName:     "company!",
   825  					ProductName:     "product!",
   826  					ProductVersion:  "version!",
   827  				},
   828  			},
   829  			expected: []string{
   830  				"cpe:2.3:a:company\\!:product\\!:2.5.1:*:*:*:*:*:*:*",
   831  				"cpe:2.3:a:company\\!:product\\!_.net:2.5.1:*:*:*:*:*:*:*",
   832  			},
   833  		},
   834  		{
   835  			name: "dotnet package.lock",
   836  			p: pkg.Package{
   837  				Name:    "Something",
   838  				Version: "2.5.1",
   839  				Type:    pkg.DotnetPkg,
   840  				Metadata: pkg.DotnetPackagesLockEntry{
   841  					Name: "Something-Else",
   842  				},
   843  			},
   844  			expected: []string{
   845  				"cpe:2.3:a:something_else:something_else:2.5.1:*:*:*:*:*:*:*",
   846  				"cpe:2.3:a:something_else:something_else_.net:2.5.1:*:*:*:*:*:*:*",
   847  			},
   848  		},
   849  		{
   850  			name: "ML model package should generate no CPEs",
   851  			p: pkg.Package{
   852  				Name:    "llama3-8b",
   853  				Version: "3.0",
   854  				Type:    pkg.ModelPkg,
   855  			},
   856  			expected: []string{},
   857  		},
   858  	}
   859  
   860  	for _, test := range tests {
   861  		t.Run(test.name, func(t *testing.T) {
   862  			actual := FromPackageAttributes(test.p)
   863  			expectedCpeSet := set.NewStringSet()
   864  			for _, cpeStr := range test.expected {
   865  				expectedCpeSet.Add("syft-generated:" + cpeStr)
   866  			}
   867  
   868  			actualCpeSet := set.NewStringSet()
   869  			for _, a := range actual {
   870  				actualCpeSet.Add(fmt.Sprintf("%s:%s", a.Source.String(), a.Attributes.String()))
   871  			}
   872  
   873  			extra := strset.Difference(actualCpeSet, expectedCpeSet).List()
   874  			sort.Strings(extra)
   875  			if len(extra) > 0 {
   876  				t.Errorf("found extra CPEs:")
   877  				for _, d := range extra {
   878  					t.Logf("   %q,\n", d)
   879  				}
   880  			}
   881  
   882  			missing := strset.Difference(expectedCpeSet, actualCpeSet).List()
   883  			sort.Strings(missing)
   884  			if len(missing) > 0 {
   885  				t.Errorf("missing CPEs:")
   886  				for _, d := range missing {
   887  					t.Logf("   %q,\n", d)
   888  				}
   889  			}
   890  		})
   891  	}
   892  }
   893  
   894  func TestCandidateProducts(t *testing.T) {
   895  	tests := []struct {
   896  		name     string
   897  		p        pkg.Package
   898  		expected []string
   899  	}{
   900  		{
   901  			name: "apache-cassandra",
   902  			p: pkg.Package{
   903  				Name: "apache-cassandra",
   904  				Type: pkg.JavaPkg,
   905  			},
   906  			expected: []string{"cassandra" /* <-- known good names | default guess --> */, "apache-cassandra", "apache_cassandra"},
   907  		},
   908  		{
   909  			name: "springframework",
   910  			p: pkg.Package{
   911  				Name: "springframework",
   912  				Type: pkg.JavaPkg,
   913  			},
   914  			expected: []string{"spring_framework", "springsource_spring_framework" /* <-- known good names | default guess --> */, "springframework"},
   915  		},
   916  		{
   917  			name: "spring-security-core",
   918  			p: pkg.Package{
   919  				Name: "spring-security-core",
   920  				Type: pkg.JavaPkg,
   921  			},
   922  			expected: []string{"spring-security-core", "spring_security", "spring_security_core"},
   923  		},
   924  		{
   925  			name: "java",
   926  			p: pkg.Package{
   927  				Name:     "some-java-package-with-group-id",
   928  				Type:     pkg.JavaPkg,
   929  				Language: pkg.Java,
   930  				Metadata: pkg.JavaArchive{
   931  					PomProperties: &pkg.JavaPomProperties{
   932  						GroupID: "com.apple.itunes",
   933  					},
   934  				},
   935  			},
   936  			expected: []string{"itunes", "some-java-package-with-group-id", "some_java_package_with_group_id"},
   937  		},
   938  		{
   939  			name: "java-with-asterisk",
   940  			p: pkg.Package{
   941  				Name:     "some-java-package-with-group-id",
   942  				Type:     pkg.JavaPkg,
   943  				Language: pkg.Java,
   944  				Metadata: pkg.JavaArchive{
   945  					PomProperties: &pkg.JavaPomProperties{
   946  						GroupID: "com.apple.itunes.*",
   947  					},
   948  				},
   949  			},
   950  			expected: []string{"itunes", "some-java-package-with-group-id", "some_java_package_with_group_id"},
   951  		},
   952  		{
   953  			name: "jenkins-plugin",
   954  			p: pkg.Package{
   955  				Name:     "some-jenkins-plugin",
   956  				Type:     pkg.JenkinsPluginPkg,
   957  				Language: pkg.Java,
   958  				Metadata: pkg.JavaArchive{
   959  					PomProperties: &pkg.JavaPomProperties{
   960  						GroupID: "com.cloudbees.jenkins.plugins",
   961  					},
   962  				},
   963  			},
   964  			expected: []string{"some-jenkins-plugin", "some_jenkins_plugin", "jenkins"},
   965  		},
   966  		{
   967  			name: "javascript",
   968  			p: pkg.Package{
   969  				Name: "handlebars.js",
   970  				Type: pkg.NpmPkg,
   971  			},
   972  			expected: []string{"handlebars" /* <-- known good names | default guess --> */, "handlebars.js"},
   973  		},
   974  		{
   975  			name: "gem",
   976  			p: pkg.Package{
   977  				Name: "RedCloth",
   978  				Type: pkg.GemPkg,
   979  			},
   980  			expected: []string{"redcloth_library" /* <-- known good names | default guess --> */, "RedCloth"},
   981  		},
   982  		{
   983  			name: "python",
   984  			p: pkg.Package{
   985  				Name: "python-rrdtool",
   986  				Type: pkg.PythonPkg,
   987  			},
   988  			expected: []string{"rrdtool" /* <-- known good names | default guess --> */, "python-rrdtool", "python_rrdtool"},
   989  		},
   990  	}
   991  
   992  	for _, test := range tests {
   993  		t.Run(test.name, func(t *testing.T) {
   994  			assert.ElementsMatch(t, test.expected, candidateProducts(test.p))
   995  		})
   996  	}
   997  }
   998  
   999  func TestCandidateVendor(t *testing.T) {
  1000  	tests := []struct {
  1001  		name     string
  1002  		p        pkg.Package
  1003  		expected []string
  1004  	}{
  1005  		{
  1006  			name: "elasticsearch",
  1007  			p: pkg.Package{
  1008  				Name: "elasticsearch",
  1009  				Type: pkg.JavaPkg,
  1010  			},
  1011  			expected: []string{"elastic" /* <-- known good names | default guess --> */, "elasticsearch"},
  1012  		},
  1013  		{
  1014  			name: "spring-security",
  1015  			p: pkg.Package{
  1016  				Name: "spring-security-core",
  1017  				Type: pkg.JavaPkg,
  1018  			},
  1019  			expected: []string{"vmware" /* <-- known good names | default guess --> */, "spring", "spring-security", "spring-security-core", "spring_security_core", "spring_security"},
  1020  		},
  1021  		{
  1022  			name: "log4j",
  1023  			p: pkg.Package{
  1024  				Name: "log4j",
  1025  				Type: pkg.JavaPkg,
  1026  			},
  1027  			expected: []string{"apache"},
  1028  		},
  1029  		{
  1030  			name: "Django",
  1031  			p: pkg.Package{
  1032  				Name: "Django",
  1033  				Type: pkg.PythonPkg,
  1034  			},
  1035  			expected: []string{"djangoproject", "python-Django", "python_Django" /* <-- known good names | default guess --> */, "python", "Django"},
  1036  		},
  1037  	}
  1038  
  1039  	for _, test := range tests {
  1040  		t.Run(test.name, func(t *testing.T) {
  1041  			assert.ElementsMatch(t, test.expected, candidateVendors(test.p))
  1042  		})
  1043  	}
  1044  }
  1045  
  1046  func Test_generateSubSelections(t *testing.T) {
  1047  	tests := []struct {
  1048  		field    string
  1049  		expected []string
  1050  	}{
  1051  		{
  1052  			field:    "jenkins",
  1053  			expected: []string{"jenkins"},
  1054  		},
  1055  		{
  1056  			field:    "jenkins-ci",
  1057  			expected: []string{"jenkins", "jenkins-ci"},
  1058  		},
  1059  		{
  1060  			field:    "jenkins--ci",
  1061  			expected: []string{"jenkins", "jenkins-ci"},
  1062  		},
  1063  		{
  1064  			field:    "jenkins_ci_tools",
  1065  			expected: []string{"jenkins", "jenkins_ci", "jenkins_ci_tools"},
  1066  		},
  1067  		{
  1068  			field:    "-jenkins",
  1069  			expected: []string{"jenkins"},
  1070  		},
  1071  		{
  1072  			field:    "jenkins_",
  1073  			expected: []string{"jenkins"},
  1074  		},
  1075  		{
  1076  			field:    "",
  1077  			expected: nil,
  1078  		},
  1079  		{
  1080  			field:    "-",
  1081  			expected: nil,
  1082  		},
  1083  		{
  1084  			field:    "_",
  1085  			expected: nil,
  1086  		},
  1087  	}
  1088  	for _, test := range tests {
  1089  		t.Run(test.field, func(t *testing.T) {
  1090  			assert.ElementsMatch(t, test.expected, generateSubSelections(test.field))
  1091  		})
  1092  	}
  1093  }
  1094  
  1095  func Test_addSeparatorVariations(t *testing.T) {
  1096  	tests := []struct {
  1097  		input    []string
  1098  		expected []string
  1099  	}{
  1100  		{
  1101  			input:    []string{"jenkins-ci"},
  1102  			expected: []string{"jenkins-ci", "jenkins_ci"}, //, "jenkinsci"},
  1103  		},
  1104  		{
  1105  			input:    []string{"jenkins_ci"},
  1106  			expected: []string{"jenkins_ci", "jenkins-ci"}, //, "jenkinsci"},
  1107  		},
  1108  		{
  1109  			input:    []string{"jenkins"},
  1110  			expected: []string{"jenkins"},
  1111  		},
  1112  		{
  1113  			input:    []string{"jenkins-ci", "circle-ci"},
  1114  			expected: []string{"jenkins-ci", "jenkins_ci", "circle-ci", "circle_ci"}, //, "jenkinsci", "circleci"},
  1115  		},
  1116  	}
  1117  	for _, test := range tests {
  1118  		t.Run(strings.Join(test.input, ","), func(t *testing.T) {
  1119  			val := newFieldCandidateSet(test.input...)
  1120  			addDelimiterVariations(val)
  1121  			assert.ElementsMatch(t, test.expected, val.values())
  1122  		})
  1123  	}
  1124  }
  1125  
  1126  func TestDictionaryFindIsWired(t *testing.T) {
  1127  
  1128  	tests := []struct {
  1129  		name       string
  1130  		pkg        pkg.Package
  1131  		want       []cpe.CPE
  1132  		wantExists bool
  1133  	}{
  1134  		{
  1135  			name: "sanity check that cpe data is wired up",
  1136  			pkg: pkg.Package{
  1137  				Name:    "openssl",
  1138  				Version: "1.0.2k",
  1139  				Type:    pkg.GemPkg,
  1140  			},
  1141  			want: []cpe.CPE{
  1142  				cpe.Must("cpe:2.3:a:ruby-lang:openssl:1.0.2k:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
  1143  				cpe.Must("cpe:2.3:a:ruby-lang:openssl:1.0.2k:*:*:*:*:ruby:*:*", cpe.NVDDictionaryLookupSource),
  1144  			},
  1145  			// without the cpe data wired up, this would be empty (generation also creates cpe:2.3:a:openssl:openssl:1.0.2k:*:*:*:*:*:*:*)
  1146  			wantExists: true,
  1147  		},
  1148  		{
  1149  			name: "ML model packages should not have dictionary CPEs",
  1150  			pkg: pkg.Package{
  1151  				Name:    "llama3-8b",
  1152  				Version: "3.0",
  1153  				Type:    pkg.ModelPkg,
  1154  			},
  1155  			want:       []cpe.CPE{},
  1156  			wantExists: false,
  1157  		},
  1158  	}
  1159  	for _, tt := range tests {
  1160  		t.Run(tt.name, func(t *testing.T) {
  1161  			got, gotExists := FromDictionaryFind(tt.pkg)
  1162  			assert.ElementsMatch(t, tt.want, got)
  1163  			assert.Equal(t, tt.wantExists, gotExists)
  1164  		})
  1165  	}
  1166  }
  1167  
  1168  // TestAddBinaryPackageDigitVariations tests the heuristic for binary package types
  1169  // where names ending with digits get variations with all suffix-digits removed (e.g. Qt5 -> Qt).
  1170  // This improves vulnerability matching for binary packages like Qt6, libfoo123, etc.
  1171  func TestAddBinaryPackageDigitVariations(t *testing.T) {
  1172  	tests := []struct {
  1173  		name            string
  1174  		packageType     pkg.Type
  1175  		inputCandidates []string
  1176  		expectedPresent []string // These should be present in the result
  1177  		expectedAbsent  []string // These should NOT be present in the result
  1178  	}{
  1179  		{
  1180  			name:            "Qt5 binary package example",
  1181  			packageType:     pkg.BinaryPkg,
  1182  			inputCandidates: []string{"Qt5"},
  1183  			expectedPresent: []string{"Qt5", "Qt"},
  1184  			expectedAbsent:  []string{},
  1185  		},
  1186  		{
  1187  			name:            "package with trailing digits",
  1188  			packageType:     pkg.BinaryPkg,
  1189  			inputCandidates: []string{"Qt5", "libfoo123", "bar42", "baz"},
  1190  			expectedPresent: []string{"Qt5", "Qt", "libfoo123", "libfoo", "bar42", "bar", "baz"},
  1191  			expectedAbsent:  []string{},
  1192  		},
  1193  		{
  1194  			name:            "multiple trailing digits",
  1195  			inputCandidates: []string{"Qt872", "package999"},
  1196  			expectedPresent: []string{"Qt872", "Qt", "package999", "package"},
  1197  			expectedAbsent:  []string{},
  1198  		},
  1199  		{
  1200  			name:            "package without trailing digits",
  1201  			inputCandidates: []string{"QtCore", "libfoo", "bar"},
  1202  			expectedPresent: []string{"QtCore", "libfoo", "bar"},
  1203  			expectedAbsent:  []string{"QtCor", "libfo", "ba"},
  1204  		},
  1205  		{
  1206  			name:            "empty candidate set",
  1207  			packageType:     pkg.BinaryPkg,
  1208  			inputCandidates: []string{},
  1209  			expectedPresent: []string{},
  1210  			expectedAbsent:  []string{},
  1211  		},
  1212  	}
  1213  
  1214  	for _, test := range tests {
  1215  		t.Run(test.name, func(t *testing.T) {
  1216  			fields := newFieldCandidateSet(test.inputCandidates...)
  1217  			addBinaryPackageDigitVariations(fields)
  1218  
  1219  			values := fields.uniqueValues()
  1220  
  1221  			for _, expected := range test.expectedPresent {
  1222  				assert.Contains(t, values, expected, "expected %q to be present", expected)
  1223  			}
  1224  
  1225  			for _, notExpected := range test.expectedAbsent {
  1226  				assert.NotContains(t, values, notExpected, "expected %q to be absent", notExpected)
  1227  			}
  1228  		})
  1229  	}
  1230  }