| 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,6 +176,8 @@ const std = @import("std.zig"); |
| 176 | 176 | ||
| 177 | pub const errors = @import("crypto/errors.zig"); | 177 | pub const errors = @import("crypto/errors.zig"); |
| 178 | 178 | ||
| 179 | pub const Tls = @import("crypto/Tls.zig"); | ||
| 180 | |||
| 179 | test { | 181 | test { |
| 180 | _ = aead.aegis.Aegis128L; | 182 | _ = aead.aegis.Aegis128L; |
| 181 | _ = aead.aegis.Aegis256; | 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,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; | 135 | _, |
| 4 | pub const Status = @import("http/status.zig").Status; | 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 | test { | 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,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 | /// See https://github.com/ziglang/zig/issues/7699 | 1697 | /// See https://github.com/ziglang/zig/issues/7699 |
| 1691 | /// See equivalent function: `std.fs.File.writev`. | 1698 | /// See equivalent function: `std.fs.File.writev`. |
| 1692 | pub fn writev(self: Stream, iovecs: []const os.iovec_const) WriteError!usize { | 1699 | pub fn writev(self: Stream, iovecs: []const os.iovec_const) WriteError!usize { |