const std = @import("std"); const util = @import("util"); const parser = @import("./parser.zig"); const http = @import("../lib.zig"); const t = std.testing; const test_case = struct { fn parse(text: []const u8, expected: struct { protocol: http.Protocol = .http_1_1, method: http.Method = .GET, headers: []const std.meta.Tuple(&.{ []const u8, []const u8 }) = &.{}, uri: []const u8 = "", }) !void { var stream = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(text) }; var actual = try parser.parse(t.allocator, stream.reader()); defer actual.parseFree(t.allocator); try t.expectEqual(expected.protocol, actual.protocol); try t.expectEqual(expected.method, actual.method); try t.expectEqualStrings(expected.uri, actual.uri); try t.expectEqual(expected.headers.len, actual.headers.count()); for (expected.headers) |hdr| { if (actual.headers.get(hdr[0])) |val| { try t.expectEqualStrings(hdr[1], val); } else { std.debug.print("Error: Header {s} expected to be present, was not.\n", .{hdr[0]}); try t.expect(false); } } } }; test "HTTP/1.x parse - No body" { try test_case.parse( util.comptimeToCrlf( \\GET / HTTP/1.1 \\ \\ ), .{ .protocol = .http_1_1, .method = .GET, .uri = "/", }, ); try test_case.parse( util.comptimeToCrlf( \\POST / HTTP/1.1 \\ \\ ), .{ .protocol = .http_1_1, .method = .POST, .uri = "/", }, ); try test_case.parse( util.comptimeToCrlf( \\GET /url/abcd HTTP/1.1 \\ \\ ), .{ .protocol = .http_1_1, .method = .GET, .uri = "/url/abcd", }, ); try test_case.parse( util.comptimeToCrlf( \\GET / HTTP/1.0 \\ \\ ), .{ .protocol = .http_1_0, .method = .GET, .uri = "/", }, ); try test_case.parse( util.comptimeToCrlf( \\GET /url/abcd HTTP/1.1 \\Content-Type: application/json \\ \\ ), .{ .protocol = .http_1_1, .method = .GET, .uri = "/url/abcd", .headers = &.{.{ "Content-Type", "application/json" }}, }, ); try test_case.parse( util.comptimeToCrlf( \\GET /url/abcd HTTP/1.1 \\Content-Type: application/json \\Authorization: bearer \\ \\ ), .{ .protocol = .http_1_1, .method = .GET, .uri = "/url/abcd", .headers = &.{ .{ "Content-Type", "application/json" }, .{ "Authorization", "bearer " }, }, }, ); // Test without CRLF try test_case.parse( \\GET /url/abcd HTTP/1.1 \\Content-Type: application/json \\Authorization: bearer \\ \\ , .{ .protocol = .http_1_1, .method = .GET, .uri = "/url/abcd", .headers = &.{ .{ "Content-Type", "application/json" }, .{ "Authorization", "bearer " }, }, }, ); try test_case.parse( \\POST / HTTP/1.1 \\ \\ , .{ .protocol = .http_1_1, .method = .POST, .uri = "/", }, ); try test_case.parse( util.comptimeToCrlf( \\GET / HTTP/1.2 \\ \\ ), .{ .protocol = .http_1_x, .method = .GET, .uri = "/", }, ); } test "HTTP/1.x parse - unsupported protocol" { try t.expectError(error.UnknownProtocol, test_case.parse( \\GET / JSON/1.1 \\ \\ , .{}, )); try t.expectError(error.UnknownProtocol, test_case.parse( \\GET / SOMETHINGELSE/3.5 \\ \\ , .{}, )); try t.expectError(error.UnknownProtocol, test_case.parse( \\GET / /1.1 \\ \\ , .{}, )); try t.expectError(error.HttpVersionNotSupported, test_case.parse( \\GET / HTTP/2.1 \\ \\ , .{}, )); } test "HTTP/1.x parse - Unknown method" { try t.expectError(error.MethodNotImplemented, test_case.parse( \\ABCD / HTTP/1.1 \\ \\ , .{}, )); try t.expectError(error.MethodNotImplemented, test_case.parse( \\PATCHPATCHPATCH / HTTP/1.1 \\ \\ , .{}, )); } test "HTTP/1.x parse - Too long" { try t.expectError(error.RequestUriTooLong, test_case.parse( std.fmt.comptimePrint("GET {s} HTTP/1.1\n\n", .{"a" ** 8192}), .{}, )); try t.expectError(error.HeaderLineTooLong, test_case.parse( std.fmt.comptimePrint("GET / HTTP/1.1\r\n{s}: abcd", .{"a" ** 8192}), .{}, )); try t.expectError(error.HeaderLineTooLong, test_case.parse( std.fmt.comptimePrint("GET / HTTP/1.1\r\nabcd: {s}", .{"a" ** 8192}), .{}, )); } test "HTTP/1.x parse - bad requests" { try t.expectError(error.BadRequest, test_case.parse( \\GET / HTTP/1.1 blah blah \\ \\ , .{}, )); try t.expectError(error.BadRequest, test_case.parse( \\GET / HTTP/1.1 \\abcd : lksjdfkl \\ , .{}, )); try t.expectError(error.BadRequest, test_case.parse( \\GET / HTTP/1.1 \\ lksjfklsjdfklj \\ , .{}, )); } test "HTTP/1.x parse - Headers" { try test_case.parse( util.comptimeToCrlf( \\GET /url/abcd HTTP/1.1 \\Content-Type: application/json \\Content-Type: application/xml \\ \\ ), .{ .protocol = .http_1_1, .method = .GET, .uri = "/url/abcd", .headers = &.{.{ "Content-Type", "application/json, application/xml" }}, }, ); }