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

This commit is contained in:
Techatrix 2023-09-11 23:00:06 +02:00 committed by GitHub
parent a0968be83c
commit 7827265ea8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 25 additions and 12 deletions

View File

@ -81,7 +81,6 @@ pub const Value = union(enum) {
} }
pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!@This() { pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!@This() {
_ = options;
// The grammar of the stack is: // The grammar of the stack is:
// (.array | .object .string)* // (.array | .object .string)*
var stack = Array.init(allocator); var stack = Array.init(allocator);
@ -93,21 +92,21 @@ pub const Value = union(enum) {
stack.items[stack.items.len - 1] == .array or stack.items[stack.items.len - 1] == .array or
(stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string)); (stack.items[stack.items.len - 2] == .object and stack.items[stack.items.len - 1] == .string));
switch (try source.nextAlloc(allocator, .alloc_always)) { switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
.allocated_string => |s| { .allocated_string => |s| {
return try handleCompleteValue(&stack, allocator, source, Value{ .string = s }) orelse continue; return try handleCompleteValue(&stack, allocator, source, Value{ .string = s }, options) orelse continue;
}, },
.allocated_number => |slice| { .allocated_number => |slice| {
return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice)) orelse continue; return try handleCompleteValue(&stack, allocator, source, Value.parseFromNumberSlice(slice), options) orelse continue;
}, },
.null => return try handleCompleteValue(&stack, allocator, source, .null) orelse continue, .null => return try handleCompleteValue(&stack, allocator, source, .null, options) orelse continue,
.true => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = true }) orelse continue, .true => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = true }, options) orelse continue,
.false => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = false }) orelse continue, .false => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = false }, options) orelse continue,
.object_begin => { .object_begin => {
switch (try source.nextAlloc(allocator, .alloc_always)) { switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
.object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = ObjectMap.init(allocator) }) orelse continue, .object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = ObjectMap.init(allocator) }, options) orelse continue,
.allocated_string => |key| { .allocated_string => |key| {
try stack.appendSlice(&[_]Value{ try stack.appendSlice(&[_]Value{
Value{ .object = ObjectMap.init(allocator) }, Value{ .object = ObjectMap.init(allocator) },
@ -120,7 +119,7 @@ pub const Value = union(enum) {
.array_begin => { .array_begin => {
try stack.append(Value{ .array = Array.init(allocator) }); try stack.append(Value{ .array = Array.init(allocator) });
}, },
.array_end => return try handleCompleteValue(&stack, allocator, source, stack.pop()) orelse continue, .array_end => return try handleCompleteValue(&stack, allocator, source, stack.pop(), options) orelse continue,
else => unreachable, else => unreachable,
} }
@ -134,7 +133,7 @@ pub const Value = union(enum) {
} }
}; };
fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, value_: Value) !?Value { fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, value_: Value, options: ParseOptions) !?Value {
if (stack.items.len == 0) return value_; if (stack.items.len == 0) return value_;
var value = value_; var value = value_;
while (true) { while (true) {
@ -152,7 +151,7 @@ fn handleCompleteValue(stack: *Array, allocator: Allocator, source: anytype, val
// This is an invalid state to leave the stack in, // This is an invalid state to leave the stack in,
// so we have to process the next token before we return. // so we have to process the next token before we return.
switch (try source.nextAlloc(allocator, .alloc_always)) { switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
.object_end => { .object_end => {
// This object is complete. // This object is complete.
value = stack.pop(); value = stack.pop();

View File

@ -302,6 +302,20 @@ test "long object value" {
try testing.expectEqualStrings(value, parsed.value.object.get("key").?.string); try testing.expectEqualStrings(value, parsed.value.object.get("key").?.string);
} }
test "ParseOptions.max_value_len" {
var arena = ArenaAllocator.init(testing.allocator);
defer arena.deinit();
const str = "\"0800fc577294c34e0b28ad2839435945\"";
const value = try std.json.parseFromSliceLeaky(std.json.Value, arena.allocator(), str, .{ .max_value_len = 32 });
try testing.expect(value == .string);
try testing.expect(value.string.len == 32);
try testing.expectError(error.ValueTooLong, std.json.parseFromSliceLeaky(std.json.Value, arena.allocator(), str, .{ .max_value_len = 31 }));
}
test "many object keys" { test "many object keys" {
const doc = const doc =
\\{ \\{