Commit 7827265ea8

Techatrix <19954306+Techatrix@users.noreply.github.com>
2023-09-11 23:00:06
json: respect max_value_len when parsing std.json.Value (#17107)
1 parent a0968be
Changed files (2)
lib/std/json/dynamic.zig
@@ -81,7 +81,6 @@ pub const Value = union(enum) {
     }
 
     pub fn jsonParse(allocator: Allocator, source: anytype, options: ParseOptions) ParseError(@TypeOf(source.*))!@This() {
-        _ = options;
         // The grammar of the stack is:
         //  (.array | .object .string)*
         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 - 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| {
-                    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| {
-                    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,
-                .true => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = true }) orelse continue,
-                .false => return try handleCompleteValue(&stack, allocator, source, Value{ .bool = false }) orelse continue,
+                .null => return try handleCompleteValue(&stack, allocator, source, .null, options) 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 }, options) orelse continue,
 
                 .object_begin => {
-                    switch (try source.nextAlloc(allocator, .alloc_always)) {
-                        .object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = ObjectMap.init(allocator) }) orelse continue,
+                    switch (try source.nextAllocMax(allocator, .alloc_always, options.max_value_len.?)) {
+                        .object_end => return try handleCompleteValue(&stack, allocator, source, Value{ .object = ObjectMap.init(allocator) }, options) orelse continue,
                         .allocated_string => |key| {
                             try stack.appendSlice(&[_]Value{
                                 Value{ .object = ObjectMap.init(allocator) },
@@ -120,7 +119,7 @@ pub const Value = union(enum) {
                 .array_begin => {
                     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,
             }
@@ -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_;
     var value = value_;
     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,
                 // 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 => {
                         // This object is complete.
                         value = stack.pop();
lib/std/json/dynamic_test.zig
@@ -302,6 +302,20 @@ test "long object value" {
     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" {
     const doc =
         \\{