From f02ece22fa917799ec3de8483e17fb603ceaf25b Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Fri, 6 Mar 2026 13:17:27 +0100 Subject: [PATCH] web: Secondary utils and helpers --- .vscode/settings.json | 8 + packages/web/build.zig | 21 ++ packages/web/build.zig.zon | 11 + packages/web/src/Connection.zig | 15 + packages/web/src/FileDescriptor.zig | 133 +++++++++ packages/web/src/Id.zig | 41 +++ packages/web/src/UUID.zig | 87 ++++++ packages/web/src/http/Parser.zig | 407 ++++++++++++++++++++++++++++ packages/web/src/http/status.zig | 58 ++++ 9 files changed, 781 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 packages/web/build.zig create mode 100644 packages/web/build.zig.zon create mode 100644 packages/web/src/Connection.zig create mode 100644 packages/web/src/FileDescriptor.zig create mode 100644 packages/web/src/Id.zig create mode 100644 packages/web/src/UUID.zig create mode 100644 packages/web/src/http/Parser.zig create mode 100644 packages/web/src/http/status.zig diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a80aa95 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.exclude": { + "**/.zig-cache": true, + }, + "files.associations": { + "**/*.{c,h}": "c", + }, +} diff --git a/packages/web/build.zig b/packages/web/build.zig new file mode 100644 index 0000000..ca12cfd --- /dev/null +++ b/packages/web/build.zig @@ -0,0 +1,21 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const module = b.addModule("http", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const tests = b.addTest(.{ + .root_module = module, + }); + + const run_tests = b.addRunArtifact(tests); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_tests.step); +} diff --git a/packages/web/build.zig.zon b/packages/web/build.zig.zon new file mode 100644 index 0000000..6565963 --- /dev/null +++ b/packages/web/build.zig.zon @@ -0,0 +1,11 @@ +.{ + .name = .web, + .version = "0.0.0", + .minimum_zig_version = "0.15.2", + .paths = .{ + "src", + "build.zig", + "build.zig.zon", + }, + .fingerprint = 0x15c938517148d390, +} diff --git a/packages/web/src/Connection.zig b/packages/web/src/Connection.zig new file mode 100644 index 0000000..85ac5c6 --- /dev/null +++ b/packages/web/src/Connection.zig @@ -0,0 +1,15 @@ +const std = @import("std"); +const Connection = @This(); + +const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor; + +const linux = std.os.linux; + +address: std.net.Address, +fd: FileDescriptor, +node: std.DoublyLinkedList.Node = .{}, + +pub fn deinit(self: *Connection) void { + self.fd.close(); + self.* = undefined; +} diff --git a/packages/web/src/FileDescriptor.zig b/packages/web/src/FileDescriptor.zig new file mode 100644 index 0000000..963bdc6 --- /dev/null +++ b/packages/web/src/FileDescriptor.zig @@ -0,0 +1,133 @@ +const std = @import("std"); + +const linux = std.os.linux; +const errno = linux.E.init; + +pub const FileDescriptor = enum(i32) { + stdin = 0, + stdout = 1, + stderr = 2, + _, + + pub fn memfd_create(name: [*:0]const u8, flags: u32) FileDescriptor { + const rc = linux.memfd_create(name, flags); + return switch (errno(rc)) { + .SUCCESS => @enumFromInt(@as(i32, @intCast(rc))), + else => error.SystemError, + }; + } + + pub fn socket(domain: u32, socket_type: u32, protocol: u32) FileDescriptor { + const rc = linux.socket(domain, socket_type, protocol); + return switch (errno(rc)) { + .SUCCESS => @enumFromInt(@as(i32, @intCast(rc))), + else => error.SystemError, + }; + } + + pub fn close(self: FileDescriptor) void { + _ = linux.close(@intFromEnum(self)); + } + + pub fn accept(self: FileDescriptor, noalias addr: ?*linux.sockaddr, noalias len: ?*linux.socklen_t) !FileDescriptor { + const rc = linux.accept(@intFromEnum(self), &addr, &len); + return switch (errno(rc)) { + .SUCCESS => @enumFromInt(@as(i32, @intCast(rc))), + .CONNABORTED => return error.ConnectionAborted, + .PERM => return error.BlockedByFirewall, + else => return error.SystemError, + }; + } + + pub fn bind(self: FileDescriptor, addr: *const linux.sockaddr, len: linux.socklen_t) !void { + const rc = linux.bind(@intFromEnum(self), addr, len); + return switch (errno(rc)) { + .SUCCESS => {}, + .ACCES, .PERM => error.AccessDenied, + .ADDRINUSE => error.AddressInUse, + .ADDRNOTAVAIL => error.AddressNotAvailable, + .AFNOSUPPORT => error.AddressFamilyNotSupported, + .LOOP => error.SymLinkLoop, + .NAMETOOLONG => error.NameTooLong, + .NOENT => error.FileNotFound, + .NOTDIR => error.NotDir, + .ROFS => error.ReadOnlyFileSystem, + else => error.SystemError, + }; + } + + pub fn ftruncate(self: FileDescriptor, length: i64) !void { + const rc = linux.ftruncate(@intFromEnum(self), length); + return switch (errno(rc)) { + .SUCCESS => {}, + else => error.SystemError, + }; + } + + pub fn getsockname(self: FileDescriptor, noalias addr: *linux.sockaddr, noalias len: *linux.socklen_t) !void { + const rc = linux.getsockname(@intFromEnum(self), addr, len); + return switch (errno(rc)) { + .SUCCESS => {}, + else => error.SystemError, + }; + } + + pub fn listen(self: FileDescriptor, backlog: u32) !void { + const rc = linux.listen(@intFromEnum(self), backlog); + return switch (errno(rc)) { + .SUCCESS => {}, + .ADDRINUSE => error.AddressInUse, + else => error.SystemError, + }; + } + + pub fn read(self: FileDescriptor, buf: []u8) !usize { + while (true) { + const rc = linux.read(@intFromEnum(self), buf.ptr, @intCast(buf.len)); + switch (errno(rc)) { + .SUCCESS => return rc, + .INTR => continue, + else => return error.SystemError, + } + } + } + + pub fn readAll(self: FileDescriptor, buf: []u8) !void { + var total_bytes_read: usize = 0; + + while (total_bytes_read < buf.len) { + const chunk = buf[total_bytes_read..]; + const bytes_read = try self.read(chunk); + total_bytes_read += bytes_read; + } + } + + pub fn setsockopt(self: FileDescriptor, level: i32, optname: u32, opt: []const u8) !void { + const rc = linux.setsockopt(@intFromEnum(self), level, optname, opt.ptr, @intCast(opt.len)); + return switch (errno(rc)) { + .SUCCESS => {}, + else => error.SystemError, + }; + } + + pub fn write(self: FileDescriptor, buf: []const u8) !usize { + while (true) { + const rc = linux.write(@intFromEnum(self), buf.ptr, @intCast(buf.len)); + switch (errno(rc)) { + .SUCCESS => return rc, + .INTR => continue, + else => return error.SystemError, + } + } + } + + pub fn writeAll(self: FileDescriptor, buf: []const u8) !void { + var total_bytes_written: usize = 0; + + while (total_bytes_written < buf.len) { + const chunk = buf[total_bytes_written..]; + const bytes_written = try self.write(chunk); + total_bytes_written += bytes_written; + } + } +}; diff --git a/packages/web/src/Id.zig b/packages/web/src/Id.zig new file mode 100644 index 0000000..53fe086 --- /dev/null +++ b/packages/web/src/Id.zig @@ -0,0 +1,41 @@ +const std = @import("std"); + +const UUID = @import("UUID.zig"); + +const decoder = &std.base64.url_safe_no_pad.Decoder; +const encoder = &std.base64.url_safe_no_pad.Encoder; + +pub fn Id(comptime _tag: @Type(.enum_literal)) type { + return struct { + pub const tag = _tag; + + uuid: UUID, + + pub fn init(uuid: UUID) @This() { + return .{ .uuid = uuid }; + } + + pub fn eql(a: @This(), b: @This()) bool { + return std.mem.eql(u8, &a.bytes, &b.bytes); + } + + pub fn decode(encoded: *const [22]u8) !@This() { + var bytes: [16]u8 = undefined; + decoder.decode(&bytes, encoded) catch |err| switch (err) { + error.InvalidCharacter => return error.InvalidId, + error.InvalidPadding, error.NoSpaceLeft => unreachable, + }; + return .{ .bytes = bytes }; + } + + pub fn encode(self: @This()) [22]u8 { + var text: [22]u8 = undefined; + self.encodeInto(&text); + return text; + } + + pub fn encodeInto(self: @This(), text: *[22]u8) void { + std.base64.url_safe_no_pad.Encoder.encode(text, self.bytes); + } + }; +} diff --git a/packages/web/src/UUID.zig b/packages/web/src/UUID.zig new file mode 100644 index 0000000..43247ab --- /dev/null +++ b/packages/web/src/UUID.zig @@ -0,0 +1,87 @@ +const std = @import("std"); +const UUID = @This(); + +bytes: [16]u8, + +pub const zero: UUID = .{ .bytes = @splat(0) }; + +pub fn v4() UUID { + var bytes: [16]u8 = undefined; + std.crypto.random.bytes(&bytes); + bytes[6] = (bytes[6] & 0x0F) | 0x40; + bytes[8] = (bytes[8] & 0x3F) | 0x80; + return .{ .bytes = bytes }; +} + +pub const namespaces = struct { + /// Name string is a fully-qualified domain name + /// + /// `6ba7b810-9dad-11d1-80b4-00c04fd430c8` + pub const dns: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x10, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } }; + /// Name string is a URL + /// + /// `6ba7b811-9dad-11d1-80b4-00c04fd430c8` + pub const url: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x11, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } }; + /// Name string is an ISO OID + /// + /// `6ba7b812-9dad-11d1-80b4-00c04fd430c8` + pub const oid: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x12, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } }; + /// Name string is an X.500 DN (in DER or a text output format) + /// + /// `6ba7b814-9dad-11d1-80b4-00c04fd430c8` + pub const x500: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x14, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } }; +}; + +pub fn v5(namespace: UUID, name: []const u8) UUID { + const hash = blk: { + var hasher = std.crypto.hash.Sha1.init(.{}); + hasher.update(namespace); + hasher.update(name); + break :blk hasher.finalResult(); + }; + + var bytes: [16]u8 = hash[0..16].*; + bytes[6] = (bytes[6] & 0x0F) | 0x50; + bytes[8] = (bytes[8] & 0x3F) | 0x80; + + return .{ .bytes = bytes }; +} + +var v7_lock: std.Thread.Mutex = .{}; +var v7_last_timestamp: std.atomic.Value(u64) = .{ .raw = 0 }; +var v7_counter: std.atomic.Value(u32) = .{ .raw = 0 }; + +fn getCount(timestamp: u64) u32 { + v7_lock.lock(); + defer v7_lock.unlock(); + + if (v7_last_timestamp.swap(timestamp, .monotonic) != timestamp) { + v7_counter.store(0, .monotonic); + } + + return v7_counter.fetchAdd(1, .monotonic) % 4096; +} + +pub fn v7() UUID { + const timestamp: u64 = @intCast(@max(0, std.time.milliTimestamp())); + const count = getCount(timestamp); + const random = blk: { + var bytes: [8]u8 = undefined; + std.crypto.random.bytes(&bytes); + break :blk bytes; + }; + + var bytes: [16]u8 = undefined; + bytes[0] = @truncate(timestamp >> 40); + bytes[1] = @truncate(timestamp >> 32); + bytes[2] = @truncate(timestamp >> 24); + bytes[3] = @truncate(timestamp >> 16); + bytes[4] = @truncate(timestamp >> 8); + bytes[5] = @truncate(timestamp); + bytes[6] = (@as(u8, 7) << 4) | @as(u8, @truncate((count >> 8) & 0x0F)); + bytes[7] = @truncate(count); + bytes[8] = (random[0] & 0x3F) | 0x80; + @memcpy(bytes[9..16], random[1..8]); + + return .{ .bytes = bytes }; +} diff --git a/packages/web/src/http/Parser.zig b/packages/web/src/http/Parser.zig new file mode 100644 index 0000000..790f1cc --- /dev/null +++ b/packages/web/src/http/Parser.zig @@ -0,0 +1,407 @@ +const std = @import("std"); +const Parser = @This(); + +const Callbacks = struct { + self: ?*anyopaque = null, + + route: ?*const fn (self: ?*anyopaque, route: Route) void = null, + header: ?*const fn (self: ?*anyopaque, name: []const u8, value: []const u8) void = null, + body: ?*const fn (self: ?*anyopaque, body: []const u8) void = null, + + pub const null_callbacks: Callbacks = .{}; +}; + +const Error = error{ + MethodNotSupported, + HttpVersionNotSupported, + MissingLineFeed, + InvalidContentLength, +}; + +const Vec = @Vector(32, u8); + +const Pattern = struct { + value: Vec, + mask: Vec, + len: u6, + + pub fn init(comptime prefix: []const u8) Pattern { + if (prefix.len > 32) { + @compileError("Prefix length is too high"); + } + + var value: [32]u8 = undefined; + var mask: [32]u8 = undefined; + for (0..32) |i| { + if (i < prefix.len) { + value[i] = prefix[i]; + mask[i] = 0xFF; + } else { + value[i] = 0x00; + mask[i] = 0x00; + } + } + } + + inline fn check(self: Pattern, vec: Vec) bool { + return @reduce(.And, vec & self.mask == self.value); + } +}; + +const patterns = struct { + method_delete: Pattern.init("DELETE "), + method_get: Pattern.init("GET "), + method_head: Pattern.init("HEAD "), + method_patch: Pattern.init("PATCH "), + method_post: Pattern.init("POST "), + method_put: Pattern.init("PUT "), + + @"version_http/1.1": Pattern.init("HTTP/1.1\r\n"), +}; + +inline fn hasSpace(vec: Vec) bool { + const has_space = vec == @as(Vec, @splat(' ')); + return @reduce(.Or, has_space); +} + +inline fn hasCRLF(vec: Vec) bool { + const has_cr = vec == @as(Vec, @splat('\r')); + const has_lf = vec == @as(Vec, @splat('\n')); + return @reduce(.Or, has_cr | has_lf); +} + +const State = union(enum) { + pub fn methodComplete(method: Method) State { + return .{ + .method_complete = .{ + .method = method, + }, + }; + } + + pub fn pathname(method: Method, p: []const u8) State { + return .{ + .pathname_state = .{ + .method = method, + .pathname = p, + }, + }; + } + + pub fn headerValue(name: []const u8, value: []const u8) State { + return .{ + .header_value = .{ + .name = name, + .value = value, + }, + }; + } + + init: void, + method_d: void, + method_g: void, + method_h: void, + method_p: void, + method_de: void, + method_ge: void, + method_he: void, + method_pa: void, + method_po: void, + method_pu: void, + method_del: void, + method_hea: void, + method_pat: void, + method_pos: void, + method_dele: void, + method_patc: void, + method_delet: void, + method_complete: struct { method: Method }, + pathname_state: struct { method: Method, pathname: []const u8 }, + pathname_complete: void, + version_h: void, + version_ht: void, + version_htt: void, + version_http: void, + @"version_http/": void, + @"version_http/1": void, + @"version_http/1.": void, + version_complete: void, + start_line_end: void, + header_name_start: void, + header_name: []const u8, + header_value: struct { name: []const u8, value: []const u8 }, + header_line_end: void, + headers_end: void, + body: []const u8, +}; + +const ConsumeResult = struct { + consumed: usize, + done: bool, +}; + +const ConsumeCharResult = enum { + not_done, + done, +}; + +pub const Method = enum { + DELETE, + GET, + HEAD, + PATCH, + POST, + PUT, +}; + +pub const Route = struct { + method: Method, + pathname: []const u8, +}; + +callbacks: Callbacks, +state: State, +current_header_is_content_length: bool, +content_length: usize, + +pub fn init(callbacks: Callbacks) Parser { + return .{ + .callbacks = callbacks, + .state = .init, + .current_header_is_content_length = false, + .content_length = 0, + }; +} + +pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult { + var i: usize = 0; + while (i < chars.len) { + switch (self.state) { + .body => |body| { + const to_consume = @min(chars.len - i, self.content_length - body.len); + const new_body = body.ptr[0 .. body.len + to_consume]; + self.state = .{ .body = new_body }; + i += to_consume; + + const done = new_body.len >= self.content_length; + + if (done) { + if (self.callbacks.body) |bodyCallback| { + bodyCallback(self.callbacks.self, new_body); + } + } + + return .{ + .consumed = i, + .done = done, + }; + }, + else => { + const res = try self.consumeChar(&chars[i]); + i += 1; + if (res == .done) return .{ + .consumed = i, + .done = true, + }; + }, + } + } + + return .{ + .consumed = chars.len, + .done = false, + }; +} + +pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult { + const c = c_ptr.*; + const c_slice = @as([*]const u8, @ptrCast(c_ptr))[0..1]; + switch (self.state) { + .init => switch (c) { + 'D' => self.state = .method_d, + 'G' => self.state = .method_g, + 'H' => self.state = .method_h, + 'P' => self.state = .method_p, + else => return error.MethodNotSupported, + }, + .method_d => switch (c) { + 'E' => self.state = .method_de, + else => return error.MethodNotSupported, + }, + .method_g => switch (c) { + 'E' => self.state = .method_ge, + else => return error.MethodNotSupported, + }, + .method_h => switch (c) { + 'E' => self.state = .method_he, + else => return error.MethodNotSupported, + }, + .method_p => switch (c) { + 'A' => self.state = .method_pa, + 'O' => self.state = .method_po, + 'U' => self.state = .method_pu, + else => return error.MethodNotSupported, + }, + .method_de => switch (c) { + 'L' => self.state = .method_del, + else => return error.MethodNotSupported, + }, + .method_ge => switch (c) { + 'T' => self.state = .methodComplete(.GET), + else => return error.MethodNotSupported, + }, + .method_he => switch (c) { + 'A' => self.state = .method_hea, + else => return error.MethodNotSupported, + }, + .method_pa => switch (c) { + 'T' => self.state = .method_pat, + else => return error.MethodNotSupported, + }, + .method_po => switch (c) { + 'S' => self.state = .method_pos, + else => return error.MethodNotSupported, + }, + .method_pu => switch (c) { + 'T' => self.state = .methodComplete(.PUT), + else => return error.MethodNotSupported, + }, + .method_del => switch (c) { + 'E' => self.state = .method_dele, + else => return error.MethodNotSupported, + }, + .method_hea => switch (c) { + 'D' => self.state = .methodComplete(.HEAD), + else => return error.MethodNotSupported, + }, + .method_pat => switch (c) { + 'C' => self.state = .method_patc, + else => return error.MethodNotSupported, + }, + .method_pos => switch (c) { + 'T' => self.state = .methodComplete(.POST), + else => return error.MethodNotSupported, + }, + .method_dele => switch (c) { + 'T' => self.state = .method_delet, + else => return error.MethodNotSupported, + }, + .method_patc => switch (c) { + 'H' => self.state = .methodComplete(.PATCH), + else => return error.MethodNotSupported, + }, + .method_delet => switch (c) { + 'E' => self.state = .methodComplete(.DELETE), + else => return error.MethodNotSupported, + }, + .method_complete => |s| switch (c) { + ' ' => self.state = .pathname(s.method, @as([*]const u8, @ptrCast(c_ptr))[1..1]), + else => return error.MethodNotSupported, + }, + .pathname_state => |s| switch (c) { + ' ' => { + self.state = .pathname_complete; + if (self.callbacks.route) |routeCallback| { + routeCallback(self.callbacks.self, .{ + .method = s.method, + .pathname = s.pathname, + }); + } + }, + else => self.state = .pathname(s.method, s.pathname.ptr[0 .. s.pathname.len + 1]), + }, + .pathname_complete => switch (c) { + 'H' => self.state = .version_h, + else => return error.HttpVersionNotSupported, + }, + .version_h => switch (c) { + 'T' => self.state = .version_ht, + else => return error.HttpVersionNotSupported, + }, + .version_ht => switch (c) { + 'T' => self.state = .version_htt, + else => return error.HttpVersionNotSupported, + }, + .version_htt => switch (c) { + 'P' => self.state = .version_http, + else => return error.HttpVersionNotSupported, + }, + .version_http => switch (c) { + '/' => self.state = .@"version_http/", + else => return error.HttpVersionNotSupported, + }, + .@"version_http/" => switch (c) { + '1' => self.state = .@"version_http/1", + else => return error.HttpVersionNotSupported, + }, + .@"version_http/1" => switch (c) { + '.' => self.state = .@"version_http/1.", + else => return error.HttpVersionNotSupported, + }, + .@"version_http/1." => switch (c) { + '1' => self.state = .version_complete, + else => return error.HttpVersionNotSupported, + }, + .version_complete => switch (c) { + '\r' => self.state = .start_line_end, + else => return error.HttpVersionNotSupported, + }, + .start_line_end => switch (c) { + '\n' => self.state = .header_name_start, + else => return error.MissingLineFeed, + }, + .header_name_start => switch (c) { + '\r' => self.state = .headers_end, + else => self.state = .{ .header_name = c_slice }, + }, + .header_name => |name| switch (c) { + ':' => { + self.state = .headerValue(name, @as([*]const u8, @ptrCast(c_ptr))[1..1]); + self.current_header_is_content_length = std.ascii.eqlIgnoreCase(name, "Content-Length"); + }, + else => self.state = .{ .header_name = name.ptr[0 .. name.len + 1] }, + }, + .header_value => |s| switch (c) { + '\r' => { + self.state = .header_line_end; + const value_trimmed = std.mem.trim(u8, s.value, " \t"); + if (self.current_header_is_content_length) { + self.content_length = std.fmt.parseInt(usize, value_trimmed, 10) catch return error.InvalidContentLength; + self.current_header_is_content_length = false; + } + if (self.callbacks.header) |headerCallback| { + headerCallback(self.callbacks.self, s.name, value_trimmed); + } + }, + else => self.state = .headerValue(s.name, s.value.ptr[0 .. s.value.len + 1]), + }, + .header_line_end => switch (c) { + '\n' => self.state = .header_name_start, + else => return error.MissingLineFeed, + }, + .headers_end => switch (c) { + '\n' => { + if (self.content_length == 0) { + if (self.callbacks.body) |bodyCallback| { + bodyCallback(self.callbacks.self, &.{}); + } + return .done; + } + self.state = .{ .body = @as([*]const u8, @ptrCast(c_ptr))[1..1] }; + }, + else => return error.MissingLineFeed, + }, + .body => |body| { + const new_body = body.ptr[0 .. body.len + 1]; + self.state = .{ .body = new_body }; + if (new_body.len >= self.content_length) { + if (self.callbacks.body) |bodyCallback| { + bodyCallback(self.callbacks.self, new_body); + } + return .done; + } + }, + } + + return .not_done; +} diff --git a/packages/web/src/http/status.zig b/packages/web/src/http/status.zig new file mode 100644 index 0000000..44df6a0 --- /dev/null +++ b/packages/web/src/http/status.zig @@ -0,0 +1,58 @@ +pub const ok = "HTTP/1.1 200 OK\r\n"; +pub const created = "HTTP/1.1 201 Created\r\n"; +pub const accepted = "HTTP/1.1 202 Accepted\r\n"; +pub const non_authoritative_information = "HTTP/1.1 203 Non-Authoritative Information\r\n"; +pub const no_content = "HTTP/1.1 204 No Content\r\n"; +pub const reset_content = "HTTP/1.1 205 Reset Content\r\n"; +pub const partial_content = "HTTP/1.1 206 Partial Content\r\n"; +pub const multi_status = "HTTP/1.1 207 Multi-Status\r\n"; +pub const already_reported = "HTTP/1.1 208 Already Reported\r\n"; + +pub const multiple_choices = "HTTP/1.1 300 Multiple Choices\r\n"; +pub const moved_permanently = "HTTP/1.1 301 Moved Permanently\r\n"; +pub const found = "HTTP/1.1 302 Found\r\n"; +pub const see_other = "HTTP/1.1 303 See Other\r\n"; +pub const not_modified = "HTTP/1.1 304 Not Modified\r\n"; +pub const temporary_redirect = "HTTP/1.1 307 Temporary Redirect\r\n"; +pub const permanent_redirect = "HTTP/1.1 308 Permanent Redirect\r\n"; + +pub const bad_request = "HTTP/1.1 400 Bad Request\r\n"; +pub const unauthorized = "HTTP/1.1 401 Unauthorized\r\n"; +pub const payment_required = "HTTP/1.1 402 Payment Required\r\n"; +pub const forbidden = "HTTP/1.1 403 Forbidden\r\n"; +pub const not_found = "HTTP/1.1 404 Not Found\r\n"; +pub const method_not_allowed = "HTTP/1.1 405 Method Not Allowed\r\n"; +pub const not_acceptable = "HTTP/1.1 406 Not Acceptable\r\n"; +pub const proxy_authentication_required = "HTTP/1.1 407 Proxy Authentication Required\r\n"; +pub const request_timeout = "HTTP/1.1 408 Request Timeout\r\n"; +pub const conflict = "HTTP/1.1 409 Conflict\r\n"; +pub const gone = "HTTP/1.1 410 Gone\r\n"; +pub const length_required = "HTTP/1.1 411 Length Required\r\n"; +pub const precondition_failed = "HTTP/1.1 412 Precondition Failed\r\n"; +pub const content_too_large = "HTTP/1.1 413 Content Too Large\r\n"; +pub const uri_too_long = "HTTP/1.1 414 URI Too Long\r\n"; +pub const unsupported_media_type = "HTTP/1.1 415 Unsupported Media Type\r\n"; +pub const range_not_satisfiable = "HTTP/1.1 416 Range Not Satisfiable\r\n"; +pub const expectation_failed = "HTTP/1.1 417 Expectation Failed\r\n"; +pub const im_a_teapot = "HTTP/1.1 418 I'm a teapot\r\n"; +pub const misdirected_request = "HTTP/1.1 421 Misdirected Request\r\n"; +pub const unprocessable_content = "HTTP/1.1 422 Unprocessable Content\r\n"; +pub const locked = "HTTP/1.1 423 Locked\r\n"; +pub const failed_dependency = "HTTP/1.1 424 Failed Dependency\r\n"; +pub const upgrade_required = "HTTP/1.1 426 Upgrade Required\r\n"; +pub const precondition_required = "HTTP/1.1 428 Precondition Required\r\n"; +pub const too_many_requests = "HTTP/1.1 429 Too Many Requests\r\n"; +pub const request_header_fields_too_large = "HTTP/1.1 431 Request Header Fields Too Large\r\n"; +pub const unavailable_for_legal_reasons = "HTTP/1.1 451 Unavailable For Legal Reasons\r\n"; + +pub const internal_server_error = "HTTP/1.1 500 Internal Server Error\r\n"; +pub const not_implemented = "HTTP/1.1 501 Not Implemented\r\n"; +pub const bad_gateway = "HTTP/1.1 502 Bad Gateway\r\n"; +pub const service_unavailable = "HTTP/1.1 503 Service Unavailable\r\n"; +pub const gateway_timeout = "HTTP/1.1 504 Gateway Timeout\r\n"; +pub const http_version_not_supported = "HTTP/1.1 505 HTTP Version Not Supported\r\n"; +pub const variant_also_negotiates = "HTTP/1.1 506 Variant Also Negotiates\r\n"; +pub const insufficient_storage = "HTTP/1.1 507 Insufficient Storage\r\n"; +pub const loop_detected = "HTTP/1.1 508 Loop Detected\r\n"; +pub const not_extended = "HTTP/1.1 510 Not Extended\r\n"; +pub const network_authentication_required = "HTTP/1.1 511 Network Authentication Required\r\n";