| author | |
| committer | |
| log | 98ee9360555730328621aa8b9b9170a4e2b0df7b |
| tree | 45b19ca078a8600129b4b2a78d0b8f7227854533 |
| parent | 8f06754a06996e1114e5ada9644fa36da4908642 |
7 files changed, 298 insertions(+), 245 deletions(-)
lib/std/Io/Writer.zig+77-19| ... | @@ -191,29 +191,87 @@ pub fn writeSplatHeader( | ... | @@ -191,29 +191,87 @@ pub fn writeSplatHeader( |
| 191 | data: []const []const u8, | 191 | data: []const []const u8, |
| 192 | splat: usize, | 192 | splat: usize, |
| 193 | ) Error!usize { | 193 | ) Error!usize { |
| 194 | const new_end = w.end + header.len; | 194 | return writeSplatHeaderLimit(w, header, data, splat, .unlimited); |
| 195 | if (new_end <= w.buffer.len) { | 195 | } |
| 196 | @memcpy(w.buffer[w.end..][0..header.len], header); | 196 | |
| 197 | w.end = new_end; | 197 | /// Equivalent to `writeSplatHeader` but writes at most `limit` bytes. |
| 198 | return header.len + try writeSplat(w, data, splat); | 198 | pub fn writeSplatHeaderLimit( |
| 199 | } | 199 | w: *Writer, |
| 200 | var vecs: [8][]const u8 = undefined; // Arbitrarily chosen size. | 200 | header: []const u8, |
| 201 | var i: usize = 1; | 201 | data: []const []const u8, |
| 202 | vecs[0] = header; | 202 | splat: usize, |
| 203 | for (data[0 .. data.len - 1]) |buf| { | 203 | limit: Limit, |
| 204 | if (buf.len == 0) continue; | 204 | ) Error!usize { |
| 205 | vecs[i] = buf; | 205 | var remaining = @intFromEnum(limit); |
| 206 | i += 1; | 206 | { |
| 207 | if (vecs.len - i == 0) break; | 207 | const copy_len = @min(header.len, w.buffer.len - w.end, remaining); |
| 208 | if (header.len - copy_len != 0) return writeSplatHeaderLimitFinish(w, header, data, splat, remaining); | ||
| 209 | @memcpy(w.buffer[w.end..][0..copy_len], header[0..copy_len]); | ||
| 210 | w.end += copy_len; | ||
| 211 | remaining -= copy_len; | ||
| 212 | } | ||
| 213 | for (data[0 .. data.len - 1], 0..) |buf, i| { | ||
| 214 | const copy_len = @min(buf.len, w.buffer.len - w.end, remaining); | ||
| 215 | if (buf.len - copy_len != 0) return @intFromEnum(limit) - remaining + | ||
| 216 | try writeSplatHeaderLimitFinish(w, &.{}, data[i..], splat, remaining); | ||
| 217 | @memcpy(w.buffer[w.end..][0..copy_len], buf[0..copy_len]); | ||
| 218 | w.end += copy_len; | ||
| 219 | remaining -= copy_len; | ||
| 208 | } | 220 | } |
| 209 | const pattern = data[data.len - 1]; | 221 | const pattern = data[data.len - 1]; |
| 210 | const new_splat = s: { | 222 | const splat_n = pattern.len * splat; |
| 211 | if (pattern.len == 0 or vecs.len - i == 0) break :s 1; | 223 | if (splat_n > @min(w.buffer.len - w.end, remaining)) { |
| 224 | const buffered_n = @intFromEnum(limit) - remaining; | ||
| 225 | const written = try writeSplatHeaderLimitFinish(w, &.{}, data[data.len - 1 ..][0..1], splat, remaining); | ||
| 226 | return buffered_n + written; | ||
| 227 | } | ||
| 228 | |||
| 229 | for (0..splat) |_| { | ||
| 230 | @memcpy(w.buffer[w.end..][0..pattern.len], pattern); | ||
| 231 | w.end += pattern.len; | ||
| 232 | } | ||
| 233 | |||
| 234 | remaining -= splat_n; | ||
| 235 | return @intFromEnum(limit) - remaining; | ||
| 236 | } | ||
| 237 | |||
| 238 | fn writeSplatHeaderLimitFinish( | ||
| 239 | w: *Writer, | ||
| 240 | header: []const u8, | ||
| 241 | data: []const []const u8, | ||
| 242 | splat: usize, | ||
| 243 | limit: usize, | ||
| 244 | ) Error!usize { | ||
| 245 | var remaining = limit; | ||
| 246 | var vecs: [8][]const u8 = undefined; | ||
| 247 | var i: usize = 0; | ||
| 248 | v: { | ||
| 249 | if (header.len != 0) { | ||
| 250 | const copy_len = @min(header.len, remaining); | ||
| 251 | vecs[i] = header[0..copy_len]; | ||
| 252 | i += 1; | ||
| 253 | remaining -= copy_len; | ||
| 254 | if (remaining == 0) break :v; | ||
| 255 | } | ||
| 256 | for (data[0 .. data.len - 1]) |buf| if (buf.len != 0) { | ||
| 257 | const copy_len = @min(header.len, remaining); | ||
| 258 | vecs[i] = buf; | ||
| 259 | i += 1; | ||
| 260 | remaining -= copy_len; | ||
| 261 | if (remaining == 0) break :v; | ||
| 262 | if (vecs.len - i == 0) break :v; | ||
| 263 | }; | ||
| 264 | const pattern = data[data.len - 1]; | ||
| 265 | if (splat == 1) { | ||
| 266 | vecs[i] = pattern[0..@min(remaining, pattern.len)]; | ||
| 267 | i += 1; | ||
| 268 | break :v; | ||
| 269 | } | ||
| 212 | vecs[i] = pattern; | 270 | vecs[i] = pattern; |
| 213 | i += 1; | 271 | i += 1; |
| 214 | break :s splat; | 272 | return w.vtable.drain(w, (&vecs)[0..i], @min(remaining / pattern.len, splat)); |
| 215 | }; | 273 | } |
| 216 | return w.vtable.drain(w, vecs[0..i], new_splat); | 274 | return w.vtable.drain(w, (&vecs)[0..i], 1); |
| 217 | } | 275 | } |
| 218 | 276 | ||
| 219 | test "writeSplatHeader splatting avoids buffer aliasing temptation" { | 277 | test "writeSplatHeader splatting avoids buffer aliasing temptation" { |
lib/std/Uri.zig+90-146| ... | @@ -4,6 +4,8 @@ | ... | @@ -4,6 +4,8 @@ |
| 4 | const std = @import("std.zig"); | 4 | const std = @import("std.zig"); |
| 5 | const testing = std.testing; | 5 | const testing = std.testing; |
| 6 | const Uri = @This(); | 6 | const Uri = @This(); |
| 7 | const Allocator = std.mem.Allocator; | ||
| 8 | const Writer = std.Io.Writer; | ||
| 7 | 9 | ||
| 8 | scheme: []const u8, | 10 | scheme: []const u8, |
| 9 | user: ?Component = null, | 11 | user: ?Component = null, |
| ... | @@ -14,6 +16,32 @@ path: Component = Component.empty, | ... | @@ -14,6 +16,32 @@ path: Component = Component.empty, |
| 14 | query: ?Component = null, | 16 | query: ?Component = null, |
| 15 | fragment: ?Component = null, | 17 | fragment: ?Component = null, |
| 16 | 18 | ||
| 19 | pub const host_name_max = 255; | ||
| 20 | |||
| 21 | /// Returned value may point into `buffer` or be the original string. | ||
| 22 | /// | ||
| 23 | /// Suggested buffer length: `host_name_max`. | ||
| 24 | /// | ||
| 25 | /// See also: | ||
| 26 | /// * `getHostAlloc` | ||
| 27 | pub fn getHost(uri: Uri, buffer: []u8) error{ UriMissingHost, UriHostTooLong }![]const u8 { | ||
| 28 | const component = uri.host orelse return error.UriMissingHost; | ||
| 29 | return component.toRaw(buffer) catch |err| switch (err) { | ||
| 30 | error.NoSpaceLeft => return error.UriHostTooLong, | ||
| 31 | }; | ||
| 32 | } | ||
| 33 | |||
| 34 | /// Returned value may point into `buffer` or be the original string. | ||
| 35 | /// | ||
| 36 | /// See also: | ||
| 37 | /// * `getHost` | ||
| 38 | pub fn getHostAlloc(uri: Uri, arena: Allocator) error{ UriMissingHost, UriHostTooLong, OutOfMemory }![]const u8 { | ||
| 39 | const component = uri.host orelse return error.UriMissingHost; | ||
| 40 | const result = try component.toRawMaybeAlloc(arena); | ||
| 41 | if (result.len > host_name_max) return error.UriHostTooLong; | ||
| 42 | return result; | ||
| 43 | } | ||
| 44 | |||
| 17 | pub const Component = union(enum) { | 45 | pub const Component = union(enum) { |
| 18 | /// Invalid characters in this component must be percent encoded | 46 | /// Invalid characters in this component must be percent encoded |
| 19 | /// before being printed as part of a URI. | 47 | /// before being printed as part of a URI. |
| ... | @@ -30,11 +58,19 @@ pub const Component = union(enum) { | ... | @@ -30,11 +58,19 @@ pub const Component = union(enum) { |
| 30 | }; | 58 | }; |
| 31 | } | 59 | } |
| 32 | 60 | ||
| 61 | /// Returned value may point into `buffer` or be the original string. | ||
| 62 | pub fn toRaw(component: Component, buffer: []u8) error{NoSpaceLeft}![]const u8 { | ||
| 63 | return switch (component) { | ||
| 64 | .raw => |raw| raw, | ||
| 65 | .percent_encoded => |percent_encoded| if (std.mem.indexOfScalar(u8, percent_encoded, '%')) |_| | ||
| 66 | try std.fmt.bufPrint(buffer, "{f}", .{std.fmt.alt(component, .formatRaw)}) | ||
| 67 | else | ||
| 68 | percent_encoded, | ||
| 69 | }; | ||
| 70 | } | ||
| 71 | |||
| 33 | /// Allocates the result with `arena` only if needed, so the result should not be freed. | 72 | /// Allocates the result with `arena` only if needed, so the result should not be freed. |
| 34 | pub fn toRawMaybeAlloc( | 73 | pub fn toRawMaybeAlloc(component: Component, arena: Allocator) Allocator.Error![]const u8 { |
| 35 | component: Component, | ||
| 36 | arena: std.mem.Allocator, | ||
| 37 | ) std.mem.Allocator.Error![]const u8 { | ||
| 38 | return switch (component) { | 74 | return switch (component) { |
| 39 | .raw => |raw| raw, | 75 | .raw => |raw| raw, |
| 40 | .percent_encoded => |percent_encoded| if (std.mem.indexOfScalar(u8, percent_encoded, '%')) |_| | 76 | .percent_encoded => |percent_encoded| if (std.mem.indexOfScalar(u8, percent_encoded, '%')) |_| |
| ... | @@ -44,7 +80,7 @@ pub const Component = union(enum) { | ... | @@ -44,7 +80,7 @@ pub const Component = union(enum) { |
| 44 | }; | 80 | }; |
| 45 | } | 81 | } |
| 46 | 82 | ||
| 47 | pub fn formatRaw(component: Component, w: *std.io.Writer) std.io.Writer.Error!void { | 83 | pub fn formatRaw(component: Component, w: *Writer) Writer.Error!void { |
| 48 | switch (component) { | 84 | switch (component) { |
| 49 | .raw => |raw| try w.writeAll(raw), | 85 | .raw => |raw| try w.writeAll(raw), |
| 50 | .percent_encoded => |percent_encoded| { | 86 | .percent_encoded => |percent_encoded| { |
| ... | @@ -67,56 +103,56 @@ pub const Component = union(enum) { | ... | @@ -67,56 +103,56 @@ pub const Component = union(enum) { |
| 67 | } | 103 | } |
| 68 | } | 104 | } |
| 69 | 105 | ||
| 70 | pub fn formatEscaped(component: Component, w: *std.io.Writer) std.io.Writer.Error!void { | 106 | pub fn formatEscaped(component: Component, w: *Writer) Writer.Error!void { |
| 71 | switch (component) { | 107 | switch (component) { |
| 72 | .raw => |raw| try percentEncode(w, raw, isUnreserved), | 108 | .raw => |raw| try percentEncode(w, raw, isUnreserved), |
| 73 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), | 109 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), |
| 74 | } | 110 | } |
| 75 | } | 111 | } |
| 76 | 112 | ||
| 77 | pub fn formatUser(component: Component, w: *std.io.Writer) std.io.Writer.Error!void { | 113 | pub fn formatUser(component: Component, w: *Writer) Writer.Error!void { |
| 78 | switch (component) { | 114 | switch (component) { |
| 79 | .raw => |raw| try percentEncode(w, raw, isUserChar), | 115 | .raw => |raw| try percentEncode(w, raw, isUserChar), |
| 80 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), | 116 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), |
| 81 | } | 117 | } |
| 82 | } | 118 | } |
| 83 | 119 | ||
| 84 | pub fn formatPassword(component: Component, w: *std.io.Writer) std.io.Writer.Error!void { | 120 | pub fn formatPassword(component: Component, w: *Writer) Writer.Error!void { |
| 85 | switch (component) { | 121 | switch (component) { |
| 86 | .raw => |raw| try percentEncode(w, raw, isPasswordChar), | 122 | .raw => |raw| try percentEncode(w, raw, isPasswordChar), |
| 87 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), | 123 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), |
| 88 | } | 124 | } |
| 89 | } | 125 | } |
| 90 | 126 | ||
| 91 | pub fn formatHost(component: Component, w: *std.io.Writer) std.io.Writer.Error!void { | 127 | pub fn formatHost(component: Component, w: *Writer) Writer.Error!void { |
| 92 | switch (component) { | 128 | switch (component) { |
| 93 | .raw => |raw| try percentEncode(w, raw, isHostChar), | 129 | .raw => |raw| try percentEncode(w, raw, isHostChar), |
| 94 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), | 130 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), |
| 95 | } | 131 | } |
| 96 | } | 132 | } |
| 97 | 133 | ||
| 98 | pub fn formatPath(component: Component, w: *std.io.Writer) std.io.Writer.Error!void { | 134 | pub fn formatPath(component: Component, w: *Writer) Writer.Error!void { |
| 99 | switch (component) { | 135 | switch (component) { |
| 100 | .raw => |raw| try percentEncode(w, raw, isPathChar), | 136 | .raw => |raw| try percentEncode(w, raw, isPathChar), |
| 101 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), | 137 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), |
| 102 | } | 138 | } |
| 103 | } | 139 | } |
| 104 | 140 | ||
| 105 | pub fn formatQuery(component: Component, w: *std.io.Writer) std.io.Writer.Error!void { | 141 | pub fn formatQuery(component: Component, w: *Writer) Writer.Error!void { |
| 106 | switch (component) { | 142 | switch (component) { |
| 107 | .raw => |raw| try percentEncode(w, raw, isQueryChar), | 143 | .raw => |raw| try percentEncode(w, raw, isQueryChar), |
| 108 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), | 144 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), |
| 109 | } | 145 | } |
| 110 | } | 146 | } |
| 111 | 147 | ||
| 112 | pub fn formatFragment(component: Component, w: *std.io.Writer) std.io.Writer.Error!void { | 148 | pub fn formatFragment(component: Component, w: *Writer) Writer.Error!void { |
| 113 | switch (component) { | 149 | switch (component) { |
| 114 | .raw => |raw| try percentEncode(w, raw, isFragmentChar), | 150 | .raw => |raw| try percentEncode(w, raw, isFragmentChar), |
| 115 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), | 151 | .percent_encoded => |percent_encoded| try w.writeAll(percent_encoded), |
| 116 | } | 152 | } |
| 117 | } | 153 | } |
| 118 | 154 | ||
| 119 | pub fn percentEncode(w: *std.io.Writer, raw: []const u8, comptime isValidChar: fn (u8) bool) std.io.Writer.Error!void { | 155 | pub fn percentEncode(w: *Writer, raw: []const u8, comptime isValidChar: fn (u8) bool) Writer.Error!void { |
| 120 | var start: usize = 0; | 156 | var start: usize = 0; |
| 121 | for (raw, 0..) |char, index| { | 157 | for (raw, 0..) |char, index| { |
| 122 | if (isValidChar(char)) continue; | 158 | if (isValidChar(char)) continue; |
| ... | @@ -165,17 +201,15 @@ pub const ParseError = error{ UnexpectedCharacter, InvalidFormat, InvalidPort }; | ... | @@ -165,17 +201,15 @@ pub const ParseError = error{ UnexpectedCharacter, InvalidFormat, InvalidPort }; |
| 165 | /// The return value will contain strings pointing into the original `text`. | 201 | /// The return value will contain strings pointing into the original `text`. |
| 166 | /// Each component that is provided, will be non-`null`. | 202 | /// Each component that is provided, will be non-`null`. |
| 167 | pub fn parseAfterScheme(scheme: []const u8, text: []const u8) ParseError!Uri { | 203 | pub fn parseAfterScheme(scheme: []const u8, text: []const u8) ParseError!Uri { |
| 168 | var reader = SliceReader{ .slice = text }; | ||
| 169 | |||
| 170 | var uri: Uri = .{ .scheme = scheme, .path = undefined }; | 204 | var uri: Uri = .{ .scheme = scheme, .path = undefined }; |
| 205 | var i: usize = 0; | ||
| 171 | 206 | ||
| 172 | if (reader.peekPrefix("//")) a: { // authority part | 207 | if (std.mem.startsWith(u8, text, "//")) a: { |
| 173 | std.debug.assert(reader.get().? == '/'); | 208 | i = std.mem.indexOfAnyPos(u8, text, 2, &authority_sep) orelse text.len; |
| 174 | std.debug.assert(reader.get().? == '/'); | 209 | const authority = text[2..i]; |
| 175 | |||
| 176 | const authority = reader.readUntil(isAuthoritySeparator); | ||
| 177 | if (authority.len == 0) { | 210 | if (authority.len == 0) { |
| 178 | if (reader.peekPrefix("/")) break :a else return error.InvalidFormat; | 211 | if (!std.mem.startsWith(u8, text[2..], "/")) return error.InvalidFormat; |
| 212 | break :a; | ||
| 179 | } | 213 | } |
| 180 | 214 | ||
| 181 | var start_of_host: usize = 0; | 215 | var start_of_host: usize = 0; |
| ... | @@ -225,26 +259,28 @@ pub fn parseAfterScheme(scheme: []const u8, text: []const u8) ParseError!Uri { | ... | @@ -225,26 +259,28 @@ pub fn parseAfterScheme(scheme: []const u8, text: []const u8) ParseError!Uri { |
| 225 | uri.host = .{ .percent_encoded = authority[start_of_host..end_of_host] }; | 259 | uri.host = .{ .percent_encoded = authority[start_of_host..end_of_host] }; |
| 226 | } | 260 | } |
| 227 | 261 | ||
| 228 | uri.path = .{ .percent_encoded = reader.readUntil(isPathSeparator) }; | 262 | const path_start = i; |
| 263 | i = std.mem.indexOfAnyPos(u8, text, path_start, &path_sep) orelse text.len; | ||
| 264 | uri.path = .{ .percent_encoded = text[path_start..i] }; | ||
| 229 | 265 | ||
| 230 | if ((reader.peek() orelse 0) == '?') { // query part | 266 | if (std.mem.startsWith(u8, text[i..], "?")) { |
| 231 | std.debug.assert(reader.get().? == '?'); | 267 | const query_start = i + 1; |
| 232 | uri.query = .{ .percent_encoded = reader.readUntil(isQuerySeparator) }; | 268 | i = std.mem.indexOfScalarPos(u8, text, query_start, '#') orelse text.len; |
| 269 | uri.query = .{ .percent_encoded = text[query_start..i] }; | ||
| 233 | } | 270 | } |
| 234 | 271 | ||
| 235 | if ((reader.peek() orelse 0) == '#') { // fragment part | 272 | if (std.mem.startsWith(u8, text[i..], "#")) { |
| 236 | std.debug.assert(reader.get().? == '#'); | 273 | uri.fragment = .{ .percent_encoded = text[i + 1 ..] }; |
| 237 | uri.fragment = .{ .percent_encoded = reader.readUntilEof() }; | ||
| 238 | } | 274 | } |
| 239 | 275 | ||
| 240 | return uri; | 276 | return uri; |
| 241 | } | 277 | } |
| 242 | 278 | ||
| 243 | pub fn format(uri: *const Uri, writer: *std.io.Writer) std.io.Writer.Error!void { | 279 | pub fn format(uri: *const Uri, writer: *Writer) Writer.Error!void { |
| 244 | return writeToStream(uri, writer, .all); | 280 | return writeToStream(uri, writer, .all); |
| 245 | } | 281 | } |
| 246 | 282 | ||
| 247 | pub fn writeToStream(uri: *const Uri, writer: *std.io.Writer, flags: Format.Flags) std.io.Writer.Error!void { | 283 | pub fn writeToStream(uri: *const Uri, writer: *Writer, flags: Format.Flags) Writer.Error!void { |
| 248 | if (flags.scheme) { | 284 | if (flags.scheme) { |
| 249 | try writer.print("{s}:", .{uri.scheme}); | 285 | try writer.print("{s}:", .{uri.scheme}); |
| 250 | if (flags.authority and uri.host != null) { | 286 | if (flags.authority and uri.host != null) { |
| ... | @@ -318,7 +354,7 @@ pub const Format = struct { | ... | @@ -318,7 +354,7 @@ pub const Format = struct { |
| 318 | }; | 354 | }; |
| 319 | }; | 355 | }; |
| 320 | 356 | ||
| 321 | pub fn default(f: Format, writer: *std.io.Writer) std.io.Writer.Error!void { | 357 | pub fn default(f: Format, writer: *Writer) Writer.Error!void { |
| 322 | return writeToStream(f.uri, writer, f.flags); | 358 | return writeToStream(f.uri, writer, f.flags); |
| 323 | } | 359 | } |
| 324 | }; | 360 | }; |
| ... | @@ -327,41 +363,33 @@ pub fn fmt(uri: *const Uri, flags: Format.Flags) std.fmt.Formatter(Format, Forma | ... | @@ -327,41 +363,33 @@ pub fn fmt(uri: *const Uri, flags: Format.Flags) std.fmt.Formatter(Format, Forma |
| 327 | return .{ .data = .{ .uri = uri, .flags = flags } }; | 363 | return .{ .data = .{ .uri = uri, .flags = flags } }; |
| 328 | } | 364 | } |
| 329 | 365 | ||
| 330 | /// Parses the URI or returns an error. | 366 | /// The return value will contain strings pointing into the original `text`. |
| 331 | /// The return value will contain strings pointing into the | 367 | /// Each component that is provided will be non-`null`. |
| 332 | /// original `text`. Each component that is provided, will be non-`null`. | ||
| 333 | pub fn parse(text: []const u8) ParseError!Uri { | 368 | pub fn parse(text: []const u8) ParseError!Uri { |
| 334 | var reader: SliceReader = .{ .slice = text }; | 369 | const end = for (text, 0..) |byte, i| { |
| 335 | const scheme = reader.readWhile(isSchemeChar); | 370 | if (!isSchemeChar(byte)) break i; |
| 336 | 371 | } else text.len; | |
| 337 | // after the scheme, a ':' must appear | 372 | // After the scheme, a ':' must appear. |
| 338 | if (reader.get()) |c| { | 373 | if (end >= text.len) return error.InvalidFormat; |
| 339 | if (c != ':') | 374 | if (text[end] != ':') return error.UnexpectedCharacter; |
| 340 | return error.UnexpectedCharacter; | 375 | return parseAfterScheme(text[0..end], text[end + 1 ..]); |
| 341 | } else { | ||
| 342 | return error.InvalidFormat; | ||
| 343 | } | ||
| 344 | |||
| 345 | return parseAfterScheme(scheme, reader.readUntilEof()); | ||
| 346 | } | 376 | } |
| 347 | 377 | ||
| 348 | pub const ResolveInPlaceError = ParseError || error{NoSpaceLeft}; | 378 | pub const ResolveInPlaceError = ParseError || error{NoSpaceLeft}; |
| 349 | 379 | ||
| 350 | /// Resolves a URI against a base URI, conforming to RFC 3986, Section 5. | 380 | /// Resolves a URI against a base URI, conforming to RFC 3986, Section 5. |
| 351 | /// Copies `new` to the beginning of `aux_buf.*`, allowing the slices to overlap, | 381 | /// |
| 352 | /// then parses `new` as a URI, and then resolves the path in place. | 382 | /// Assumes new location is already copied to the beginning of `aux_buf.*`. |
| 383 | /// Parses that new location as a URI, and then resolves the path in place. | ||
| 384 | /// | ||
| 353 | /// If a merge needs to take place, the newly constructed path will be stored | 385 | /// If a merge needs to take place, the newly constructed path will be stored |
| 354 | /// in `aux_buf.*` just after the copied `new`, and `aux_buf.*` will be modified | 386 | /// in `aux_buf.*` just after the copied location, and `aux_buf.*` will be |
| 355 | /// to only contain the remaining unused space. | 387 | /// modified to only contain the remaining unused space. |
| 356 | pub fn resolve_inplace(base: Uri, new: []const u8, aux_buf: *[]u8) ResolveInPlaceError!Uri { | 388 | pub fn resolveInPlace(base: Uri, new_len: usize, aux_buf: *[]u8) ResolveInPlaceError!Uri { |
| 357 | std.mem.copyForwards(u8, aux_buf.*, new); | 389 | const new = aux_buf.*[0..new_len]; |
| 358 | // At this point, new is an invalid pointer. | 390 | const new_parsed = parse(new) catch |err| (parseAfterScheme("", new) catch return err); |
| 359 | const new_mut = aux_buf.*[0..new.len]; | 391 | aux_buf.* = aux_buf.*[new_len..]; |
| 360 | aux_buf.* = aux_buf.*[new.len..]; | 392 | // As you can see above, `new` is not a const pointer. |
| 361 | |||
| 362 | const new_parsed = parse(new_mut) catch |err| | ||
| 363 | (parseAfterScheme("", new_mut) catch return err); | ||
| 364 | // As you can see above, `new_mut` is not a const pointer. | ||
| 365 | const new_path: []u8 = @constCast(new_parsed.path.percent_encoded); | 393 | const new_path: []u8 = @constCast(new_parsed.path.percent_encoded); |
| 366 | 394 | ||
| 367 | if (new_parsed.scheme.len > 0) return .{ | 395 | if (new_parsed.scheme.len > 0) return .{ |
| ... | @@ -461,7 +489,7 @@ test remove_dot_segments { | ... | @@ -461,7 +489,7 @@ test remove_dot_segments { |
| 461 | 489 | ||
| 462 | /// 5.2.3. Merge Paths | 490 | /// 5.2.3. Merge Paths |
| 463 | fn merge_paths(base: Component, new: []u8, aux_buf: *[]u8) error{NoSpaceLeft}!Component { | 491 | fn merge_paths(base: Component, new: []u8, aux_buf: *[]u8) error{NoSpaceLeft}!Component { |
| 464 | var aux: std.io.Writer = .fixed(aux_buf.*); | 492 | var aux: Writer = .fixed(aux_buf.*); |
| 465 | if (!base.isEmpty()) { | 493 | if (!base.isEmpty()) { |
| 466 | base.formatPath(&aux) catch return error.NoSpaceLeft; | 494 | base.formatPath(&aux) catch return error.NoSpaceLeft; |
| 467 | aux.end = std.mem.lastIndexOfScalar(u8, aux.buffered(), '/') orelse return remove_dot_segments(new); | 495 | aux.end = std.mem.lastIndexOfScalar(u8, aux.buffered(), '/') orelse return remove_dot_segments(new); |
| ... | @@ -472,59 +500,6 @@ fn merge_paths(base: Component, new: []u8, aux_buf: *[]u8) error{NoSpaceLeft}!Co | ... | @@ -472,59 +500,6 @@ fn merge_paths(base: Component, new: []u8, aux_buf: *[]u8) error{NoSpaceLeft}!Co |
| 472 | return merged_path; | 500 | return merged_path; |
| 473 | } | 501 | } |
| 474 | 502 | ||
| 475 | const SliceReader = struct { | ||
| 476 | const Self = @This(); | ||
| 477 | |||
| 478 | slice: []const u8, | ||
| 479 | offset: usize = 0, | ||
| 480 | |||
| 481 | fn get(self: *Self) ?u8 { | ||
| 482 | if (self.offset >= self.slice.len) | ||
| 483 | return null; | ||
| 484 | const c = self.slice[self.offset]; | ||
| 485 | self.offset += 1; | ||
| 486 | return c; | ||
| 487 | } | ||
| 488 | |||
| 489 | fn peek(self: Self) ?u8 { | ||
| 490 | if (self.offset >= self.slice.len) | ||
| 491 | return null; | ||
| 492 | return self.slice[self.offset]; | ||
| 493 | } | ||
| 494 | |||
| 495 | fn readWhile(self: *Self, comptime predicate: fn (u8) bool) []const u8 { | ||
| 496 | const start = self.offset; | ||
| 497 | var end = start; | ||
| 498 | while (end < self.slice.len and predicate(self.slice[end])) { | ||
| 499 | end += 1; | ||
| 500 | } | ||
| 501 | self.offset = end; | ||
| 502 | return self.slice[start..end]; | ||
| 503 | } | ||
| 504 | |||
| 505 | fn readUntil(self: *Self, comptime predicate: fn (u8) bool) []const u8 { | ||
| 506 | const start = self.offset; | ||
| 507 | var end = start; | ||
| 508 | while (end < self.slice.len and !predicate(self.slice[end])) { | ||
| 509 | end += 1; | ||
| 510 | } | ||
| 511 | self.offset = end; | ||
| 512 | return self.slice[start..end]; | ||
| 513 | } | ||
| 514 | |||
| 515 | fn readUntilEof(self: *Self) []const u8 { | ||
| 516 | const start = self.offset; | ||
| 517 | self.offset = self.slice.len; | ||
| 518 | return self.slice[start..]; | ||
| 519 | } | ||
| 520 | |||
| 521 | fn peekPrefix(self: Self, prefix: []const u8) bool { | ||
| 522 | if (self.offset + prefix.len > self.slice.len) | ||
| 523 | return false; | ||
| 524 | return std.mem.eql(u8, self.slice[self.offset..][0..prefix.len], prefix); | ||
| 525 | } | ||
| 526 | }; | ||
| 527 | |||
| 528 | /// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) | 503 | /// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) |
| 529 | fn isSchemeChar(c: u8) bool { | 504 | fn isSchemeChar(c: u8) bool { |
| 530 | return switch (c) { | 505 | return switch (c) { |
| ... | @@ -533,19 +508,6 @@ fn isSchemeChar(c: u8) bool { | ... | @@ -533,19 +508,6 @@ fn isSchemeChar(c: u8) bool { |
| 533 | }; | 508 | }; |
| 534 | } | 509 | } |
| 535 | 510 | ||
| 536 | /// reserved = gen-delims / sub-delims | ||
| 537 | fn isReserved(c: u8) bool { | ||
| 538 | return isGenLimit(c) or isSubLimit(c); | ||
| 539 | } | ||
| 540 | |||
| 541 | /// gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" | ||
| 542 | fn isGenLimit(c: u8) bool { | ||
| 543 | return switch (c) { | ||
| 544 | ':', ',', '?', '#', '[', ']', '@' => true, | ||
| 545 | else => false, | ||
| 546 | }; | ||
| 547 | } | ||
| 548 | |||
| 549 | /// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" | 511 | /// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" |
| 550 | /// / "*" / "+" / "," / ";" / "=" | 512 | /// / "*" / "+" / "," / ";" / "=" |
| 551 | fn isSubLimit(c: u8) bool { | 513 | fn isSubLimit(c: u8) bool { |
| ... | @@ -585,26 +547,8 @@ fn isQueryChar(c: u8) bool { | ... | @@ -585,26 +547,8 @@ fn isQueryChar(c: u8) bool { |
| 585 | 547 | ||
| 586 | const isFragmentChar = isQueryChar; | 548 | const isFragmentChar = isQueryChar; |
| 587 | 549 | ||
| 588 | fn isAuthoritySeparator(c: u8) bool { | 550 | const authority_sep: [3]u8 = .{ '/', '?', '#' }; |
| 589 | return switch (c) { | 551 | const path_sep: [2]u8 = .{ '?', '#' }; |
| 590 | '/', '?', '#' => true, | ||
| 591 | else => false, | ||
| 592 | }; | ||
| 593 | } | ||
| 594 | |||
| 595 | fn isPathSeparator(c: u8) bool { | ||
| 596 | return switch (c) { | ||
| 597 | '?', '#' => true, | ||
| 598 | else => false, | ||
| 599 | }; | ||
| 600 | } | ||
| 601 | |||
| 602 | fn isQuerySeparator(c: u8) bool { | ||
| 603 | return switch (c) { | ||
| 604 | '#' => true, | ||
| 605 | else => false, | ||
| 606 | }; | ||
| 607 | } | ||
| 608 | 552 | ||
| 609 | test "basic" { | 553 | test "basic" { |
| 610 | const parsed = try parse("https://ziglang.org/download"); | 554 | const parsed = try parse("https://ziglang.org/download"); |
lib/std/crypto/tls/Client.zig+3-1| ... | @@ -328,7 +328,9 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client | ... | @@ -328,7 +328,9 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client |
| 328 | var cleartext_bufs: [2][tls.max_ciphertext_inner_record_len]u8 = undefined; | 328 | var cleartext_bufs: [2][tls.max_ciphertext_inner_record_len]u8 = undefined; |
| 329 | fragment: while (true) { | 329 | fragment: while (true) { |
| 330 | // Ensure the input buffer pointer is stable in this scope. | 330 | // Ensure the input buffer pointer is stable in this scope. |
| 331 | input.rebaseCapacity(tls.max_ciphertext_record_len); | 331 | input.rebase(tls.max_ciphertext_record_len) catch |err| switch (err) { |
| 332 | error.EndOfStream => {}, // We have assurance the remainder of stream can be buffered. | ||
| 333 | }; | ||
| 332 | const record_header = input.peek(tls.record_header_len) catch |err| switch (err) { | 334 | const record_header = input.peek(tls.record_header_len) catch |err| switch (err) { |
| 333 | error.EndOfStream => return error.TlsConnectionTruncated, | 335 | error.EndOfStream => return error.TlsConnectionTruncated, |
| 334 | error.ReadFailed => return error.ReadFailed, | 336 | error.ReadFailed => return error.ReadFailed, |
lib/std/http.zig+63-19| ... | @@ -343,9 +343,6 @@ pub const Reader = struct { | ... | @@ -343,9 +343,6 @@ pub const Reader = struct { |
| 343 | /// read from `in`. | 343 | /// read from `in`. |
| 344 | trailers: []const u8 = &.{}, | 344 | trailers: []const u8 = &.{}, |
| 345 | body_err: ?BodyError = null, | 345 | body_err: ?BodyError = null, |
| 346 | /// Determines at which point `error.HttpHeadersOversize` occurs, as well | ||
| 347 | /// as the minimum buffer capacity of `in`. | ||
| 348 | max_head_len: usize, | ||
| 349 | 346 | ||
| 350 | pub const RemainingChunkLen = enum(u64) { | 347 | pub const RemainingChunkLen = enum(u64) { |
| 351 | head = 0, | 348 | head = 0, |
| ... | @@ -397,27 +394,34 @@ pub const Reader = struct { | ... | @@ -397,27 +394,34 @@ pub const Reader = struct { |
| 397 | ReadFailed, | 394 | ReadFailed, |
| 398 | }; | 395 | }; |
| 399 | 396 | ||
| 400 | /// Buffers the entire head. | 397 | /// Buffers the entire head inside `in`. |
| 401 | pub fn receiveHead(reader: *Reader) HeadError!void { | 398 | /// |
| 399 | /// The resulting memory is invalidated by any subsequent consumption of | ||
| 400 | /// the input stream. | ||
| 401 | pub fn receiveHead(reader: *Reader) HeadError![]const u8 { | ||
| 402 | reader.trailers = &.{}; | 402 | reader.trailers = &.{}; |
| 403 | const in = reader.in; | 403 | const in = reader.in; |
| 404 | try in.rebase(reader.max_head_len); | ||
| 405 | var hp: HeadParser = .{}; | 404 | var hp: HeadParser = .{}; |
| 406 | var head_end: usize = 0; | 405 | var head_len: usize = 0; |
| 407 | while (true) { | 406 | while (true) { |
| 408 | if (head_end >= in.buffer.len) return error.HttpHeadersOversize; | 407 | if (in.buffer.len - head_len == 0) return error.HttpHeadersOversize; |
| 409 | in.fillMore() catch |err| switch (err) { | 408 | const remaining = in.buffered()[head_len..]; |
| 410 | error.EndOfStream => switch (head_end) { | 409 | if (remaining.len == 0) { |
| 411 | 0 => return error.HttpConnectionClosing, | 410 | in.fillMore() catch |err| switch (err) { |
| 412 | else => return error.HttpRequestTruncated, | 411 | error.EndOfStream => switch (head_len) { |
| 413 | }, | 412 | 0 => return error.HttpConnectionClosing, |
| 414 | error.ReadFailed => return error.ReadFailed, | 413 | else => return error.HttpRequestTruncated, |
| 415 | }; | 414 | }, |
| 416 | head_end += hp.feed(in.buffered()[head_end..]); | 415 | error.ReadFailed => return error.ReadFailed, |
| 416 | }; | ||
| 417 | continue; | ||
| 418 | } | ||
| 419 | head_len += hp.feed(remaining); | ||
| 417 | if (hp.state == .finished) { | 420 | if (hp.state == .finished) { |
| 418 | reader.head_buffer = in.steal(head_end); | ||
| 419 | reader.state = .received_head; | 421 | reader.state = .received_head; |
| 420 | return; | 422 | const head_buffer = in.buffered()[0..head_len]; |
| 423 | in.toss(head_len); | ||
| 424 | return head_buffer; | ||
| 421 | } | 425 | } |
| 422 | } | 426 | } |
| 423 | } | 427 | } |
| ... | @@ -786,7 +790,7 @@ pub const BodyWriter = struct { | ... | @@ -786,7 +790,7 @@ pub const BodyWriter = struct { |
| 786 | }; | 790 | }; |
| 787 | 791 | ||
| 788 | pub fn isEliding(w: *const BodyWriter) bool { | 792 | pub fn isEliding(w: *const BodyWriter) bool { |
| 789 | return w.writer.vtable.drain == Writer.discardingDrain; | 793 | return w.writer.vtable.drain == elidingDrain; |
| 790 | } | 794 | } |
| 791 | 795 | ||
| 792 | /// Sends all buffered data across `BodyWriter.http_protocol_output`. | 796 | /// Sends all buffered data across `BodyWriter.http_protocol_output`. |
| ... | @@ -930,6 +934,46 @@ pub const BodyWriter = struct { | ... | @@ -930,6 +934,46 @@ pub const BodyWriter = struct { |
| 930 | return w.consume(n); | 934 | return w.consume(n); |
| 931 | } | 935 | } |
| 932 | 936 | ||
| 937 | pub fn elidingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize { | ||
| 938 | const bw: *BodyWriter = @fieldParentPtr("writer", w); | ||
| 939 | const slice = data[0 .. data.len - 1]; | ||
| 940 | const pattern = data[slice.len]; | ||
| 941 | var written: usize = pattern.len * splat; | ||
| 942 | for (slice) |bytes| written += bytes.len; | ||
| 943 | switch (bw.state) { | ||
| 944 | .content_length => |*len| len.* -= written + w.end, | ||
| 945 | else => {}, | ||
| 946 | } | ||
| 947 | w.end = 0; | ||
| 948 | return written; | ||
| 949 | } | ||
| 950 | |||
| 951 | pub fn elidingSendFile(w: *Writer, file_reader: *File.Reader, limit: std.Io.Limit) Writer.FileError!usize { | ||
| 952 | const bw: *BodyWriter = @fieldParentPtr("writer", w); | ||
| 953 | if (File.Handle == void) return error.Unimplemented; | ||
| 954 | if (builtin.zig_backend == .stage2_aarch64) return error.Unimplemented; | ||
| 955 | switch (bw.state) { | ||
| 956 | .content_length => |*len| len.* -= w.end, | ||
| 957 | else => {}, | ||
| 958 | } | ||
| 959 | w.end = 0; | ||
| 960 | if (limit == .nothing) return 0; | ||
| 961 | if (file_reader.getSize()) |size| { | ||
| 962 | const n = limit.minInt64(size - file_reader.pos); | ||
| 963 | if (n == 0) return error.EndOfStream; | ||
| 964 | file_reader.seekBy(@intCast(n)) catch return error.Unimplemented; | ||
| 965 | switch (bw.state) { | ||
| 966 | .content_length => |*len| len.* -= n, | ||
| 967 | else => {}, | ||
| 968 | } | ||
| 969 | return n; | ||
| 970 | } else |_| { | ||
| 971 | // Error is observable on `file_reader` instance, and it is better to | ||
| 972 | // treat the file as a pipe. | ||
| 973 | return error.Unimplemented; | ||
| 974 | } | ||
| 975 | } | ||
| 976 | |||
| 933 | /// Returns `null` if size cannot be computed without making any syscalls. | 977 | /// Returns `null` if size cannot be computed without making any syscalls. |
| 934 | pub fn noneSendFile(w: *Writer, file_reader: *File.Reader, limit: std.Io.Limit) Writer.FileError!usize { | 978 | pub fn noneSendFile(w: *Writer, file_reader: *File.Reader, limit: std.Io.Limit) Writer.FileError!usize { |
| 935 | const bw: *BodyWriter = @fieldParentPtr("writer", w); | 979 | const bw: *BodyWriter = @fieldParentPtr("writer", w); |
lib/std/http/Client.zig+15-14| ... | @@ -821,7 +821,6 @@ pub const Request = struct { | ... | @@ -821,7 +821,6 @@ pub const Request = struct { |
| 821 | 821 | ||
| 822 | /// Returns the request's `Connection` back to the pool of the `Client`. | 822 | /// Returns the request's `Connection` back to the pool of the `Client`. |
| 823 | pub fn deinit(r: *Request) void { | 823 | pub fn deinit(r: *Request) void { |
| 824 | r.reader.restituteHeadBuffer(); | ||
| 825 | if (r.connection) |connection| { | 824 | if (r.connection) |connection| { |
| 826 | connection.closing = connection.closing or switch (r.reader.state) { | 825 | connection.closing = connection.closing or switch (r.reader.state) { |
| 827 | .ready => false, | 826 | .ready => false, |
| ... | @@ -908,13 +907,13 @@ pub const Request = struct { | ... | @@ -908,13 +907,13 @@ pub const Request = struct { |
| 908 | const connection = r.connection.?; | 907 | const connection = r.connection.?; |
| 909 | const w = connection.writer(); | 908 | const w = connection.writer(); |
| 910 | 909 | ||
| 911 | try r.method.write(w); | 910 | try r.method.format(w); |
| 912 | try w.writeByte(' '); | 911 | try w.writeByte(' '); |
| 913 | 912 | ||
| 914 | if (r.method == .CONNECT) { | 913 | if (r.method == .CONNECT) { |
| 915 | try uri.writeToStream(.{ .authority = true }, w); | 914 | try uri.writeToStream(w, .{ .authority = true }); |
| 916 | } else { | 915 | } else { |
| 917 | try uri.writeToStream(.{ | 916 | try uri.writeToStream(w, .{ |
| 918 | .scheme = connection.proxied, | 917 | .scheme = connection.proxied, |
| 919 | .authentication = connection.proxied, | 918 | .authentication = connection.proxied, |
| 920 | .authority = connection.proxied, | 919 | .authority = connection.proxied, |
| ... | @@ -928,7 +927,7 @@ pub const Request = struct { | ... | @@ -928,7 +927,7 @@ pub const Request = struct { |
| 928 | 927 | ||
| 929 | if (try emitOverridableHeader("host: ", r.headers.host, w)) { | 928 | if (try emitOverridableHeader("host: ", r.headers.host, w)) { |
| 930 | try w.writeAll("host: "); | 929 | try w.writeAll("host: "); |
| 931 | try uri.writeToStream(.{ .authority = true }, w); | 930 | try uri.writeToStream(w, .{ .authority = true }); |
| 932 | try w.writeAll("\r\n"); | 931 | try w.writeAll("\r\n"); |
| 933 | } | 932 | } |
| 934 | 933 | ||
| ... | @@ -1046,10 +1045,10 @@ pub const Request = struct { | ... | @@ -1046,10 +1045,10 @@ pub const Request = struct { |
| 1046 | pub fn receiveHead(r: *Request, redirect_buffer: []u8) ReceiveHeadError!Response { | 1045 | pub fn receiveHead(r: *Request, redirect_buffer: []u8) ReceiveHeadError!Response { |
| 1047 | var aux_buf = redirect_buffer; | 1046 | var aux_buf = redirect_buffer; |
| 1048 | while (true) { | 1047 | while (true) { |
| 1049 | try r.reader.receiveHead(); | 1048 | const head_buffer = try r.reader.receiveHead(); |
| 1050 | const response: Response = .{ | 1049 | const response: Response = .{ |
| 1051 | .request = r, | 1050 | .request = r, |
| 1052 | .head = Response.Head.parse(r.reader.head_buffer) catch return error.HttpHeadersInvalid, | 1051 | .head = Response.Head.parse(head_buffer) catch return error.HttpHeadersInvalid, |
| 1053 | }; | 1052 | }; |
| 1054 | const head = &response.head; | 1053 | const head = &response.head; |
| 1055 | 1054 | ||
| ... | @@ -1121,7 +1120,6 @@ pub const Request = struct { | ... | @@ -1121,7 +1120,6 @@ pub const Request = struct { |
| 1121 | _ = reader.discardRemaining() catch |err| switch (err) { | 1120 | _ = reader.discardRemaining() catch |err| switch (err) { |
| 1122 | error.ReadFailed => return r.reader.body_err.?, | 1121 | error.ReadFailed => return r.reader.body_err.?, |
| 1123 | }; | 1122 | }; |
| 1124 | r.reader.restituteHeadBuffer(); | ||
| 1125 | } | 1123 | } |
| 1126 | const new_uri = r.uri.resolveInPlace(location.len, aux_buf) catch |err| switch (err) { | 1124 | const new_uri = r.uri.resolveInPlace(location.len, aux_buf) catch |err| switch (err) { |
| 1127 | error.UnexpectedCharacter => return error.HttpRedirectLocationInvalid, | 1125 | error.UnexpectedCharacter => return error.HttpRedirectLocationInvalid, |
| ... | @@ -1302,12 +1300,13 @@ pub const basic_authorization = struct { | ... | @@ -1302,12 +1300,13 @@ pub const basic_authorization = struct { |
| 1302 | } | 1300 | } |
| 1303 | 1301 | ||
| 1304 | pub fn write(uri: Uri, out: *Writer) Writer.Error!void { | 1302 | pub fn write(uri: Uri, out: *Writer) Writer.Error!void { |
| 1305 | var buf: [max_user_len + ":".len + max_password_len]u8 = undefined; | 1303 | var buf: [max_user_len + 1 + max_password_len]u8 = undefined; |
| 1306 | var w: Writer = .fixed(&buf); | 1304 | var w: Writer = .fixed(&buf); |
| 1307 | w.print("{fuser}:{fpassword}", .{ | 1305 | const user: Uri.Component = uri.user orelse .empty; |
| 1308 | uri.user orelse Uri.Component.empty, | 1306 | const password: Uri.Component = uri.user orelse .empty; |
| 1309 | uri.password orelse Uri.Component.empty, | 1307 | user.formatUser(&w) catch unreachable; |
| 1310 | }) catch unreachable; | 1308 | w.writeByte(':') catch unreachable; |
| 1309 | password.formatPassword(&w) catch unreachable; | ||
| 1311 | try out.print("Basic {b64}", .{w.buffered()}); | 1310 | try out.print("Basic {b64}", .{w.buffered()}); |
| 1312 | } | 1311 | } |
| 1313 | }; | 1312 | }; |
| ... | @@ -1697,6 +1696,7 @@ pub const FetchError = Uri.ParseError || RequestError || Request.ReceiveHeadErro | ... | @@ -1697,6 +1696,7 @@ pub const FetchError = Uri.ParseError || RequestError || Request.ReceiveHeadErro |
| 1697 | StreamTooLong, | 1696 | StreamTooLong, |
| 1698 | /// TODO provide optional diagnostics when this occurs or break into more error codes | 1697 | /// TODO provide optional diagnostics when this occurs or break into more error codes |
| 1699 | WriteFailed, | 1698 | WriteFailed, |
| 1699 | UnsupportedCompressionMethod, | ||
| 1700 | }; | 1700 | }; |
| 1701 | 1701 | ||
| 1702 | /// Perform a one-shot HTTP request with the provided options. | 1702 | /// Perform a one-shot HTTP request with the provided options. |
| ... | @@ -1748,7 +1748,8 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult { | ... | @@ -1748,7 +1748,8 @@ pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult { |
| 1748 | const decompress_buffer: []u8 = switch (response.head.content_encoding) { | 1748 | const decompress_buffer: []u8 = switch (response.head.content_encoding) { |
| 1749 | .identity => &.{}, | 1749 | .identity => &.{}, |
| 1750 | .zstd => options.decompress_buffer orelse try client.allocator.alloc(u8, std.compress.zstd.default_window_len), | 1750 | .zstd => options.decompress_buffer orelse try client.allocator.alloc(u8, std.compress.zstd.default_window_len), |
| 1751 | else => options.decompress_buffer orelse try client.allocator.alloc(u8, 8 * 1024), | 1751 | .deflate, .gzip => options.decompress_buffer orelse try client.allocator.alloc(u8, std.compress.flate.max_window_len), |
| 1752 | .compress => return error.UnsupportedCompressionMethod, | ||
| 1752 | }; | 1753 | }; |
| 1753 | defer if (options.decompress_buffer == null) client.allocator.free(decompress_buffer); | 1754 | defer if (options.decompress_buffer == null) client.allocator.free(decompress_buffer); |
| 1754 | 1755 |
lib/std/http/Server.zig+21-19| ... | @@ -6,7 +6,7 @@ const mem = std.mem; | ... | @@ -6,7 +6,7 @@ const mem = std.mem; |
| 6 | const Uri = std.Uri; | 6 | const Uri = std.Uri; |
| 7 | const assert = std.debug.assert; | 7 | const assert = std.debug.assert; |
| 8 | const testing = std.testing; | 8 | const testing = std.testing; |
| 9 | const Writer = std.io.Writer; | 9 | const Writer = std.Io.Writer; |
| 10 | 10 | ||
| 11 | const Server = @This(); | 11 | const Server = @This(); |
| 12 | 12 | ||
| ... | @@ -21,7 +21,7 @@ reader: http.Reader, | ... | @@ -21,7 +21,7 @@ reader: http.Reader, |
| 21 | /// header, otherwise `receiveHead` returns `error.HttpHeadersOversize`. | 21 | /// header, otherwise `receiveHead` returns `error.HttpHeadersOversize`. |
| 22 | /// | 22 | /// |
| 23 | /// The returned `Server` is ready for `receiveHead` to be called. | 23 | /// The returned `Server` is ready for `receiveHead` to be called. |
| 24 | pub fn init(in: *std.io.Reader, out: *Writer) Server { | 24 | pub fn init(in: *std.Io.Reader, out: *Writer) Server { |
| 25 | return .{ | 25 | return .{ |
| 26 | .reader = .{ | 26 | .reader = .{ |
| 27 | .in = in, | 27 | .in = in, |
| ... | @@ -33,25 +33,22 @@ pub fn init(in: *std.io.Reader, out: *Writer) Server { | ... | @@ -33,25 +33,22 @@ pub fn init(in: *std.io.Reader, out: *Writer) Server { |
| 33 | }; | 33 | }; |
| 34 | } | 34 | } |
| 35 | 35 | ||
| 36 | pub fn deinit(s: *Server) void { | ||
| 37 | s.reader.restituteHeadBuffer(); | ||
| 38 | } | ||
| 39 | |||
| 40 | pub const ReceiveHeadError = http.Reader.HeadError || error{ | 36 | pub const ReceiveHeadError = http.Reader.HeadError || error{ |
| 41 | /// Client sent headers that did not conform to the HTTP protocol. | 37 | /// Client sent headers that did not conform to the HTTP protocol. |
| 42 | /// | 38 | /// |
| 43 | /// To find out more detailed diagnostics, `http.Reader.head_buffer` can be | 39 | /// To find out more detailed diagnostics, `Request.head_buffer` can be |
| 44 | /// passed directly to `Request.Head.parse`. | 40 | /// passed directly to `Request.Head.parse`. |
| 45 | HttpHeadersInvalid, | 41 | HttpHeadersInvalid, |
| 46 | }; | 42 | }; |
| 47 | 43 | ||
| 48 | pub fn receiveHead(s: *Server) ReceiveHeadError!Request { | 44 | pub fn receiveHead(s: *Server) ReceiveHeadError!Request { |
| 49 | try s.reader.receiveHead(); | 45 | const head_buffer = try s.reader.receiveHead(); |
| 50 | return .{ | 46 | return .{ |
| 51 | .server = s, | 47 | .server = s, |
| 48 | .head_buffer = head_buffer, | ||
| 52 | // No need to track the returned error here since users can repeat the | 49 | // No need to track the returned error here since users can repeat the |
| 53 | // parse with the header buffer to get detailed diagnostics. | 50 | // parse with the header buffer to get detailed diagnostics. |
| 54 | .head = Request.Head.parse(s.reader.head_buffer) catch return error.HttpHeadersInvalid, | 51 | .head = Request.Head.parse(head_buffer) catch return error.HttpHeadersInvalid, |
| 55 | }; | 52 | }; |
| 56 | } | 53 | } |
| 57 | 54 | ||
| ... | @@ -60,6 +57,7 @@ pub const Request = struct { | ... | @@ -60,6 +57,7 @@ pub const Request = struct { |
| 60 | /// Pointers in this struct are invalidated with the next call to | 57 | /// Pointers in this struct are invalidated with the next call to |
| 61 | /// `receiveHead`. | 58 | /// `receiveHead`. |
| 62 | head: Head, | 59 | head: Head, |
| 60 | head_buffer: []const u8, | ||
| 63 | respond_err: ?RespondError = null, | 61 | respond_err: ?RespondError = null, |
| 64 | 62 | ||
| 65 | pub const RespondError = error{ | 63 | pub const RespondError = error{ |
| ... | @@ -229,7 +227,7 @@ pub const Request = struct { | ... | @@ -229,7 +227,7 @@ pub const Request = struct { |
| 229 | 227 | ||
| 230 | pub fn iterateHeaders(r: *Request) http.HeaderIterator { | 228 | pub fn iterateHeaders(r: *Request) http.HeaderIterator { |
| 231 | assert(r.server.reader.state == .received_head); | 229 | assert(r.server.reader.state == .received_head); |
| 232 | return http.HeaderIterator.init(r.server.reader.head_buffer); | 230 | return http.HeaderIterator.init(r.head_buffer); |
| 233 | } | 231 | } |
| 234 | 232 | ||
| 235 | test iterateHeaders { | 233 | test iterateHeaders { |
| ... | @@ -244,7 +242,6 @@ pub const Request = struct { | ... | @@ -244,7 +242,6 @@ pub const Request = struct { |
| 244 | .reader = .{ | 242 | .reader = .{ |
| 245 | .in = undefined, | 243 | .in = undefined, |
| 246 | .state = .received_head, | 244 | .state = .received_head, |
| 247 | .head_buffer = @constCast(request_bytes), | ||
| 248 | .interface = undefined, | 245 | .interface = undefined, |
| 249 | }, | 246 | }, |
| 250 | .out = undefined, | 247 | .out = undefined, |
| ... | @@ -253,6 +250,7 @@ pub const Request = struct { | ... | @@ -253,6 +250,7 @@ pub const Request = struct { |
| 253 | var request: Request = .{ | 250 | var request: Request = .{ |
| 254 | .server = &server, | 251 | .server = &server, |
| 255 | .head = undefined, | 252 | .head = undefined, |
| 253 | .head_buffer = @constCast(request_bytes), | ||
| 256 | }; | 254 | }; |
| 257 | 255 | ||
| 258 | var it = request.iterateHeaders(); | 256 | var it = request.iterateHeaders(); |
| ... | @@ -435,10 +433,8 @@ pub const Request = struct { | ... | @@ -435,10 +433,8 @@ pub const Request = struct { |
| 435 | 433 | ||
| 436 | for (o.extra_headers) |header| { | 434 | for (o.extra_headers) |header| { |
| 437 | assert(header.name.len != 0); | 435 | assert(header.name.len != 0); |
| 438 | try out.writeAll(header.name); | 436 | var bufs: [4][]const u8 = .{ header.name, ": ", header.value, "\r\n" }; |
| 439 | try out.writeAll(": "); | 437 | try out.writeVecAll(&bufs); |
| 440 | try out.writeAll(header.value); | ||
| 441 | try out.writeAll("\r\n"); | ||
| 442 | } | 438 | } |
| 443 | 439 | ||
| 444 | try out.writeAll("\r\n"); | 440 | try out.writeAll("\r\n"); |
| ... | @@ -453,7 +449,13 @@ pub const Request = struct { | ... | @@ -453,7 +449,13 @@ pub const Request = struct { |
| 453 | return if (elide_body) .{ | 449 | return if (elide_body) .{ |
| 454 | .http_protocol_output = request.server.out, | 450 | .http_protocol_output = request.server.out, |
| 455 | .state = state, | 451 | .state = state, |
| 456 | .writer = .discarding(buffer), | 452 | .writer = .{ |
| 453 | .buffer = buffer, | ||
| 454 | .vtable = &.{ | ||
| 455 | .drain = http.BodyWriter.elidingDrain, | ||
| 456 | .sendFile = http.BodyWriter.elidingSendFile, | ||
| 457 | }, | ||
| 458 | }, | ||
| 457 | } else .{ | 459 | } else .{ |
| 458 | .http_protocol_output = request.server.out, | 460 | .http_protocol_output = request.server.out, |
| 459 | .state = state, | 461 | .state = state, |
| ... | @@ -564,7 +566,7 @@ pub const Request = struct { | ... | @@ -564,7 +566,7 @@ pub const Request = struct { |
| 564 | /// | 566 | /// |
| 565 | /// See `readerExpectNone` for an infallible alternative that cannot write | 567 | /// See `readerExpectNone` for an infallible alternative that cannot write |
| 566 | /// to the server output stream. | 568 | /// to the server output stream. |
| 567 | pub fn readerExpectContinue(request: *Request, buffer: []u8) ExpectContinueError!*std.io.Reader { | 569 | pub fn readerExpectContinue(request: *Request, buffer: []u8) ExpectContinueError!*std.Io.Reader { |
| 568 | const flush = request.head.expect != null; | 570 | const flush = request.head.expect != null; |
| 569 | try writeExpectContinue(request); | 571 | try writeExpectContinue(request); |
| 570 | if (flush) try request.server.out.flush(); | 572 | if (flush) try request.server.out.flush(); |
| ... | @@ -576,7 +578,7 @@ pub const Request = struct { | ... | @@ -576,7 +578,7 @@ pub const Request = struct { |
| 576 | /// this function. | 578 | /// this function. |
| 577 | /// | 579 | /// |
| 578 | /// Asserts that this function is only called once. | 580 | /// Asserts that this function is only called once. |
| 579 | pub fn readerExpectNone(request: *Request, buffer: []u8) *std.io.Reader { | 581 | pub fn readerExpectNone(request: *Request, buffer: []u8) *std.Io.Reader { |
| 580 | assert(request.server.reader.state == .received_head); | 582 | assert(request.server.reader.state == .received_head); |
| 581 | assert(request.head.expect == null); | 583 | assert(request.head.expect == null); |
| 582 | if (!request.head.method.requestHasBody()) return .ending; | 584 | if (!request.head.method.requestHasBody()) return .ending; |
| ... | @@ -640,7 +642,7 @@ pub const Request = struct { | ... | @@ -640,7 +642,7 @@ pub const Request = struct { |
| 640 | /// See https://tools.ietf.org/html/rfc6455 | 642 | /// See https://tools.ietf.org/html/rfc6455 |
| 641 | pub const WebSocket = struct { | 643 | pub const WebSocket = struct { |
| 642 | key: []const u8, | 644 | key: []const u8, |
| 643 | input: *std.io.Reader, | 645 | input: *std.Io.Reader, |
| 644 | output: *Writer, | 646 | output: *Writer, |
| 645 | 647 | ||
| 646 | pub const Header0 = packed struct(u8) { | 648 | pub const Header0 = packed struct(u8) { |
lib/std/http/test.zig+29-27| ... | @@ -65,7 +65,7 @@ test "trailers" { | ... | @@ -65,7 +65,7 @@ test "trailers" { |
| 65 | try req.sendBodiless(); | 65 | try req.sendBodiless(); |
| 66 | var response = try req.receiveHead(&.{}); | 66 | var response = try req.receiveHead(&.{}); |
| 67 | 67 | ||
| 68 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 68 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 69 | defer gpa.free(body); | 69 | defer gpa.free(body); |
| 70 | 70 | ||
| 71 | try expectEqualStrings("Hello, World!\n", body); | 71 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -183,7 +183,11 @@ test "echo content server" { | ... | @@ -183,7 +183,11 @@ test "echo content server" { |
| 183 | if (request.head.expect) |expect_header_value| { | 183 | if (request.head.expect) |expect_header_value| { |
| 184 | if (mem.eql(u8, expect_header_value, "garbage")) { | 184 | if (mem.eql(u8, expect_header_value, "garbage")) { |
| 185 | try expectError(error.HttpExpectationFailed, request.readerExpectContinue(&.{})); | 185 | try expectError(error.HttpExpectationFailed, request.readerExpectContinue(&.{})); |
| 186 | try request.respond("", .{ .keep_alive = false }); | 186 | request.head.expect = null; |
| 187 | try request.respond("", .{ | ||
| 188 | .keep_alive = false, | ||
| 189 | .status = .expectation_failed, | ||
| 190 | }); | ||
| 187 | continue; | 191 | continue; |
| 188 | } | 192 | } |
| 189 | } | 193 | } |
| ... | @@ -204,7 +208,7 @@ test "echo content server" { | ... | @@ -204,7 +208,7 @@ test "echo content server" { |
| 204 | // request.head.target, | 208 | // request.head.target, |
| 205 | //}); | 209 | //}); |
| 206 | 210 | ||
| 207 | const body = try (try request.readerExpectContinue(&.{})).allocRemaining(std.testing.allocator, .limited(8192)); | 211 | const body = try (try request.readerExpectContinue(&.{})).allocRemaining(std.testing.allocator, .unlimited); |
| 208 | defer std.testing.allocator.free(body); | 212 | defer std.testing.allocator.free(body); |
| 209 | 213 | ||
| 210 | try expect(mem.startsWith(u8, request.head.target, "/echo-content")); | 214 | try expect(mem.startsWith(u8, request.head.target, "/echo-content")); |
| ... | @@ -273,7 +277,6 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { | ... | @@ -273,7 +277,6 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { |
| 273 | for (0..500) |i| { | 277 | for (0..500) |i| { |
| 274 | try w.print("{d}, ah ha ha!\n", .{i}); | 278 | try w.print("{d}, ah ha ha!\n", .{i}); |
| 275 | } | 279 | } |
| 276 | try expectEqual(7390, w.count); | ||
| 277 | try w.flush(); | 280 | try w.flush(); |
| 278 | try response.end(); | 281 | try response.end(); |
| 279 | try expectEqual(.closing, server.reader.state); | 282 | try expectEqual(.closing, server.reader.state); |
| ... | @@ -291,7 +294,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { | ... | @@ -291,7 +294,7 @@ test "Server.Request.respondStreaming non-chunked, unknown content-length" { |
| 291 | 294 | ||
| 292 | var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded | 295 | var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded |
| 293 | var stream_reader = stream.reader(&tiny_buffer); | 296 | var stream_reader = stream.reader(&tiny_buffer); |
| 294 | const response = try stream_reader.interface().allocRemaining(gpa, .limited(8192)); | 297 | const response = try stream_reader.interface().allocRemaining(gpa, .unlimited); |
| 295 | defer gpa.free(response); | 298 | defer gpa.free(response); |
| 296 | 299 | ||
| 297 | var expected_response = std.ArrayList(u8).init(gpa); | 300 | var expected_response = std.ArrayList(u8).init(gpa); |
| ... | @@ -362,7 +365,7 @@ test "receiving arbitrary http headers from the client" { | ... | @@ -362,7 +365,7 @@ test "receiving arbitrary http headers from the client" { |
| 362 | 365 | ||
| 363 | var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded | 366 | var tiny_buffer: [1]u8 = undefined; // allows allocRemaining to detect limit exceeded |
| 364 | var stream_reader = stream.reader(&tiny_buffer); | 367 | var stream_reader = stream.reader(&tiny_buffer); |
| 365 | const response = try stream_reader.interface().allocRemaining(gpa, .limited(8192)); | 368 | const response = try stream_reader.interface().allocRemaining(gpa, .unlimited); |
| 366 | defer gpa.free(response); | 369 | defer gpa.free(response); |
| 367 | 370 | ||
| 368 | var expected_response = std.ArrayList(u8).init(gpa); | 371 | var expected_response = std.ArrayList(u8).init(gpa); |
| ... | @@ -408,12 +411,10 @@ test "general client/server API coverage" { | ... | @@ -408,12 +411,10 @@ test "general client/server API coverage" { |
| 408 | fn handleRequest(request: *http.Server.Request, listen_port: u16) !void { | 411 | fn handleRequest(request: *http.Server.Request, listen_port: u16) !void { |
| 409 | const log = std.log.scoped(.server); | 412 | const log = std.log.scoped(.server); |
| 410 | 413 | ||
| 411 | log.info("{f} {s} {s}", .{ | 414 | log.info("{f} {t} {s}", .{ request.head.method, request.head.version, request.head.target }); |
| 412 | request.head.method, @tagName(request.head.version), request.head.target, | ||
| 413 | }); | ||
| 414 | 415 | ||
| 415 | const gpa = std.testing.allocator; | 416 | const gpa = std.testing.allocator; |
| 416 | const body = try (try request.readerExpectContinue(&.{})).allocRemaining(gpa, .limited(8192)); | 417 | const body = try (try request.readerExpectContinue(&.{})).allocRemaining(gpa, .unlimited); |
| 417 | defer gpa.free(body); | 418 | defer gpa.free(body); |
| 418 | 419 | ||
| 419 | if (mem.startsWith(u8, request.head.target, "/get")) { | 420 | if (mem.startsWith(u8, request.head.target, "/get")) { |
| ... | @@ -447,7 +448,8 @@ test "general client/server API coverage" { | ... | @@ -447,7 +448,8 @@ test "general client/server API coverage" { |
| 447 | try w.writeAll("Hello, World!\n"); | 448 | try w.writeAll("Hello, World!\n"); |
| 448 | } | 449 | } |
| 449 | 450 | ||
| 450 | try w.writeAll("Hello, World!\n" ** 1024); | 451 | var vec: [1][]const u8 = .{"Hello, World!\n"}; |
| 452 | try w.writeSplatAll(&vec, 1024); | ||
| 451 | 453 | ||
| 452 | i = 0; | 454 | i = 0; |
| 453 | while (i < 5) : (i += 1) { | 455 | while (i < 5) : (i += 1) { |
| ... | @@ -556,7 +558,7 @@ test "general client/server API coverage" { | ... | @@ -556,7 +558,7 @@ test "general client/server API coverage" { |
| 556 | try req.sendBodiless(); | 558 | try req.sendBodiless(); |
| 557 | var response = try req.receiveHead(&redirect_buffer); | 559 | var response = try req.receiveHead(&redirect_buffer); |
| 558 | 560 | ||
| 559 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 561 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 560 | defer gpa.free(body); | 562 | defer gpa.free(body); |
| 561 | 563 | ||
| 562 | try expectEqualStrings("Hello, World!\n", body); | 564 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -579,7 +581,7 @@ test "general client/server API coverage" { | ... | @@ -579,7 +581,7 @@ test "general client/server API coverage" { |
| 579 | try req.sendBodiless(); | 581 | try req.sendBodiless(); |
| 580 | var response = try req.receiveHead(&redirect_buffer); | 582 | var response = try req.receiveHead(&redirect_buffer); |
| 581 | 583 | ||
| 582 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192 * 1024)); | 584 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 583 | defer gpa.free(body); | 585 | defer gpa.free(body); |
| 584 | 586 | ||
| 585 | try expectEqual(@as(usize, 14 * 1024 + 14 * 10), body.len); | 587 | try expectEqual(@as(usize, 14 * 1024 + 14 * 10), body.len); |
| ... | @@ -601,7 +603,7 @@ test "general client/server API coverage" { | ... | @@ -601,7 +603,7 @@ test "general client/server API coverage" { |
| 601 | try req.sendBodiless(); | 603 | try req.sendBodiless(); |
| 602 | var response = try req.receiveHead(&redirect_buffer); | 604 | var response = try req.receiveHead(&redirect_buffer); |
| 603 | 605 | ||
| 604 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 606 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 605 | defer gpa.free(body); | 607 | defer gpa.free(body); |
| 606 | 608 | ||
| 607 | try expectEqualStrings("", body); | 609 | try expectEqualStrings("", body); |
| ... | @@ -625,7 +627,7 @@ test "general client/server API coverage" { | ... | @@ -625,7 +627,7 @@ test "general client/server API coverage" { |
| 625 | try req.sendBodiless(); | 627 | try req.sendBodiless(); |
| 626 | var response = try req.receiveHead(&redirect_buffer); | 628 | var response = try req.receiveHead(&redirect_buffer); |
| 627 | 629 | ||
| 628 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 630 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 629 | defer gpa.free(body); | 631 | defer gpa.free(body); |
| 630 | 632 | ||
| 631 | try expectEqualStrings("Hello, World!\n", body); | 633 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -648,7 +650,7 @@ test "general client/server API coverage" { | ... | @@ -648,7 +650,7 @@ test "general client/server API coverage" { |
| 648 | try req.sendBodiless(); | 650 | try req.sendBodiless(); |
| 649 | var response = try req.receiveHead(&redirect_buffer); | 651 | var response = try req.receiveHead(&redirect_buffer); |
| 650 | 652 | ||
| 651 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 653 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 652 | defer gpa.free(body); | 654 | defer gpa.free(body); |
| 653 | 655 | ||
| 654 | try expectEqualStrings("", body); | 656 | try expectEqualStrings("", body); |
| ... | @@ -674,7 +676,7 @@ test "general client/server API coverage" { | ... | @@ -674,7 +676,7 @@ test "general client/server API coverage" { |
| 674 | try req.sendBodiless(); | 676 | try req.sendBodiless(); |
| 675 | var response = try req.receiveHead(&redirect_buffer); | 677 | var response = try req.receiveHead(&redirect_buffer); |
| 676 | 678 | ||
| 677 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 679 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 678 | defer gpa.free(body); | 680 | defer gpa.free(body); |
| 679 | 681 | ||
| 680 | try expectEqualStrings("Hello, World!\n", body); | 682 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -703,7 +705,7 @@ test "general client/server API coverage" { | ... | @@ -703,7 +705,7 @@ test "general client/server API coverage" { |
| 703 | 705 | ||
| 704 | try std.testing.expectEqual(.ok, response.head.status); | 706 | try std.testing.expectEqual(.ok, response.head.status); |
| 705 | 707 | ||
| 706 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 708 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 707 | defer gpa.free(body); | 709 | defer gpa.free(body); |
| 708 | 710 | ||
| 709 | try expectEqualStrings("", body); | 711 | try expectEqualStrings("", body); |
| ... | @@ -740,7 +742,7 @@ test "general client/server API coverage" { | ... | @@ -740,7 +742,7 @@ test "general client/server API coverage" { |
| 740 | try req.sendBodiless(); | 742 | try req.sendBodiless(); |
| 741 | var response = try req.receiveHead(&redirect_buffer); | 743 | var response = try req.receiveHead(&redirect_buffer); |
| 742 | 744 | ||
| 743 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 745 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 744 | defer gpa.free(body); | 746 | defer gpa.free(body); |
| 745 | 747 | ||
| 746 | try expectEqualStrings("Hello, World!\n", body); | 748 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -762,7 +764,7 @@ test "general client/server API coverage" { | ... | @@ -762,7 +764,7 @@ test "general client/server API coverage" { |
| 762 | try req.sendBodiless(); | 764 | try req.sendBodiless(); |
| 763 | var response = try req.receiveHead(&redirect_buffer); | 765 | var response = try req.receiveHead(&redirect_buffer); |
| 764 | 766 | ||
| 765 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 767 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 766 | defer gpa.free(body); | 768 | defer gpa.free(body); |
| 767 | 769 | ||
| 768 | try expectEqualStrings("Hello, World!\n", body); | 770 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -784,7 +786,7 @@ test "general client/server API coverage" { | ... | @@ -784,7 +786,7 @@ test "general client/server API coverage" { |
| 784 | try req.sendBodiless(); | 786 | try req.sendBodiless(); |
| 785 | var response = try req.receiveHead(&redirect_buffer); | 787 | var response = try req.receiveHead(&redirect_buffer); |
| 786 | 788 | ||
| 787 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 789 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 788 | defer gpa.free(body); | 790 | defer gpa.free(body); |
| 789 | 791 | ||
| 790 | try expectEqualStrings("Hello, World!\n", body); | 792 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -825,7 +827,7 @@ test "general client/server API coverage" { | ... | @@ -825,7 +827,7 @@ test "general client/server API coverage" { |
| 825 | try req.sendBodiless(); | 827 | try req.sendBodiless(); |
| 826 | var response = try req.receiveHead(&redirect_buffer); | 828 | var response = try req.receiveHead(&redirect_buffer); |
| 827 | 829 | ||
| 828 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 830 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 829 | defer gpa.free(body); | 831 | defer gpa.free(body); |
| 830 | 832 | ||
| 831 | try expectEqualStrings("Encoded redirect successful!\n", body); | 833 | try expectEqualStrings("Encoded redirect successful!\n", body); |
| ... | @@ -915,7 +917,7 @@ test "Server streams both reading and writing" { | ... | @@ -915,7 +917,7 @@ test "Server streams both reading and writing" { |
| 915 | try body_writer.writer.writeAll("fish"); | 917 | try body_writer.writer.writeAll("fish"); |
| 916 | try body_writer.end(); | 918 | try body_writer.end(); |
| 917 | 919 | ||
| 918 | const body = try response.reader(&.{}).allocRemaining(std.testing.allocator, .limited(8192)); | 920 | const body = try response.reader(&.{}).allocRemaining(std.testing.allocator, .unlimited); |
| 919 | defer std.testing.allocator.free(body); | 921 | defer std.testing.allocator.free(body); |
| 920 | 922 | ||
| 921 | try expectEqualStrings("ONE FISH", body); | 923 | try expectEqualStrings("ONE FISH", body); |
| ... | @@ -947,7 +949,7 @@ fn echoTests(client: *http.Client, port: u16) !void { | ... | @@ -947,7 +949,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 947 | 949 | ||
| 948 | var response = try req.receiveHead(&redirect_buffer); | 950 | var response = try req.receiveHead(&redirect_buffer); |
| 949 | 951 | ||
| 950 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 952 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 951 | defer gpa.free(body); | 953 | defer gpa.free(body); |
| 952 | 954 | ||
| 953 | try expectEqualStrings("Hello, World!\n", body); | 955 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -980,7 +982,7 @@ fn echoTests(client: *http.Client, port: u16) !void { | ... | @@ -980,7 +982,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 980 | 982 | ||
| 981 | var response = try req.receiveHead(&redirect_buffer); | 983 | var response = try req.receiveHead(&redirect_buffer); |
| 982 | 984 | ||
| 983 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 985 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 984 | defer gpa.free(body); | 986 | defer gpa.free(body); |
| 985 | 987 | ||
| 986 | try expectEqualStrings("Hello, World!\n", body); | 988 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -1034,7 +1036,7 @@ fn echoTests(client: *http.Client, port: u16) !void { | ... | @@ -1034,7 +1036,7 @@ fn echoTests(client: *http.Client, port: u16) !void { |
| 1034 | var response = try req.receiveHead(&redirect_buffer); | 1036 | var response = try req.receiveHead(&redirect_buffer); |
| 1035 | try expectEqual(.ok, response.head.status); | 1037 | try expectEqual(.ok, response.head.status); |
| 1036 | 1038 | ||
| 1037 | const body = try response.reader(&.{}).allocRemaining(gpa, .limited(8192)); | 1039 | const body = try response.reader(&.{}).allocRemaining(gpa, .unlimited); |
| 1038 | defer gpa.free(body); | 1040 | defer gpa.free(body); |
| 1039 | 1041 | ||
| 1040 | try expectEqualStrings("Hello, World!\n", body); | 1042 | try expectEqualStrings("Hello, World!\n", body); |
| ... | @@ -1175,7 +1177,7 @@ test "redirect to different connection" { | ... | @@ -1175,7 +1177,7 @@ test "redirect to different connection" { |
| 1175 | var response = try req.receiveHead(&redirect_buffer); | 1177 | var response = try req.receiveHead(&redirect_buffer); |
| 1176 | var reader = response.reader(&.{}); | 1178 | var reader = response.reader(&.{}); |
| 1177 | 1179 | ||
| 1178 | const body = try reader.allocRemaining(gpa, .limited(8192)); | 1180 | const body = try reader.allocRemaining(gpa, .unlimited); |
| 1179 | defer gpa.free(body); | 1181 | defer gpa.free(body); |
| 1180 | 1182 | ||
| 1181 | try expectEqualStrings("good job, you pass", body); | 1183 | try expectEqualStrings("good job, you pass", body); |