aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-12-01 11:20:16 -0800
committerRose Hogenson <rosehogenson@posteo.net>2025-12-01 11:21:40 -0800
commit9414a7fd024dba6c70c3c204e7a98dd807572f4a (patch)
tree6c91b2d04d30d36ab69fcb4b6c39bb0434332e0a
parent55b358e280b16d1d3335b85e5fe12702a264cc1d (diff)
downloadccl-9414a7fd024dba6c70c3c204e7a98dd807572f4a.tar.zst
Allow octal and hex escapes to take fewer digits
-rw-r--r--ccl.go26
-rw-r--r--ccl_test.go23
2 files changed, 36 insertions, 13 deletions
diff --git a/ccl.go b/ccl.go
index 17cb5e9..5d37310 100644
--- a/ccl.go
+++ b/ccl.go
@@ -60,7 +60,7 @@
//
// Backslash characters inside a string are interpreted as an escape sequence.
// Any escape sequence not described below is an error. The escape sequences
-// are identical to C11, with the exception that \x always takes exactly 2
+// are identical to C11, with the exception that \x takes at most 2
// hex characters.
//
// \' single quote 0x27
@@ -409,14 +409,17 @@ func (p *parser) unescape(rawStr []byte) ([]byte, error) {
}
case 'x':
i++
- if i+2 > len(rawStr) {
- return nil, p.error("invalid hex escape %q", rawStr[i-2:min(i+2, len(rawStr))])
+ end := i
+ for ; end < i+2 && end < len(rawStr) && ('0' <= rawStr[end] && rawStr[end] <= '9' || 'a' <= rawStr[end] && rawStr[end] <= 'f' || 'A' <= rawStr[end] && rawStr[end] <= 'F'); end++ {
}
- n, err := strconv.ParseUint(string(rawStr[i:i+2]), 16, 8)
+ if end == i {
+ return nil, p.error("invalid hex escape %q", rawStr[i-2:end])
+ }
+ n, err := strconv.ParseUint(string(rawStr[i:end]), 16, 8)
if err != nil {
- return nil, p.error("invalid hex escape %q: %s", rawStr[i-2:i+2], err)
+ return nil, p.error("invalid hex escape %q (unreachable)", rawStr[i-2:end])
}
- i++
+ i = end - 1
b = []byte{byte(n)}
case 'u', 'U':
nBytes := 4
@@ -434,14 +437,17 @@ func (p *parser) unescape(rawStr []byte) ([]byte, error) {
i += nBytes - 1
b = utf8.AppendRune(nil, rune(n))
default:
- if i+3 > len(rawStr) {
+ end := i
+ for ; end < i+3 && end < len(rawStr) && '0' <= rawStr[end] && rawStr[end] <= '7'; end++ {
+ }
+ if end == i {
return nil, p.error("invalid string escape %q", rawStr[i-1:i+1])
}
- n, err := strconv.ParseUint(string(rawStr[i:i+3]), 8, 8)
+ n, err := strconv.ParseUint(string(rawStr[i:end]), 8, 8)
if err != nil {
- return nil, p.error("invalid octal escape %q: %s", rawStr[i-1:i+3], err)
+ return nil, p.error("invalid octal escape %q: %s", rawStr[i-1:end], err)
}
- i += 2
+ i = end - 1
b = []byte{byte(n)}
}
escaped = append(escaped, b...)
diff --git a/ccl_test.go b/ccl_test.go
index 8aaca98..42c71a9 100644
--- a/ccl_test.go
+++ b/ccl_test.go
@@ -260,6 +260,26 @@ can just span multiple lines"`,
msg: `string: '\033'`,
want: message{String: "\033"},
}, {
+ desc: "StringOctalOne",
+ msg: `string:'\0asdf'`,
+ want: message{String: "\x00asdf"},
+ }, {
+ desc: "StringOctalTwo",
+ msg: `string:'\33['`,
+ want: message{String: "\033["},
+ }, {
+ desc: "StringOctalFour",
+ msg: `string:'\1234'`,
+ want: message{String: "\1234"},
+ }, {
+ desc: "StringHexOne",
+ msg: `string:'\xfhello'`,
+ want: message{String: "\x0fhello"},
+ }, {
+ desc: "StringHexThree",
+ msg: `string:'\x0ff'`,
+ want: message{String: "\x0ff"},
+ }, {
desc: "StringStripCarriageReturn",
msg: "string:'a\r\nb'",
want: message{String: "a\nb"},
@@ -404,9 +424,6 @@ func TestUnmarshal_Invalid(t *testing.T) {
desc: "StringBadReturnEscape",
msg: "string:'\\\r'",
}, {
- desc: "StringShortHex",
- msg: `string:"\x1"`,
- }, {
desc: "StringBadHex",
msg: `string:"\xgg"`,
}, {