structstd.http.Server.Request.Head[src]

Fields

method: http.Method
target: []const u8
version: http.Version
expect: ?[]const u8
content_type: ?[]const u8
content_length: ?u64
transfer_encoding: http.TransferEncoding
transfer_compression: http.ContentEncoding
keep_alive: bool
compression: Compression

Error Sets

Error SetParseError[src]

Errors

anyerror means the error set is known only at runtime.

CompressionUnsupported
HttpConnectionHeaderUnsupported
HttpHeaderContinuationsUnsupported
HttpHeadersInvalid
HttpTransferEncodingUnsupported
InvalidContentLength
MissingFinalNewline
UnknownHttpMethod

Source Code

Source code
pub const ParseError = error{
    UnknownHttpMethod,
    HttpHeadersInvalid,
    HttpHeaderContinuationsUnsupported,
    HttpTransferEncodingUnsupported,
    HttpConnectionHeaderUnsupported,
    InvalidContentLength,
    CompressionUnsupported,
    MissingFinalNewline,
}

Functions

Functionparse[src]

pub fn parse(bytes: []const u8) ParseError!Head

Parameters

bytes: []const u8

Example Usage

test parse {
    const request_bytes = "GET /hi HTTP/1.0\r\n" ++
        "content-tYpe: text/plain\r\n" ++
        "content-Length:10\r\n" ++
        "expeCt:   100-continue \r\n" ++
        "TRansfer-encoding:\tdeflate, chunked \r\n" ++
        "connectioN:\t keep-alive \r\n\r\n";

    const req = try parse(request_bytes);

    try testing.expectEqual(.GET, req.method);
    try testing.expectEqual(.@"HTTP/1.0", req.version);
    try testing.expectEqualStrings("/hi", req.target);

    try testing.expectEqualStrings("text/plain", req.content_type.?);
    try testing.expectEqualStrings("100-continue", req.expect.?);

    try testing.expectEqual(true, req.keep_alive);
    try testing.expectEqual(10, req.content_length.?);
    try testing.expectEqual(.chunked, req.transfer_encoding);
    try testing.expectEqual(.deflate, req.transfer_compression);
}

Source Code

Source code
pub fn parse(bytes: []const u8) ParseError!Head {
    var it = mem.splitSequence(u8, bytes, "\r\n");

    const first_line = it.next().?;
    if (first_line.len < 10)
        return error.HttpHeadersInvalid;

    const method_end = mem.indexOfScalar(u8, first_line, ' ') orelse
        return error.HttpHeadersInvalid;
    if (method_end > 24) return error.HttpHeadersInvalid;

    const method_str = first_line[0..method_end];
    const method: http.Method = @enumFromInt(http.Method.parse(method_str));

    const version_start = mem.lastIndexOfScalar(u8, first_line, ' ') orelse
        return error.HttpHeadersInvalid;
    if (version_start == method_end) return error.HttpHeadersInvalid;

    const version_str = first_line[version_start + 1 ..];
    if (version_str.len != 8) return error.HttpHeadersInvalid;
    const version: http.Version = switch (int64(version_str[0..8])) {
        int64("HTTP/1.0") => .@"HTTP/1.0",
        int64("HTTP/1.1") => .@"HTTP/1.1",
        else => return error.HttpHeadersInvalid,
    };

    const target = first_line[method_end + 1 .. version_start];

    var head: Head = .{
        .method = method,
        .target = target,
        .version = version,
        .expect = null,
        .content_type = null,
        .content_length = null,
        .transfer_encoding = .none,
        .transfer_compression = .identity,
        .keep_alive = switch (version) {
            .@"HTTP/1.0" => false,
            .@"HTTP/1.1" => true,
        },
        .compression = .none,
    };

    while (it.next()) |line| {
        if (line.len == 0) return head;
        switch (line[0]) {
            ' ', '\t' => return error.HttpHeaderContinuationsUnsupported,
            else => {},
        }

        var line_it = mem.splitScalar(u8, line, ':');
        const header_name = line_it.next().?;
        const header_value = mem.trim(u8, line_it.rest(), " \t");
        if (header_name.len == 0) return error.HttpHeadersInvalid;

        if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
            head.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close");
        } else if (std.ascii.eqlIgnoreCase(header_name, "expect")) {
            head.expect = header_value;
        } else if (std.ascii.eqlIgnoreCase(header_name, "content-type")) {
            head.content_type = header_value;
        } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) {
            if (head.content_length != null) return error.HttpHeadersInvalid;
            head.content_length = std.fmt.parseInt(u64, header_value, 10) catch
                return error.InvalidContentLength;
        } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) {
            if (head.transfer_compression != .identity) return error.HttpHeadersInvalid;

            const trimmed = mem.trim(u8, header_value, " ");

            if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
                head.transfer_compression = ce;
            } else {
                return error.HttpTransferEncodingUnsupported;
            }
        } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) {
            // Transfer-Encoding: second, first
            // Transfer-Encoding: deflate, chunked
            var iter = mem.splitBackwardsScalar(u8, header_value, ',');

            const first = iter.first();
            const trimmed_first = mem.trim(u8, first, " ");

            var next: ?[]const u8 = first;
            if (std.meta.stringToEnum(http.TransferEncoding, trimmed_first)) |transfer| {
                if (head.transfer_encoding != .none)
                    return error.HttpHeadersInvalid; // we already have a transfer encoding
                head.transfer_encoding = transfer;

                next = iter.next();
            }

            if (next) |second| {
                const trimmed_second = mem.trim(u8, second, " ");

                if (std.meta.stringToEnum(http.ContentEncoding, trimmed_second)) |transfer| {
                    if (head.transfer_compression != .identity)
                        return error.HttpHeadersInvalid; // double compression is not supported
                    head.transfer_compression = transfer;
                } else {
                    return error.HttpTransferEncodingUnsupported;
                }
            }

            if (iter.next()) |_| return error.HttpTransferEncodingUnsupported;
        }
    }
    return error.MissingFinalNewline;
}

Source Code

Source code
pub const Head = struct {
    method: http.Method,
    target: []const u8,
    version: http.Version,
    expect: ?[]const u8,
    content_type: ?[]const u8,
    content_length: ?u64,
    transfer_encoding: http.TransferEncoding,
    transfer_compression: http.ContentEncoding,
    keep_alive: bool,
    compression: Compression,

    pub const ParseError = error{
        UnknownHttpMethod,
        HttpHeadersInvalid,
        HttpHeaderContinuationsUnsupported,
        HttpTransferEncodingUnsupported,
        HttpConnectionHeaderUnsupported,
        InvalidContentLength,
        CompressionUnsupported,
        MissingFinalNewline,
    };

    pub fn parse(bytes: []const u8) ParseError!Head {
        var it = mem.splitSequence(u8, bytes, "\r\n");

        const first_line = it.next().?;
        if (first_line.len < 10)
            return error.HttpHeadersInvalid;

        const method_end = mem.indexOfScalar(u8, first_line, ' ') orelse
            return error.HttpHeadersInvalid;
        if (method_end > 24) return error.HttpHeadersInvalid;

        const method_str = first_line[0..method_end];
        const method: http.Method = @enumFromInt(http.Method.parse(method_str));

        const version_start = mem.lastIndexOfScalar(u8, first_line, ' ') orelse
            return error.HttpHeadersInvalid;
        if (version_start == method_end) return error.HttpHeadersInvalid;

        const version_str = first_line[version_start + 1 ..];
        if (version_str.len != 8) return error.HttpHeadersInvalid;
        const version: http.Version = switch (int64(version_str[0..8])) {
            int64("HTTP/1.0") => .@"HTTP/1.0",
            int64("HTTP/1.1") => .@"HTTP/1.1",
            else => return error.HttpHeadersInvalid,
        };

        const target = first_line[method_end + 1 .. version_start];

        var head: Head = .{
            .method = method,
            .target = target,
            .version = version,
            .expect = null,
            .content_type = null,
            .content_length = null,
            .transfer_encoding = .none,
            .transfer_compression = .identity,
            .keep_alive = switch (version) {
                .@"HTTP/1.0" => false,
                .@"HTTP/1.1" => true,
            },
            .compression = .none,
        };

        while (it.next()) |line| {
            if (line.len == 0) return head;
            switch (line[0]) {
                ' ', '\t' => return error.HttpHeaderContinuationsUnsupported,
                else => {},
            }

            var line_it = mem.splitScalar(u8, line, ':');
            const header_name = line_it.next().?;
            const header_value = mem.trim(u8, line_it.rest(), " \t");
            if (header_name.len == 0) return error.HttpHeadersInvalid;

            if (std.ascii.eqlIgnoreCase(header_name, "connection")) {
                head.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close");
            } else if (std.ascii.eqlIgnoreCase(header_name, "expect")) {
                head.expect = header_value;
            } else if (std.ascii.eqlIgnoreCase(header_name, "content-type")) {
                head.content_type = header_value;
            } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) {
                if (head.content_length != null) return error.HttpHeadersInvalid;
                head.content_length = std.fmt.parseInt(u64, header_value, 10) catch
                    return error.InvalidContentLength;
            } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) {
                if (head.transfer_compression != .identity) return error.HttpHeadersInvalid;

                const trimmed = mem.trim(u8, header_value, " ");

                if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
                    head.transfer_compression = ce;
                } else {
                    return error.HttpTransferEncodingUnsupported;
                }
            } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) {
                // Transfer-Encoding: second, first
                // Transfer-Encoding: deflate, chunked
                var iter = mem.splitBackwardsScalar(u8, header_value, ',');

                const first = iter.first();
                const trimmed_first = mem.trim(u8, first, " ");

                var next: ?[]const u8 = first;
                if (std.meta.stringToEnum(http.TransferEncoding, trimmed_first)) |transfer| {
                    if (head.transfer_encoding != .none)
                        return error.HttpHeadersInvalid; // we already have a transfer encoding
                    head.transfer_encoding = transfer;

                    next = iter.next();
                }

                if (next) |second| {
                    const trimmed_second = mem.trim(u8, second, " ");

                    if (std.meta.stringToEnum(http.ContentEncoding, trimmed_second)) |transfer| {
                        if (head.transfer_compression != .identity)
                            return error.HttpHeadersInvalid; // double compression is not supported
                        head.transfer_compression = transfer;
                    } else {
                        return error.HttpTransferEncodingUnsupported;
                    }
                }

                if (iter.next()) |_| return error.HttpTransferEncodingUnsupported;
            }
        }
        return error.MissingFinalNewline;
    }

    test parse {
        const request_bytes = "GET /hi HTTP/1.0\r\n" ++
            "content-tYpe: text/plain\r\n" ++
            "content-Length:10\r\n" ++
            "expeCt:   100-continue \r\n" ++
            "TRansfer-encoding:\tdeflate, chunked \r\n" ++
            "connectioN:\t keep-alive \r\n\r\n";

        const req = try parse(request_bytes);

        try testing.expectEqual(.GET, req.method);
        try testing.expectEqual(.@"HTTP/1.0", req.version);
        try testing.expectEqualStrings("/hi", req.target);

        try testing.expectEqualStrings("text/plain", req.content_type.?);
        try testing.expectEqualStrings("100-continue", req.expect.?);

        try testing.expectEqual(true, req.keep_alive);
        try testing.expectEqual(10, req.content_length.?);
        try testing.expectEqual(.chunked, req.transfer_encoding);
        try testing.expectEqual(.deflate, req.transfer_compression);
    }

    inline fn int64(array: *const [8]u8) u64 {
        return @bitCast(array.*);
    }
}