Skip to content

Commit 08a589d

Browse files
committed
zlog: fix incorrect escaping logic
The previous way of doing this was (purposefully! there was a test!) inserting invalid characters into values. This does the percent-encoding in one spot, instead of doing a second pass that can introduce disallowed characters. See-also: quay/claircore#1582 Signed-off-by: Hank Donnay <hdonnay@redhat.com>
1 parent 8394d82 commit 08a589d

File tree

2 files changed

+38
-30
lines changed

2 files changed

+38
-30
lines changed

context.go

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,54 @@ package zlog
22

33
import (
44
"context"
5-
"fmt"
65
"regexp"
7-
"strconv"
6+
"strings"
7+
"unicode/utf8"
88

99
"go.opentelemetry.io/otel/baggage"
1010
)
1111

1212
// NeedEscape matches a string that needs to be escaped either into an ASCII or a percent-encoded representation.
13-
var needEscape = regexp.MustCompile(`%(?:$|([0-9a-fA-F]?[^0-9a-fA-F]))|[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]`)
13+
var needEscape = regexp.MustCompile(`%(?:$|([0-9a-fA-F]?[^0-9a-fA-F]))|[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]|[\x80-\x{10FFFF}]`)
1414

1515
// PctEncode matches a string that requires some characters to be percent-encoded.
16-
var pctEncode = regexp.MustCompile(`%(?:$|([0-9a-fA-F][^0-9a-fA-F])|[^0-9a-fA-F])| |"|,|;|\\`)
16+
var pctEncode = regexp.MustCompile(`%(?:$|([0-9a-fA-F][^0-9a-fA-F])|[^0-9a-fA-F])|[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]+|[\x80-\x{10FFFF}]+`)
17+
18+
// EscapeOne is the set of 1-byte utf8 characters that should be percent encoded.
19+
//
20+
// This could be avoided if the [pctEncode] regexp was made robust enough to
21+
// ignore correct hex escapes and only capture "lone" percent symbols.
22+
var escapeOne = regexp.MustCompile(`[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]|%| |"|,|;|\\`)
1723

1824
func escapeValue(v string) string {
19-
v = pctEncode.ReplaceAllStringFunc(v, func(m string) (r string) {
20-
for _, c := range m {
21-
switch c {
22-
case '%':
23-
r += "%25"
24-
case ' ':
25-
r += "%20"
26-
case '"':
27-
r += "%22"
28-
case ',':
29-
r += "%2C"
30-
case ';':
31-
r += "%3B"
32-
case '\\':
33-
r += "%5C"
34-
default:
35-
// Just copy to the return value.
36-
// This is (hopefully) just picking up the positions where percent-encoded nybbles would be.
37-
r += string(c)
25+
const hexchar = `0123456789ABCDEF`
26+
var b strings.Builder
27+
b.Grow(4 * 3)
28+
return pctEncode.ReplaceAllStringFunc(v, func(v string) string {
29+
b.Reset()
30+
for _, c := range v {
31+
n := utf8.RuneLen(c)
32+
if n == 1 {
33+
c := byte(c)
34+
if escapeOne.Match([]byte{c}) {
35+
b.WriteRune('%')
36+
b.WriteByte(hexchar[c>>4])
37+
b.WriteByte(hexchar[c&15])
38+
} else {
39+
b.WriteByte(c)
40+
}
41+
continue
42+
}
43+
p := make([]byte, n)
44+
utf8.EncodeRune(p, c)
45+
for _, c := range p {
46+
b.WriteRune('%')
47+
b.WriteByte(hexchar[c>>4])
48+
b.WriteByte(hexchar[c&15])
3849
}
3950
}
40-
if len(m) == len(r) {
41-
panic(fmt.Sprintf("programmer error: pulled odd string %q", m))
42-
}
43-
return r
51+
return b.String()
4452
})
45-
v = strconv.QuoteToASCII(v)
46-
return v[1 : len(v)-1]
4753
}
4854

4955
// ContextWithValues is a helper for the go.opentelemetry.io/otel/baggage v1

zlog_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func TestEscape(t *testing.T) {
2929
{",", true},
3030
{";", true},
3131
{"\\", true},
32+
{"\n", true},
3233
}
3334
for _, tc := range tt {
3435
if got, want := needEscape.MatchString(tc.In), tc.Want; got != want {
@@ -41,13 +42,14 @@ func TestEscape(t *testing.T) {
4142
In string
4243
Want string
4344
}{
44-
{`"🆒"`, `%22\U0001f192%22`},
45+
{`"🆒"`, `%22%F0%9F%86%92%22`},
4546
{`https://example.com?data=%70%62%73%73%77%6F%72%64&a=b`, `https://example.com?data=%70%62%73%73%77%6F%72%64&a=b`},
4647
{`20% done`, `20%25%20done`},
4748
{`%9Z`, `%259Z`},
4849
{`\n`, `%5Cn`},
4950
{`,`, `%2C`},
5051
{`;`, `%3B`},
52+
{"\n", `%0A`},
5153
}
5254
for _, tc := range tt {
5355
if got, want := escapeValue(tc.In), tc.Want; got != want {

0 commit comments

Comments
 (0)