authorgravatar for quae@daurnimator.comdaurnimator <quae@daurnimator.com> 2021-05-30 17:27:42+10:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2021-05-31 14:09:59+03:00
log57cf9f7ea6d018714cf4afa711799c67ff730f12
tree94633d543a4b4260fa6c51a24c3341bffbfe7aa3
parent556d3e3d800378efcf9d7d35ad63879cc06c5e88

std: by default, disallow trailing data when parsing json


1 files changed, 25 insertions(+), 6 deletions(-)

lib/std/json.zig+25-6
...@@ -1464,6 +1464,8 @@ pub const ParseOptions = struct {...@@ -1464,6 +1464,8 @@ pub const ParseOptions = struct {
14641464
1465 /// If false, finding an unknown field returns an error.1465 /// If false, finding an unknown field returns an error.
1466 ignore_unknown_fields: bool = false,1466 ignore_unknown_fields: bool = false,
1467
1468 allow_trailing_data: bool = false,
1467};1469};
14681470
1469fn skipValue(tokens: *TokenStream) !void {1471fn skipValue(tokens: *TokenStream) !void {
...@@ -1627,6 +1629,8 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:...@@ -1627,6 +1629,8 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
1627 .ObjectEnd => break,1629 .ObjectEnd => break,
1628 .String => |stringToken| {1630 .String => |stringToken| {
1629 const key_source_slice = stringToken.slice(tokens.slice, tokens.i - 1);1631 const key_source_slice = stringToken.slice(tokens.slice, tokens.i - 1);
1632 var child_options = options;
1633 child_options.allow_trailing_data = true;
1630 var found = false;1634 var found = false;
1631 inline for (structInfo.fields) |field, i| {1635 inline for (structInfo.fields) |field, i| {
1632 // TODO: using switches here segfault the compiler (#2727?)1636 // TODO: using switches here segfault the compiler (#2727?)
...@@ -1643,24 +1647,24 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:...@@ -1643,24 +1647,24 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
1643 // }1647 // }
1644 if (options.duplicate_field_behavior == .UseFirst) {1648 if (options.duplicate_field_behavior == .UseFirst) {
1645 // unconditonally ignore value. for comptime fields, this skips check against default_value1649 // unconditonally ignore value. for comptime fields, this skips check against default_value
1646 parseFree(field.field_type, try parse(field.field_type, tokens, options), options);1650 parseFree(field.field_type, try parse(field.field_type, tokens, child_options), child_options);
1647 found = true;1651 found = true;
1648 break;1652 break;
1649 } else if (options.duplicate_field_behavior == .Error) {1653 } else if (options.duplicate_field_behavior == .Error) {
1650 return error.DuplicateJSONField;1654 return error.DuplicateJSONField;
1651 } else if (options.duplicate_field_behavior == .UseLast) {1655 } else if (options.duplicate_field_behavior == .UseLast) {
1652 if (!field.is_comptime) {1656 if (!field.is_comptime) {
1653 parseFree(field.field_type, @field(r, field.name), options);1657 parseFree(field.field_type, @field(r, field.name), child_options);
1654 }1658 }
1655 fields_seen[i] = false;1659 fields_seen[i] = false;
1656 }1660 }
1657 }1661 }
1658 if (field.is_comptime) {1662 if (field.is_comptime) {
1659 if (!try parsesTo(field.field_type, field.default_value.?, tokens, options)) {1663 if (!try parsesTo(field.field_type, field.default_value.?, tokens, child_options)) {
1660 return error.UnexpectedValue;1664 return error.UnexpectedValue;
1661 }1665 }
1662 } else {1666 } else {
1663 @field(r, field.name) = try parse(field.field_type, tokens, options);1667 @field(r, field.name) = try parse(field.field_type, tokens, child_options);
1664 }1668 }
1665 fields_seen[i] = true;1669 fields_seen[i] = true;
1666 found = true;1670 found = true;
...@@ -1697,6 +1701,8 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:...@@ -1697,6 +1701,8 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
1697 .ArrayBegin => {1701 .ArrayBegin => {
1698 var r: T = undefined;1702 var r: T = undefined;
1699 var i: usize = 0;1703 var i: usize = 0;
1704 var child_options = options;
1705 child_options.allow_trailing_data = true;
1700 errdefer {1706 errdefer {
1701 // Without the r.len check `r[i]` is not allowed1707 // Without the r.len check `r[i]` is not allowed
1702 if (r.len > 0) while (true) : (i -= 1) {1708 if (r.len > 0) while (true) : (i -= 1) {
...@@ -1705,7 +1711,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:...@@ -1705,7 +1711,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
1705 };1711 };
1706 }1712 }
1707 while (i < r.len) : (i += 1) {1713 while (i < r.len) : (i += 1) {
1708 r[i] = try parse(arrayInfo.child, tokens, options);1714 r[i] = try parse(arrayInfo.child, tokens, child_options);
1709 }1715 }
1710 const tok = (try tokens.next()) orelse return error.UnexpectedEndOfJson;1716 const tok = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
1711 switch (tok) {1717 switch (tok) {
...@@ -1786,7 +1792,13 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:...@@ -1786,7 +1792,13 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
17861792
1787pub fn parse(comptime T: type, tokens: *TokenStream, options: ParseOptions) !T {1793pub fn parse(comptime T: type, tokens: *TokenStream, options: ParseOptions) !T {
1788 const token = (try tokens.next()) orelse return error.UnexpectedEndOfJson;1794 const token = (try tokens.next()) orelse return error.UnexpectedEndOfJson;
1789 return parseInternal(T, token, tokens, options);1795 const r = try parseInternal(T, token, tokens, options);
1796 errdefer parseFree(T, r, options);
1797 if (!options.allow_trailing_data) {
1798 if ((try tokens.next()) != null) unreachable;
1799 assert(tokens.i >= tokens.slice.len);
1800 }
1801 return r;
1790}1802}
17911803
1792/// Releases resources created by `parse`.1804/// Releases resources created by `parse`.
...@@ -1871,6 +1883,13 @@ test "parse into enum" {...@@ -1871,6 +1883,13 @@ test "parse into enum" {
1871 try testing.expectError(error.InvalidEnumTag, parse(T, &TokenStream.init("\"Qux\""), ParseOptions{}));1883 try testing.expectError(error.InvalidEnumTag, parse(T, &TokenStream.init("\"Qux\""), ParseOptions{}));
1872}1884}
18731885
1886test "parse with trailing data" {
1887 try testing.expectEqual(false, try parse(bool, &TokenStream.init("falsed"), ParseOptions{ .allow_trailing_data = true }));
1888 try testing.expectError(error.InvalidTopLevelTrailing, parse(bool, &TokenStream.init("falsed"), ParseOptions{ .allow_trailing_data = false }));
1889 // trailing whitespace is okay
1890 try testing.expectEqual(false, try parse(bool, &TokenStream.init("false \n"), ParseOptions{ .allow_trailing_data = false }));
1891}
1892
1874test "parse into that allocates a slice" {1893test "parse into that allocates a slice" {
1875 try testing.expectError(error.AllocatorRequired, parse([]u8, &TokenStream.init("\"foo\""), ParseOptions{}));1894 try testing.expectError(error.AllocatorRequired, parse([]u8, &TokenStream.init("\"foo\""), ParseOptions{}));
18761895