Changes
1 changed files (+177/-15)
-
-
@@ -537,7 +537,8 @@ return WellKnownHeaders{.content_type = content_type orelse return ParseError.MissingContentType, .content_length = content_length orelse return ParseError.MissingContentLength, .request_id = request_id orelse return ParseError.MissingRequestID, .body_start_position = i, // `i` does not include a final newline. .body_start_position = i + 1, }; }
-
@@ -551,7 +552,7 @@ }}; test "WellKnownHeaders.parse should parse well-known headers" { const message = "MOO/1 REQUEST foo\nContent-Type: application/json; charset=utf-8\nContent-Length: 876\nRequest-Id: 1\n"; const message = "MOO/1 REQUEST foo\nContent-Type: application/json; charset=utf-8\nContent-Length: 876\nRequest-Id: 1\n\n"; const meta = try Metadata.parse(message); const header = try WellKnownHeaders.parse(message, meta.context);
-
@@ -677,7 +678,8 @@ .map = map,.context = .{ .content_type = content_type orelse return ParseError.MissingContentType, .content_length = content_length orelse return ParseError.MissingContentLength, .start_position = i, // `i` does not include a final newline. .start_position = i + 1, }, }; }
-
@@ -688,7 +690,7 @@ }}; test "HashMapHeaders.parse should save every headers" { const message = "MOO/1 REQUEST foo\nContent-Type: application/json; charset=utf-8\nContent-Length: 876\nRequest-Id: 1\nFoo: Bar\n"; const message = "MOO/1 REQUEST foo\nContent-Type: application/json; charset=utf-8\nContent-Length: 876\nRequest-Id: 1\nFoo: Bar\n\n"; const meta = try Metadata.parse(message); var header = try HashMapHeaders.parse(std.testing.allocator, message, meta.context); defer header.deinit();
-
@@ -706,15 +708,51 @@ /// then accessing the bytes field results in use-after-free.pub const RawBody = struct { bytes: []const u8, pub const ParseError = error{}; pub const ParseError = error{ ContentLengthMismatch, }; pub fn parse(message: []const u8, context: BodyParsingContext) ParseError!@This() { _ = message; _ = context; @panic("Not implemented"); const start = @min(context.start_position, message.len); const bytes = message[start..]; if (bytes.len != context.content_length) { return ParseError.ContentLengthMismatch; } return .{ .bytes = bytes }; } }; test "RawBody.parse should save rest of the bytes" { const message = "MOO/1 REQUEST foo\nContent-Type: text/plain\nContent-Length: 13\nRequest-Id: 1\n\nHello, World!"; const meta = try Metadata.parse(message); const header = try WellKnownHeaders.parse(message, meta.context); const body = try RawBody.parse(message, header.getContext()); try std.testing.expectEqualStrings("Hello, World!", body.bytes); } test "RawBody.parse should reject mismatching Content-Length" { { const message = "MOO/1 REQUEST foo\nContent-Type: text/plain\nContent-Length: 12\nRequest-Id: 1\n\nHello, World!"; const meta = try Metadata.parse(message); const header = try WellKnownHeaders.parse(message, meta.context); const err = RawBody.parse(message, header.getContext()); try std.testing.expectError(RawBody.ParseError.ContentLengthMismatch, err); } { const message = "MOO/1 REQUEST foo\nContent-Type: text/plain\nContent-Length: 14\nRequest-Id: 1\n\nHello, World!"; const meta = try Metadata.parse(message); const header = try WellKnownHeaders.parse(message, meta.context); const err = RawBody.parse(message, header.getContext()); try std.testing.expectError(RawBody.ParseError.ContentLengthMismatch, err); } } /// JsonBody stores MOO message body as JSON value. User has to provided an expected /// type (schema) as "T". pub fn JsonBody(comptime T: type) type {
-
@@ -723,18 +761,142 @@ allocator: std.mem.Allocator,value: *const T, parsed: std.json.Parsed(T), pub const ParseError = error{}; pub const ParseError = error{ ContentLengthMismatch, ContentTypeIsNotApplicationJson, } || std.json.ParseError(std.json.Scanner); pub fn parse(allocator: std.mem.Allocator, message: []const u8, context: BodyParsingContext) ParseError!@This() { _ = allocator; _ = message; _ = context; @panic("Not implemented"); // Same MIME checking as one in the official Node.js API. // This does not match to "application/json; charset=utf-8" or such. // (not doing the strict MIME check because parsing MIME is complex task) if (!std.mem.eql(u8, "application/json", context.content_type)) { return ParseError.ContentTypeIsNotApplicationJson; } const start = @min(context.start_position, message.len); const bytes = message[start..]; if (bytes.len != context.content_length) { return ParseError.ContentLengthMismatch; } const parsed = try std.json.parseFromSlice(T, allocator, bytes, .{}); return .{ .allocator = allocator, .value = &parsed.value, .parsed = parsed, }; } pub fn deinit(self: @This()) void { pub fn deinit(self: *const @This()) void { self.parsed.deinit(); self.* = undefined; } }; } test "JsonBody.parse should deserialize body as JSON" { const payload = "{\"foo\": 8}"; var message_buffer = std.ArrayList(u8).init(std.testing.allocator); defer message_buffer.deinit(); try std.fmt.format( message_buffer.writer(), "MOO/1 REQUEST foo\nContent-Type: application/json\nContent-Length: {d}\nRequest-Id: 1\n\n{s}", .{ payload.len, payload }, ); const message = try message_buffer.toOwnedSlice(); defer std.testing.allocator.free(message); const TestData = struct { foo: i32, }; const meta = try Metadata.parse(message); const header = try WellKnownHeaders.parse(message, meta.context); const body = try JsonBody(TestData).parse(std.testing.allocator, message, header.getContext()); defer body.deinit(); try std.testing.expectEqual(body.value.foo, 8); } test "JsonBody.parse should reject invalid JSON text" { const payload = "{\"foo\": 0x8}"; var message_buffer = std.ArrayList(u8).init(std.testing.allocator); defer message_buffer.deinit(); try std.fmt.format( message_buffer.writer(), "MOO/1 REQUEST foo\nContent-Type: application/json\nContent-Length: {d}\nRequest-Id: 1\n\n{s}", .{ payload.len, payload }, ); const message = try message_buffer.toOwnedSlice(); defer std.testing.allocator.free(message); const TestData = struct { foo: i32, }; const meta = try Metadata.parse(message); const header = try WellKnownHeaders.parse(message, meta.context); const err = JsonBody(TestData).parse(std.testing.allocator, message, header.getContext()); try std.testing.expectError(JsonBody(TestData).ParseError.SyntaxError, err); } test "JsonBody.parse should reject mismatching Content-Length" { const payload = "{\"foo\": 8}"; var message_buffer = std.ArrayList(u8).init(std.testing.allocator); defer message_buffer.deinit(); try std.fmt.format( message_buffer.writer(), "MOO/1 REQUEST foo\nContent-Type: application/json\nContent-Length: {d}\nRequest-Id: 1\n\n{s}", .{ payload.len + 1, payload }, ); const message = try message_buffer.toOwnedSlice(); defer std.testing.allocator.free(message); const TestData = struct { foo: i32, }; const meta = try Metadata.parse(message); const header = try WellKnownHeaders.parse(message, meta.context); const err = JsonBody(TestData).parse(std.testing.allocator, message, header.getContext()); try std.testing.expectError(JsonBody(TestData).ParseError.ContentLengthMismatch, err); } test "JsonBody.parse should reject Content-Type other than application/json" { const payload = "{\"foo\": 8}"; var message_buffer = std.ArrayList(u8).init(std.testing.allocator); defer message_buffer.deinit(); try std.fmt.format( message_buffer.writer(), "MOO/1 REQUEST foo\nContent-Type: text/json\nContent-Length: {d}\nRequest-Id: 1\n\n{s}", .{ payload.len, payload }, ); const message = try message_buffer.toOwnedSlice(); defer std.testing.allocator.free(message); const TestData = struct { foo: i32, }; const meta = try Metadata.parse(message); const header = try WellKnownHeaders.parse(message, meta.context); const err = JsonBody(TestData).parse(std.testing.allocator, message, header.getContext()); try std.testing.expectError(JsonBody(TestData).ParseError.ContentTypeIsNotApplicationJson, err); }
-