diff options
author | 2021-05-20 02:34:42 -0700 | |
---|---|---|
committer | 2021-05-20 02:34:42 -0700 | |
commit | 8b75f56577abc3ad2479b87fc59b7243add6a29a (patch) | |
tree | 5501729e5f81a6aa65ed986148956882e207d656 /src/deps | |
parent | 9541d268ded6ed38d739e62cb8b4cf597cf69193 (diff) | |
download | bun-8b75f56577abc3ad2479b87fc59b7243add6a29a.tar.gz bun-8b75f56577abc3ad2479b87fc59b7243add6a29a.tar.zst bun-8b75f56577abc3ad2479b87fc59b7243add6a29a.zip |
pico
Former-commit-id: cee857ac4e25638e5f93d838d54124ffaca938b7
Diffstat (limited to 'src/deps')
-rw-r--r-- | src/deps/picohttp.zig | 261 | ||||
m--------- | src/deps/picohttpparser | 0 |
2 files changed, 261 insertions, 0 deletions
diff --git a/src/deps/picohttp.zig b/src/deps/picohttp.zig new file mode 100644 index 000000000..407c203a1 --- /dev/null +++ b/src/deps/picohttp.zig @@ -0,0 +1,261 @@ +const std = @import("std"); +const c = @cImport(@cInclude("picohttpparser.h")); +const ExactSizeMatcher = @import("../exact_size_matcher.zig").ExactSizeMatcher; +const Match = ExactSizeMatcher(2); + +const fmt = std.fmt; + +const assert = std.debug.assert; + +pub fn addTo(step: *std.build.LibExeObjStep, comptime dir: []const u8) void { + step.addCSourceFile(dir ++ "/lib/picohttpparser.c", &[_][]const u8{}); + step.addIncludeDir(dir ++ "/lib"); + + step.addPackage(.{ + .name = "picohttp", + .path = dir ++ "/picohttp.zig", + }); +} + +pub const Header = struct { + name: []const u8, + value: []const u8, + + pub fn isMultiline(self: Header) bool { + return @ptrToInt(self.name.ptr) == 0; + } + + pub fn format(self: Header, comptime layout: []const u8, opts: fmt.FormatOptions, writer: anytype) !void { + if (self.isMultiline()) { + try fmt.format(writer, "{s}", .{self.value}); + } else { + try fmt.format(writer, "{s}: {s}", .{ self.name, self.value }); + } + } + + comptime { + assert(@sizeOf(Header) == @sizeOf(c.phr_header)); + assert(@alignOf(Header) == @alignOf(c.phr_header)); + } +}; + +pub const Request = struct { + method_: []const u8, + method: Method, + path: []const u8, + minor_version: usize, + headers: []const Header, + + pub const Method = enum { + GET, + HEAD, + PATCH, + PUT, + POST, + OPTIONS, + CONNECT, + TRACE, + + pub fn which(str: []const u8) ?Method { + if (str.len < 3) { + return null; + } + + switch (Match.match(str[0..2])) { + Match.case("GE"), Match.case("ge") => { + return .GET; + }, + Match.case("HE"), Match.case("he") => { + return .HEAD; + }, + Match.case("PA"), Match.case("pa") => { + return .PATCH; + }, + Match.case("PO"), Match.case("po") => { + return .POST; + }, + Match.case("PU"), Match.case("pu") => { + return .PUT; + }, + Match.case("OP"), Match.case("op") => { + return .OPTIONS; + }, + Match.case("CO"), Match.case("co") => { + return .CONNECT; + }, + Match.case("TR"), Match.case("tr") => { + return .TRACE; + }, + else => { + return null; + }, + } + } + }; + + pub fn parse(buf: []const u8, src: []Header) !Request { + var method: []const u8 = undefined; + var path: []const u8 = undefined; + var minor_version: c_int = undefined; + var num_headers: usize = src.len; + + const rc = c.phr_parse_request( + buf.ptr, + buf.len, + @ptrCast([*c][*c]const u8, &method.ptr), + &method.len, + @ptrCast([*c][*c]const u8, &path.ptr), + &path.len, + &minor_version, + @ptrCast([*c]c.phr_header, src.ptr), + &num_headers, + 0, + ); + + return switch (rc) { + -1 => error.BadRequest, + -2 => error.ShortRead, + else => |bytes_read| Request{ + .method_ = method, + .method = Request.Method.which(method) orelse return error.InvalidMethod, + .path = path, + .minor_version = @intCast(usize, minor_version), + .headers = src[0..num_headers], + }, + }; + } +}; + +test "pico_http: parse request" { + const REQ = "GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n" ++ + "Host: www.kittyhell.com\r\n" ++ + "User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 " ++ + "Pathtraq/0.9\r\n" ++ + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" ++ + "Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n" ++ + "Accept-Encoding: gzip,deflate\r\n" ++ + "Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n" ++ + "Keep-Alive: 115\r\n" ++ + "Connection: keep-alive\r\n" ++ + "TestMultiline: Hello world\r\n" ++ + " This is a second line in the header!\r\n" ++ + "Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; " ++ + "__utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; " ++ + "__utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral\r\n" ++ + "\r\n"; + + var headers: [32]Header = undefined; + + const req = try Request.parse(REQ, &headers); + + std.debug.print("Method: {s}\n", .{req.method}); + std.debug.print("Path: {s}\n", .{req.path}); + std.debug.print("Minor Version: {}\n", .{req.minor_version}); + + for (req.headers) |header| { + std.debug.print("{}\n", .{header}); + } +} + +pub const Response = struct { + minor_version: usize, + status_code: usize, + status: []const u8, + headers: []const Header, + + pub fn parse(buf: []const u8, src: []Header) !Response { + var minor_version: c_int = undefined; + var status_code: c_int = undefined; + var status: []const u8 = undefined; + var num_headers: usize = src.len; + + const rc = c.phr_parse_response( + buf.ptr, + buf.len, + &minor_version, + &status_code, + @ptrCast([*c][*c]const u8, &status.ptr), + &status.len, + @ptrCast([*c]c.phr_header, src.ptr), + &num_headers, + 0, + ); + + return switch (rc) { + -1 => error.BadResponse, + -2 => error.ShortRead, + else => |bytes_read| Response{ + .minor_version = @intCast(usize, minor_version), + .status_code = @intCast(usize, status_code), + .status = status, + .headers = src[0..num_headers], + }, + }; + } +}; + +test "pico_http: parse response" { + const RES = "HTTP/1.1 200 OK\r\n" ++ + "Date: Mon, 22 Mar 2021 08:15:54 GMT\r\n" ++ + "Content-Type: text/html; charset=utf-8\r\n" ++ + "Content-Length: 9593\r\n" ++ + "Connection: keep-alive\r\n" ++ + "Server: gunicorn/19.9.0\r\n" ++ + "Access-Control-Allow-Origin: *\r\n" ++ + "Access-Control-Allow-Credentials: true\r\n" ++ + "\r\n"; + + var headers: [32]Header = undefined; + + const res = try Response.parse(RES, &headers); + + std.debug.print("Minor Version: {}\n", .{res.minor_version}); + std.debug.print("Status Code: {}\n", .{res.status_code}); + std.debug.print("Status: {s}\n", .{res.status}); + + for (res.headers) |header| { + std.debug.print("{}\n", .{header}); + } +} + +pub const Headers = struct { + headers: []const Header, + + pub fn parse(buf: []const u8, src: []Header) !Headers { + var num_headers: usize = src.len; + + const rc = c.phr_parse_headers( + buf.ptr, + buf.len, + @ptrCast([*c]c.phr_header, src.ptr), + @ptrCast([*c]usize, &num_headers), + 0, + ); + + return switch (rc) { + -1 => error.BadHeaders, + -2 => error.ShortRead, + else => |bytes_read| Headers{ + .headers = src[0..num_headers], + }, + }; + } +}; + +test "pico_http: parse headers" { + const HEADERS = "Date: Mon, 22 Mar 2021 08:15:54 GMT\r\n" ++ + "Content-Type: text/html; charset=utf-8\r\n" ++ + "Content-Length: 9593\r\n" ++ + "Connection: keep-alive\r\n" ++ + "Server: gunicorn/19.9.0\r\n" ++ + "Access-Control-Allow-Origin: *\r\n" ++ + "Access-Control-Allow-Credentials: true\r\n" ++ + "\r\n"; + + var headers: [32]Header = undefined; + + const result = try Headers.parse(HEADERS, &headers); + for (result.headers) |header| { + std.debug.print("{}\n", .{header}); + } +} diff --git a/src/deps/picohttpparser b/src/deps/picohttpparser new file mode 160000 +Subproject 066d2b1e9ab820703db0837a7255d92d30f0c9f |