github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/binary/classifiers.go (about)

     1  package binary
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/anchore/packageurl-go"
     7  	"github.com/anchore/syft/syft/cpe"
     8  	"github.com/anchore/syft/syft/pkg/cataloger/internal/binutils"
     9  )
    10  
    11  // in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL]
    12  var pythonVersionTemplate = `(?m)\x00(?P<version>{{ .version }}[-._a-zA-Z0-9]*)\x00`
    13  
    14  //nolint:funlen
    15  func DefaultClassifiers() []binutils.Classifier {
    16  	m := binutils.ContextualEvidenceMatchers{CatalogerName: catalogerName}
    17  
    18  	var libpythonMatcher = m.FileNameTemplateVersionMatcher(
    19  		`(?:.*/|^)libpython(?P<version>[0-9]+(?:\.[0-9]+)+)[a-z]?\.so.*$`,
    20  		pythonVersionTemplate,
    21  	)
    22  
    23  	var rubyMatcher = m.FileContentsVersionMatcher(
    24  		// ruby 3.4.0dev (2024-09-15T01:06:11Z master 532af89e3b) [x86_64-linux]
    25  		// ruby 3.4.0preview1 (2024-05-16 master 9d69619623) [x86_64-linux]
    26  		// ruby 3.3.0rc1 (2023-12-11 master a49643340e) [x86_64-linux]
    27  		// ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux]
    28  		// ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5) [x86_64-linux]
    29  		`(?m)ruby (?P<version>[0-9]+\.[0-9]+\.[0-9]+((p|preview|rc|dev)[0-9]*)?) `)
    30  
    31  	classifiers := []binutils.Classifier{
    32  		{
    33  			Class:    "python-binary",
    34  			FileGlob: "**/python*",
    35  			EvidenceMatcher: binutils.MatchAny(
    36  				// try to find version information from libpython shared libraries
    37  				binutils.SharedLibraryLookup(
    38  					`^libpython[0-9]+(?:\.[0-9]+)+[a-z]?\.so.*$`,
    39  					libpythonMatcher),
    40  				// check for version information in the binary
    41  				m.FileNameTemplateVersionMatcher(
    42  					`(?:.*/|^)python(?P<version>[0-9]+(?:\.[0-9]+)+)$`,
    43  					pythonVersionTemplate),
    44  			),
    45  			Package: "python",
    46  			PURL:    mustPURL("pkg:generic/python@version"),
    47  			CPEs: []cpe.CPE{
    48  				cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
    49  				cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
    50  			},
    51  		},
    52  		{
    53  			Class:           "python-binary-lib",
    54  			FileGlob:        "**/libpython*.so*",
    55  			EvidenceMatcher: libpythonMatcher,
    56  			Package:         "python",
    57  			PURL:            mustPURL("pkg:generic/python@version"),
    58  			CPEs: []cpe.CPE{
    59  				cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
    60  				cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
    61  			},
    62  		},
    63  		{
    64  			Class:    "pypy-binary-lib",
    65  			FileGlob: "**/libpypy*.so*",
    66  			EvidenceMatcher: m.FileContentsVersionMatcher(
    67  				`(?m)\[PyPy (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
    68  			Package: "pypy",
    69  			PURL:    mustPURL("pkg:generic/pypy@version"),
    70  		},
    71  		{
    72  			Class:    "go-binary",
    73  			FileGlob: "**/go",
    74  			EvidenceMatcher: m.FileContentsVersionMatcher(
    75  				`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)\x00`),
    76  			Package: "go",
    77  			PURL:    mustPURL("pkg:generic/go@version"),
    78  			CPEs:    singleCPE("cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
    79  		},
    80  		{
    81  			Class:    "julia-binary",
    82  			FileGlob: "**/libjulia-internal.so",
    83  			EvidenceMatcher: m.FileContentsVersionMatcher(
    84  				`(?m)__init__\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00verify`),
    85  			Package: "julia",
    86  			PURL:    mustPURL("pkg:generic/julia@version"),
    87  			CPEs:    singleCPE("cpe:2.3:a:julialang:julia:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
    88  		},
    89  		{
    90  			Class:    "helm",
    91  			FileGlob: "**/helm",
    92  			EvidenceMatcher: m.FileContentsVersionMatcher(
    93  				`(?m)\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
    94  			Package: "helm",
    95  			PURL:    mustPURL("pkg:golang/helm.sh/helm@version"),
    96  			CPEs:    singleCPE("cpe:2.3:a:helm:helm:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
    97  		},
    98  		{
    99  			Class:    "redis-binary",
   100  			FileGlob: "**/redis-server",
   101  			EvidenceMatcher: binutils.MatchAny(
   102  				// matches most recent versions of redis (~v7), e.g. "7.0.14buildkitsandbox-1702957741000000000"
   103  				m.FileContentsVersionMatcher(`[^\d](?P<version>\d+.\d+\.\d+)buildkitsandbox-\d+`),
   104  				// matches against older versions of redis (~v3 - v6), e.g. "4.0.11841ce7054bd9-1542359302000000000"
   105  				m.FileContentsVersionMatcher(`[^\d](?P<version>[0-9]+\.[0-9]+\.[0-9]+)\w{12}-\d+`),
   106  				// matches against older versions of redis (~v2), e.g. "Server started, Redis version 2.8.23"
   107  				m.FileContentsVersionMatcher(`Redis version (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
   108  			),
   109  			Package: "redis",
   110  			PURL:    mustPURL("pkg:generic/redis@version"),
   111  			CPEs: []cpe.CPE{
   112  				cpe.Must("cpe:2.3:a:redislabs:redis:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   113  				cpe.Must("cpe:2.3:a:redis:redis:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   114  			},
   115  		},
   116  		{
   117  			Class:    "nodejs-binary",
   118  			FileGlob: "**/node",
   119  			EvidenceMatcher: binutils.MatchAny(
   120  				// [NUL]node v0.10.48[NUL]
   121  				// [NUL]v0.12.18[NUL]
   122  				// [NUL]v4.9.1[NUL]
   123  				// node.js/v22.9.0
   124  				m.FileContentsVersionMatcher(`(?m)\x00(node )?v(?P<version>(0|4|5|6)\.[0-9]+\.[0-9]+)\x00`),
   125  				m.FileContentsVersionMatcher(`(?m)node\.js\/v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
   126  			),
   127  			Package: "node",
   128  			PURL:    mustPURL("pkg:generic/node@version"),
   129  			CPEs:    singleCPE("cpe:2.3:a:nodejs:node.js:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   130  		},
   131  		{
   132  			Class:    "go-binary-hint",
   133  			FileGlob: "**/VERSION*",
   134  			EvidenceMatcher: m.FileContentsVersionMatcher(
   135  				`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?(-[0-9a-f]{7})?)`),
   136  			Package: "go",
   137  			PURL:    mustPURL("pkg:generic/go@version"),
   138  			CPEs:    singleCPE("cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   139  		},
   140  		{
   141  			Class:    "busybox-binary",
   142  			FileGlob: "**/busybox",
   143  			EvidenceMatcher: m.FileContentsVersionMatcher(
   144  				`(?m)BusyBox\s+v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
   145  			Package: "busybox",
   146  			PURL:    mustPURL("pkg:generic/busybox@version"),
   147  			CPEs:    singleCPE("cpe:2.3:a:busybox:busybox:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   148  		},
   149  		{
   150  			Class:    "util-linux-binary",
   151  			FileGlob: "**/getopt",
   152  			EvidenceMatcher: m.FileContentsVersionMatcher(
   153  				`\x00util-linux\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`),
   154  			Package: "util-linux",
   155  			PURL:    mustPURL("pkg:generic/util-linux@version"),
   156  			CPEs:    singleCPE("cpe:2.3:a:kernel:util-linux:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   157  		},
   158  		{
   159  			Class:    "haproxy-binary",
   160  			FileGlob: "**/haproxy",
   161  			EvidenceMatcher: binutils.MatchAny(
   162  				m.FileContentsVersionMatcher(`(?m)version (?P<version>[0-9]+\.[0-9]+(\.|-dev|-rc)[0-9]+)(-[a-z0-9]{7})?, released 20`),
   163  				m.FileContentsVersionMatcher(`(?m)HA-Proxy version (?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)`),
   164  				m.FileContentsVersionMatcher(`(?m)(?P<version>[0-9]+\.[0-9]+(\.|-dev)[0-9]+)-[0-9a-zA-Z]{7}.+HAProxy version`),
   165  			),
   166  			Package: "haproxy",
   167  			PURL:    mustPURL("pkg:generic/haproxy@version"),
   168  			CPEs:    singleCPE("cpe:2.3:a:haproxy:haproxy:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   169  		},
   170  		{
   171  			Class:    "perl-binary",
   172  			FileGlob: "**/perl",
   173  			EvidenceMatcher: m.FileContentsVersionMatcher(
   174  				`(?m)\/usr\/local\/lib\/perl\d\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
   175  			Package: "perl",
   176  			PURL:    mustPURL("pkg:generic/perl@version"),
   177  			CPEs:    singleCPE("cpe:2.3:a:perl:perl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   178  		},
   179  		{
   180  			Class:    "php-composer-binary",
   181  			FileGlob: "**/composer*",
   182  			EvidenceMatcher: m.FileContentsVersionMatcher(
   183  				`(?m)'pretty_version'\s*=>\s*'(?P<version>[0-9]+\.[0-9]+\.[0-9]+(beta[0-9]+|alpha[0-9]+|RC[0-9]+)?)'`),
   184  			Package: "composer",
   185  			PURL:    mustPURL("pkg:generic/composer@version"),
   186  			CPEs:    singleCPE("cpe:2.3:a:getcomposer:composer:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   187  		},
   188  		{
   189  			Class:    "httpd-binary",
   190  			FileGlob: "**/httpd",
   191  			EvidenceMatcher: m.FileContentsVersionMatcher(
   192  				`(?m)Apache\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
   193  			Package: "httpd",
   194  			PURL:    mustPURL("pkg:generic/httpd@version"),
   195  			CPEs:    singleCPE("cpe:2.3:a:apache:http_server:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   196  		},
   197  		{
   198  			Class:    "memcached-binary",
   199  			FileGlob: "**/memcached",
   200  			EvidenceMatcher: m.FileContentsVersionMatcher(
   201  				`(?m)memcached\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
   202  			Package: "memcached",
   203  			PURL:    mustPURL("pkg:generic/memcached@version"),
   204  			CPEs:    singleCPE("cpe:2.3:a:memcached:memcached:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   205  		},
   206  		{
   207  			Class:    "traefik-binary",
   208  			FileGlob: "**/traefik",
   209  			EvidenceMatcher: m.FileContentsVersionMatcher(
   210  				// [NUL]v1.7.34[NUL]
   211  				// [NUL]2.9.6[NUL]
   212  				// 3.0.4[NUL]
   213  				`(?m)(\x00|\x{FFFD})?v?(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-alpha[0-9]|-beta[0-9]|-rc[0-9])?)\x00`),
   214  			Package: "traefik",
   215  			PURL:    mustPURL("pkg:generic/traefik@version"),
   216  			CPEs:    singleCPE("cpe:2.3:a:traefik:traefik:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   217  		},
   218  		{
   219  			Class:    "arangodb-binary",
   220  			FileGlob: "**/arangosh",
   221  			EvidenceMatcher: m.FileContentsVersionMatcher(
   222  				`(?m)\x00*(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?)\s\[linux\]`),
   223  			Package: "arangodb",
   224  			PURL:    mustPURL("pkg:generic/arangodb@version"),
   225  			CPEs:    singleCPE("cpe:2.3:a:arangodb:arangodb:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   226  		},
   227  		{
   228  			Class:    "postgresql-binary",
   229  			FileGlob: "**/postgres",
   230  			EvidenceMatcher: m.FileContentsVersionMatcher(
   231  				// [NUL]PostgreSQL 15beta4
   232  				// [NUL]PostgreSQL 15.1
   233  				// [NUL]PostgreSQL 9.6.24
   234  				// ?PostgreSQL 9.5alpha1
   235  				`(?m)(\x00|\?)PostgreSQL (?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
   236  			Package: "postgresql",
   237  			PURL:    mustPURL("pkg:generic/postgresql@version"),
   238  			CPEs:    singleCPE("cpe:2.3:a:postgresql:postgresql:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   239  		},
   240  		{
   241  			Class:    "mysql-binary",
   242  			FileGlob: "**/mysql",
   243  			EvidenceMatcher: binutils.MatchAny(
   244  				// shutdown[NUL]8.0.37[NUL][NUL][NUL][NUL][NUL]mysql_real_esc
   245  				m.FileContentsVersionMatcher(`\x00(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)\x00+mysql`),
   246  				// /export/home/pb2/build/sb_0-26781090-1516292385.58/release/mysql-8.0.4-rc/mysys_ssl/my_default.cc
   247  				m.FileContentsVersionMatcher(`(?m).*/mysql-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
   248  			),
   249  			Package: "mysql",
   250  			PURL:    mustPURL("pkg:generic/mysql@version"),
   251  			CPEs:    singleCPE("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   252  		},
   253  		{
   254  			Class:    "mysql-binary",
   255  			FileGlob: "**/mysql",
   256  			EvidenceMatcher: m.FileContentsVersionMatcher(
   257  				`(?m).*/percona-server-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
   258  			Package: "percona-server",
   259  			PURL:    mustPURL("pkg:generic/percona-server@version"),
   260  			CPEs: []cpe.CPE{
   261  				cpe.Must("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   262  				cpe.Must("cpe:2.3:a:percona:percona_server:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   263  			},
   264  		},
   265  		{
   266  			Class:    "mysql-binary",
   267  			FileGlob: "**/mysql",
   268  			EvidenceMatcher: m.FileContentsVersionMatcher(
   269  				`(?m).*/Percona-XtraDB-Cluster-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
   270  			Package: "percona-xtradb-cluster",
   271  			PURL:    mustPURL("pkg:generic/percona-xtradb-cluster@version"),
   272  			CPEs: []cpe.CPE{
   273  				cpe.Must("cpe:2.3:a:oracle:mysql:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   274  				cpe.Must("cpe:2.3:a:percona:percona_server:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   275  				cpe.Must("cpe:2.3:a:percona:xtradb_cluster:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   276  			},
   277  		},
   278  		{
   279  			Class:    "xtrabackup-binary",
   280  			FileGlob: "**/xtrabackup",
   281  			EvidenceMatcher: m.FileContentsVersionMatcher(
   282  				`(?m).*/percona-xtrabackup-(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)`),
   283  			Package: "percona-xtrabackup",
   284  			PURL:    mustPURL("pkg:generic/percona-xtrabackup@version"),
   285  			CPEs:    singleCPE("cpe:2.3:a:percona:xtrabackup:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   286  		},
   287  		{
   288  			Class:    "mariadb-binary",
   289  			FileGlob: "**/{mariadb,mysql}",
   290  			EvidenceMatcher: m.FileContentsVersionMatcher(
   291  				// 10.6.15-MariaDB
   292  				`(?m)(?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)?(alpha[0-9]|beta[0-9]|rc[0-9])?)-MariaDB`),
   293  			Package: "mariadb",
   294  			PURL:    mustPURL("pkg:generic/mariadb@version"),
   295  			CPEs:    singleCPE("cpe:2.3:a:mariadb:mariadb:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   296  		},
   297  		{
   298  			Class:    "rust-standard-library-linux",
   299  			FileGlob: "**/libstd-????????????????.so",
   300  			EvidenceMatcher: m.FileContentsVersionMatcher(
   301  				// clang LLVM (rustc version 1.48.0 (7eac88abb 2020-11-16))
   302  				`(?m)(\x00)clang LLVM \(rustc version (?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)) \(\w+ \d{4}\-\d{2}\-\d{2}\)`),
   303  			Package: "rust",
   304  			PURL:    mustPURL("pkg:generic/rust@version"),
   305  			CPEs:    singleCPE("cpe:2.3:a:rust-lang:rust:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   306  		},
   307  		{
   308  			Class:    "rust-standard-library-macos",
   309  			FileGlob: "**/libstd-????????????????.dylib",
   310  			EvidenceMatcher: m.FileContentsVersionMatcher(
   311  				// c 1.48.0 (7eac88abb 2020-11-16)
   312  				`(?m)c (?P<version>[0-9]+(\.[0-9]+)?(\.[0-9]+)) \(\w+ \d{4}\-\d{2}\-\d{2}\)`),
   313  			Package: "rust",
   314  			PURL:    mustPURL("pkg:generic/rust@version"),
   315  			CPEs:    singleCPE("cpe:2.3:a:rust-lang:rust:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   316  		},
   317  		{
   318  			Class:    "ruby-binary",
   319  			FileGlob: "**/ruby",
   320  			EvidenceMatcher: binutils.MatchAny(
   321  				rubyMatcher,
   322  				binutils.SharedLibraryLookup(
   323  					// try to find version information from libruby shared libraries
   324  					`^libruby\.so.*$`,
   325  					rubyMatcher),
   326  			),
   327  			Package: "ruby",
   328  			PURL:    mustPURL("pkg:generic/ruby@version"),
   329  			CPEs:    singleCPE("cpe:2.3:a:ruby-lang:ruby:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   330  		},
   331  		{
   332  			Class:    "erlang-binary",
   333  			FileGlob: "**/erlexec",
   334  			EvidenceMatcher: binutils.MatchAny(
   335  				m.FileContentsVersionMatcher(
   336  					// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
   337  					`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
   338  				),
   339  				m.FileContentsVersionMatcher(
   340  					// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
   341  					`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
   342  				),
   343  			),
   344  			Package: "erlang",
   345  			PURL:    mustPURL("pkg:generic/erlang@version"),
   346  			CPEs:    singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   347  		},
   348  		{
   349  			Class:    "erlang-alpine-binary",
   350  			FileGlob: "**/beam.smp",
   351  			EvidenceMatcher: binutils.MatchAny(
   352  				m.FileContentsVersionMatcher(
   353  					// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
   354  					`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
   355  				),
   356  				m.FileContentsVersionMatcher(
   357  					// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
   358  					`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
   359  				),
   360  				m.FileContentsVersionMatcher(
   361  					// [NUL][NUL]26.1.2[NUL][NUL][NUL][NUL][NUL][NUL][NUL]NUL[NUL][NUL]Erlang/OTP
   362  					`\x00+(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)\x00+Erlang/OTP`,
   363  				),
   364  			),
   365  			Package: "erlang",
   366  			PURL:    mustPURL("pkg:generic/erlang@version"),
   367  			CPEs:    singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   368  		},
   369  		{
   370  			Class:    "erlang-library",
   371  			FileGlob: "**/liberts_internal.a",
   372  			EvidenceMatcher: binutils.MatchAny(
   373  				m.FileContentsVersionMatcher(
   374  					// <artificial>[NUL]/usr/src/otp_src_25.3.2.6/erts/
   375  					`(?m)/src/otp_src_(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
   376  				),
   377  				m.FileContentsVersionMatcher(
   378  					// <artificial>[NUL]/usr/local/src/otp-25.3.2.7/erts/
   379  					`(?m)/usr/local/src/otp-(?P<version>[0-9]+\.[0-9]+(\.[0-9]+){0,2}(-rc[0-9])?)/erts/`,
   380  				),
   381  			),
   382  			Package: "erlang",
   383  			PURL:    mustPURL("pkg:generic/erlang@version"),
   384  			CPEs:    singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   385  		},
   386  		{
   387  			Class:    "swipl-binary",
   388  			FileGlob: "**/swipl",
   389  			EvidenceMatcher: m.FileContentsVersionMatcher(
   390  				`(?m)swipl-(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\/`,
   391  			),
   392  			Package: "swipl",
   393  			PURL:    mustPURL("pkg:generic/swipl@version"),
   394  			CPEs:    singleCPE("cpe:2.3:a:erlang:erlang\\/otp:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   395  		},
   396  		{
   397  			Class:    "dart-binary",
   398  			FileGlob: "**/dart",
   399  			EvidenceMatcher: m.FileContentsVersionMatcher(
   400  				// MathAtan[NUL]2.12.4 (stable)
   401  				// "%s"[NUL]3.0.0 (stable)
   402  				// Dart,GC"[NUL]3.5.2 (stable)
   403  				// Dart,GC"[NUL]3.6.0-216.1.beta (beta)
   404  				`(?m)\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+(\.[0-9]+)?\.beta)?) `,
   405  			),
   406  			Package: "dart",
   407  			PURL:    mustPURL("pkg:generic/dart@version"),
   408  			CPEs:    singleCPE("cpe:2.3:a:dart:dart_software_development_kit:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   409  		},
   410  		{
   411  			Class:    "haskell-ghc-binary",
   412  			FileGlob: "**/ghc*",
   413  			EvidenceMatcher: m.FileContentsVersionMatcher(
   414  				`(?m)\x00GHC (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
   415  			),
   416  			Package: "haskell/ghc",
   417  			PURL:    mustPURL("pkg:generic/haskell/ghc@version"),
   418  			CPEs:    singleCPE("cpe:2.3:a:haskell:ghc:*:*:*:*:*:*:*:*"),
   419  		},
   420  		{
   421  			Class:    "haskell-cabal-binary",
   422  			FileGlob: "**/cabal",
   423  			EvidenceMatcher: m.FileContentsVersionMatcher(
   424  				`(?m)\x00Cabal-(?P<version>[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?)-`,
   425  			),
   426  			Package: "haskell/cabal",
   427  			PURL:    mustPURL("pkg:generic/haskell/cabal@version"),
   428  			CPEs:    singleCPE("cpe:2.3:a:haskell:cabal:*:*:*:*:*:*:*:*"),
   429  		},
   430  		{
   431  			Class:    "haskell-stack-binary",
   432  			FileGlob: "**/stack",
   433  			EvidenceMatcher: m.FileContentsVersionMatcher(
   434  				`(?m)Version\s*(?P<version>[0-9]+\.[0-9]+\.[0-9]+),\s*Git`,
   435  			),
   436  			Package: "haskell/stack",
   437  			PURL:    mustPURL("pkg:generic/haskell/stack@version"),
   438  			CPEs:    singleCPE("cpe:2.3:a:haskell:stack:*:*:*:*:*:*:*:*"),
   439  		},
   440  		{
   441  			Class:    "consul-binary",
   442  			FileGlob: "**/consul",
   443  			EvidenceMatcher: m.FileContentsVersionMatcher(
   444  				// NOTE: This is brittle and may not work for past or future versions
   445  				`CONSUL_VERSION: (?P<version>\d+\.\d+\.\d+)`,
   446  			),
   447  			Package: "consul",
   448  			PURL:    mustPURL("pkg:golang/github.com/hashicorp/consul@version"),
   449  			CPEs:    singleCPE("cpe:2.3:a:hashicorp:consul:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   450  		},
   451  		{
   452  			Class:    "hashicorp-vault-binary",
   453  			FileGlob: "**/vault",
   454  			EvidenceMatcher: m.FileContentsVersionMatcher(
   455  				// revoke1.18.0
   456  				`(?m)revoke(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
   457  			Package: "github.com/hashicorp/vault",
   458  			PURL:    mustPURL("pkg:golang/github.com/hashicorp/vault@version"),
   459  			CPEs:    singleCPE("cpe:2.3:a:hashicorp:vault:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   460  		},
   461  		{
   462  			Class:    "nginx-binary",
   463  			FileGlob: "**/nginx",
   464  			EvidenceMatcher: m.FileContentsVersionMatcher(
   465  				// [NUL]nginx version: nginx/1.25.1 - fetches '1.25.1'
   466  				// [NUL]nginx version: openresty/1.21.4.1 - fetches '1.21.4' as this is the nginx version part
   467  				`(?m)(\x00|\?)nginx version: [^\/]+\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(?:\+\d+)?(?:-\d+)?)`,
   468  			),
   469  			Package: "nginx",
   470  			PURL:    mustPURL("pkg:generic/nginx@version"),
   471  			CPEs: []cpe.CPE{
   472  				cpe.Must("cpe:2.3:a:f5:nginx:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   473  				cpe.Must("cpe:2.3:a:nginx:nginx:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   474  			},
   475  		},
   476  		{
   477  			Class:    "bash-binary",
   478  			FileGlob: "**/bash",
   479  			EvidenceMatcher: m.FileContentsVersionMatcher(
   480  				// @(#)Bash version 5.2.15(1) release GNU
   481  				// @(#)Bash version 5.2.0(1) alpha GNU
   482  				// @(#)Bash version 5.2.0(1) beta GNU
   483  				// @(#)Bash version 5.2.0(1) rc4 GNU
   484  				`(?m)@\(#\)Bash version (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\([0-9]\) [a-z0-9]+ GNU`,
   485  			),
   486  			Package: "bash",
   487  			PURL:    mustPURL("pkg:generic/bash@version"),
   488  			CPEs:    singleCPE("cpe:2.3:a:gnu:bash:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   489  		},
   490  		{
   491  			Class:    "openssl-binary",
   492  			FileGlob: "**/openssl",
   493  			EvidenceMatcher: m.FileContentsVersionMatcher(
   494  				// [NUL]OpenSSL 3.1.4'
   495  				// [NUL]OpenSSL 1.1.1w'
   496  				`\x00OpenSSL (?P<version>[0-9]+\.[0-9]+\.[0-9]+([a-z]+|-alpha[0-9]|-beta[0-9]|-rc[0-9])?)`,
   497  			),
   498  			Package: "openssl",
   499  			PURL:    mustPURL("pkg:generic/openssl@version"),
   500  			CPEs:    singleCPE("cpe:2.3:a:openssl:openssl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   501  		},
   502  		{
   503  			Class:    "gcc-binary",
   504  			FileGlob: "**/gcc",
   505  			EvidenceMatcher: m.FileContentsVersionMatcher(
   506  				// GCC: \(GNU\)  12.3.0'
   507  				`GCC: \(GNU\) (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
   508  			),
   509  			Package: "gcc",
   510  			PURL:    mustPURL("pkg:generic/gcc@version"),
   511  			CPEs:    singleCPE("cpe:2.3:a:gnu:gcc:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   512  		},
   513  		{
   514  			Class:    "fluent-bit-binary",
   515  			FileGlob: "**/fluent-bit",
   516  			EvidenceMatcher: m.FileContentsVersionMatcher(
   517  				// [NUL]3.0.2[NUL]%sFluent Bit
   518  				// [NUL]2.2.3[NUL]Fluent Bit
   519  				// [NUL]2.2.1[NUL][NUL][NUL]Fluent Bit
   520  				// [NUL]1.7.0[NUL]\x1b[1m[NUL]%sFluent Bit (versions 1.7.0-dev-3 through 1.7.0-dev-9 and 1.7.0-rc4 through 1.7.0-rc8)
   521  				// [NUL][NUL]1.3.10[NUL][NUL]Fluent Bit v%s
   522  				`\x00(\x00)?(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00(\x1b\[1m\x00|\x00|\x00\x00)?(%s)?Fluent`,
   523  			),
   524  			Package: "fluent-bit",
   525  			PURL:    mustPURL("pkg:github/fluent/fluent-bit@version"),
   526  			CPEs:    singleCPE("cpe:2.3:a:treasuredata:fluent_bit:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   527  		},
   528  		{
   529  			Class:    "wordpress-cli-binary",
   530  			FileGlob: "**/wp",
   531  			EvidenceMatcher: m.FileContentsVersionMatcher(
   532  				// wp-cli/wp-cli 2.9.0'
   533  				`(?m)wp-cli/wp-cli (?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
   534  			),
   535  			Package: "wp-cli",
   536  			PURL:    mustPURL("pkg:generic/wp-cli@version"),
   537  			CPEs:    singleCPE("cpe:2.3:a:wp-cli:wp-cli:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   538  		},
   539  		{
   540  			Class:    "curl-binary",
   541  			FileGlob: "**/curl",
   542  			EvidenceMatcher: m.FileContentsVersionMatcher(
   543  				`curl/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
   544  			),
   545  			Package: "curl",
   546  			PURL:    mustPURL("pkg:generic/curl@version"),
   547  			CPEs:    singleCPE("cpe:2.3:a:haxx:curl:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   548  		},
   549  		{
   550  			Class:    "lighttpd-binary",
   551  			FileGlob: "**/lighttpd",
   552  			EvidenceMatcher: m.FileContentsVersionMatcher(
   553  				`\x00lighttpd/(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
   554  			),
   555  			Package: "lighttpd",
   556  			PURL:    mustPURL("pkg:generic/lighttpd@version"),
   557  			CPEs:    singleCPE("cpe:2.3:a:lighttpd:lighttpd:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   558  		},
   559  		{
   560  			Class:    "proftpd-binary",
   561  			FileGlob: "**/proftpd",
   562  			EvidenceMatcher: m.FileContentsVersionMatcher(
   563  				`\x00ProFTPD Version (?P<version>[0-9]+\.[0-9]+\.[0-9]+[a-z]?)\x00`,
   564  			),
   565  			Package: "proftpd",
   566  			PURL:    mustPURL("pkg:generic/proftpd@version"),
   567  			CPEs:    singleCPE("cpe:2.3:a:proftpd:proftpd:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   568  		},
   569  		{
   570  			Class:    "zstd-binary",
   571  			FileGlob: "**/zstd",
   572  			EvidenceMatcher: m.FileContentsVersionMatcher(
   573  				`\x00v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
   574  			),
   575  			Package: "zstd",
   576  			PURL:    mustPURL("pkg:generic/zstd@version"),
   577  			CPEs:    singleCPE("cpe:2.3:a:facebook:zstandard:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   578  		},
   579  		{
   580  			Class:    "xz-binary",
   581  			FileGlob: "**/xz",
   582  			EvidenceMatcher: m.FileContentsVersionMatcher(
   583  				`\x00xz \(XZ Utils\) (?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
   584  			),
   585  			Package: "xz",
   586  			PURL:    mustPURL("pkg:generic/xz@version"),
   587  			CPEs:    singleCPE("cpe:2.3:a:tukaani:xz:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   588  		},
   589  		{
   590  			Class:    "gzip-binary",
   591  			FileGlob: "**/gzip",
   592  			EvidenceMatcher: m.FileContentsVersionMatcher(
   593  				`\x00(?P<version>[0-9]+\.[0-9]+)\x00`,
   594  			),
   595  			Package: "gzip",
   596  			PURL:    mustPURL("pkg:generic/gzip@version"),
   597  			CPEs:    singleCPE("cpe:2.3:a:gnu:gzip:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   598  		},
   599  		{
   600  			Class:    "sqlcipher-binary",
   601  			FileGlob: "**/sqlcipher",
   602  			EvidenceMatcher: m.FileContentsVersionMatcher(
   603  				`[^0-9]\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+)\x00`,
   604  			),
   605  			Package: "sqlcipher",
   606  			PURL:    mustPURL("pkg:generic/sqlcipher@version"),
   607  			CPEs:    singleCPE("cpe:2.3:a:zetetic:sqlcipher:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   608  		},
   609  		{
   610  			Class:    "jq-binary",
   611  			FileGlob: "**/jq",
   612  			EvidenceMatcher: m.FileContentsVersionMatcher(
   613  				`\x00(?P<version>[0-9]{1,3}\.[0-9]{1,3}(\.[0-9]+)?)\x00`,
   614  			),
   615  			Package: "jq",
   616  			PURL:    mustPURL("pkg:generic/jq@version"),
   617  			CPEs:    singleCPE("cpe:2.3:a:jqlang:jq:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   618  		},
   619  		{
   620  			Class:    "chrome-binary",
   621  			FileGlob: "**/chrome",
   622  			EvidenceMatcher: m.FileContentsVersionMatcher(
   623  				// [NUL]127.0.6533.119[NUL]Default
   624  				`\x00(?P<version>[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\x00Default`,
   625  			),
   626  			Package: "chrome",
   627  			PURL:    mustPURL("pkg:generic/chrome@version"),
   628  			CPEs:    singleCPE("cpe:2.3:a:google:chrome:*:*:*:*:*:*:*:*"),
   629  		},
   630  		{
   631  			Class:    "ffmpeg-binary",
   632  			FileGlob: "**/ffmpeg",
   633  			EvidenceMatcher: m.FileContentsVersionMatcher(
   634  				// Pattern found in the binary: "%s version 7.1.1" or "%s version 6.0"
   635  				// When executed outputs: "ffmpeg version 7.1.1" or "ffmpeg version 6.0"
   636  				`(?m)%s version (?P<version>[0-9]+\.[0-9]+(\.[0-9]+)?)`,
   637  			),
   638  			Package: "ffmpeg",
   639  			PURL:    mustPURL("pkg:generic/ffmpeg@version"),
   640  			CPEs:    singleCPE("cpe:2.3:a:ffmpeg:ffmpeg:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   641  		},
   642  		{
   643  			Class:    "ffmpeg-library",
   644  			FileGlob: "**/libav*",
   645  			EvidenceMatcher: binutils.MatchAny(
   646  				// Primary pattern: FFmpeg version found in most libraries
   647  				m.FileContentsVersionMatcher(`(?m)FFmpeg version (?P<version>[0-9]+\.[0-9]+(\.[0-9]+)?)`),
   648  				// Fallback: library-specific version patterns for libavcodec and libavformat
   649  				m.FileContentsVersionMatcher(`(?m)Lavc(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
   650  				m.FileContentsVersionMatcher(`(?m)Lavf(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
   651  			),
   652  			Package: "ffmpeg",
   653  			PURL:    mustPURL("pkg:generic/ffmpeg@version"),
   654  			CPEs:    singleCPE("cpe:2.3:a:ffmpeg:ffmpeg:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   655  		},
   656  		{
   657  			Class:    "ffmpeg-library",
   658  			FileGlob: "**/libswresample*",
   659  			EvidenceMatcher: m.FileContentsVersionMatcher(
   660  				// FFmpeg version pattern for libswresample
   661  				`(?m)FFmpeg version (?P<version>[0-9]+\.[0-9]+(\.[0-9]+)?)`),
   662  			Package: "ffmpeg",
   663  			PURL:    mustPURL("pkg:generic/ffmpeg@version"),
   664  			CPEs:    singleCPE("cpe:2.3:a:ffmpeg:ffmpeg:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   665  		},
   666  		{
   667  			Class:    "elixir-binary",
   668  			FileGlob: "**/elixir",
   669  			EvidenceMatcher: m.FileContentsVersionMatcher(
   670  				`(?m)ELIXIR_VERSION=(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
   671  			Package: "elixir",
   672  			PURL:    mustPURL("pkg:generic/elixir@version"),
   673  			CPEs: []cpe.CPE{
   674  				cpe.Must("cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   675  			},
   676  		},
   677  		{
   678  			Class:    "elixir-library",
   679  			FileGlob: "**/elixir/ebin/elixir.app",
   680  			EvidenceMatcher: m.FileContentsVersionMatcher(
   681  				`(?m)\{vsn,"(?P<version>[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9]+)?)"\}`),
   682  			Package: "elixir",
   683  			PURL:    mustPURL("pkg:generic/elixir@version"),
   684  			CPEs:    singleCPE("cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
   685  		},
   686  	}
   687  
   688  	return append(classifiers, defaultJavaClassifiers()...)
   689  }
   690  
   691  // singleCPE returns a []cpe.CPE with Source: Generated based on the cpe string or panics if the
   692  // cpe string cannot be parsed into valid CPE Attributes
   693  func singleCPE(cpeString string, source ...cpe.Source) []cpe.CPE {
   694  	src := cpe.GeneratedSource
   695  	if len(source) > 0 {
   696  		src = source[0]
   697  	}
   698  	return []cpe.CPE{
   699  		cpe.Must(cpeString, src),
   700  	}
   701  }
   702  
   703  func mustPURL(purl string) packageurl.PackageURL {
   704  	p, err := packageurl.FromString(purl)
   705  	if err != nil {
   706  		panic(fmt.Sprintf("invalid PURL: %s", p))
   707  	}
   708  	return p
   709  }