summaryrefslogtreecommitdiff
path: root/test/http.zig
blob: d5064e43ea43bd63e064ea8c9779c3da36ceb113 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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));
    }
}