authorgravatar for techatrix@mailbox.orgTechatrix <techatrix@mailbox.org> 2023-09-11 23:00:06+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-09-11 17:00:06-04:00
log7827265ea81c72e44da9b7d9b91e68d557e4db6d
tree173f2120d984ce177110521d2613e423fe0da33a
parenta0968be83c1bc5cf6433e9dc7ebb2f94025c5aa5
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

json: respect max_value_len when parsing std.json.Value (#17107)


2 files changed, 25 insertions(+), 12 deletions(-)

lib/std/json/dynamic.zig+11-12
......@@ -81,7 +81,6 @@ pub const Value = union(enum) {
8181 }
8282
8383 pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!@This() {
84 _ = options;
8584 // The grammar of the stack is:
8685 // (.array | .object .string)*
8786 var stack = Array.init(allocator);
......@@ -93,21 +92,21 @@ pub const Value = union(enum) {
9392 stack.items[stack.items.len - 1] == .array or
9493 (stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string));
9594
96 switch (try source.nextAlloc(allocator, .alloc_always)) {
95 switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
9796 .allocated_string => |s| {
98 return try handleCompleteValue(&stack, allocator, source, Value{ .string = s }) orelse continue;
97 return try handleCompleteValue(&stack, allocator, source, Value{ .string = s }, options) orelse continue;
9998 },
10099 .allocated_number => |slice| {
101 return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice)) orelse continue;
100 return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice), options) orelse continue;
102101 },
103102
104 .null => return try handleCompleteValue(&stack, allocator, source, .null) orelse continue,
105 .true => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = true }) orelse continue,
106 .false => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = false }) orelse continue,
103 .null => return try handleCompleteValue(&stack, allocator, source, .null, options) orelse continue,
104 .true => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = true }, options) orelse continue,
105 .false => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = false }, options) orelse continue,
107106
108107 .object_begin => {
109 switch (try source.nextAlloc(allocator, .alloc_always)) {
110 .object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = ObjectMap.init(allocator) }) orelse continue,
108 switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
109 .object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = ObjectMap.init(allocator) }, options) orelse continue,
111110 .allocated_string => |key| {
112111 try stack.appendSlice(&[_]Value{
113112 Value{ .object = ObjectMap.init(allocator) },
......@@ -120,7 +119,7 @@ pub const Value = union(enum) {
120119 .array_begin => {
121120 try stack.append(Value{ .array = Array.init(allocator) });
122121 },
123 .array_end => return try handleCompleteValue(&stack, allocator, source, stack.pop()) orelse continue,
122 .array_end => return try handleCompleteValue(&stack, allocator, source, stack.pop(), options) orelse continue,
124123
125124 else => unreachable,
126125 }
......@@ -134,7 +133,7 @@ pub const Value = union(enum) {
134133 }
135134};
136135
137fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, value_: Value) !?Value {
136fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, value_: Value, options: ParseOptions) !?Value {
138137 if (stack.items.len == 0) return value_;
139138 var value = value_;
140139 while (true) {
......@@ -152,7 +151,7 @@ fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, val
152151
153152 // This is an invalid state to leave the stack in,
154153 // so we have to process the next token before we return.
155 switch (try source.nextAlloc(allocator, .alloc_always)) {
154 switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
156155 .object_end => {
157156 // This object is complete.
158157 value = stack.pop();
lib/std/json/dynamic_test.zig+14
......@@ -302,6 +302,20 @@ test "long object value" {
302302 try testing.expectEqualStrings(value, parsed.value.object.get("key").?.string);
303303}
304304
305test "ParseOptions.max_value_len" {
306 var arena = ArenaAllocator.init(testing.allocator);
307 defer arena.deinit();
308
309 const str = "\"0800fc577294c34e0b28ad2839435945\"";
310
311 const value = try std.json.parseFromSliceLeaky(std.json.Value, arena.allocator(), str, .{ .max_value_len = 32 });
312
313 try testing.expect(value == .string);
314 try testing.expect(value.string.len == 32);
315
316 try testing.expectError(error.ValueTooLong, std.json.parseFromSliceLeaky(std.json.Value, arena.allocator(), str, .{ .max_value_len = 31 }));
317}
318
305319test "many object keys" {
306320 const doc =
307321 \\{