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