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 }