Skip to content

Commit c2ef1b5

Browse files
committed
fix: Parse character, octal, and hex string escapes
Support additional protobuf character escapes, including escaped quotes, plus bounded octal and hexadecimal escapes. Add tests for parsed defaults and numeric escape boundaries. Unicode escapes and validation of malformed or unknown escapes remain unsupported.
1 parent 1169f5d commit c2ef1b5

3 files changed

Lines changed: 28 additions & 7 deletions

File tree

src/tokenize.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ var setCommentRe = /^ *[*/]+ */,
99
setCommentAltRe = /^\s*\*?\/*/,
1010
setCommentSplitRe = /\n/g,
1111
whitespaceRe = /\s/,
12-
unescapeRe = /\\(.?)/g;
12+
unescapeRe = /\\([0-7]{1,3}|[xX][0-9a-fA-F]{1,2}|.?)/g;
1313

1414
var unescapeMap = {
15-
"0": "\0",
15+
"a": "\x07",
16+
"b": "\b",
17+
"f": "\f",
1618
"r": "\r",
1719
"n": "\n",
18-
"t": "\t"
20+
"t": "\t",
21+
"v": "\v",
22+
"?": "?",
23+
"'": "'",
24+
"\"": "\""
1925
};
2026

2127
/**
@@ -27,6 +33,11 @@ var unescapeMap = {
2733
*/
2834
function unescape(str) {
2935
return str.replace(unescapeRe, function($0, $1) {
36+
var first = $1.charAt(0);
37+
if (first >= "0" && first <= "7")
38+
return String.fromCharCode(parseInt($1, 8) & 255);
39+
if (first === "x" || first === "X")
40+
return String.fromCharCode(parseInt($1.substring(1), 16));
3041
switch ($1) {
3142
case "\\":
3243
case "":

tests/api_tokenize.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,26 @@ var tokenize = protobuf.tokenize;
77
tape.test("tokenize", function(test) {
88

99
test.test(test.name + " - unescape", function(test) {
10-
test.equal(tokenize.unescape("\\\\0 \\\0 \\0 \0"), "\\0 \0 \0", "should propery unescape zero-sequences");
11-
test.equal(tokenize.unescape("\\\t\\t\\r\\n"), "\t\r\n", "should propery unescape tabs and line feeds");
10+
test.equal(tokenize.unescape("\\\\0\\0"), "\\0\0", "should properly unescape zero-sequences");
11+
test.equal(tokenize.unescape("\\t\\r\\n"), "\t\r\n", "should properly unescape tabs and line feeds");
12+
test.equal(tokenize.unescape("\\a\\b\\f\\v\\?\\'\\\""), "\x07\b\f\v?'\"", "should unescape single-character sequences");
13+
test.equal(tokenize.unescape("\\101\\102\\103"), "ABC", "should unescape octal sequences");
14+
test.equal(tokenize.unescape("\\x41\\x42\\X43"), "ABC", "should unescape hexadecimal sequences");
15+
test.equal(tokenize.unescape("\\1x\\12x\\123x\\x1x\\x12x"), "\x01x\nxSx\x01x\x12x", "should consume bounded numeric escapes");
1216
test.end();
1317
});
1418

1519
test.ok(expect("", [null]), "should instantly finish for an empty source");
1620
test.ok(expect("'hello\\nworld'", ["'", "hello\nworld", "'", null]), "should parse single quoted strings");
1721
test.ok(expect("\"hello\\nworld\"", ["\"", "hello\nworld", "\"", null]), "should parse double quoted strings");
22+
test.ok(expect("'a\\'b'", ["'", "a'b", "'", null]), "should parse escaped single quotes");
23+
test.ok(expect("\"a\\\"b\"", ["\"", "a\"b", "\"", null]), "should parse escaped double quotes");
1824
test.ok(expectError("\"as\"d\""), "should throw for invalid strings");
1925

26+
var escapedDefault = protobuf.parse("syntax = \"proto2\"; message M { optional string value = 1 [default = \"default<>\\'\\\"abc\\101\\x42\"]; }")
27+
.root.lookupType("M").fields.value.options.default;
28+
test.equal(escapedDefault, "default<>'\"abcAB", "should preserve escaped string defaults");
29+
2030
var tn = tokenize("message Test {}");
2131
test.throws(function() {
2232
tn.skip("somethingelse", false);

tests/data/test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4489,7 +4489,7 @@ $root.jspb = (function() {
44894489
* @memberof jspb.test.DefaultValues
44904490
* @instance
44914491
*/
4492-
DefaultValues.prototype.stringField = "default<>abc";
4492+
DefaultValues.prototype.stringField = "default<>'\"abc";
44934493

44944494
/**
44954495
* DefaultValues boolField.
@@ -4818,7 +4818,7 @@ $root.jspb = (function() {
48184818
throw $Error("max depth exceeded");
48194819
var object = {};
48204820
if (options.defaults) {
4821-
object.stringField = "default<>abc";
4821+
object.stringField = "default<>'\"abc";
48224822
object.boolField = true;
48234823
if ($util.Long) {
48244824
var long = new $util.Long(11, 0, false);

0 commit comments

Comments
 (0)