mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
364 lines
13 KiB
Zig
364 lines
13 KiB
Zig
//! Finds the end of an HTTP head in a stream.
|
|
|
|
state: State = .start,
|
|
|
|
pub const State = enum {
|
|
start,
|
|
seen_n,
|
|
seen_r,
|
|
seen_rn,
|
|
seen_rnr,
|
|
finished,
|
|
};
|
|
|
|
/// Returns the number of bytes consumed by headers. This is always less
|
|
/// than or equal to `bytes.len`.
|
|
///
|
|
/// If the amount returned is less than `bytes.len`, the parser is in a
|
|
/// content state and the first byte of content is located at
|
|
/// `bytes[result]`.
|
|
pub fn feed(p: *HeadParser, bytes: []const u8) usize {
|
|
const vector_len: comptime_int = @max(std.simd.suggestVectorLength(u8) orelse 1, 8);
|
|
var index: usize = 0;
|
|
|
|
while (true) {
|
|
switch (p.state) {
|
|
.finished => return index,
|
|
.start => switch (bytes.len - index) {
|
|
0 => return index,
|
|
1 => {
|
|
switch (bytes[index]) {
|
|
'\r' => p.state = .seen_r,
|
|
'\n' => p.state = .seen_n,
|
|
else => {},
|
|
}
|
|
|
|
return index + 1;
|
|
},
|
|
2 => {
|
|
const b16 = int16(bytes[index..][0..2]);
|
|
const b8 = intShift(u8, b16);
|
|
|
|
switch (b8) {
|
|
'\r' => p.state = .seen_r,
|
|
'\n' => p.state = .seen_n,
|
|
else => {},
|
|
}
|
|
|
|
switch (b16) {
|
|
int16("\r\n") => p.state = .seen_rn,
|
|
int16("\n\n") => p.state = .finished,
|
|
else => {},
|
|
}
|
|
|
|
return index + 2;
|
|
},
|
|
3 => {
|
|
const b24 = int24(bytes[index..][0..3]);
|
|
const b16 = intShift(u16, b24);
|
|
const b8 = intShift(u8, b24);
|
|
|
|
switch (b8) {
|
|
'\r' => p.state = .seen_r,
|
|
'\n' => p.state = .seen_n,
|
|
else => {},
|
|
}
|
|
|
|
switch (b16) {
|
|
int16("\r\n") => p.state = .seen_rn,
|
|
int16("\n\n") => p.state = .finished,
|
|
else => {},
|
|
}
|
|
|
|
switch (b24) {
|
|
int24("\r\n\r") => p.state = .seen_rnr,
|
|
else => {},
|
|
}
|
|
|
|
return index + 3;
|
|
},
|
|
4...vector_len - 1 => {
|
|
const b32 = int32(bytes[index..][0..4]);
|
|
const b24 = intShift(u24, b32);
|
|
const b16 = intShift(u16, b32);
|
|
const b8 = intShift(u8, b32);
|
|
|
|
switch (b8) {
|
|
'\r' => p.state = .seen_r,
|
|
'\n' => p.state = .seen_n,
|
|
else => {},
|
|
}
|
|
|
|
switch (b16) {
|
|
int16("\r\n") => p.state = .seen_rn,
|
|
int16("\n\n") => p.state = .finished,
|
|
else => {},
|
|
}
|
|
|
|
switch (b24) {
|
|
int24("\r\n\r") => p.state = .seen_rnr,
|
|
else => {},
|
|
}
|
|
|
|
switch (b32) {
|
|
int32("\r\n\r\n") => p.state = .finished,
|
|
else => {},
|
|
}
|
|
|
|
index += 4;
|
|
continue;
|
|
},
|
|
else => {
|
|
const Vector = @Vector(vector_len, u8);
|
|
// const BoolVector = @Vector(vector_len, bool);
|
|
const BitVector = @Vector(vector_len, u1);
|
|
const SizeVector = @Vector(vector_len, u8);
|
|
|
|
const chunk = bytes[index..][0..vector_len];
|
|
const v: Vector = chunk.*;
|
|
// depends on https://github.com/ziglang/zig/issues/19755
|
|
// const matches_r: BitVector = @bitCast(v == @as(Vector, @splat('\r')));
|
|
// const matches_n: BitVector = @bitCast(v == @as(Vector, @splat('\n')));
|
|
const matches_r: BitVector = @select(u1, v == @as(Vector, @splat('\r')), @as(Vector, @splat(1)), @as(Vector, @splat(0)));
|
|
const matches_n: BitVector = @select(u1, v == @as(Vector, @splat('\n')), @as(Vector, @splat(1)), @as(Vector, @splat(0)));
|
|
const matches_or: SizeVector = matches_r | matches_n;
|
|
|
|
const matches = @reduce(.Add, matches_or);
|
|
switch (matches) {
|
|
0 => {},
|
|
1 => switch (chunk[vector_len - 1]) {
|
|
'\r' => p.state = .seen_r,
|
|
'\n' => p.state = .seen_n,
|
|
else => {},
|
|
},
|
|
2 => {
|
|
const b16 = int16(chunk[vector_len - 2 ..][0..2]);
|
|
const b8 = intShift(u8, b16);
|
|
|
|
switch (b8) {
|
|
'\r' => p.state = .seen_r,
|
|
'\n' => p.state = .seen_n,
|
|
else => {},
|
|
}
|
|
|
|
switch (b16) {
|
|
int16("\r\n") => p.state = .seen_rn,
|
|
int16("\n\n") => p.state = .finished,
|
|
else => {},
|
|
}
|
|
},
|
|
3 => {
|
|
const b24 = int24(chunk[vector_len - 3 ..][0..3]);
|
|
const b16 = intShift(u16, b24);
|
|
const b8 = intShift(u8, b24);
|
|
|
|
switch (b8) {
|
|
'\r' => p.state = .seen_r,
|
|
'\n' => p.state = .seen_n,
|
|
else => {},
|
|
}
|
|
|
|
switch (b16) {
|
|
int16("\r\n") => p.state = .seen_rn,
|
|
int16("\n\n") => p.state = .finished,
|
|
else => {},
|
|
}
|
|
|
|
switch (b24) {
|
|
int24("\r\n\r") => p.state = .seen_rnr,
|
|
else => {},
|
|
}
|
|
},
|
|
4...vector_len => {
|
|
inline for (0..vector_len - 3) |i_usize| {
|
|
const i = @as(u32, @truncate(i_usize));
|
|
|
|
const b32 = int32(chunk[i..][0..4]);
|
|
const b16 = intShift(u16, b32);
|
|
|
|
if (b32 == int32("\r\n\r\n")) {
|
|
p.state = .finished;
|
|
return index + i + 4;
|
|
} else if (b16 == int16("\n\n")) {
|
|
p.state = .finished;
|
|
return index + i + 2;
|
|
}
|
|
}
|
|
|
|
const b24 = int24(chunk[vector_len - 3 ..][0..3]);
|
|
const b16 = intShift(u16, b24);
|
|
const b8 = intShift(u8, b24);
|
|
|
|
switch (b8) {
|
|
'\r' => p.state = .seen_r,
|
|
'\n' => p.state = .seen_n,
|
|
else => {},
|
|
}
|
|
|
|
switch (b16) {
|
|
int16("\r\n") => p.state = .seen_rn,
|
|
int16("\n\n") => p.state = .finished,
|
|
else => {},
|
|
}
|
|
|
|
switch (b24) {
|
|
int24("\r\n\r") => p.state = .seen_rnr,
|
|
else => {},
|
|
}
|
|
},
|
|
else => unreachable,
|
|
}
|
|
|
|
index += vector_len;
|
|
continue;
|
|
},
|
|
},
|
|
.seen_n => switch (bytes.len - index) {
|
|
0 => return index,
|
|
else => {
|
|
switch (bytes[index]) {
|
|
'\n' => p.state = .finished,
|
|
else => p.state = .start,
|
|
}
|
|
|
|
index += 1;
|
|
continue;
|
|
},
|
|
},
|
|
.seen_r => switch (bytes.len - index) {
|
|
0 => return index,
|
|
1 => {
|
|
switch (bytes[index]) {
|
|
'\n' => p.state = .seen_rn,
|
|
'\r' => p.state = .seen_r,
|
|
else => p.state = .start,
|
|
}
|
|
|
|
return index + 1;
|
|
},
|
|
2 => {
|
|
const b16 = int16(bytes[index..][0..2]);
|
|
const b8 = intShift(u8, b16);
|
|
|
|
switch (b8) {
|
|
'\r' => p.state = .seen_r,
|
|
'\n' => p.state = .seen_rn,
|
|
else => p.state = .start,
|
|
}
|
|
|
|
switch (b16) {
|
|
int16("\r\n") => p.state = .seen_rn,
|
|
int16("\n\r") => p.state = .seen_rnr,
|
|
int16("\n\n") => p.state = .finished,
|
|
else => {},
|
|
}
|
|
|
|
return index + 2;
|
|
},
|
|
else => {
|
|
const b24 = int24(bytes[index..][0..3]);
|
|
const b16 = intShift(u16, b24);
|
|
const b8 = intShift(u8, b24);
|
|
|
|
switch (b8) {
|
|
'\r' => p.state = .seen_r,
|
|
'\n' => p.state = .seen_n,
|
|
else => p.state = .start,
|
|
}
|
|
|
|
switch (b16) {
|
|
int16("\r\n") => p.state = .seen_rn,
|
|
int16("\n\n") => p.state = .finished,
|
|
else => {},
|
|
}
|
|
|
|
switch (b24) {
|
|
int24("\n\r\n") => p.state = .finished,
|
|
else => {},
|
|
}
|
|
|
|
index += 3;
|
|
continue;
|
|
},
|
|
},
|
|
.seen_rn => switch (bytes.len - index) {
|
|
0 => return index,
|
|
1 => {
|
|
switch (bytes[index]) {
|
|
'\r' => p.state = .seen_rnr,
|
|
'\n' => p.state = .seen_n,
|
|
else => p.state = .start,
|
|
}
|
|
|
|
return index + 1;
|
|
},
|
|
else => {
|
|
const b16 = int16(bytes[index..][0..2]);
|
|
const b8 = intShift(u8, b16);
|
|
|
|
switch (b8) {
|
|
'\r' => p.state = .seen_rnr,
|
|
'\n' => p.state = .seen_n,
|
|
else => p.state = .start,
|
|
}
|
|
|
|
switch (b16) {
|
|
int16("\r\n") => p.state = .finished,
|
|
int16("\n\n") => p.state = .finished,
|
|
else => {},
|
|
}
|
|
|
|
index += 2;
|
|
continue;
|
|
},
|
|
},
|
|
.seen_rnr => switch (bytes.len - index) {
|
|
0 => return index,
|
|
else => {
|
|
switch (bytes[index]) {
|
|
'\n' => p.state = .finished,
|
|
else => p.state = .start,
|
|
}
|
|
|
|
index += 1;
|
|
continue;
|
|
},
|
|
},
|
|
}
|
|
|
|
return index;
|
|
}
|
|
}
|
|
|
|
inline fn int16(array: *const [2]u8) u16 {
|
|
return @bitCast(array.*);
|
|
}
|
|
|
|
inline fn int24(array: *const [3]u8) u24 {
|
|
return @bitCast(array.*);
|
|
}
|
|
|
|
inline fn int32(array: *const [4]u8) u32 {
|
|
return @bitCast(array.*);
|
|
}
|
|
|
|
inline fn intShift(comptime T: type, x: anytype) T {
|
|
switch (@import("builtin").cpu.arch.endian()) {
|
|
.little => return @truncate(x >> (@bitSizeOf(@TypeOf(x)) - @bitSizeOf(T))),
|
|
.big => return @truncate(x),
|
|
}
|
|
}
|
|
|
|
const HeadParser = @This();
|
|
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
|
|
test feed {
|
|
const data = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\nHello";
|
|
|
|
for (0..36) |i| {
|
|
var p: HeadParser = .{};
|
|
try std.testing.expectEqual(i, p.feed(data[0..i]));
|
|
try std.testing.expectEqual(35 - i, p.feed(data[i..]));
|
|
}
|
|
}
|