summaryrefslogtreecommitdiff
path: root/test/http.zig
diff options
context:
space:
mode:
Diffstat (limited to 'test/http.zig')
-rw-r--r--test/http.zig92
1 files changed, 92 insertions, 0 deletions
diff --git a/test/http.zig b/test/http.zig
new file mode 100644
index 0000000..d5064e4
--- /dev/null
+++ b/test/http.zig
@@ -0,0 +1,92 @@
+const std = @import("std");
+const c9 = @import("cloud9");
+const testing = std.testing;
+const http = c9.http;
+
+test "client and server preserve 9P frames at WebSocket length boundaries" {
+ var bytes: [70000]u8 = undefined;
+ var data: [66000]u8 = @splat(0xa5);
+ var receive_buffer: [70000]u8 = undefined;
+ for ([_]usize{ 0, 114, 115, 65524, 65525 }) |length| {
+ const frame = try c9.encode(.{ .rread = .{ .data = data[0..length] } }, 7, &bytes);
+ for ([_]http.Role{ .client, .server }) |role| {
+ var writer: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer writer.deinit();
+ var empty: std.Io.Reader = .fixed("");
+ var sender: http.WebSocket = .{ .input = &empty, .output = &writer.writer, .role = role };
+ try sender.send(frame, .binary, if (role == .client) .{ 1, 2, 3, 4 } else null);
+ var reader: testing.Reader = .init(&.{}, &.{.{ .buffer = writer.written() }});
+ reader.artificial_limit = .limited(1);
+ var receiver: http.WebSocket = .{ .input = &reader.interface, .output = &writer.writer, .role = if (role == .client) .server else .client };
+ const message = try receiver.receive(&receive_buffer);
+ try testing.expectEqual(.binary, message.opcode);
+ try testing.expectEqualSlices(u8, frame, message.data);
+ }
+ }
+}
+
+test "fragmented message survives an interleaved ping" {
+ // Rflush tag 1 split after its size field. Ping between the fragments.
+ var reader: std.Io.Reader = .fixed(&.{ 0x02, 4, 7, 0, 0, 0, 0x89, 1, 'x', 0x80, 3, 109, 1, 0 });
+ var writer: std.Io.Writer = .fixed(&.{});
+ var socket: http.WebSocket = .{ .input = &reader, .output = &writer, .role = .client };
+ var buffer: [64]u8 = undefined;
+ const ping = try socket.receive(&buffer);
+ try testing.expectEqual(.ping, ping.opcode);
+ try testing.expectEqualStrings("x", ping.data);
+ const frame = try socket.receive(&buffer);
+ const decoded = try c9.decode(frame.data);
+ try testing.expectEqual(c9.Type.rflush, decoded.msg.msgType());
+ try testing.expectEqual(@as(u16, 1), decoded.tag);
+}
+
+test "framing rejects invalid masks, reserved bits, lengths and controls" {
+ const cases = .{
+ .{ &[_]u8{ 0x82, 0x80 }, error.InvalidMask },
+ .{ &[_]u8{ 0xc2, 0 }, error.ReservedBits },
+ .{ &[_]u8{ 0x82, 126, 0, 7 }, error.NonCanonicalLength },
+ .{ &[_]u8{ 0x89, 126, 0, 126 }, error.InvalidControl },
+ .{ &[_]u8{ 0x09, 0 }, error.InvalidControl },
+ .{ &[_]u8{ 0x80, 0 }, error.UnexpectedContinuation },
+ .{ &[_]u8{ 0x81, 0 }, error.ExpectedBinary },
+ .{ &[_]u8{ 0x88, 1, 0 }, error.InvalidClose },
+ .{ &[_]u8{ 0x88, 2, 3, 237 }, error.InvalidClose },
+ .{ &[_]u8{ 0x88, 3, 3, 232, 255 }, error.InvalidClose },
+ .{ &[_]u8{ 0x82, 100 }, error.MessageTooLarge },
+ };
+ inline for (cases) |case| {
+ var reader: std.Io.Reader = .fixed(case[0]);
+ var writer: std.Io.Writer = .fixed(&.{});
+ var socket: http.WebSocket = .{ .input = &reader, .output = &writer, .role = .client };
+ var buffer: [64]u8 = undefined;
+ try testing.expectError(case[1], socket.receive(&buffer));
+ }
+}
+
+test "binary messages must contain exactly one complete 9P frame" {
+ var reader: std.Io.Reader = .fixed(&.{ 0x82, 7, 8, 0, 0, 0, 109, 1, 0 });
+ var writer: std.Io.Writer = .fixed(&.{});
+ var socket: http.WebSocket = .{ .input = &reader, .output = &writer, .role = .client };
+ var buffer: [64]u8 = undefined;
+ try testing.expectError(error.Truncated, socket.receive(&buffer));
+}
+
+test "upgrade validates nonce and uses RFC 6455 accept value" {
+ var reader: std.Io.Reader = .fixed("GET /9p HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive, Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n\r\n");
+ var writer: std.Io.Writer.Allocating = .init(testing.allocator);
+ defer writer.deinit();
+ var server: std.http.Server = .init(&reader, &writer.writer);
+ var request = try server.receiveHead();
+ _ = try http.accept(&request);
+ try testing.expect(std.mem.find(u8, writer.written(), "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") != null);
+}
+
+test "upgrade rejects invalid nonce before responding" {
+ inline for (.{ "not-a-nonce", "AAAAAAAAAAAAAAAAAAAAAAAA", "AAAAAAAAAAAAAAAAAAAAAAA=", "!!!!!!!!!!!!!!!!!!!!!!==" }) |nonce| {
+ var reader: std.Io.Reader = .fixed("GET /9p HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: " ++ nonce ++ "\r\n\r\n");
+ var writer: std.Io.Writer = .fixed(&.{});
+ var server: std.http.Server = .init(&reader, &writer);
+ var request = try server.receiveHead();
+ try testing.expectError(error.InvalidUpgrade, http.accept(&request));
+ }
+}