| author | |
| committer | |
| log | ba44513c2fe363b55b2c534be98179286b832b7e |
| tree | bda249eafac429caabb4ce217065e1688aafd4c8 |
| parent | cd0d514643404103a83881fc4d7c46674ed9f991 |
TLS is capable of sending a Client Hello7 files changed, 712 insertions(+), 251 deletions(-)
lib/std/crypto.zig+2| ... | ... | @@ -176,6 +176,8 @@ const std = @import("std.zig"); |
| 176 | 176 | |
| 177 | 177 | pub const errors = @import("crypto/errors.zig"); |
| 178 | 178 | |
| 179 | pub const Tls = @import("crypto/Tls.zig"); | |
| 180 | ||
| 179 | 181 | test { |
| 180 | 182 | _ = aead.aegis.Aegis128L; |
| 181 | 183 | _ = aead.aegis.Aegis256; |
lib/std/crypto/Tls.zig created+342| ... | ... | @@ -0,0 +1,342 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const Tls = @This(); | |
| 3 | const net = std.net; | |
| 4 | const mem = std.mem; | |
| 5 | const crypto = std.crypto; | |
| 6 | const assert = std.debug.assert; | |
| 7 | ||
| 8 | state: State = .start, | |
| 9 | x25519_priv_key: [32]u8 = undefined, | |
| 10 | x25519_pub_key: [32]u8 = undefined, | |
| 11 | ||
| 12 | const State = enum { | |
| 13 | /// In this state, all fields are undefined except state. | |
| 14 | start, | |
| 15 | sent_hello, | |
| 16 | }; | |
| 17 | ||
| 18 | const ContentType = enum(u8) { | |
| 19 | invalid = 0, | |
| 20 | change_cipher_spec = 20, | |
| 21 | alert = 21, | |
| 22 | handshake = 22, | |
| 23 | application_data = 23, | |
| 24 | _, | |
| 25 | }; | |
| 26 | ||
| 27 | const HandshakeType = enum(u8) { | |
| 28 | client_hello = 1, | |
| 29 | server_hello = 2, | |
| 30 | new_session_ticket = 4, | |
| 31 | end_of_early_data = 5, | |
| 32 | encrypted_extensions = 8, | |
| 33 | certificate = 11, | |
| 34 | certificate_request = 13, | |
| 35 | certificate_verify = 15, | |
| 36 | finished = 20, | |
| 37 | key_update = 24, | |
| 38 | message_hash = 254, | |
| 39 | }; | |
| 40 | ||
| 41 | const ExtensionType = enum(u16) { | |
| 42 | /// RFC 6066 | |
| 43 | server_name = 0, | |
| 44 | /// RFC 6066 | |
| 45 | max_fragment_length = 1, | |
| 46 | /// RFC 6066 | |
| 47 | status_request = 5, | |
| 48 | /// RFC 8422, 7919 | |
| 49 | supported_groups = 10, | |
| 50 | /// RFC 8446 | |
| 51 | signature_algorithms = 13, | |
| 52 | /// RFC 5764 | |
| 53 | use_srtp = 14, | |
| 54 | /// RFC 6520 | |
| 55 | heartbeat = 15, | |
| 56 | /// RFC 7301 | |
| 57 | application_layer_protocol_negotiation = 16, | |
| 58 | /// RFC 6962 | |
| 59 | signed_certificate_timestamp = 18, | |
| 60 | /// RFC 7250 | |
| 61 | client_certificate_type = 19, | |
| 62 | /// RFC 7250 | |
| 63 | server_certificate_type = 20, | |
| 64 | /// RFC 7685 | |
| 65 | padding = 21, | |
| 66 | /// RFC 8446 | |
| 67 | pre_shared_key = 41, | |
| 68 | /// RFC 8446 | |
| 69 | early_data = 42, | |
| 70 | /// RFC 8446 | |
| 71 | supported_versions = 43, | |
| 72 | /// RFC 8446 | |
| 73 | cookie = 44, | |
| 74 | /// RFC 8446 | |
| 75 | psk_key_exchange_modes = 45, | |
| 76 | /// RFC 8446 | |
| 77 | certificate_authorities = 47, | |
| 78 | /// RFC 8446 | |
| 79 | oid_filters = 48, | |
| 80 | /// RFC 8446 | |
| 81 | post_handshake_auth = 49, | |
| 82 | /// RFC 8446 | |
| 83 | signature_algorithms_cert = 50, | |
| 84 | /// RFC 8446 | |
| 85 | key_share = 51, | |
| 86 | }; | |
| 87 | ||
| 88 | const AlertLevel = enum(u8) { | |
| 89 | warning = 1, | |
| 90 | fatal = 2, | |
| 91 | _, | |
| 92 | }; | |
| 93 | ||
| 94 | const AlertDescription = enum(u8) { | |
| 95 | close_notify = 0, | |
| 96 | unexpected_message = 10, | |
| 97 | bad_record_mac = 20, | |
| 98 | record_overflow = 22, | |
| 99 | handshake_failure = 40, | |
| 100 | bad_certificate = 42, | |
| 101 | unsupported_certificate = 43, | |
| 102 | certificate_revoked = 44, | |
| 103 | certificate_expired = 45, | |
| 104 | certificate_unknown = 46, | |
| 105 | illegal_parameter = 47, | |
| 106 | unknown_ca = 48, | |
| 107 | access_denied = 49, | |
| 108 | decode_error = 50, | |
| 109 | decrypt_error = 51, | |
| 110 | protocol_version = 70, | |
| 111 | insufficient_security = 71, | |
| 112 | internal_error = 80, | |
| 113 | inappropriate_fallback = 86, | |
| 114 | user_canceled = 90, | |
| 115 | missing_extension = 109, | |
| 116 | unsupported_extension = 110, | |
| 117 | unrecognized_name = 112, | |
| 118 | bad_certificate_status_response = 113, | |
| 119 | unknown_psk_identity = 115, | |
| 120 | certificate_required = 116, | |
| 121 | no_application_protocol = 120, | |
| 122 | _, | |
| 123 | }; | |
| 124 | ||
| 125 | const SignatureScheme = enum(u16) { | |
| 126 | // RSASSA-PKCS1-v1_5 algorithms | |
| 127 | rsa_pkcs1_sha256 = 0x0401, | |
| 128 | rsa_pkcs1_sha384 = 0x0501, | |
| 129 | rsa_pkcs1_sha512 = 0x0601, | |
| 130 | ||
| 131 | // ECDSA algorithms | |
| 132 | ecdsa_secp256r1_sha256 = 0x0403, | |
| 133 | ecdsa_secp384r1_sha384 = 0x0503, | |
| 134 | ecdsa_secp521r1_sha512 = 0x0603, | |
| 135 | ||
| 136 | // RSASSA-PSS algorithms with public key OID rsaEncryption | |
| 137 | rsa_pss_rsae_sha256 = 0x0804, | |
| 138 | rsa_pss_rsae_sha384 = 0x0805, | |
| 139 | rsa_pss_rsae_sha512 = 0x0806, | |
| 140 | ||
| 141 | // EdDSA algorithms | |
| 142 | ed25519 = 0x0807, | |
| 143 | ed448 = 0x0808, | |
| 144 | ||
| 145 | // RSASSA-PSS algorithms with public key OID RSASSA-PSS | |
| 146 | rsa_pss_pss_sha256 = 0x0809, | |
| 147 | rsa_pss_pss_sha384 = 0x080a, | |
| 148 | rsa_pss_pss_sha512 = 0x080b, | |
| 149 | ||
| 150 | // Legacy algorithms | |
| 151 | rsa_pkcs1_sha1 = 0x0201, | |
| 152 | ecdsa_sha1 = 0x0203, | |
| 153 | ||
| 154 | _, | |
| 155 | }; | |
| 156 | ||
| 157 | const NamedGroup = enum(u16) { | |
| 158 | // Elliptic Curve Groups (ECDHE) | |
| 159 | secp256r1 = 0x0017, | |
| 160 | secp384r1 = 0x0018, | |
| 161 | secp521r1 = 0x0019, | |
| 162 | x25519 = 0x001D, | |
| 163 | x448 = 0x001E, | |
| 164 | ||
| 165 | // Finite Field Groups (DHE) | |
| 166 | ffdhe2048 = 0x0100, | |
| 167 | ffdhe3072 = 0x0101, | |
| 168 | ffdhe4096 = 0x0102, | |
| 169 | ffdhe6144 = 0x0103, | |
| 170 | ffdhe8192 = 0x0104, | |
| 171 | ||
| 172 | _, | |
| 173 | }; | |
| 174 | ||
| 175 | // Plaintext: | |
| 176 | // * type: ContentType | |
| 177 | // * legacy_record_version: u16 = 0x0303, | |
| 178 | // * length: u16, | |
| 179 | // - The length (in bytes) of the following TLSPlaintext.fragment. The | |
| 180 | // length MUST NOT exceed 2^14 bytes. | |
| 181 | // * fragment: opaque | |
| 182 | // - the data being transmitted | |
| 183 | ||
| 184 | // Handshake: | |
| 185 | // * type: HandshakeType | |
| 186 | // * length: u24 | |
| 187 | // * data: opaque | |
| 188 | ||
| 189 | const CipherSuite = enum(u16) { | |
| 190 | TLS_AES_128_GCM_SHA256 = 0x1301, | |
| 191 | TLS_AES_256_GCM_SHA384 = 0x1302, | |
| 192 | TLS_CHACHA20_POLY1305_SHA256 = 0x1303, | |
| 193 | TLS_AES_128_CCM_SHA256 = 0x1304, | |
| 194 | TLS_AES_128_CCM_8_SHA256 = 0x1305, | |
| 195 | }; | |
| 196 | ||
| 197 | const cipher_suites = blk: { | |
| 198 | const fields = @typeInfo(CipherSuite).Enum.fields; | |
| 199 | var result: [(fields.len + 1) * 2]u8 = undefined; | |
| 200 | mem.writeIntBig(u16, result[0..2], result.len - 2); | |
| 201 | for (fields) |field, i| { | |
| 202 | const int = @enumToInt(@field(CipherSuite, field.name)); | |
| 203 | result[(i + 1) * 2] = @truncate(u8, int >> 8); | |
| 204 | result[(i + 1) * 2 + 1] = @truncate(u8, int); | |
| 205 | } | |
| 206 | break :blk result; | |
| 207 | }; | |
| 208 | ||
| 209 | pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void { | |
| 210 | assert(tls.state == .start); | |
| 211 | crypto.random.bytes(&tls.x25519_priv_key); | |
| 212 | tls.x25519_pub_key = try crypto.dh.X25519.recoverPublicKey(tls.x25519_priv_key); | |
| 213 | ||
| 214 | // random (u32) | |
| 215 | var rand_buf: [32]u8 = undefined; | |
| 216 | crypto.random.bytes(&rand_buf); | |
| 217 | ||
| 218 | const extensions_header = [_]u8{ | |
| 219 | // Extensions byte length | |
| 220 | undefined, undefined, | |
| 221 | ||
| 222 | // Extension: supported_versions (only TLS 1.3) | |
| 223 | 0, 43, // ExtensionType.supported_versions | |
| 224 | 0x00, 0x05, // byte length of this extension payload | |
| 225 | 0x04, // byte length of supported versions | |
| 226 | 0x03, 0x04, // TLS 1.3 | |
| 227 | 0x03, 0x03, // TLS 1.2 | |
| 228 | ||
| 229 | // Extension: signature_algorithms | |
| 230 | 0, 13, // ExtensionType.signature_algorithms | |
| 231 | 0x00, 0x22, // byte length of this extension payload | |
| 232 | 0x00, 0x20, // byte length of signature algorithms list | |
| 233 | 0x04, 0x01, // rsa_pkcs1_sha256 | |
| 234 | 0x05, 0x01, // rsa_pkcs1_sha384 | |
| 235 | 0x06, 0x01, // rsa_pkcs1_sha512 | |
| 236 | 0x04, 0x03, // ecdsa_secp256r1_sha256 | |
| 237 | 0x05, 0x03, // ecdsa_secp384r1_sha384 | |
| 238 | 0x06, 0x03, // ecdsa_secp521r1_sha512 | |
| 239 | 0x08, 0x04, // rsa_pss_rsae_sha256 | |
| 240 | 0x08, 0x05, // rsa_pss_rsae_sha384 | |
| 241 | 0x08, 0x06, // rsa_pss_rsae_sha512 | |
| 242 | 0x08, 0x07, // ed25519 | |
| 243 | 0x08, 0x08, // ed448 | |
| 244 | 0x08, 0x09, // rsa_pss_pss_sha256 | |
| 245 | 0x08, 0x0a, // rsa_pss_pss_sha384 | |
| 246 | 0x08, 0x0b, // rsa_pss_pss_sha512 | |
| 247 | 0x02, 0x01, // rsa_pkcs1_sha1 | |
| 248 | 0x02, 0x03, // ecdsa_sha1 | |
| 249 | ||
| 250 | // Extension: supported_groups | |
| 251 | 0, 10, // ExtensionType.supported_groups | |
| 252 | 0x00, 0x0c, // byte length of this extension payload | |
| 253 | 0x00, 0x0a, // byte length of supported groups list | |
| 254 | 0x00, 0x17, // secp256r1 | |
| 255 | 0x00, 0x18, // secp384r1 | |
| 256 | 0x00, 0x19, // secp521r1 | |
| 257 | 0x00, 0x1D, // x25519 | |
| 258 | 0x00, 0x1E, // x448 | |
| 259 | ||
| 260 | // Extension: key_share | |
| 261 | 0, 51, // ExtensionType.key_share | |
| 262 | 0x00, 38, // byte length of this extension payload | |
| 263 | 0x00, 36, // byte length of client_shares | |
| 264 | 0x00, 0x1D, // NamedGroup.x25519 | |
| 265 | 0x00, 32, // byte length of key_exchange | |
| 266 | } ++ tls.x25519_pub_key ++ [_]u8{ | |
| 267 | ||
| 268 | // Extension: server_name | |
| 269 | 0, 0, // ExtensionType.server_name | |
| 270 | undefined, undefined, // byte length of this extension payload | |
| 271 | undefined, undefined, // server_name_list byte count | |
| 272 | 0x00, // name_type | |
| 273 | undefined, undefined, // host name len | |
| 274 | }; | |
| 275 | ||
| 276 | var hello_header = [_]u8{ | |
| 277 | // Plaintext header | |
| 278 | @enumToInt(ContentType.handshake), | |
| 279 | 0x03, 0x01, // legacy_record_version | |
| 280 | undefined, undefined, // Plaintext fragment length (u16) | |
| 281 | ||
| 282 | // Handshake header | |
| 283 | @enumToInt(HandshakeType.client_hello), | |
| 284 | undefined, undefined, undefined, // handshake length (u24) | |
| 285 | ||
| 286 | // ClientHello | |
| 287 | 0x03, 0x03, // legacy_version | |
| 288 | } ++ rand_buf ++ [1]u8{0} ++ cipher_suites ++ [_]u8{ | |
| 289 | 0x01, 0x00, // legacy_compression_methods | |
| 290 | } ++ extensions_header; | |
| 291 | ||
| 292 | mem.writeIntBig(u16, hello_header[3..][0..2], @intCast(u16, hello_header.len - 5 + host.len)); | |
| 293 | mem.writeIntBig(u24, hello_header[6..][0..3], @intCast(u24, hello_header.len - 9 + host.len)); | |
| 294 | mem.writeIntBig( | |
| 295 | u16, | |
| 296 | hello_header[hello_header.len - extensions_header.len ..][0..2], | |
| 297 | @intCast(u16, extensions_header.len - 2 + host.len), | |
| 298 | ); | |
| 299 | mem.writeIntBig(u16, hello_header[hello_header.len - 7 ..][0..2], @intCast(u16, 5 + host.len)); | |
| 300 | mem.writeIntBig(u16, hello_header[hello_header.len - 5 ..][0..2], @intCast(u16, 3 + host.len)); | |
| 301 | mem.writeIntBig(u16, hello_header[hello_header.len - 2 ..][0..2], @intCast(u16, 0 + host.len)); | |
| 302 | ||
| 303 | var iovecs = [_]std.os.iovec_const{ | |
| 304 | .{ | |
| 305 | .iov_base = &hello_header, | |
| 306 | .iov_len = hello_header.len, | |
| 307 | }, | |
| 308 | .{ | |
| 309 | .iov_base = host.ptr, | |
| 310 | .iov_len = host.len, | |
| 311 | }, | |
| 312 | }; | |
| 313 | try stream.writevAll(&iovecs); | |
| 314 | ||
| 315 | { | |
| 316 | var buf: [1000]u8 = undefined; | |
| 317 | const amt = try stream.read(&buf); | |
| 318 | const resp = buf[0..amt]; | |
| 319 | const ct = @intToEnum(ContentType, resp[0]); | |
| 320 | if (ct == .alert) { | |
| 321 | //const prot_ver = @bitCast(u16, resp[1..][0..2].*); | |
| 322 | const len = std.mem.readIntBig(u16, resp[3..][0..2]); | |
| 323 | const alert = resp[5..][0..len]; | |
| 324 | const level = @intToEnum(AlertLevel, alert[0]); | |
| 325 | const desc = @intToEnum(AlertDescription, alert[1]); | |
| 326 | std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); | |
| 327 | std.process.exit(1); | |
| 328 | } else { | |
| 329 | std.debug.print("content_type: {s}\n", .{@tagName(ct)}); | |
| 330 | std.debug.print("got {d} bytes: {s}\n", .{ amt, std.fmt.fmtSliceHexLower(resp) }); | |
| 331 | } | |
| 332 | } | |
| 333 | ||
| 334 | tls.state = .sent_hello; | |
| 335 | } | |
| 336 | ||
| 337 | pub fn writeAll(tls: *Tls, stream: net.Stream, buffer: []const u8) !void { | |
| 338 | _ = tls; | |
| 339 | _ = stream; | |
| 340 | _ = buffer; | |
| 341 | @panic("hold on a minute, we didn't finish implementing the handshake yet"); | |
| 342 | } |
lib/std/http.zig+247-4| ... | ... | @@ -1,8 +1,251 @@ |
| 1 | const std = @import("std.zig"); | |
| 1 | pub const Client = @import("http/Client.zig"); | |
| 2 | ||
| 3 | /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods | |
| 4 | /// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definiton | |
| 5 | /// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH | |
| 6 | pub const Method = enum { | |
| 7 | GET, | |
| 8 | HEAD, | |
| 9 | POST, | |
| 10 | PUT, | |
| 11 | DELETE, | |
| 12 | CONNECT, | |
| 13 | OPTIONS, | |
| 14 | TRACE, | |
| 15 | PATCH, | |
| 16 | ||
| 17 | /// Returns true if a request of this method is allowed to have a body | |
| 18 | /// Actual behavior from servers may vary and should still be checked | |
| 19 | pub fn requestHasBody(self: Method) bool { | |
| 20 | return switch (self) { | |
| 21 | .POST, .PUT, .PATCH => true, | |
| 22 | .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false, | |
| 23 | }; | |
| 24 | } | |
| 25 | ||
| 26 | /// Returns true if a response to this method is allowed to have a body | |
| 27 | /// Actual behavior from clients may vary and should still be checked | |
| 28 | pub fn responseHasBody(self: Method) bool { | |
| 29 | return switch (self) { | |
| 30 | .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true, | |
| 31 | .HEAD, .PUT, .TRACE => false, | |
| 32 | }; | |
| 33 | } | |
| 34 | ||
| 35 | /// An HTTP method is safe if it doesn't alter the state of the server. | |
| 36 | /// https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP | |
| 37 | /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1 | |
| 38 | pub fn safe(self: Method) bool { | |
| 39 | return switch (self) { | |
| 40 | .GET, .HEAD, .OPTIONS, .TRACE => true, | |
| 41 | .POST, .PUT, .DELETE, .CONNECT, .PATCH => false, | |
| 42 | }; | |
| 43 | } | |
| 44 | ||
| 45 | /// 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. | |
| 46 | /// https://developer.mozilla.org/en-US/docs/Glossary/Idempotent | |
| 47 | /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.2 | |
| 48 | pub fn idempotent(self: Method) bool { | |
| 49 | return switch (self) { | |
| 50 | .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true, | |
| 51 | .CONNECT, .POST, .PATCH => false, | |
| 52 | }; | |
| 53 | } | |
| 54 | ||
| 55 | /// 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. | |
| 56 | /// https://developer.mozilla.org/en-US/docs/Glossary/cacheable | |
| 57 | /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.3 | |
| 58 | pub fn cacheable(self: Method) bool { | |
| 59 | return switch (self) { | |
| 60 | .GET, .HEAD => true, | |
| 61 | .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false, | |
| 62 | }; | |
| 63 | } | |
| 64 | }; | |
| 65 | ||
| 66 | /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status | |
| 67 | pub const Status = enum(u10) { | |
| 68 | @"continue" = 100, // RFC7231, Section 6.2.1 | |
| 69 | switching_protocols = 101, // RFC7231, Section 6.2.2 | |
| 70 | processing = 102, // RFC2518 | |
| 71 | early_hints = 103, // RFC8297 | |
| 72 | ||
| 73 | ok = 200, // RFC7231, Section 6.3.1 | |
| 74 | created = 201, // RFC7231, Section 6.3.2 | |
| 75 | accepted = 202, // RFC7231, Section 6.3.3 | |
| 76 | non_authoritative_info = 203, // RFC7231, Section 6.3.4 | |
| 77 | no_content = 204, // RFC7231, Section 6.3.5 | |
| 78 | reset_content = 205, // RFC7231, Section 6.3.6 | |
| 79 | partial_content = 206, // RFC7233, Section 4.1 | |
| 80 | multi_status = 207, // RFC4918 | |
| 81 | already_reported = 208, // RFC5842 | |
| 82 | im_used = 226, // RFC3229 | |
| 83 | ||
| 84 | multiple_choice = 300, // RFC7231, Section 6.4.1 | |
| 85 | moved_permanently = 301, // RFC7231, Section 6.4.2 | |
| 86 | found = 302, // RFC7231, Section 6.4.3 | |
| 87 | see_other = 303, // RFC7231, Section 6.4.4 | |
| 88 | not_modified = 304, // RFC7232, Section 4.1 | |
| 89 | use_proxy = 305, // RFC7231, Section 6.4.5 | |
| 90 | temporary_redirect = 307, // RFC7231, Section 6.4.7 | |
| 91 | permanent_redirect = 308, // RFC7538 | |
| 92 | ||
| 93 | bad_request = 400, // RFC7231, Section 6.5.1 | |
| 94 | unauthorized = 401, // RFC7235, Section 3.1 | |
| 95 | payment_required = 402, // RFC7231, Section 6.5.2 | |
| 96 | forbidden = 403, // RFC7231, Section 6.5.3 | |
| 97 | not_found = 404, // RFC7231, Section 6.5.4 | |
| 98 | method_not_allowed = 405, // RFC7231, Section 6.5.5 | |
| 99 | not_acceptable = 406, // RFC7231, Section 6.5.6 | |
| 100 | proxy_auth_required = 407, // RFC7235, Section 3.2 | |
| 101 | request_timeout = 408, // RFC7231, Section 6.5.7 | |
| 102 | conflict = 409, // RFC7231, Section 6.5.8 | |
| 103 | gone = 410, // RFC7231, Section 6.5.9 | |
| 104 | length_required = 411, // RFC7231, Section 6.5.10 | |
| 105 | precondition_failed = 412, // RFC7232, Section 4.2][RFC8144, Section 3.2 | |
| 106 | payload_too_large = 413, // RFC7231, Section 6.5.11 | |
| 107 | uri_too_long = 414, // RFC7231, Section 6.5.12 | |
| 108 | unsupported_media_type = 415, // RFC7231, Section 6.5.13][RFC7694, Section 3 | |
| 109 | range_not_satisfiable = 416, // RFC7233, Section 4.4 | |
| 110 | expectation_failed = 417, // RFC7231, Section 6.5.14 | |
| 111 | teapot = 418, // RFC 7168, 2.3.3 | |
| 112 | misdirected_request = 421, // RFC7540, Section 9.1.2 | |
| 113 | unprocessable_entity = 422, // RFC4918 | |
| 114 | locked = 423, // RFC4918 | |
| 115 | failed_dependency = 424, // RFC4918 | |
| 116 | too_early = 425, // RFC8470 | |
| 117 | upgrade_required = 426, // RFC7231, Section 6.5.15 | |
| 118 | precondition_required = 428, // RFC6585 | |
| 119 | too_many_requests = 429, // RFC6585 | |
| 120 | header_fields_too_large = 431, // RFC6585 | |
| 121 | unavailable_for_legal_reasons = 451, // RFC7725 | |
| 122 | ||
| 123 | internal_server_error = 500, // RFC7231, Section 6.6.1 | |
| 124 | not_implemented = 501, // RFC7231, Section 6.6.2 | |
| 125 | bad_gateway = 502, // RFC7231, Section 6.6.3 | |
| 126 | service_unavailable = 503, // RFC7231, Section 6.6.4 | |
| 127 | gateway_timeout = 504, // RFC7231, Section 6.6.5 | |
| 128 | http_version_not_supported = 505, // RFC7231, Section 6.6.6 | |
| 129 | variant_also_negotiates = 506, // RFC2295 | |
| 130 | insufficient_storage = 507, // RFC4918 | |
| 131 | loop_detected = 508, // RFC5842 | |
| 132 | not_extended = 510, // RFC2774 | |
| 133 | network_authentication_required = 511, // RFC6585 | |
| 2 | 134 | |
| 3 | pub const Method = @import("http/method.zig").Method; | |
| 4 | pub const Status = @import("http/status.zig").Status; | |
| 135 | _, | |
| 136 | ||
| 137 | pub fn phrase(self: Status) ?[]const u8 { | |
| 138 | return switch (self) { | |
| 139 | // 1xx statuses | |
| 140 | .@"continue" => "Continue", | |
| 141 | .switching_protocols => "Switching Protocols", | |
| 142 | .processing => "Processing", | |
| 143 | .early_hints => "Early Hints", | |
| 144 | ||
| 145 | // 2xx statuses | |
| 146 | .ok => "OK", | |
| 147 | .created => "Created", | |
| 148 | .accepted => "Accepted", | |
| 149 | .non_authoritative_info => "Non-Authoritative Information", | |
| 150 | .no_content => "No Content", | |
| 151 | .reset_content => "Reset Content", | |
| 152 | .partial_content => "Partial Content", | |
| 153 | .multi_status => "Multi-Status", | |
| 154 | .already_reported => "Already Reported", | |
| 155 | .im_used => "IM Used", | |
| 156 | ||
| 157 | // 3xx statuses | |
| 158 | .multiple_choice => "Multiple Choice", | |
| 159 | .moved_permanently => "Moved Permanently", | |
| 160 | .found => "Found", | |
| 161 | .see_other => "See Other", | |
| 162 | .not_modified => "Not Modified", | |
| 163 | .use_proxy => "Use Proxy", | |
| 164 | .temporary_redirect => "Temporary Redirect", | |
| 165 | .permanent_redirect => "Permanent Redirect", | |
| 166 | ||
| 167 | // 4xx statuses | |
| 168 | .bad_request => "Bad Request", | |
| 169 | .unauthorized => "Unauthorized", | |
| 170 | .payment_required => "Payment Required", | |
| 171 | .forbidden => "Forbidden", | |
| 172 | .not_found => "Not Found", | |
| 173 | .method_not_allowed => "Method Not Allowed", | |
| 174 | .not_acceptable => "Not Acceptable", | |
| 175 | .proxy_auth_required => "Proxy Authentication Required", | |
| 176 | .request_timeout => "Request Timeout", | |
| 177 | .conflict => "Conflict", | |
| 178 | .gone => "Gone", | |
| 179 | .length_required => "Length Required", | |
| 180 | .precondition_failed => "Precondition Failed", | |
| 181 | .payload_too_large => "Payload Too Large", | |
| 182 | .uri_too_long => "URI Too Long", | |
| 183 | .unsupported_media_type => "Unsupported Media Type", | |
| 184 | .range_not_satisfiable => "Range Not Satisfiable", | |
| 185 | .expectation_failed => "Expectation Failed", | |
| 186 | .teapot => "I'm a teapot", | |
| 187 | .misdirected_request => "Misdirected Request", | |
| 188 | .unprocessable_entity => "Unprocessable Entity", | |
| 189 | .locked => "Locked", | |
| 190 | .failed_dependency => "Failed Dependency", | |
| 191 | .too_early => "Too Early", | |
| 192 | .upgrade_required => "Upgrade Required", | |
| 193 | .precondition_required => "Precondition Required", | |
| 194 | .too_many_requests => "Too Many Requests", | |
| 195 | .header_fields_too_large => "Request Header Fields Too Large", | |
| 196 | .unavailable_for_legal_reasons => "Unavailable For Legal Reasons", | |
| 197 | ||
| 198 | // 5xx statuses | |
| 199 | .internal_server_error => "Internal Server Error", | |
| 200 | .not_implemented => "Not Implemented", | |
| 201 | .bad_gateway => "Bad Gateway", | |
| 202 | .service_unavailable => "Service Unavailable", | |
| 203 | .gateway_timeout => "Gateway Timeout", | |
| 204 | .http_version_not_supported => "HTTP Version Not Supported", | |
| 205 | .variant_also_negotiates => "Variant Also Negotiates", | |
| 206 | .insufficient_storage => "Insufficient Storage", | |
| 207 | .loop_detected => "Loop Detected", | |
| 208 | .not_extended => "Not Extended", | |
| 209 | .network_authentication_required => "Network Authentication Required", | |
| 210 | ||
| 211 | else => return null, | |
| 212 | }; | |
| 213 | } | |
| 214 | ||
| 215 | pub const Class = enum { | |
| 216 | informational, | |
| 217 | success, | |
| 218 | redirect, | |
| 219 | client_error, | |
| 220 | server_error, | |
| 221 | }; | |
| 222 | ||
| 223 | pub fn class(self: Status) ?Class { | |
| 224 | return switch (@enumToInt(self)) { | |
| 225 | 100...199 => .informational, | |
| 226 | 200...299 => .success, | |
| 227 | 300...399 => .redirect, | |
| 228 | 400...499 => .client_error, | |
| 229 | 500...599 => .server_error, | |
| 230 | else => null, | |
| 231 | }; | |
| 232 | } | |
| 233 | ||
| 234 | test { | |
| 235 | try std.testing.expectEqualStrings("OK", Status.ok.phrase().?); | |
| 236 | try std.testing.expectEqualStrings("Not Found", Status.not_found.phrase().?); | |
| 237 | } | |
| 238 | ||
| 239 | test { | |
| 240 | try std.testing.expectEqual(@as(?Status.Class, Status.Class.success), Status.ok.class()); | |
| 241 | try std.testing.expectEqual(@as(?Status.Class, Status.Class.client_error), Status.not_found.class()); | |
| 242 | } | |
| 243 | }; | |
| 244 | ||
| 245 | const std = @import("std.zig"); | |
| 5 | 246 | |
| 6 | 247 | test { |
| 7 | std.testing.refAllDecls(@This()); | |
| 248 | _ = Client; | |
| 249 | _ = Method; | |
| 250 | _ = Status; | |
| 8 | 251 | } |
lib/std/http/Client.zig created+114| ... | ... | @@ -0,0 +1,114 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const http = std.http; | |
| 4 | const net = std.net; | |
| 5 | const Client = @This(); | |
| 6 | ||
| 7 | allocator: std.mem.Allocator, | |
| 8 | headers: std.ArrayListUnmanaged(u8) = .{}, | |
| 9 | active_requests: usize = 0, | |
| 10 | ||
| 11 | pub const Request = struct { | |
| 12 | client: *Client, | |
| 13 | stream: net.Stream, | |
| 14 | headers: std.ArrayListUnmanaged(u8) = .{}, | |
| 15 | tls: std.crypto.Tls = .{}, | |
| 16 | protocol: Protocol, | |
| 17 | ||
| 18 | pub const Protocol = enum { http, https }; | |
| 19 | ||
| 20 | pub const Options = struct { | |
| 21 | family: Family = .any, | |
| 22 | protocol: Protocol = .https, | |
| 23 | method: http.Method = .GET, | |
| 24 | host: []const u8 = "localhost", | |
| 25 | path: []const u8 = "/", | |
| 26 | port: u16 = 0, | |
| 27 | ||
| 28 | pub const Family = enum { any, ip4, ip6 }; | |
| 29 | }; | |
| 30 | ||
| 31 | pub fn deinit(req: *Request) void { | |
| 32 | req.client.active_requests -= 1; | |
| 33 | req.headers.deinit(req.client.allocator); | |
| 34 | req.* = undefined; | |
| 35 | } | |
| 36 | ||
| 37 | pub fn addHeader(req: *Request, name: []const u8, value: []const u8) !void { | |
| 38 | const gpa = req.client.allocator; | |
| 39 | // Ensure an extra +2 for the \r\n in end() | |
| 40 | try req.headers.ensureUnusedCapacity(gpa, name.len + value.len + 6); | |
| 41 | req.headers.appendSliceAssumeCapacity(name); | |
| 42 | req.headers.appendSliceAssumeCapacity(": "); | |
| 43 | req.headers.appendSliceAssumeCapacity(value); | |
| 44 | req.headers.appendSliceAssumeCapacity("\r\n"); | |
| 45 | } | |
| 46 | ||
| 47 | pub fn end(req: *Request) !void { | |
| 48 | req.headers.appendSliceAssumeCapacity("\r\n"); | |
| 49 | switch (req.protocol) { | |
| 50 | .http => { | |
| 51 | try req.stream.writeAll(req.headers.items); | |
| 52 | }, | |
| 53 | .https => { | |
| 54 | try req.tls.writeAll(req.stream, req.headers.items); | |
| 55 | }, | |
| 56 | } | |
| 57 | } | |
| 58 | }; | |
| 59 | ||
| 60 | pub fn deinit(client: *Client) void { | |
| 61 | assert(client.active_requests == 0); | |
| 62 | client.headers.denit(client.allocator); | |
| 63 | client.* = undefined; | |
| 64 | } | |
| 65 | ||
| 66 | pub fn request(client: *Client, options: Request.Options) !Request { | |
| 67 | var req: Request = .{ | |
| 68 | .client = client, | |
| 69 | .stream = try net.tcpConnectToHost(client.allocator, options.host, options.port), | |
| 70 | .protocol = options.protocol, | |
| 71 | }; | |
| 72 | errdefer req.deinit(); | |
| 73 | ||
| 74 | switch (options.protocol) { | |
| 75 | .http => {}, | |
| 76 | .https => { | |
| 77 | try req.tls.init(req.stream, options.host); | |
| 78 | }, | |
| 79 | } | |
| 80 | ||
| 81 | try req.headers.ensureUnusedCapacity( | |
| 82 | client.allocator, | |
| 83 | @tagName(options.method).len + | |
| 84 | 1 + | |
| 85 | options.path.len + | |
| 86 | " HTTP/2\r\nHost: ".len + | |
| 87 | options.host.len + | |
| 88 | "\r\nUpgrade-Insecure-Requests: 1\r\n".len + | |
| 89 | client.headers.items.len + | |
| 90 | 2, // for the \r\n at the end of headers | |
| 91 | ); | |
| 92 | req.headers.appendSliceAssumeCapacity(@tagName(options.method)); | |
| 93 | req.headers.appendSliceAssumeCapacity(" "); | |
| 94 | req.headers.appendSliceAssumeCapacity(options.path); | |
| 95 | req.headers.appendSliceAssumeCapacity(" HTTP/2\r\nHost: "); | |
| 96 | req.headers.appendSliceAssumeCapacity(options.host); | |
| 97 | switch (options.protocol) { | |
| 98 | .https => req.headers.appendSliceAssumeCapacity("\r\nUpgrade-Insecure-Requests: 1\r\n"), | |
| 99 | .http => req.headers.appendSliceAssumeCapacity("\r\n"), | |
| 100 | } | |
| 101 | req.headers.appendSliceAssumeCapacity(client.headers.items); | |
| 102 | ||
| 103 | client.active_requests += 1; | |
| 104 | return req; | |
| 105 | } | |
| 106 | ||
| 107 | pub fn addHeader(client: *Client, name: []const u8, value: []const u8) !void { | |
| 108 | const gpa = client.allocator; | |
| 109 | try client.headers.ensureUnusedCapacity(gpa, name.len + value.len + 4); | |
| 110 | client.headers.appendSliceAssumeCapacity(name); | |
| 111 | client.headers.appendSliceAssumeCapacity(": "); | |
| 112 | client.headers.appendSliceAssumeCapacity(value); | |
| 113 | client.headers.appendSliceAssumeCapacity("\r\n"); | |
| 114 | } |
lib/std/http/method.zig deleted-65| ... | ... | @@ -1,65 +0,0 @@ |
| 1 | //! HTTP Methods | |
| 2 | //! https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods | |
| 3 | ||
| 4 | // Style guide is violated here so that @tagName can be used effectively | |
| 5 | /// https://datatracker.ietf.org/doc/html/rfc7231#section-4 Initial definiton | |
| 6 | /// https://datatracker.ietf.org/doc/html/rfc5789#section-2 PATCH | |
| 7 | pub const Method = enum { | |
| 8 | GET, | |
| 9 | HEAD, | |
| 10 | POST, | |
| 11 | PUT, | |
| 12 | DELETE, | |
| 13 | CONNECT, | |
| 14 | OPTIONS, | |
| 15 | TRACE, | |
| 16 | PATCH, | |
| 17 | ||
| 18 | /// Returns true if a request of this method is allowed to have a body | |
| 19 | /// Actual behavior from servers may vary and should still be checked | |
| 20 | pub fn requestHasBody(self: Method) bool { | |
| 21 | return switch (self) { | |
| 22 | .POST, .PUT, .PATCH => true, | |
| 23 | .GET, .HEAD, .DELETE, .CONNECT, .OPTIONS, .TRACE => false, | |
| 24 | }; | |
| 25 | } | |
| 26 | ||
| 27 | /// Returns true if a response to this method is allowed to have a body | |
| 28 | /// Actual behavior from clients may vary and should still be checked | |
| 29 | pub fn responseHasBody(self: Method) bool { | |
| 30 | return switch (self) { | |
| 31 | .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true, | |
| 32 | .HEAD, .PUT, .TRACE => false, | |
| 33 | }; | |
| 34 | } | |
| 35 | ||
| 36 | /// An HTTP method is safe if it doesn't alter the state of the server. | |
| 37 | /// https://developer.mozilla.org/en-US/docs/Glossary/Safe/HTTP | |
| 38 | /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1 | |
| 39 | pub fn safe(self: Method) bool { | |
| 40 | return switch (self) { | |
| 41 | .GET, .HEAD, .OPTIONS, .TRACE => true, | |
| 42 | .POST, .PUT, .DELETE, .CONNECT, .PATCH => false, | |
| 43 | }; | |
| 44 | } | |
| 45 | ||
| 46 | /// 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. | |
| 47 | /// https://developer.mozilla.org/en-US/docs/Glossary/Idempotent | |
| 48 | /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.2 | |
| 49 | pub fn idempotent(self: Method) bool { | |
| 50 | return switch (self) { | |
| 51 | .GET, .HEAD, .PUT, .DELETE, .OPTIONS, .TRACE => true, | |
| 52 | .CONNECT, .POST, .PATCH => false, | |
| 53 | }; | |
| 54 | } | |
| 55 | ||
| 56 | /// 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. | |
| 57 | /// https://developer.mozilla.org/en-US/docs/Glossary/cacheable | |
| 58 | /// https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.3 | |
| 59 | pub fn cacheable(self: Method) bool { | |
| 60 | return switch (self) { | |
| 61 | .GET, .HEAD => true, | |
| 62 | .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .TRACE, .PATCH => false, | |
| 63 | }; | |
| 64 | } | |
| 65 | }; |
lib/std/http/status.zig deleted-182| ... | ... | @@ -1,182 +0,0 @@ |
| 1 | //! HTTP Status | |
| 2 | //! https://developer.mozilla.org/en-US/docs/Web/HTTP/Status | |
| 3 | ||
| 4 | const std = @import("../std.zig"); | |
| 5 | ||
| 6 | pub const Status = enum(u10) { | |
| 7 | @"continue" = 100, // RFC7231, Section 6.2.1 | |
| 8 | switching_protocols = 101, // RFC7231, Section 6.2.2 | |
| 9 | processing = 102, // RFC2518 | |
| 10 | early_hints = 103, // RFC8297 | |
| 11 | ||
| 12 | ok = 200, // RFC7231, Section 6.3.1 | |
| 13 | created = 201, // RFC7231, Section 6.3.2 | |
| 14 | accepted = 202, // RFC7231, Section 6.3.3 | |
| 15 | non_authoritative_info = 203, // RFC7231, Section 6.3.4 | |
| 16 | no_content = 204, // RFC7231, Section 6.3.5 | |
| 17 | reset_content = 205, // RFC7231, Section 6.3.6 | |
| 18 | partial_content = 206, // RFC7233, Section 4.1 | |
| 19 | multi_status = 207, // RFC4918 | |
| 20 | already_reported = 208, // RFC5842 | |
| 21 | im_used = 226, // RFC3229 | |
| 22 | ||
| 23 | multiple_choice = 300, // RFC7231, Section 6.4.1 | |
| 24 | moved_permanently = 301, // RFC7231, Section 6.4.2 | |
| 25 | found = 302, // RFC7231, Section 6.4.3 | |
| 26 | see_other = 303, // RFC7231, Section 6.4.4 | |
| 27 | not_modified = 304, // RFC7232, Section 4.1 | |
| 28 | use_proxy = 305, // RFC7231, Section 6.4.5 | |
| 29 | temporary_redirect = 307, // RFC7231, Section 6.4.7 | |
| 30 | permanent_redirect = 308, // RFC7538 | |
| 31 | ||
| 32 | bad_request = 400, // RFC7231, Section 6.5.1 | |
| 33 | unauthorized = 401, // RFC7235, Section 3.1 | |
| 34 | payment_required = 402, // RFC7231, Section 6.5.2 | |
| 35 | forbidden = 403, // RFC7231, Section 6.5.3 | |
| 36 | not_found = 404, // RFC7231, Section 6.5.4 | |
| 37 | method_not_allowed = 405, // RFC7231, Section 6.5.5 | |
| 38 | not_acceptable = 406, // RFC7231, Section 6.5.6 | |
| 39 | proxy_auth_required = 407, // RFC7235, Section 3.2 | |
| 40 | request_timeout = 408, // RFC7231, Section 6.5.7 | |
| 41 | conflict = 409, // RFC7231, Section 6.5.8 | |
| 42 | gone = 410, // RFC7231, Section 6.5.9 | |
| 43 | length_required = 411, // RFC7231, Section 6.5.10 | |
| 44 | precondition_failed = 412, // RFC7232, Section 4.2][RFC8144, Section 3.2 | |
| 45 | payload_too_large = 413, // RFC7231, Section 6.5.11 | |
| 46 | uri_too_long = 414, // RFC7231, Section 6.5.12 | |
| 47 | unsupported_media_type = 415, // RFC7231, Section 6.5.13][RFC7694, Section 3 | |
| 48 | range_not_satisfiable = 416, // RFC7233, Section 4.4 | |
| 49 | expectation_failed = 417, // RFC7231, Section 6.5.14 | |
| 50 | teapot = 418, // RFC 7168, 2.3.3 | |
| 51 | misdirected_request = 421, // RFC7540, Section 9.1.2 | |
| 52 | unprocessable_entity = 422, // RFC4918 | |
| 53 | locked = 423, // RFC4918 | |
| 54 | failed_dependency = 424, // RFC4918 | |
| 55 | too_early = 425, // RFC8470 | |
| 56 | upgrade_required = 426, // RFC7231, Section 6.5.15 | |
| 57 | precondition_required = 428, // RFC6585 | |
| 58 | too_many_requests = 429, // RFC6585 | |
| 59 | header_fields_too_large = 431, // RFC6585 | |
| 60 | unavailable_for_legal_reasons = 451, // RFC7725 | |
| 61 | ||
| 62 | internal_server_error = 500, // RFC7231, Section 6.6.1 | |
| 63 | not_implemented = 501, // RFC7231, Section 6.6.2 | |
| 64 | bad_gateway = 502, // RFC7231, Section 6.6.3 | |
| 65 | service_unavailable = 503, // RFC7231, Section 6.6.4 | |
| 66 | gateway_timeout = 504, // RFC7231, Section 6.6.5 | |
| 67 | http_version_not_supported = 505, // RFC7231, Section 6.6.6 | |
| 68 | variant_also_negotiates = 506, // RFC2295 | |
| 69 | insufficient_storage = 507, // RFC4918 | |
| 70 | loop_detected = 508, // RFC5842 | |
| 71 | not_extended = 510, // RFC2774 | |
| 72 | network_authentication_required = 511, // RFC6585 | |
| 73 | ||
| 74 | _, | |
| 75 | ||
| 76 | pub fn phrase(self: Status) ?[]const u8 { | |
| 77 | return switch (self) { | |
| 78 | // 1xx statuses | |
| 79 | .@"continue" => "Continue", | |
| 80 | .switching_protocols => "Switching Protocols", | |
| 81 | .processing => "Processing", | |
| 82 | .early_hints => "Early Hints", | |
| 83 | ||
| 84 | // 2xx statuses | |
| 85 | .ok => "OK", | |
| 86 | .created => "Created", | |
| 87 | .accepted => "Accepted", | |
| 88 | .non_authoritative_info => "Non-Authoritative Information", | |
| 89 | .no_content => "No Content", | |
| 90 | .reset_content => "Reset Content", | |
| 91 | .partial_content => "Partial Content", | |
| 92 | .multi_status => "Multi-Status", | |
| 93 | .already_reported => "Already Reported", | |
| 94 | .im_used => "IM Used", | |
| 95 | ||
| 96 | // 3xx statuses | |
| 97 | .multiple_choice => "Multiple Choice", | |
| 98 | .moved_permanently => "Moved Permanently", | |
| 99 | .found => "Found", | |
| 100 | .see_other => "See Other", | |
| 101 | .not_modified => "Not Modified", | |
| 102 | .use_proxy => "Use Proxy", | |
| 103 | .temporary_redirect => "Temporary Redirect", | |
| 104 | .permanent_redirect => "Permanent Redirect", | |
| 105 | ||
| 106 | // 4xx statuses | |
| 107 | .bad_request => "Bad Request", | |
| 108 | .unauthorized => "Unauthorized", | |
| 109 | .payment_required => "Payment Required", | |
| 110 | .forbidden => "Forbidden", | |
| 111 | .not_found => "Not Found", | |
| 112 | .method_not_allowed => "Method Not Allowed", | |
| 113 | .not_acceptable => "Not Acceptable", | |
| 114 | .proxy_auth_required => "Proxy Authentication Required", | |
| 115 | .request_timeout => "Request Timeout", | |
| 116 | .conflict => "Conflict", | |
| 117 | .gone => "Gone", | |
| 118 | .length_required => "Length Required", | |
| 119 | .precondition_failed => "Precondition Failed", | |
| 120 | .payload_too_large => "Payload Too Large", | |
| 121 | .uri_too_long => "URI Too Long", | |
| 122 | .unsupported_media_type => "Unsupported Media Type", | |
| 123 | .range_not_satisfiable => "Range Not Satisfiable", | |
| 124 | .expectation_failed => "Expectation Failed", | |
| 125 | .teapot => "I'm a teapot", | |
| 126 | .misdirected_request => "Misdirected Request", | |
| 127 | .unprocessable_entity => "Unprocessable Entity", | |
| 128 | .locked => "Locked", | |
| 129 | .failed_dependency => "Failed Dependency", | |
| 130 | .too_early => "Too Early", | |
| 131 | .upgrade_required => "Upgrade Required", | |
| 132 | .precondition_required => "Precondition Required", | |
| 133 | .too_many_requests => "Too Many Requests", | |
| 134 | .header_fields_too_large => "Request Header Fields Too Large", | |
| 135 | .unavailable_for_legal_reasons => "Unavailable For Legal Reasons", | |
| 136 | ||
| 137 | // 5xx statuses | |
| 138 | .internal_server_error => "Internal Server Error", | |
| 139 | .not_implemented => "Not Implemented", | |
| 140 | .bad_gateway => "Bad Gateway", | |
| 141 | .service_unavailable => "Service Unavailable", | |
| 142 | .gateway_timeout => "Gateway Timeout", | |
| 143 | .http_version_not_supported => "HTTP Version Not Supported", | |
| 144 | .variant_also_negotiates => "Variant Also Negotiates", | |
| 145 | .insufficient_storage => "Insufficient Storage", | |
| 146 | .loop_detected => "Loop Detected", | |
| 147 | .not_extended => "Not Extended", | |
| 148 | .network_authentication_required => "Network Authentication Required", | |
| 149 | ||
| 150 | else => return null, | |
| 151 | }; | |
| 152 | } | |
| 153 | ||
| 154 | pub const Class = enum { | |
| 155 | informational, | |
| 156 | success, | |
| 157 | redirect, | |
| 158 | client_error, | |
| 159 | server_error, | |
| 160 | }; | |
| 161 | ||
| 162 | pub fn class(self: Status) ?Class { | |
| 163 | return switch (@enumToInt(self)) { | |
| 164 | 100...199 => .informational, | |
| 165 | 200...299 => .success, | |
| 166 | 300...399 => .redirect, | |
| 167 | 400...499 => .client_error, | |
| 168 | 500...599 => .server_error, | |
| 169 | else => null, | |
| 170 | }; | |
| 171 | } | |
| 172 | }; | |
| 173 | ||
| 174 | test { | |
| 175 | try std.testing.expectEqualStrings("OK", Status.ok.phrase().?); | |
| 176 | try std.testing.expectEqualStrings("Not Found", Status.not_found.phrase().?); | |
| 177 | } | |
| 178 | ||
| 179 | test { | |
| 180 | try std.testing.expectEqual(@as(?Status.Class, Status.Class.success), Status.ok.class()); | |
| 181 | try std.testing.expectEqual(@as(?Status.Class, Status.Class.client_error), Status.not_found.class()); | |
| 182 | } |
lib/std/net.zig+7| ... | ... | @@ -1687,6 +1687,13 @@ pub const Stream = struct { |
| 1687 | 1687 | } |
| 1688 | 1688 | } |
| 1689 | 1689 | |
| 1690 | pub fn writeAll(self: Stream, bytes: []const u8) WriteError!void { | |
| 1691 | var index: usize = 0; | |
| 1692 | while (index < bytes.len) { | |
| 1693 | index += try self.write(bytes[index..]); | |
| 1694 | } | |
| 1695 | } | |
| 1696 | ||
| 1690 | 1697 | /// See https://github.com/ziglang/zig/issues/7699 |
| 1691 | 1698 | /// See equivalent function: `std.fs.File.writev`. |
| 1692 | 1699 | pub fn writev(self: Stream, iovecs: []const os.iovec_const) WriteError!usize { |