github.com/anchore/syft@v1.38.2/syft/cpe/cpe_test.go (about)

     1  package cpe
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func Test_NewAttributes(t *testing.T) {
    16  	tests := []struct {
    17  		name     string
    18  		input    string
    19  		expected Attributes
    20  		wantErr  require.ErrorAssertionFunc
    21  	}{
    22  		{
    23  			name:     "gocase",
    24  			input:    `cpe:/a:10web:form_maker:1.0.0::~~~wordpress~~`,
    25  			expected: MustAttributes(`cpe:2.3:a:10web:form_maker:1.0.0:*:*:*:*:wordpress:*:*`),
    26  		},
    27  		{
    28  			name:     "dashes",
    29  			input:    `cpe:/a:7-zip:7-zip:4.56:beta:~~~windows~~`,
    30  			expected: MustAttributes(`cpe:2.3:a:7-zip:7-zip:4.56:beta:*:*:*:windows:*:*`),
    31  		},
    32  		{
    33  			name:     "URL escape characters",
    34  			input:    `cpe:/a:%240.99_kindle_books_project:%240.99_kindle_books:6::~~~android~~`,
    35  			expected: MustAttributes(`cpe:2.3:a:\$0.99_kindle_books_project:\$0.99_kindle_books:6:*:*:*:*:android:*:*`),
    36  		},
    37  		{
    38  			name:    "null byte in version for some reason",
    39  			input:   "cpe:2.3:a:oracle:openjdk:11.0.22+7\u0000-J-ms8m:*:*:*:*:*:*:*",
    40  			wantErr: require.Error,
    41  		},
    42  	}
    43  
    44  	for _, test := range tests {
    45  		t.Run(test.name, func(t *testing.T) {
    46  			actual, err := NewAttributes(test.input)
    47  			if test.wantErr != nil {
    48  				test.wantErr(t, err)
    49  				return
    50  			}
    51  			require.NoError(t, err)
    52  
    53  			if d := cmp.Diff(actual, test.expected); d != "" {
    54  				t.Errorf("Attributes mismatch (-want +got):\n%s", d)
    55  			}
    56  
    57  		})
    58  	}
    59  }
    60  
    61  func Test_normalizeCpeField(t *testing.T) {
    62  
    63  	tests := []struct {
    64  		field    string
    65  		expected string
    66  	}{
    67  		{
    68  			field:    "something",
    69  			expected: "something",
    70  		},
    71  		{
    72  			field:    "some\\thing",
    73  			expected: `some\thing`,
    74  		},
    75  		{
    76  			field:    "*",
    77  			expected: "",
    78  		},
    79  		{
    80  			field:    "",
    81  			expected: "",
    82  		},
    83  	}
    84  	for _, test := range tests {
    85  		t.Run(test.field, func(t *testing.T) {
    86  			assert.Equal(t, test.expected, normalizeField(test.field))
    87  		})
    88  	}
    89  }
    90  
    91  func Test_CPEParser(t *testing.T) {
    92  	var testCases []struct {
    93  		CPEString string     `json:"cpe-string"`
    94  		CPEUrl    string     `json:"cpe-url"`
    95  		WFN       Attributes `json:"wfn"`
    96  	}
    97  	out, err := os.ReadFile("test-fixtures/cpe-data.json")
    98  	require.NoError(t, err)
    99  	require.NoError(t, json.Unmarshal(out, &testCases))
   100  
   101  	for _, test := range testCases {
   102  		t.Run(test.CPEString, func(t *testing.T) {
   103  			c1, err := NewAttributes(test.CPEString)
   104  			assert.NoError(t, err)
   105  			c2, err := NewAttributes(test.CPEUrl)
   106  			assert.NoError(t, err)
   107  			assert.Equal(t, c1, c2)
   108  			assert.Equal(t, c1, test.WFN)
   109  			assert.Equal(t, c2, test.WFN)
   110  			assert.Equal(t, test.WFN.String(), test.CPEString)
   111  		})
   112  	}
   113  }
   114  
   115  func Test_InvalidCPE(t *testing.T) {
   116  	type testcase struct {
   117  		name        string
   118  		in          string
   119  		expected    string
   120  		expectedErr bool
   121  	}
   122  
   123  	tests := []testcase{
   124  		{
   125  			// 5.3.2: The underscore (x5f) MAY be used, and it SHOULD be used in place of whitespace characters (which SHALL NOT be used)
   126  			name:     "translates spaces",
   127  			in:       "cpe:2.3:a:some-vendor:name:1 2:*:*:*:*:*:*:*",
   128  			expected: "cpe:2.3:a:some-vendor:name:1_2:*:*:*:*:*:*:*",
   129  		},
   130  		{
   131  			// it isn't easily possible in the string formatted string to detect improper escaping of : (it will fail parsing)
   132  			name:        "unescaped ':' cannot be helped -- too many fields",
   133  			in:          "cpe:2.3:a:some-vendor:name:::*:*:*:*:*:*:*",
   134  			expectedErr: true,
   135  		},
   136  		{
   137  			name:     "too few fields",
   138  			in:       "cpe:2.3:a:some-vendor:name:*:*:*:*:*:*:*",
   139  			expected: "cpe:2.3:a:some-vendor:name:*:*:*:*:*:*:*:*",
   140  		},
   141  		// Note: though the CPE spec does not allow for ? and * as escaped character input, these seem to be allowed in
   142  		// the NVD CPE validator for this reason these edge cases were removed
   143  	}
   144  
   145  	// the wfn library does not account for escapes of . and -
   146  	exceptions := ".-"
   147  	// it isn't easily possible in the string formatted string to detect improper escaping of : (it will fail parsing)
   148  	skip := ":"
   149  
   150  	// make escape exceptions for section 5.3.2 of the CPE spec (2.3)
   151  	for _, char := range allowedCPEPunctuation {
   152  		if strings.Contains(skip, string(char)) {
   153  			continue
   154  		}
   155  
   156  		in := fmt.Sprintf("cpe:2.3:a:some-vendor:name:*:%s:*:*:*:*:*:*", string(char))
   157  		exp := fmt.Sprintf(`cpe:2.3:a:some-vendor:name:*:\%s:*:*:*:*:*:*`, string(char))
   158  		if strings.Contains(exceptions, string(char)) {
   159  			exp = in
   160  		}
   161  
   162  		tests = append(tests, testcase{
   163  			name:        fmt.Sprintf("allowes future escape of character (%s)", string(char)),
   164  			in:          in,
   165  			expected:    exp,
   166  			expectedErr: false,
   167  		})
   168  	}
   169  
   170  	for _, test := range tests {
   171  		t.Run(test.name, func(t *testing.T) {
   172  			c, err := NewAttributes(test.in)
   173  			if test.expectedErr {
   174  				assert.Error(t, err)
   175  				if t.Failed() {
   176  					t.Logf("got Attributes: %q details: %+v", c, c)
   177  				}
   178  				return
   179  			}
   180  			require.NoError(t, err)
   181  			assert.Equal(t, test.expected, c.String())
   182  		})
   183  	}
   184  }
   185  
   186  func Test_RoundTrip(t *testing.T) {
   187  	tests := []struct {
   188  		name      string
   189  		cpe       string
   190  		parsedCPE Attributes
   191  	}{
   192  		{
   193  			name: "normal",
   194  			cpe:  "cpe:2.3:a:some-vendor:name:3.2:*:*:*:*:*:*:*",
   195  			parsedCPE: Attributes{
   196  				Part:    "a",
   197  				Vendor:  "some-vendor",
   198  				Product: "name",
   199  				Version: "3.2",
   200  			},
   201  		},
   202  		{
   203  			name: "escaped colon",
   204  			cpe:  "cpe:2.3:a:some-vendor:name:1\\:3.2:*:*:*:*:*:*:*",
   205  			parsedCPE: Attributes{
   206  				Part:    "a",
   207  				Vendor:  "some-vendor",
   208  				Product: "name",
   209  				Version: "1:3.2",
   210  			},
   211  		},
   212  		{
   213  			name: "escaped forward slash",
   214  			cpe:  "cpe:2.3:a:test\\/some-vendor:name:3.2:*:*:*:*:*:*:*",
   215  			parsedCPE: Attributes{
   216  				Part:    "a",
   217  				Vendor:  "test/some-vendor",
   218  				Product: "name",
   219  				Version: "3.2",
   220  			},
   221  		},
   222  	}
   223  
   224  	for _, test := range tests {
   225  		t.Run(test.name, func(t *testing.T) {
   226  			// Attributes string must be preserved through a round trip
   227  			assert.Equal(t, test.cpe, MustAttributes(test.cpe).String())
   228  			// The parsed Attributes must be the same after a round trip
   229  			assert.Equal(t, MustAttributes(test.cpe), MustAttributes(MustAttributes(test.cpe).String()))
   230  			// The test case parsed Attributes must be the same after parsing the input string
   231  			assert.Equal(t, test.parsedCPE, MustAttributes(test.cpe))
   232  			// The test case parsed Attributes must produce the same string as the input cpe
   233  			assert.Equal(t, test.parsedCPE.String(), test.cpe)
   234  		})
   235  	}
   236  }