structstd.http.protocol.HeadersParser[src]

Fields

state: State = .start
header_bytes_buffer: []u8

A fixed buffer of len max_header_bytes. Pointers into this buffer are not stable until after a message is complete.

header_bytes_len: u32
next_chunk_length: u64
done: bool

false: headers. true: trailers.

Error Sets

Error SetCheckCompleteHeadError[src]

Errors

anyerror means the error set is known only at runtime.

HttpHeadersOversize

Source Code

Source code
pub const CheckCompleteHeadError = error{HttpHeadersOversize}

Error SetReadError[src]

Errors

anyerror means the error set is known only at runtime.

HttpChunkInvalid

Source Code

Source code
pub const ReadError = error{
    HttpChunkInvalid,
}

Functions

Functioninit[src]

pub fn init(buf: []u8) HeadersParser

Initializes the parser with a provided buffer buf.

Parameters

buf: []u8

Source Code

Source code
pub fn init(buf: []u8) HeadersParser {
    return .{
        .header_bytes_buffer = buf,
        .header_bytes_len = 0,
        .done = false,
        .next_chunk_length = 0,
    };
}

Functionreset[src]

pub fn reset(hp: *HeadersParser) void

Reinitialize the parser. Asserts the parser is in the "done" state.

Parameters

Source Code

Source code
pub fn reset(hp: *HeadersParser) void {
    assert(hp.done);
    hp.* = .{
        .state = .start,
        .header_bytes_buffer = hp.header_bytes_buffer,
        .header_bytes_len = 0,
        .done = false,
        .next_chunk_length = 0,
    };
}

Functionget[src]

pub fn get(hp: HeadersParser) []u8

Parameters

Source Code

Source code
pub fn get(hp: HeadersParser) []u8 {
    return hp.header_bytes_buffer[0..hp.header_bytes_len];
}

FunctionfindHeadersEnd[src]

pub fn findHeadersEnd(r: *HeadersParser, bytes: []const u8) u32

Parameters

bytes: []const u8

Source Code

Source code
pub fn findHeadersEnd(r: *HeadersParser, bytes: []const u8) u32 {
    var hp: std.http.HeadParser = .{
        .state = switch (r.state) {
            .start => .start,
            .seen_n => .seen_n,
            .seen_r => .seen_r,
            .seen_rn => .seen_rn,
            .seen_rnr => .seen_rnr,
            .finished => .finished,
            else => unreachable,
        },
    };
    const result = hp.feed(bytes);
    r.state = switch (hp.state) {
        .start => .start,
        .seen_n => .seen_n,
        .seen_r => .seen_r,
        .seen_rn => .seen_rn,
        .seen_rnr => .seen_rnr,
        .finished => .finished,
    };
    return @intCast(result);
}

FunctionfindChunkedLen[src]

pub fn findChunkedLen(r: *HeadersParser, bytes: []const u8) u32

Parameters

bytes: []const u8

Source Code

Source code
pub fn findChunkedLen(r: *HeadersParser, bytes: []const u8) u32 {
    var cp: std.http.ChunkParser = .{
        .state = switch (r.state) {
            .chunk_head_size => .head_size,
            .chunk_head_ext => .head_ext,
            .chunk_head_r => .head_r,
            .chunk_data => .data,
            .chunk_data_suffix => .data_suffix,
            .chunk_data_suffix_r => .data_suffix_r,
            .invalid => .invalid,
            else => unreachable,
        },
        .chunk_len = r.next_chunk_length,
    };
    const result = cp.feed(bytes);
    r.state = switch (cp.state) {
        .head_size => .chunk_head_size,
        .head_ext => .chunk_head_ext,
        .head_r => .chunk_head_r,
        .data => .chunk_data,
        .data_suffix => .chunk_data_suffix,
        .data_suffix_r => .chunk_data_suffix_r,
        .invalid => .invalid,
    };
    r.next_chunk_length = cp.chunk_len;
    return @intCast(result);
}

FunctionisComplete[src]

pub fn isComplete(r: *HeadersParser) bool

Returns whether or not the parser has finished parsing a complete message. A message is only complete after the entire body has been read and any trailing headers have been parsed.

Parameters

Source Code

Source code
pub fn isComplete(r: *HeadersParser) bool {
    return r.done and r.state == .finished;
}

FunctioncheckCompleteHead[src]

pub fn checkCompleteHead(hp: *HeadersParser, in: []const u8) CheckCompleteHeadError!u32

Pushes in into the parser. Returns the number of bytes consumed by the header. Any header bytes are appended to header_bytes_buffer.

Parameters

in: []const u8

Source Code

Source code
pub fn checkCompleteHead(hp: *HeadersParser, in: []const u8) CheckCompleteHeadError!u32 {
    if (hp.state.isContent()) return 0;

    const i = hp.findHeadersEnd(in);
    const data = in[0..i];
    if (hp.header_bytes_len + data.len > hp.header_bytes_buffer.len)
        return error.HttpHeadersOversize;

    @memcpy(hp.header_bytes_buffer[hp.header_bytes_len..][0..data.len], data);
    hp.header_bytes_len += @intCast(data.len);

    return i;
}

Functionread[src]

pub fn read(r: *HeadersParser, conn: anytype, buffer: []u8, skip: bool) !usize

Reads the body of the message into buffer. Returns the number of bytes placed in the buffer.

If skip is true, the buffer will be unused and the body will be skipped.

See std.http.Client.Connection for an example of conn.

Parameters

buffer: []u8
skip: bool

Source Code

Source code
pub fn read(r: *HeadersParser, conn: anytype, buffer: []u8, skip: bool) !usize {
    assert(r.state.isContent());
    if (r.done) return 0;

    var out_index: usize = 0;
    while (true) {
        switch (r.state) {
            .invalid, .start, .seen_n, .seen_r, .seen_rn, .seen_rnr => unreachable,
            .finished => {
                const data_avail = r.next_chunk_length;

                if (skip) {
                    conn.fill() catch |err| switch (err) {
                        error.EndOfStream => {
                            r.done = true;
                            return 0;
                        },
                        else => |e| return e,
                    };

                    const nread = @min(conn.peek().len, data_avail);
                    conn.drop(@intCast(nread));
                    r.next_chunk_length -= nread;

                    if (r.next_chunk_length == 0 or nread == 0) r.done = true;

                    return out_index;
                } else if (out_index < buffer.len) {
                    const out_avail = buffer.len - out_index;

                    const can_read = @as(usize, @intCast(@min(data_avail, out_avail)));
                    const nread = try conn.read(buffer[0..can_read]);
                    r.next_chunk_length -= nread;

                    if (r.next_chunk_length == 0 or nread == 0) r.done = true;

                    return nread;
                } else {
                    return out_index;
                }
            },
            .chunk_data_suffix, .chunk_data_suffix_r, .chunk_head_size, .chunk_head_ext, .chunk_head_r => {
                conn.fill() catch |err| switch (err) {
                    error.EndOfStream => {
                        r.done = true;
                        return 0;
                    },
                    else => |e| return e,
                };

                const i = r.findChunkedLen(conn.peek());
                conn.drop(@intCast(i));

                switch (r.state) {
                    .invalid => return error.HttpChunkInvalid,
                    .chunk_data => if (r.next_chunk_length == 0) {
                        if (std.mem.eql(u8, conn.peek(), "\r\n")) {
                            r.state = .finished;
                            conn.drop(2);
                        } else {
                            // The trailer section is formatted identically
                            // to the header section.
                            r.state = .seen_rn;
                        }
                        r.done = true;

                        return out_index;
                    },
                    else => return out_index,
                }

                continue;
            },
            .chunk_data => {
                const data_avail = r.next_chunk_length;
                const out_avail = buffer.len - out_index;

                if (skip) {
                    conn.fill() catch |err| switch (err) {
                        error.EndOfStream => {
                            r.done = true;
                            return 0;
                        },
                        else => |e| return e,
                    };

                    const nread = @min(conn.peek().len, data_avail);
                    conn.drop(@intCast(nread));
                    r.next_chunk_length -= nread;
                } else if (out_avail > 0) {
                    const can_read: usize = @intCast(@min(data_avail, out_avail));
                    const nread = try conn.read(buffer[out_index..][0..can_read]);
                    r.next_chunk_length -= nread;
                    out_index += nread;
                }

                if (r.next_chunk_length == 0) {
                    r.state = .chunk_data_suffix;
                    continue;
                }

                return out_index;
            },
        }
    }
}

Source Code

Source code
pub const HeadersParser = struct {
    state: State = .start,
    /// A fixed buffer of len `max_header_bytes`.
    /// Pointers into this buffer are not stable until after a message is complete.
    header_bytes_buffer: []u8,
    header_bytes_len: u32,
    next_chunk_length: u64,
    /// `false`: headers. `true`: trailers.
    done: bool,

    /// Initializes the parser with a provided buffer `buf`.
    pub fn init(buf: []u8) HeadersParser {
        return .{
            .header_bytes_buffer = buf,
            .header_bytes_len = 0,
            .done = false,
            .next_chunk_length = 0,
        };
    }

    /// Reinitialize the parser.
    /// Asserts the parser is in the "done" state.
    pub fn reset(hp: *HeadersParser) void {
        assert(hp.done);
        hp.* = .{
            .state = .start,
            .header_bytes_buffer = hp.header_bytes_buffer,
            .header_bytes_len = 0,
            .done = false,
            .next_chunk_length = 0,
        };
    }

    pub fn get(hp: HeadersParser) []u8 {
        return hp.header_bytes_buffer[0..hp.header_bytes_len];
    }

    pub fn findHeadersEnd(r: *HeadersParser, bytes: []const u8) u32 {
        var hp: std.http.HeadParser = .{
            .state = switch (r.state) {
                .start => .start,
                .seen_n => .seen_n,
                .seen_r => .seen_r,
                .seen_rn => .seen_rn,
                .seen_rnr => .seen_rnr,
                .finished => .finished,
                else => unreachable,
            },
        };
        const result = hp.feed(bytes);
        r.state = switch (hp.state) {
            .start => .start,
            .seen_n => .seen_n,
            .seen_r => .seen_r,
            .seen_rn => .seen_rn,
            .seen_rnr => .seen_rnr,
            .finished => .finished,
        };
        return @intCast(result);
    }

    pub fn findChunkedLen(r: *HeadersParser, bytes: []const u8) u32 {
        var cp: std.http.ChunkParser = .{
            .state = switch (r.state) {
                .chunk_head_size => .head_size,
                .chunk_head_ext => .head_ext,
                .chunk_head_r => .head_r,
                .chunk_data => .data,
                .chunk_data_suffix => .data_suffix,
                .chunk_data_suffix_r => .data_suffix_r,
                .invalid => .invalid,
                else => unreachable,
            },
            .chunk_len = r.next_chunk_length,
        };
        const result = cp.feed(bytes);
        r.state = switch (cp.state) {
            .head_size => .chunk_head_size,
            .head_ext => .chunk_head_ext,
            .head_r => .chunk_head_r,
            .data => .chunk_data,
            .data_suffix => .chunk_data_suffix,
            .data_suffix_r => .chunk_data_suffix_r,
            .invalid => .invalid,
        };
        r.next_chunk_length = cp.chunk_len;
        return @intCast(result);
    }

    /// Returns whether or not the parser has finished parsing a complete
    /// message. A message is only complete after the entire body has been read
    /// and any trailing headers have been parsed.
    pub fn isComplete(r: *HeadersParser) bool {
        return r.done and r.state == .finished;
    }

    pub const CheckCompleteHeadError = error{HttpHeadersOversize};

    /// Pushes `in` into the parser. Returns the number of bytes consumed by
    /// the header. Any header bytes are appended to `header_bytes_buffer`.
    pub fn checkCompleteHead(hp: *HeadersParser, in: []const u8) CheckCompleteHeadError!u32 {
        if (hp.state.isContent()) return 0;

        const i = hp.findHeadersEnd(in);
        const data = in[0..i];
        if (hp.header_bytes_len + data.len > hp.header_bytes_buffer.len)
            return error.HttpHeadersOversize;

        @memcpy(hp.header_bytes_buffer[hp.header_bytes_len..][0..data.len], data);
        hp.header_bytes_len += @intCast(data.len);

        return i;
    }

    pub const ReadError = error{
        HttpChunkInvalid,
    };

    /// Reads the body of the message into `buffer`. Returns the number of
    /// bytes placed in the buffer.
    ///
    /// If `skip` is true, the buffer will be unused and the body will be skipped.
    ///
    /// See `std.http.Client.Connection for an example of `conn`.
    pub fn read(r: *HeadersParser, conn: anytype, buffer: []u8, skip: bool) !usize {
        assert(r.state.isContent());
        if (r.done) return 0;

        var out_index: usize = 0;
        while (true) {
            switch (r.state) {
                .invalid, .start, .seen_n, .seen_r, .seen_rn, .seen_rnr => unreachable,
                .finished => {
                    const data_avail = r.next_chunk_length;

                    if (skip) {
                        conn.fill() catch |err| switch (err) {
                            error.EndOfStream => {
                                r.done = true;
                                return 0;
                            },
                            else => |e| return e,
                        };

                        const nread = @min(conn.peek().len, data_avail);
                        conn.drop(@intCast(nread));
                        r.next_chunk_length -= nread;

                        if (r.next_chunk_length == 0 or nread == 0) r.done = true;

                        return out_index;
                    } else if (out_index < buffer.len) {
                        const out_avail = buffer.len - out_index;

                        const can_read = @as(usize, @intCast(@min(data_avail, out_avail)));
                        const nread = try conn.read(buffer[0..can_read]);
                        r.next_chunk_length -= nread;

                        if (r.next_chunk_length == 0 or nread == 0) r.done = true;

                        return nread;
                    } else {
                        return out_index;
                    }
                },
                .chunk_data_suffix, .chunk_data_suffix_r, .chunk_head_size, .chunk_head_ext, .chunk_head_r => {
                    conn.fill() catch |err| switch (err) {
                        error.EndOfStream => {
                            r.done = true;
                            return 0;
                        },
                        else => |e| return e,
                    };

                    const i = r.findChunkedLen(conn.peek());
                    conn.drop(@intCast(i));

                    switch (r.state) {
                        .invalid => return error.HttpChunkInvalid,
                        .chunk_data => if (r.next_chunk_length == 0) {
                            if (std.mem.eql(u8, conn.peek(), "\r\n")) {
                                r.state = .finished;
                                conn.drop(2);
                            } else {
                                // The trailer section is formatted identically
                                // to the header section.
                                r.state = .seen_rn;
                            }
                            r.done = true;

                            return out_index;
                        },
                        else => return out_index,
                    }

                    continue;
                },
                .chunk_data => {
                    const data_avail = r.next_chunk_length;
                    const out_avail = buffer.len - out_index;

                    if (skip) {
                        conn.fill() catch |err| switch (err) {
                            error.EndOfStream => {
                                r.done = true;
                                return 0;
                            },
                            else => |e| return e,
                        };

                        const nread = @min(conn.peek().len, data_avail);
                        conn.drop(@intCast(nread));
                        r.next_chunk_length -= nread;
                    } else if (out_avail > 0) {
                        const can_read: usize = @intCast(@min(data_avail, out_avail));
                        const nread = try conn.read(buffer[out_index..][0..can_read]);
                        r.next_chunk_length -= nread;
                        out_index += nread;
                    }

                    if (r.next_chunk_length == 0) {
                        r.state = .chunk_data_suffix;
                        continue;
                    }

                    return out_index;
                },
            }
        }
    }
}