/
cpe_name_binder.go
294 lines (280 loc) · 6.53 KB
/
cpe_name_binder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
package naming
import (
"fmt"
"strings"
"github.com/knqyf263/go-cpe/common"
)
// BindToURI a {@link WellFormedName} object to a URI.
// @param w WellFormedName to be bound to URI
// @return URI binding of WFN
func BindToURI(w common.WellFormedName) (uri string) {
// Initialize the output with the CPE v2.2 URI prefix.
uri = "cpe:/"
// Define the attributes that correspond to the seven components in a v2.2. CPE.
attributes := []string{"part", "vendor", "product", "version", "update", "edition", "language"}
for _, a := range attributes {
v := ""
if a == "edition" {
// Call the pack() helper function to compute the proper binding for the edition element.
ed := bindValueForURI(w.Get(common.AttributeEdition))
swEd := bindValueForURI(w.Get(common.AttributeSwEdition))
targetSw := bindValueForURI(w.Get(common.AttributeTargetSw))
targetHw := bindValueForURI(w.Get(common.AttributeTargetHw))
other := bindValueForURI(w.Get(common.AttributeOther))
v = pack(ed, swEd, targetSw, targetHw, other)
} else {
// Get the value for a in w, then bind to a string
// for inclusion in the URI.
v = bindValueForURI(w.Get(a))
}
// Append v to the URI then add a colon.
uri += v + ":"
}
return strings.TrimRight(uri, ":")
}
// BindToFS is top-level function used to bind WFN w to formatted string.
// @param w WellFormedName to bind
// @return Formatted String
func BindToFS(w common.WellFormedName) (fs string) {
// Initialize the output with the CPE v2.3 string prefix.
fs = "cpe:2.3:"
attributes := []string{"part", "vendor", "product", "version",
"update", "edition", "language", "sw_edition", "target_sw",
"target_hw", "other"}
for _, a := range attributes {
v := bindValueForFS(w.Get(a))
fs += v
if a != common.AttributeOther {
fs += ":"
}
}
return fs
}
// bindValueForURI converts a string to the proper string for including in a CPE v2.2-conformant URI.
// The logical value ANY binds to the blank in the 2.2-conformant URI.
// @param s string to be converted
// @return converted string
func bindValueForURI(s interface{}) string {
if lv, ok := s.(common.LogicalValue); ok {
// The value NA binds to a blank.
if lv.IsANY() {
return ""
}
// The value NA binds to a single hyphen.
if lv.IsNA() {
return "-"
}
}
if str, ok := s.(string); ok {
return transformForURI(str)
}
return ""
}
// bindValueForFS converts the value v to its proper string representation for insertion to formatted string.
// @param v value to convert
// @return Formatted value
func bindValueForFS(v interface{}) string {
if lv, ok := v.(common.LogicalValue); ok {
// The value NA binds to a asterisk.
if lv.IsANY() {
return "*"
}
// The value NA binds to a single hyphen.
if lv.IsNA() {
return "-"
}
}
if str, ok := v.(string); ok {
return processQuotedChars(str)
}
return ""
}
// Inspect each character in string s. Certain nonalpha characters pass
// thru without escaping into the result, but most retain escaping.
// @param s
// @return
func processQuotedChars(s string) (result string) {
idx := 0
for idx < len(s) {
c := s[idx : idx+1]
if c == "\\" {
// escaped characters are examined.
nextchr := s[idx+1 : idx+2]
// the period, hyphen and underscore pass unharmed.
if nextchr == "." || nextchr == "-" || nextchr == "_" {
result += nextchr
idx += 2
continue
} else {
// all others retain escaping.
result += "\\" + nextchr
idx += 2
continue
}
}
// unquoted characters pass thru unharmed.
result += c
idx = idx + 1
}
return result
}
// transformForURI scans an input string and performs the following transformations:
// - Pass alphanumeric characters thru untouched
// - Percent-encode quoted non-alphanumerics as needed
// - Unquoted special characters are mapped to their special forms
// @param s string to be transformed
// @return transformed string
func transformForURI(s string) (result string) {
idx := 0
for idx < len(s) {
// Get the idx'th character of s.
thischar := s[idx : idx+1]
if common.IsAlphanum(thischar) {
result += thischar
idx++
continue
}
// Check for escape character.
if thischar == "\\" {
idx++
nxtchar := s[idx : idx+1]
result += pctEncode(nxtchar)
idx++
continue
}
// Bind the unquoted '?' special character to "%01".
if thischar == "?" {
result += "%01"
}
// Bind the unquoted '*' special character to "%02".
if thischar == "*" {
result += "%02"
}
idx++
}
return result
}
// pctEncode returns the appropriate percent-encoding of character c.
// Certain characters are returned without encoding.
// @param c the single character string to be encoded
// @return the percent encoded string
func pctEncode(c string) string {
if c == "!" {
return "%21"
}
if c == "\"" {
return "%22"
}
if c == "#" {
return "%23"
}
if c == "$" {
return "%24"
}
if c == "%" {
return "%25"
}
if c == "&" {
return "%26"
}
if c == "'" {
return "%27"
}
if c == "(" {
return "%28"
}
if c == ")" {
return "%29"
}
if c == "*" {
return "%2a"
}
if c == "+" {
return "%2b"
}
if c == "," {
return "%2c"
}
// bound without encoding.
if c == "-" {
return c
}
// bound without encoding.
if c == "." {
return c
}
if c == "/" {
return "%2f"
}
if c == ":" {
return "%3a"
}
if c == ";" {
return "%3b"
}
if c == "<" {
return "%3c"
}
if c == "=" {
return "%3d"
}
if c == ">" {
return "%3e"
}
if c == "?" {
return "%3f"
}
if c == "@" {
return "%40"
}
if c == "[" {
return "%5b"
}
if c == "\\" {
return "%5c"
}
if c == "]" {
return "%5d"
}
if c == "^" {
return "%5e"
}
if c == "`" {
return "%60"
}
if c == "{" {
return "%7b"
}
if c == "|" {
return "%7c"
}
if c == "}" {
return "%7d"
}
if c == "~" {
return "%7e"
}
// Shouldn't reach here, return original character
return c
}
/**
* Packs the values of the five arguments into the single
* edition component. If all the values are blank, the
* function returns a blank.
* @param ed edition string
* @param swEd software edition string
* @param tSw target software string
* @param tHw target hardware string
* @param oth other edition information string
* @return the packed string, or blank
*/
func pack(ed, swEd, tSw, tHw, oth string) string {
if swEd == "" && tSw == "" && tHw == "" && oth == "" {
// All the extended attributes are blank, so don't do
// any packing, just return ed.
return ed
}
// Otherwise, pack the five values into a single string
// prefixed and internally delimited with the tilde.
return fmt.Sprintf("~%s~%s~%s~%s~%s", ed, swEd, tSw, tHw, oth)
}