Commit ba44513c2f
Changed files (7)
lib
lib/std/crypto/Tls.zig
@@ -0,0 +1,342 @@
+const std = @import("../std.zig");
+const Tls = @This();
+const net = std.net;
+const mem = std.mem;
+const crypto = std.crypto;
+const assert = std.debug.assert;
+
+state: State = .start,
+x25519_priv_key: [32]u8 = undefined,
+x25519_pub_key: [32]u8 = undefined,
+
+const State = enum {
+ /// In this state, all fields are undefined except state.
+ start,
+ sent_hello,
+};
+
+const ContentType = enum(u8) {
+ invalid = 0,
+ change_cipher_spec = 20,
+ alert = 21,
+ handshake = 22,
+ application_data = 23,
+ _,
+};
+
+const HandshakeType = enum(u8) {
+ client_hello = 1,
+ server_hello = 2,
+ new_session_ticket = 4,
+ end_of_early_data = 5,
+ encrypted_extensions = 8,
+ certificate = 11,
+ certificate_request = 13,
+ certificate_verify = 15,
+ finished = 20,
+ key_update = 24,
+ message_hash = 254,
+};
+
+const ExtensionType = enum(u16) {
+ /// RFC 6066
+ server_name = 0,
+ /// RFC 6066
+ max_fragment_length = 1,
+ /// RFC 6066
+ status_request = 5,
+ /// RFC 8422, 7919
+ supported_groups = 10,
+ /// RFC 8446
+ signature_algorithms = 13,
+ /// RFC 5764
+ use_srtp = 14,
+ /// RFC 6520
+ heartbeat = 15,
+ /// RFC 7301
+ application_layer_protocol_negotiation = 16,
+ /// RFC 6962
+ signed_certificate_timestamp = 18,
+ /// RFC 7250
+ client_certificate_type = 19,
+ /// RFC 7250
+ server_certificate_type = 20,
+ /// RFC 7685
+ padding = 21,
+ /// RFC 8446
+ pre_shared_key = 41,
+ /// RFC 8446
+ early_data = 42,
+ /// RFC 8446
+ supported_versions = 43,
+ /// RFC 8446
+ cookie = 44,
+ /// RFC 8446
+ psk_key_exchange_modes = 45,
+ /// RFC 8446
+ certificate_authorities = 47,
+ /// RFC 8446
+ oid_filters = 48,
+ /// RFC 8446
+ post_handshake_auth = 49,
+ /// RFC 8446
+ signature_algorithms_cert = 50,
+ /// RFC 8446
+ key_share = 51,
+};
+
+const AlertLevel = enum(u8) {
+ warning = 1,
+ fatal = 2,
+ _,
+};
+
+const AlertDescription = enum(u8) {
+ close_notify = 0,
+ unexpected_message = 10,
+ bad_record_mac = 20,
+ record_overflow = 22,
+ handshake_failure = 40,
+ bad_certificate = 42,
+ unsupported_certificate = 43,
+ certificate_revoked = 44,
+ certificate_expired = 45,
+ certificate_unknown = 46,
+ illegal_parameter = 47,
+ unknown_ca = 48,
+ access_denied = 49,
+ decode_error = 50,
+ decrypt_error = 51,
+ protocol_version = 70,
+ insufficient_security = 71,
+ internal_error = 80,
+ inappropriate_fallback = 86,
+ user_canceled = 90,
+ missing_extension = 109,
+ unsupported_extension = 110,
+ unrecognized_name = 112,
+ bad_certificate_status_response = 113,
+ unknown_psk_identity = 115,
+ certificate_required = 116,
+ no_application_protocol = 120,
+ _,
+};
+
+const SignatureScheme = enum(u16) {
+ // RSASSA-PKCS1-v1_5 algorithms
+ rsa_pkcs1_sha256 = 0x0401,
+ rsa_pkcs1_sha384 = 0x0501,
+ rsa_pkcs1_sha512 = 0x0601,
+
+ // ECDSA algorithms
+ ecdsa_secp256r1_sha256 = 0x0403,
+ ecdsa_secp384r1_sha384 = 0x0503,
+ ecdsa_secp521r1_sha512 = 0x0603,
+
+ // RSASSA-PSS algorithms with public key OID rsaEncryption
+ rsa_pss_rsae_sha256 = 0x0804,
+ rsa_pss_rsae_sha384 = 0x0805,
+ rsa_pss_rsae_sha512 = 0x0806,
+
+ // EdDSA algorithms
+ ed25519 = 0x0807,
+ ed448 = 0x0808,
+
+ // RSASSA-PSS algorithms with public key OID RSASSA-PSS
+ rsa_pss_pss_sha256 = 0x0809,
+ rsa_pss_pss_sha384 = 0x080a,
+ rsa_pss_pss_sha512 = 0x080b,
+
+ // Legacy algorithms
+ rsa_pkcs1_sha1 = 0x0201,
+ ecdsa_sha1 = 0x0203,
+
+ _,
+};
+
+const NamedGroup = enum(u16) {
+ // Elliptic Curve Groups (ECDHE)
+ secp256r1 = 0x0017,
+ secp384r1 = 0x0018,
+ secp521r1 = 0x0019,
+ x25519 = 0x001D,
+ x448 = 0x001E,
+
+ // Finite Field Groups (DHE)
+ ffdhe2048 = 0x0100,
+ ffdhe3072 = 0x0101,
+ ffdhe4096 = 0x0102,
+ ffdhe6144 = 0x0103,
+ ffdhe8192 = 0x0104,
+
+ _,
+};
+
+// Plaintext:
+// * type: ContentType
+// * legacy_record_version: u16 = 0x0303,
+// * length: u16,
+// - The length (in bytes) of the following TLSPlaintext.fragment. The
+// length MUST NOT exceed 2^14 bytes.
+// * fragment: opaque
+// - the data being transmitted
+
+// Handshake:
+// * type: HandshakeType
+// * length: u24
+// * data: opaque
+
+const CipherSuite = enum(u16) {
+ TLS_AES_128_GCM_SHA256 = 0x1301,
+ TLS_AES_256_GCM_SHA384 = 0x1302,
+ TLS_CHACHA20_POLY1305_SHA256 = 0x1303,
+ TLS_AES_128_CCM_SHA256 = 0x1304,
+ TLS_AES_128_CCM_8_SHA256 = 0x1305,
+};
+
+const cipher_suites = blk: {
+ const fields = @typeInfo(CipherSuite).Enum.fields;
+ var result: [(fields.len + 1) * 2]u8 = undefined;
+ mem.writeIntBig(u16, result[0..2], result.len - 2);
+ for (fields) |field, i| {
+ const int = @enumToInt(@field(CipherSuite, field.name));
+ result[(i + 1) * 2] = @truncate(u8, int >> 8);
+ result[(i + 1) * 2 + 1] = @truncate(u8, int);
+ }
+ break :blk result;
+};
+
+pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
+ assert(tls.state == .start);
+ crypto.random.bytes(&tls.x25519_priv_key);
+ tls.x25519_pub_key = try crypto.dh.X25519.recoverPublicKey(tls.x25519_priv_key);
+
+ // random (u32)
+ var rand_buf: [32]u8 = undefined;
+ crypto.random.bytes(&rand_buf);
+
+ const extensions_header = [_]u8{
+ // Extensions byte length
+ undefined, undefined,
+
+ // Extension: supported_versions (only TLS 1.3)
+ 0, 43, // ExtensionType.supported_versions
+ 0x00, 0x05, // byte length of this extension payload
+ 0x04, // byte length of supported versions
+ 0x03, 0x04, // TLS 1.3
+ 0x03, 0x03, // TLS 1.2
+
+ // Extension: signature_algorithms
+ 0, 13, // ExtensionType.signature_algorithms
+ 0x00, 0x22, // byte length of this extension payload
+ 0x00, 0x20, // byte length of signature algorithms list
+ 0x04, 0x01, // rsa_pkcs1_sha256
+ 0x05, 0x01, // rsa_pkcs1_sha384
+ 0x06, 0x01, // rsa_pkcs1_sha512
+ 0x04, 0x03, // ecdsa_secp256r1_sha256
+ 0x05, 0x03, // ecdsa_secp384r1_sha384
+ 0x06, 0x03, // ecdsa_secp521r1_sha512
+ 0x08, 0x04, // rsa_pss_rsae_sha256
+ 0x08, 0x05, // rsa_pss_rsae_sha384
+ 0x08, 0x06, // rsa_pss_rsae_sha512
+ 0x08, 0x07, // ed25519
+ 0x08, 0x08, // ed448
+ 0x08, 0x09, // rsa_pss_pss_sha256
+ 0x08, 0x0a, // rsa_pss_pss_sha384
+ 0x08, 0x0b, // rsa_pss_pss_sha512
+ 0x02, 0x01, // rsa_pkcs1_sha1
+ 0x02, 0x03, // ecdsa_sha1
+
+ // Extension: supported_groups
+ 0, 10, // ExtensionType.supported_groups
+ 0x00, 0x0c, // byte length of this extension payload
+ 0x00, 0x0a, // byte length of supported groups list
+ 0x00, 0x17, // secp256r1
+ 0x00, 0x18, // secp384r1
+ 0x00, 0x19, // secp521r1
+ 0x00, 0x1D, // x25519
+ 0x00, 0x1E, // x448
+
+ // Extension: key_share
+ 0, 51, // ExtensionType.key_share
+ 0x00, 38, // byte length of this extension payload
+ 0x00, 36, // byte length of client_shares
+ 0x00, 0x1D, // NamedGroup.x25519
+ 0x00, 32, // byte length of key_exchange
+ } ++ tls.x25519_pub_key ++ [_]u8{
+
+ // Extension: server_name
+ 0, 0, // ExtensionType.server_name
+ undefined, undefined, // byte length of this extension payload
+ undefined, undefined, // server_name_list byte count
+ 0x00, // name_type
+ undefined, undefined, // host name len
+ };
+
+ var hello_header = [_]u8{
+ // Plaintext header
+ @enumToInt(ContentType.handshake),
+ 0x03, 0x01, // legacy_record_version
+ undefined, undefined, // Plaintext fragment length (u16)
+
+ // Handshake header
+ @enumToInt(HandshakeType.client_hello),
+ undefined, undefined, undefined, // handshake length (u24)
+
+ // ClientHello
+ 0x03, 0x03, // legacy_version
+ } ++ rand_buf ++ [1]u8{0} ++ cipher_suites ++ [_]u8{
+ 0x01, 0x00, // legacy_compression_methods
+ } ++ extensions_header;
+
+ mem.writeIntBig(u16, hello_header[3..][0..2], @intCast(u16, hello_header.len - 5 + host.len));
+ mem.writeIntBig(u24, hello_header[6..][0..3], @intCast(u24, hello_header.len - 9 + host.len));
+ mem.writeIntBig(
+ u16,
+ hello_header[hello_header.len - extensions_header.len ..][0..2],
+ @intCast(u16, extensions_header.len - 2 + host.len),
+ );
+ mem.writeIntBig(u16, hello_header[hello_header.len - 7 ..][0..2], @intCast(u16, 5 + host.len));
+ mem.writeIntBig(u16, hello_header[hello_header.len - 5 ..][0..2], @intCast(u16, 3 + host.len));
+ mem.writeIntBig(u16, hello_header[hello_header.len - 2 ..][0..2], @intCast(u16, 0 + host.len));
+
+ var iovecs = [_]std.os.iovec_const{
+ .{
+ .iov_base = &hello_header,
+ .iov_len = hello_header.len,
+ },
+ .{
+ .iov_base = host.ptr,
+ .iov_len = host.len,
+ },
+ };
+ try stream.writevAll(&iovecs);
+
+ {
+ var buf: [1000]u8 = undefined;
+ const amt = try stream.read(&buf);
+ const resp = buf[0..amt];
+ const ct = @intToEnum(ContentType, resp[0]);
+ if (ct == .alert) {
+ //const prot_ver = @bitCast(u16, resp[1..][0..2].*);
+ const len = std.mem.readIntBig(u16, resp[3..][0..2]);
+ const alert = resp[5..][0..len];
+ const level = @intToEnum(AlertLevel, alert[0]);
+ const desc = @intToEnum(AlertDescription, alert[1]);
+ std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) });
+ std.process.exit(1);
+ } else {
+ std.debug.print("content_type: {s}\n", .{@tagName(ct)});
+ std.debug.print("got {d} bytes: {s}\n", .{ amt, std.fmt.fmtSliceHexLower(resp) });
+ }
+ }
+
+ tls.state = .sent_hello;
+}
+
+pub fn writeAll(tls: *Tls, stream: net.Stream, buffer: []const u8) !void {
+ _ = tls;
+ _ = stream;
+ _ = buffer;
+ @panic("hold on a minute, we didn't finish implementing the handshake yet");
+}
lib/std/http/Client.zig
@@ -0,0 +1,114 @@
+const std = @import("../std.zig");
+const assert = std.debug.assert;
+const http = std.http;
+const net = std.net;
+const Client = @This();
+
+allocator: std.mem.Allocator,
+headers: std.ArrayListUnmanaged(u8) = .{},
+active_requests: usize = 0,
+
+pub const Request = struct {
+ client: *Client,
+ stream: net.Stream,
+ headers: std.ArrayListUnmanaged(u8) = .{},
+ tls: std.crypto.Tls = .{},
+ protocol: Protocol,
+
+ pub const Protocol = enum { http, https };
+
+ pub const Options = struct {
+ family: Family = .any,
+ protocol: Protocol = .https,
+ method: http.Method = .GET,
+ host: []const u8 = "localhost",
+ path: []const u8 = "/",
+ port: u16 = 0,
+
+ pub const Family = enum { any, ip4, ip6 };
+ };
+
+ pub fn deinit(req: *Request) void {
+ req.client.active_requests -= 1;
+ req.headers.deinit(req.client.allocator);
+ req.* = undefined;
+ }
+
+ pub fn addHeader(req: *Request, name: []const u8, value: []const u8) !void {
+ const gpa = req.client.allocator;
+ // Ensure an extra +2 for the \r\n in end()
+ try req.headers.ensureUnusedCapacity(gpa, name.len + value.len + 6);
+ req.headers.appendSliceAssumeCapacity(name);
+ req.headers.appendSliceAssumeCapacity(": ");
+ req.headers.appendSliceAssumeCapacity(value);
+ req.headers.appendSliceAssumeCapacity("\r\n");
+ }
+
+ pub fn end(req: *Request) !void {
+ req.headers.appendSliceAssumeCapacity("\r\n");
+ switch (req.protocol) {
+ .http => {
+ try req.stream.writeAll(req.headers.items);
+ },
+ .https => {
+ try req.tls.writeAll(req.stream, req.headers.items);
+ },
+ }
+ }
+};
+
+pub fn deinit(client: *Client) void {
+ assert(client.active_requests == 0);
+ client.headers.denit(client.allocator);
+ client.* = undefined;
+}
+
+pub fn request(client: *Client, options: Request.Options) !Request {
+ var req: Request = .{
+ .client = client,
+ .stream = try net.tcpConnectToHost(client.allocator, options.host, options.port),
+ .protocol = options.protocol,
+ };
+ errdefer req.deinit();
+
+ switch (options.protocol) {
+ .http => {},
+ .https => {
+ try req.tls.init(req.stream, options.host);
+ },
+ }
+
+ try req.headers.ensureUnusedCapacity(
+ client.allocator,
+ @tagName(options.method).len +
+ 1 +
+ options.path.len +
+ " HTTP/2\r\nHost: ".len +
+ options.host.len +
+ "\r\nUpgrade-Insecure-Requests: 1\r\n".len +
+ client.headers.items.len +
+ 2, // for the \r\n at the end of headers
+ );
+ req.headers.appendSliceAssumeCapacity(@tagName(options.method));
+ req.headers.appendSliceAssumeCapacity(" ");
+ req.headers.appendSliceAssumeCapacity(options.path);
+ req.headers.appendSliceAssumeCapacity(" HTTP/2\r\nHost: ");
+ req.headers.appendSliceAssumeCapacity(options.host);
+ switch (options.protocol) {
+ .https => req.headers.appendSliceAssumeCapacity("\r\nUpgrade-Insecure-Requests: 1\r\n"),
+ .http => req.headers.appendSliceAssumeCapacity("\r\n"),
+ }
+ req.headers.appendSliceAssumeCapacity(client.headers.items);
+
+ client.active_requests += 1;
+ return req;
+}
+
+pub fn addHeader(client: *Client, name: []const u8, value: []const u8) !void {
+ const gpa = client.allocator;
+ try client.headers.ensureUnusedCapacity(gpa, name.len + value.len + 4);
+ client.headers.appendSliceAssumeCapacity(name);
+ client.headers.appendSliceAssumeCapacity(": ");
+ client.headers.appendSliceAssumeCapacity(value);
+ client.headers.appendSliceAssumeCapacity("\r\n");
+}
lib/std/http/method.zig
@@ -1,65 +0,0 @@
-//! HTTP Methods
-//! https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
-
-// Style guide is violated here so that @tagName can be used effectively
-/// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definiton
-/// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH
-pub const Method = enum {
- GET,
- HEAD,
- POST,
- PUT,
- DELETE,
- CONNECT,
- OPTIONS,
- TRACE,
- PATCH,
-
- /// Returns true if a request of this method is allowed to have a body
- /// Actual behavior from servers may vary and should still be checked
- pub fn requestHasBody(self: Method) bool {
- return switch (self) {
- .POST, .PUT, .PATCH => true,
- .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false,
- };
- }
-
- /// Returns true if a response to this method is allowed to have a body
- /// Actual behavior from clients may vary and should still be checked
- pub fn responseHasBody(self: Method) bool {
- return switch (self) {
- .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true,
- .HEAD, .PUT, .TRACE => false,
- };
- }
-
- /// An HTTP method is safe if it doesn't alter the state of the server.
- /// https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP
- /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1
- pub fn safe(self: Method) bool {
- return switch (self) {
- .GET, .HEAD, .OPTIONS, .TRACE => true,
- .POST, .PUT, .DELETE, .CONNECT, .PATCH => false,
- };
- }
-
- /// An HTTP method is idempotent if an identical request can be made once or several times in a row with the same effect while leaving the server in the same state.
- /// https://developer.mozilla.org/en-US/docs/Glossary/Idempotent
- /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.2
- pub fn idempotent(self: Method) bool {
- return switch (self) {
- .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true,
- .CONNECT, .POST, .PATCH => false,
- };
- }
-
- /// A cacheable response is an HTTP response that can be cached, that is stored to be retrieved and used later, saving a new request to the server.
- /// https://developer.mozilla.org/en-US/docs/Glossary/cacheable
- /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.3
- pub fn cacheable(self: Method) bool {
- return switch (self) {
- .GET, .HEAD => true,
- .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false,
- };
- }
-};
lib/std/http/status.zig
@@ -1,182 +0,0 @@
-//! HTTP Status
-//! https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
-
-const std = @import("../std.zig");
-
-pub const Status = enum(u10) {
- @"continue" = 100, // RFC7231, Section 6.2.1
- switching_protocols = 101, // RFC7231, Section 6.2.2
- processing = 102, // RFC2518
- early_hints = 103, // RFC8297
-
- ok = 200, // RFC7231, Section 6.3.1
- created = 201, // RFC7231, Section 6.3.2
- accepted = 202, // RFC7231, Section 6.3.3
- non_authoritative_info = 203, // RFC7231, Section 6.3.4
- no_content = 204, // RFC7231, Section 6.3.5
- reset_content = 205, // RFC7231, Section 6.3.6
- partial_content = 206, // RFC7233, Section 4.1
- multi_status = 207, // RFC4918
- already_reported = 208, // RFC5842
- im_used = 226, // RFC3229
-
- multiple_choice = 300, // RFC7231, Section 6.4.1
- moved_permanently = 301, // RFC7231, Section 6.4.2
- found = 302, // RFC7231, Section 6.4.3
- see_other = 303, // RFC7231, Section 6.4.4
- not_modified = 304, // RFC7232, Section 4.1
- use_proxy = 305, // RFC7231, Section 6.4.5
- temporary_redirect = 307, // RFC7231, Section 6.4.7
- permanent_redirect = 308, // RFC7538
-
- bad_request = 400, // RFC7231, Section 6.5.1
- unauthorized = 401, // RFC7235, Section 3.1
- payment_required = 402, // RFC7231, Section 6.5.2
- forbidden = 403, // RFC7231, Section 6.5.3
- not_found = 404, // RFC7231, Section 6.5.4
- method_not_allowed = 405, // RFC7231, Section 6.5.5
- not_acceptable = 406, // RFC7231, Section 6.5.6
- proxy_auth_required = 407, // RFC7235, Section 3.2
- request_timeout = 408, // RFC7231, Section 6.5.7
- conflict = 409, // RFC7231, Section 6.5.8
- gone = 410, // RFC7231, Section 6.5.9
- length_required = 411, // RFC7231, Section 6.5.10
- precondition_failed = 412, // RFC7232, Section 4.2][RFC8144, Section 3.2
- payload_too_large = 413, // RFC7231, Section 6.5.11
- uri_too_long = 414, // RFC7231, Section 6.5.12
- unsupported_media_type = 415, // RFC7231, Section 6.5.13][RFC7694, Section 3
- range_not_satisfiable = 416, // RFC7233, Section 4.4
- expectation_failed = 417, // RFC7231, Section 6.5.14
- teapot = 418, // RFC 7168, 2.3.3
- misdirected_request = 421, // RFC7540, Section 9.1.2
- unprocessable_entity = 422, // RFC4918
- locked = 423, // RFC4918
- failed_dependency = 424, // RFC4918
- too_early = 425, // RFC8470
- upgrade_required = 426, // RFC7231, Section 6.5.15
- precondition_required = 428, // RFC6585
- too_many_requests = 429, // RFC6585
- header_fields_too_large = 431, // RFC6585
- unavailable_for_legal_reasons = 451, // RFC7725
-
- internal_server_error = 500, // RFC7231, Section 6.6.1
- not_implemented = 501, // RFC7231, Section 6.6.2
- bad_gateway = 502, // RFC7231, Section 6.6.3
- service_unavailable = 503, // RFC7231, Section 6.6.4
- gateway_timeout = 504, // RFC7231, Section 6.6.5
- http_version_not_supported = 505, // RFC7231, Section 6.6.6
- variant_also_negotiates = 506, // RFC2295
- insufficient_storage = 507, // RFC4918
- loop_detected = 508, // RFC5842
- not_extended = 510, // RFC2774
- network_authentication_required = 511, // RFC6585
-
- _,
-
- pub fn phrase(self: Status) ?[]const u8 {
- return switch (self) {
- // 1xx statuses
- .@"continue" => "Continue",
- .switching_protocols => "Switching Protocols",
- .processing => "Processing",
- .early_hints => "Early Hints",
-
- // 2xx statuses
- .ok => "OK",
- .created => "Created",
- .accepted => "Accepted",
- .non_authoritative_info => "Non-Authoritative Information",
- .no_content => "No Content",
- .reset_content => "Reset Content",
- .partial_content => "Partial Content",
- .multi_status => "Multi-Status",
- .already_reported => "Already Reported",
- .im_used => "IM Used",
-
- // 3xx statuses
- .multiple_choice => "Multiple Choice",
- .moved_permanently => "Moved Permanently",
- .found => "Found",
- .see_other => "See Other",
- .not_modified => "Not Modified",
- .use_proxy => "Use Proxy",
- .temporary_redirect => "Temporary Redirect",
- .permanent_redirect => "Permanent Redirect",
-
- // 4xx statuses
- .bad_request => "Bad Request",
- .unauthorized => "Unauthorized",
- .payment_required => "Payment Required",
- .forbidden => "Forbidden",
- .not_found => "Not Found",
- .method_not_allowed => "Method Not Allowed",
- .not_acceptable => "Not Acceptable",
- .proxy_auth_required => "Proxy Authentication Required",
- .request_timeout => "Request Timeout",
- .conflict => "Conflict",
- .gone => "Gone",
- .length_required => "Length Required",
- .precondition_failed => "Precondition Failed",
- .payload_too_large => "Payload Too Large",
- .uri_too_long => "URI Too Long",
- .unsupported_media_type => "Unsupported Media Type",
- .range_not_satisfiable => "Range Not Satisfiable",
- .expectation_failed => "Expectation Failed",
- .teapot => "I'm a teapot",
- .misdirected_request => "Misdirected Request",
- .unprocessable_entity => "Unprocessable Entity",
- .locked => "Locked",
- .failed_dependency => "Failed Dependency",
- .too_early => "Too Early",
- .upgrade_required => "Upgrade Required",
- .precondition_required => "Precondition Required",
- .too_many_requests => "Too Many Requests",
- .header_fields_too_large => "Request Header Fields Too Large",
- .unavailable_for_legal_reasons => "Unavailable For Legal Reasons",
-
- // 5xx statuses
- .internal_server_error => "Internal Server Error",
- .not_implemented => "Not Implemented",
- .bad_gateway => "Bad Gateway",
- .service_unavailable => "Service Unavailable",
- .gateway_timeout => "Gateway Timeout",
- .http_version_not_supported => "HTTP Version Not Supported",
- .variant_also_negotiates => "Variant Also Negotiates",
- .insufficient_storage => "Insufficient Storage",
- .loop_detected => "Loop Detected",
- .not_extended => "Not Extended",
- .network_authentication_required => "Network Authentication Required",
-
- else => return null,
- };
- }
-
- pub const Class = enum {
- informational,
- success,
- redirect,
- client_error,
- server_error,
- };
-
- pub fn class(self: Status) ?Class {
- return switch (@enumToInt(self)) {
- 100...199 => .informational,
- 200...299 => .success,
- 300...399 => .redirect,
- 400...499 => .client_error,
- 500...599 => .server_error,
- else => null,
- };
- }
-};
-
-test {
- try std.testing.expectEqualStrings("OK", Status.ok.phrase().?);
- try std.testing.expectEqualStrings("Not Found", Status.not_found.phrase().?);
-}
-
-test {
- try std.testing.expectEqual(@as(?Status.Class, Status.Class.success), Status.ok.class());
- try std.testing.expectEqual(@as(?Status.Class, Status.Class.client_error), Status.not_found.class());
-}
lib/std/crypto.zig
@@ -176,6 +176,8 @@ const std = @import("std.zig");
pub const errors = @import("crypto/errors.zig");
+pub const Tls = @import("crypto/Tls.zig");
+
test {
_ = aead.aegis.Aegis128L;
_ = aead.aegis.Aegis256;
lib/std/http.zig
@@ -1,8 +1,251 @@
-const std = @import("std.zig");
+pub const Client = @import("http/Client.zig");
+
+/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
+/// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definiton
+/// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH
+pub const Method = enum {
+ GET,
+ HEAD,
+ POST,
+ PUT,
+ DELETE,
+ CONNECT,
+ OPTIONS,
+ TRACE,
+ PATCH,
+
+ /// Returns true if a request of this method is allowed to have a body
+ /// Actual behavior from servers may vary and should still be checked
+ pub fn requestHasBody(self: Method) bool {
+ return switch (self) {
+ .POST, .PUT, .PATCH => true,
+ .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false,
+ };
+ }
+
+ /// Returns true if a response to this method is allowed to have a body
+ /// Actual behavior from clients may vary and should still be checked
+ pub fn responseHasBody(self: Method) bool {
+ return switch (self) {
+ .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true,
+ .HEAD, .PUT, .TRACE => false,
+ };
+ }
+
+ /// An HTTP method is safe if it doesn't alter the state of the server.
+ /// https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP
+ /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1
+ pub fn safe(self: Method) bool {
+ return switch (self) {
+ .GET, .HEAD, .OPTIONS, .TRACE => true,
+ .POST, .PUT, .DELETE, .CONNECT, .PATCH => false,
+ };
+ }
+
+ /// An HTTP method is idempotent if an identical request can be made once or several times in a row with the same effect while leaving the server in the same state.
+ /// https://developer.mozilla.org/en-US/docs/Glossary/Idempotent
+ /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.2
+ pub fn idempotent(self: Method) bool {
+ return switch (self) {
+ .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true,
+ .CONNECT, .POST, .PATCH => false,
+ };
+ }
+
+ /// A cacheable response is an HTTP response that can be cached, that is stored to be retrieved and used later, saving a new request to the server.
+ /// https://developer.mozilla.org/en-US/docs/Glossary/cacheable
+ /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.3
+ pub fn cacheable(self: Method) bool {
+ return switch (self) {
+ .GET, .HEAD => true,
+ .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false,
+ };
+ }
+};
+
+/// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
+pub const Status = enum(u10) {
+ @"continue" = 100, // RFC7231, Section 6.2.1
+ switching_protocols = 101, // RFC7231, Section 6.2.2
+ processing = 102, // RFC2518
+ early_hints = 103, // RFC8297
+
+ ok = 200, // RFC7231, Section 6.3.1
+ created = 201, // RFC7231, Section 6.3.2
+ accepted = 202, // RFC7231, Section 6.3.3
+ non_authoritative_info = 203, // RFC7231, Section 6.3.4
+ no_content = 204, // RFC7231, Section 6.3.5
+ reset_content = 205, // RFC7231, Section 6.3.6
+ partial_content = 206, // RFC7233, Section 4.1
+ multi_status = 207, // RFC4918
+ already_reported = 208, // RFC5842
+ im_used = 226, // RFC3229
+
+ multiple_choice = 300, // RFC7231, Section 6.4.1
+ moved_permanently = 301, // RFC7231, Section 6.4.2
+ found = 302, // RFC7231, Section 6.4.3
+ see_other = 303, // RFC7231, Section 6.4.4
+ not_modified = 304, // RFC7232, Section 4.1
+ use_proxy = 305, // RFC7231, Section 6.4.5
+ temporary_redirect = 307, // RFC7231, Section 6.4.7
+ permanent_redirect = 308, // RFC7538
+
+ bad_request = 400, // RFC7231, Section 6.5.1
+ unauthorized = 401, // RFC7235, Section 3.1
+ payment_required = 402, // RFC7231, Section 6.5.2
+ forbidden = 403, // RFC7231, Section 6.5.3
+ not_found = 404, // RFC7231, Section 6.5.4
+ method_not_allowed = 405, // RFC7231, Section 6.5.5
+ not_acceptable = 406, // RFC7231, Section 6.5.6
+ proxy_auth_required = 407, // RFC7235, Section 3.2
+ request_timeout = 408, // RFC7231, Section 6.5.7
+ conflict = 409, // RFC7231, Section 6.5.8
+ gone = 410, // RFC7231, Section 6.5.9
+ length_required = 411, // RFC7231, Section 6.5.10
+ precondition_failed = 412, // RFC7232, Section 4.2][RFC8144, Section 3.2
+ payload_too_large = 413, // RFC7231, Section 6.5.11
+ uri_too_long = 414, // RFC7231, Section 6.5.12
+ unsupported_media_type = 415, // RFC7231, Section 6.5.13][RFC7694, Section 3
+ range_not_satisfiable = 416, // RFC7233, Section 4.4
+ expectation_failed = 417, // RFC7231, Section 6.5.14
+ teapot = 418, // RFC 7168, 2.3.3
+ misdirected_request = 421, // RFC7540, Section 9.1.2
+ unprocessable_entity = 422, // RFC4918
+ locked = 423, // RFC4918
+ failed_dependency = 424, // RFC4918
+ too_early = 425, // RFC8470
+ upgrade_required = 426, // RFC7231, Section 6.5.15
+ precondition_required = 428, // RFC6585
+ too_many_requests = 429, // RFC6585
+ header_fields_too_large = 431, // RFC6585
+ unavailable_for_legal_reasons = 451, // RFC7725
+
+ internal_server_error = 500, // RFC7231, Section 6.6.1
+ not_implemented = 501, // RFC7231, Section 6.6.2
+ bad_gateway = 502, // RFC7231, Section 6.6.3
+ service_unavailable = 503, // RFC7231, Section 6.6.4
+ gateway_timeout = 504, // RFC7231, Section 6.6.5
+ http_version_not_supported = 505, // RFC7231, Section 6.6.6
+ variant_also_negotiates = 506, // RFC2295
+ insufficient_storage = 507, // RFC4918
+ loop_detected = 508, // RFC5842
+ not_extended = 510, // RFC2774
+ network_authentication_required = 511, // RFC6585
-pub const Method = @import("http/method.zig").Method;
-pub const Status = @import("http/status.zig").Status;
+ _,
+
+ pub fn phrase(self: Status) ?[]const u8 {
+ return switch (self) {
+ // 1xx statuses
+ .@"continue" => "Continue",
+ .switching_protocols => "Switching Protocols",
+ .processing => "Processing",
+ .early_hints => "Early Hints",
+
+ // 2xx statuses
+ .ok => "OK",
+ .created => "Created",
+ .accepted => "Accepted",
+ .non_authoritative_info => "Non-Authoritative Information",
+ .no_content => "No Content",
+ .reset_content => "Reset Content",
+ .partial_content => "Partial Content",
+ .multi_status => "Multi-Status",
+ .already_reported => "Already Reported",
+ .im_used => "IM Used",
+
+ // 3xx statuses
+ .multiple_choice => "Multiple Choice",
+ .moved_permanently => "Moved Permanently",
+ .found => "Found",
+ .see_other => "See Other",
+ .not_modified => "Not Modified",
+ .use_proxy => "Use Proxy",
+ .temporary_redirect => "Temporary Redirect",
+ .permanent_redirect => "Permanent Redirect",
+
+ // 4xx statuses
+ .bad_request => "Bad Request",
+ .unauthorized => "Unauthorized",
+ .payment_required => "Payment Required",
+ .forbidden => "Forbidden",
+ .not_found => "Not Found",
+ .method_not_allowed => "Method Not Allowed",
+ .not_acceptable => "Not Acceptable",
+ .proxy_auth_required => "Proxy Authentication Required",
+ .request_timeout => "Request Timeout",
+ .conflict => "Conflict",
+ .gone => "Gone",
+ .length_required => "Length Required",
+ .precondition_failed => "Precondition Failed",
+ .payload_too_large => "Payload Too Large",
+ .uri_too_long => "URI Too Long",
+ .unsupported_media_type => "Unsupported Media Type",
+ .range_not_satisfiable => "Range Not Satisfiable",
+ .expectation_failed => "Expectation Failed",
+ .teapot => "I'm a teapot",
+ .misdirected_request => "Misdirected Request",
+ .unprocessable_entity => "Unprocessable Entity",
+ .locked => "Locked",
+ .failed_dependency => "Failed Dependency",
+ .too_early => "Too Early",
+ .upgrade_required => "Upgrade Required",
+ .precondition_required => "Precondition Required",
+ .too_many_requests => "Too Many Requests",
+ .header_fields_too_large => "Request Header Fields Too Large",
+ .unavailable_for_legal_reasons => "Unavailable For Legal Reasons",
+
+ // 5xx statuses
+ .internal_server_error => "Internal Server Error",
+ .not_implemented => "Not Implemented",
+ .bad_gateway => "Bad Gateway",
+ .service_unavailable => "Service Unavailable",
+ .gateway_timeout => "Gateway Timeout",
+ .http_version_not_supported => "HTTP Version Not Supported",
+ .variant_also_negotiates => "Variant Also Negotiates",
+ .insufficient_storage => "Insufficient Storage",
+ .loop_detected => "Loop Detected",
+ .not_extended => "Not Extended",
+ .network_authentication_required => "Network Authentication Required",
+
+ else => return null,
+ };
+ }
+
+ pub const Class = enum {
+ informational,
+ success,
+ redirect,
+ client_error,
+ server_error,
+ };
+
+ pub fn class(self: Status) ?Class {
+ return switch (@enumToInt(self)) {
+ 100...199 => .informational,
+ 200...299 => .success,
+ 300...399 => .redirect,
+ 400...499 => .client_error,
+ 500...599 => .server_error,
+ else => null,
+ };
+ }
+
+ test {
+ try std.testing.expectEqualStrings("OK", Status.ok.phrase().?);
+ try std.testing.expectEqualStrings("Not Found", Status.not_found.phrase().?);
+ }
+
+ test {
+ try std.testing.expectEqual(@as(?Status.Class, Status.Class.success), Status.ok.class());
+ try std.testing.expectEqual(@as(?Status.Class, Status.Class.client_error), Status.not_found.class());
+ }
+};
+
+const std = @import("std.zig");
test {
- std.testing.refAllDecls(@This());
+ _ = Client;
+ _ = Method;
+ _ = Status;
}
lib/std/net.zig
@@ -1687,6 +1687,13 @@ pub const Stream = struct {
}
}
+ pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void {
+ var index: usize = 0;
+ while (index < bytes.len) {
+ index += try self.write(bytes[index..]);
+ }
+ }
+
/// See https://github.com/ziglang/zig/issues/7699
/// See equivalent function: `std.fs.File.writev`.
pub fn writev(self: Stream, iovecs: []const os.iovec_const) WriteError!usize {