Codebase list golang-github-go-logr-logr / 058c4ca
funcr: Escape strings when they are not known-safe Tim Hockin 2 years ago
2 changed file(s) with 119 addition(s) and 39 deletion(s). Raw diff Collapse all Expand all
235235 if hook := f.opts.RenderBuiltinsHook; hook != nil {
236236 vals = hook(f.sanitize(vals))
237237 }
238 f.flatten(buf, vals, false)
238 f.flatten(buf, vals, false, false) // keys are ours, no need to escape
239239 continuing := len(builtins) > 0
240240 if len(f.valuesStr) > 0 {
241241 if continuing {
252252 if hook := f.opts.RenderArgsHook; hook != nil {
253253 vals = hook(f.sanitize(vals))
254254 }
255 f.flatten(buf, vals, continuing)
255 f.flatten(buf, vals, continuing, true) // escape user-provided keys
256256 if f.outputFormat == outputJSON {
257257 buf.WriteByte('}')
258258 }
262262 // flatten renders a list of key-value pairs into a buffer. If continuing is
263263 // true, it assumes that the buffer has previous values and will emit a
264264 // separator (which depends on the output format) before the first pair it
265 // writes. This also returns a potentially modified version of kvList, which
265 // writes. If escapeKeys is true, the keys are assumed to have
266 // non-JSON-compatible characters in them and must be evaluated for escapes.
267 //
268 // This function returns a potentially modified version of kvList, which
266269 // ensures that there is a value for every key (adding a value if needed) and
267270 // that each key is a string (substituting a key if needed).
268 func (f Formatter) flatten(buf *bytes.Buffer, kvList []interface{}, continuing bool) []interface{} {
271 func (f Formatter) flatten(buf *bytes.Buffer, kvList []interface{}, continuing bool, escapeKeys bool) []interface{} {
269272 // This logic overlaps with sanitize() but saves one type-cast per key,
270273 // which can be measurable.
271274 if len(kvList)%2 != 0 {
289292 }
290293 }
291294
292 buf.WriteByte('"')
293 buf.WriteString(k)
294 buf.WriteByte('"')
295 if escapeKeys {
296 buf.WriteString(prettyString(k))
297 } else {
298 // this is faster
299 buf.WriteByte('"')
300 buf.WriteString(k)
301 buf.WriteByte('"')
302 }
295303 if f.outputFormat == outputJSON {
296304 buf.WriteByte(':')
297305 } else {
307315 }
308316
309317 const (
310 flagRawString = 0x1 // do not print quotes on strings
311 flagRawStruct = 0x2 // do not print braces on structs
318 flagRawStruct = 0x1 // do not print braces on structs
312319 )
313320
314321 // TODO: This is not fast. Most of the overhead goes here.
333340 case bool:
334341 return strconv.FormatBool(v)
335342 case string:
336 if flags&flagRawString > 0 {
337 return v
338 }
339 // This is empirically faster than strings.Builder.
340 return strconv.Quote(v)
343 return prettyString(v)
341344 case int:
342345 return strconv.FormatInt(int64(v), 10)
343346 case int8:
378381 if i > 0 {
379382 buf.WriteByte(',')
380383 }
381 buf.WriteByte('"')
382 buf.WriteString(v[i].(string))
383 buf.WriteByte('"')
384 // arbitrary keys might need escaping
385 buf.WriteString(prettyString(v[i].(string)))
384386 buf.WriteByte(':')
385387 buf.WriteString(f.pretty(v[i+1]))
386388 }
400402 case reflect.Bool:
401403 return strconv.FormatBool(v.Bool())
402404 case reflect.String:
403 if flags&flagRawString > 0 {
404 return v.String()
405 }
406 // This is empirically faster than strings.Builder.
407 return `"` + v.String() + `"`
405 return prettyString(v.String())
408406 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
409407 return strconv.FormatInt(int64(v.Int()), 10)
410408 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
462460 if name == "" {
463461 name = fld.Name
464462 }
463 // field names can't contain characters which need escaping
465464 buf.WriteByte('"')
466465 buf.WriteString(name)
467466 buf.WriteByte('"')
492491 if i > 0 {
493492 buf.WriteByte(',')
494493 }
495 // JSON only does string keys.
496 buf.WriteByte('"')
497 buf.WriteString(f.prettyWithFlags(it.Key().Interface(), flagRawString))
498 buf.WriteByte('"')
494 // prettyWithFlags will produce already-escaped values
495 keystr := f.prettyWithFlags(it.Key().Interface(), 0)
496 if t.Key().Kind() != reflect.String {
497 // JSON only does string keys. Unlike Go's standard JSON, we'll
498 // convert just about anything to a string.
499 keystr = prettyString(keystr)
500 }
501 buf.WriteString(keystr)
499502 buf.WriteByte(':')
500503 buf.WriteString(f.pretty(it.Value().Interface()))
501504 i++
509512 return f.pretty(v.Elem().Interface())
510513 }
511514 return fmt.Sprintf(`"<unhandled-%s>"`, t.Kind().String())
515 }
516
517 func prettyString(s string) string {
518 // Avoid escaping (which does allocations) if we can.
519 if needsEscape(s) {
520 return strconv.Quote(s)
521 }
522 b := bytes.NewBuffer(make([]byte, 0, 1024))
523 b.WriteByte('"')
524 b.WriteString(s)
525 b.WriteByte('"')
526 return b.String()
527 }
528
529 // needsEscape determines whether the input string needs to be escaped or not,
530 // without doing any allocations.
531 func needsEscape(s string) bool {
532 for _, r := range s {
533 if !strconv.IsPrint(r) || r == '\\' || r == '"' {
534 return true
535 }
536 }
537 return false
512538 }
513539
514540 func isEmpty(v reflect.Value) bool {
682708
683709 // Pre-render values, so we don't have to do it on each Info/Error call.
684710 buf := bytes.NewBuffer(make([]byte, 0, 1024))
685 f.values = f.flatten(buf, vals, false)
711 f.values = f.flatten(buf, vals, false, true) // escape user-provided keys
686712 f.valuesStr = buf.String()
687713 }
688714
2626 "github.com/go-logr/logr"
2727 )
2828
29 // Will be handled via reflection instead of type assertions.
2930 type substr string
3031
3132 func ptrint(i int) *int {
197198 exp string // used in cases where JSON can't handle it
198199 }{
199200 {val: "strval"},
201 {val: "strval\nwith\t\"escapes\""},
200202 {val: substr("substrval")},
203 {val: substr("substrval\nwith\t\"escapes\"")},
201204 {val: true},
202205 {val: false},
203206 {val: int(93)},
234237 exp: `[]`,
235238 },
236239 {val: []int{9, 3, 7, 6}},
240 {val: []string{"str", "with\tescape"}},
241 {val: []substr{"substr", "with\tescape"}},
237242 {val: [4]int{9, 3, 7, 6}},
243 {val: [2]string{"str", "with\tescape"}},
244 {val: [2]substr{"substr", "with\tescape"}},
238245 {
239246 val: struct {
240247 Int int
255262 },
256263 },
257264 {
265 val: map[string]int{
266 "with\tescape": 76,
267 },
268 },
269 {
258270 val: map[substr]int{
259271 "nine": 3,
260272 },
273 },
274 {
275 val: map[substr]int{
276 "with\tescape": 76,
277 },
278 },
279 {
280 val: map[int]int{
281 9: 3,
282 },
283 },
284 {
285 val: map[float64]int{
286 9.5: 3,
287 },
288 exp: `{"9.5":3}`,
261289 },
262290 {
263291 val: struct {
282310 val: []struct{ X, Y string }{
283311 {"nine", "three"},
284312 {"seven", "six"},
313 {"with\t", "\tescapes"},
285314 },
286315 },
287316 {
436465 {
437466 val: PseudoStruct(makeKV("f1", 1, "f2", true, "f3", []int{})),
438467 exp: `{"f1":1,"f2":true,"f3":[]}`,
468 },
469 {
470 val: map[TjsontagsString]int{
471 {String1: `"quoted"`, String4: `unquoted`}: 1,
472 },
473 exp: `{"{\"string1\":\"\\\"quoted\\\"\",\"-\":\"\",\"string4\":\"unquoted\",\"String5\":\"\"}":1}`,
474 },
475 {
476 val: map[TjsontagsInt]int{
477 {Int1: 1, Int2: 2}: 3,
478 },
479 exp: `{"{\"int1\":1,\"-\":0,\"Int5\":0}":3}`,
480 },
481 {
482 val: map[[2]struct{ S string }]int{
483 {{S: `"quoted"`}, {S: "unquoted"}}: 1,
484 },
485 exp: `{"[{\"S\":\"\\\"quoted\\\"\"},{\"S\":\"unquoted\"}]":1}`,
439486 },
440487 }
441488
448495 } else {
449496 jb, err := json.Marshal(tc.val)
450497 if err != nil {
451 t.Errorf("[%d]: unexpected error: %v", i, err)
498 t.Fatalf("[%d]: unexpected error: %v", i, err)
452499 }
453500 want = string(jb)
454501 }
496543 expectKV: `"int"={"intsub":1} "str"={"strsub":"2"} "bool"={"boolsub":true}`,
497544 expectJSON: `{"int":{"intsub":1},"str":{"strsub":"2"},"bool":{"boolsub":true}}`,
498545 }, {
546 name: "escapes",
547 builtins: makeKV("\"1\"", 1), // will not be escaped, but should never happen
548 values: makeKV("\tstr", "ABC"), // escaped
549 args: makeKV("bool\n", true), // escaped
550 expectKV: `""1""=1 "\tstr"="ABC" "bool\n"=true`,
551 expectJSON: `{""1"":1,"\tstr":"ABC","bool\n":true}`,
552 }, {
499553 name: "missing value",
500554 builtins: makeKV("builtin"),
501555 values: makeKV("value"),
504558 expectJSON: `{"builtin":"<no-value>","value":"<no-value>","arg":"<no-value>"}`,
505559 }, {
506560 name: "non-string key int",
507 args: makeKV(123, "val"),
561 builtins: makeKV(123, "val"), // should never happen
508562 values: makeKV(456, "val"),
509 builtins: makeKV(789, "val"),
510 expectKV: `"<non-string-key: 789>"="val" "<non-string-key: 456>"="val" "<non-string-key: 123>"="val"`,
511 expectJSON: `{"<non-string-key: 789>":"val","<non-string-key: 456>":"val","<non-string-key: 123>":"val"}`,
563 args: makeKV(789, "val"),
564 expectKV: `"<non-string-key: 123>"="val" "<non-string-key: 456>"="val" "<non-string-key: 789>"="val"`,
565 expectJSON: `{"<non-string-key: 123>":"val","<non-string-key: 456>":"val","<non-string-key: 789>":"val"}`,
512566 }, {
513567 name: "non-string key struct",
514 args: makeKV(struct {
568 builtins: makeKV(struct { // will not be escaped, but should never happen
515569 F1 string
516570 F2 int
517 }{"arg", 123}, "val"),
571 }{"builtin", 123}, "val"),
518572 values: makeKV(struct {
519573 F1 string
520574 F2 int
521575 }{"value", 456}, "val"),
522 builtins: makeKV(struct {
576 args: makeKV(struct {
523577 F1 string
524578 F2 int
525 }{"builtin", 789}, "val"),
526 expectKV: `"<non-string-key: {"F1":"builtin",>"="val" "<non-string-key: {"F1":"value","F>"="val" "<non-string-key: {"F1":"arg","F2">"="val"`,
527 expectJSON: `{"<non-string-key: {"F1":"builtin",>":"val","<non-string-key: {"F1":"value","F>":"val","<non-string-key: {"F1":"arg","F2">":"val"}`,
579 }{"arg", 789}, "val"),
580 expectKV: `"<non-string-key: {"F1":"builtin",>"="val" "<non-string-key: {\"F1\":\"value\",\"F>"="val" "<non-string-key: {\"F1\":\"arg\",\"F2\">"="val"`,
581 expectJSON: `{"<non-string-key: {"F1":"builtin",>":"val","<non-string-key: {\"F1\":\"value\",\"F>":"val","<non-string-key: {\"F1\":\"arg\",\"F2\">":"val"}`,
528582 }}
529583
530584 for _, tc := range testCases {
533587 formatter.AddValues(tc.values)
534588 r := formatter.render(tc.builtins, tc.args)
535589 if r != expect {
536 t.Errorf("wrong output:\nexpected %q\n got %q", expect, r)
590 t.Errorf("wrong output:\nexpected %v\n got %v", expect, r)
537591 }
538592 }
539593 t.Run("KV", func(t *testing.T) {