aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--asspb.go48
-rw-r--r--asspb_test.go10
2 files changed, 43 insertions, 15 deletions
diff --git a/asspb.go b/asspb.go
index 624f3ca..35cdc51 100644
--- a/asspb.go
+++ b/asspb.go
@@ -34,13 +34,15 @@
// # Strings
//
// Strings are written with " or ' and any sequence of intermediate bytes (with
-// the exception of escape sequences which are described below). "any bytes"
-// means that strings can contain newline without needing an escape sequence
+// the exception of escape sequences which are described below). Strings must be
+// valid UTF-8 after escape sequences are expanded.
//
// 'asdf'
// "that's cool"
// "\tall\n\tyour\n\tfavorite\n\tescape\n\tsequences"
//
+// Note that strings can contain newline without needing an escape sequence
+//
// 'a multiline
// string'
//
@@ -64,7 +66,7 @@
// \nnn 3-digit octal value nnn
// \xnn 2-digit hex value nn
// \unnnn unicode code point U+nnnn
-// \Unnnnnnnn unicode code point U+nnnnnnnn
+// \Unnnnnnnn unicode code point U+nnnnnnnn (UTF8)
//
// As an extension to the C11 escapes, a backslash immediately before a newline
// character (0x0a) will remove the newline character from the resulting string
@@ -259,28 +261,44 @@ func unescape(idx int, rawStr []byte) ([]byte, error) {
case "\\\n", "\\\r\n":
return nil
}
- if bytes.HasPrefix(escape, []byte(`\x`)) || bytes.HasPrefix(escape, []byte(`\u`)) || bytes.HasPrefix(escape, []byte(`\U`)) {
+ switch {
+ case bytes.HasPrefix(escape, []byte(`\x`)):
+ var n uint64
+ if n, err = strconv.ParseUint(string(escape[2:]), 16, 8); err != nil {
+ err = fmt.Errorf("%d: syntax error: invalid hex escape %q: %s", idx, escape, err)
+ return nil
+ }
+ return []byte{byte(n)}
+ case bytes.HasPrefix(escape, []byte(`\u`)), bytes.HasPrefix(escape, []byte(`\U`)):
var n int64
if n, err = strconv.ParseInt(string(escape[2:]), 16, 32); err != nil {
- err = fmt.Errorf("%d: syntax error: invalid hex escape %q: %s", idx, escape, err)
+ err = fmt.Errorf("%d: syntax error: invalid unicode escape %q: %s", idx, escape, err)
return nil
}
return utf8.AppendRune(nil, rune(n))
+ default:
+ if len(escape) != 4 {
+ err = fmt.Errorf("%d: syntax error: invalid string escape %q", idx, escape)
+ return nil
+ }
+ var n int64
+ if n, err = strconv.ParseInt(string(escape[1:]), 8, 32); err != nil {
+ err = fmt.Errorf("%d: syntax error: invalid string escape %q", idx, escape)
+ return nil
+ }
+ if n > 255 {
+ err = fmt.Errorf("%d: invalid octal escape %q %d > 255", idx, escape, n)
+ return nil
+ }
+ return []byte{byte(n)}
}
- if len(escape) != 4 {
- err = fmt.Errorf("%d: syntax error: invalid string escape %q", idx, escape)
- return nil
- }
- var n int64
- if n, err = strconv.ParseInt(string(escape[1:]), 8, 32); err != nil {
- err = fmt.Errorf("%d: syntax error: invalid string escape %q", idx, escape)
- return nil
- }
- return utf8.AppendRune(nil, rune(n))
})
if err != nil {
return nil, err
}
+ if !utf8.Valid(escaped) {
+ return nil, fmt.Errorf("%d: syntax error: string %q is not UTF-8 encoded", idx, escaped)
+ }
return escaped, nil
}
diff --git a/asspb_test.go b/asspb_test.go
index 2425571..b974b2f 100644
--- a/asspb_test.go
+++ b/asspb_test.go
@@ -151,6 +151,10 @@ can just span multiple lines"`,
msg: `field: '\x0a'`,
want: map[string]any{"field": "\n"},
}, {
+ desc: "StringHexHighByte",
+ msg: `field: "\xe4\xb8\x96"`,
+ want: map[string]any{"field": "世"},
+ }, {
desc: "StringUnicode",
msg: `field: '\u2014'`,
want: map[string]any{"field": "—"},
@@ -265,6 +269,12 @@ func TestUnmarshal_Invalid(t *testing.T) {
}, {
desc: "IntLeadingZero",
msg: `field: 0644`,
+ }, {
+ desc: "InvalidOctal",
+ msg: `field: "\777"`,
+ }, {
+ desc: "InvalidUTF8",
+ msg: `field: "\x80"`,
}} {
t.Run(tc.desc, func(t *testing.T) {
t.Parallel()