authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-09-29 17:39:35-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-09-29 17:40:37-07:00
log402f967ed5339fa3d828b7fe1d57cdb5bf38dbf2
treefa749bc193207022ec0156aec4a93a5c25b36b34
parentd1cea16f5cd29eb143ff9b3302e7ec56731647ea

move std.http to the standard library orphanage

I want to take the design of this in a different direction. I think this abstraction is too high level. I want to start bottom-up. std-lib-orphanage commit 179ae67d61455758d71037434704fd4a17a635a9

3 files changed, 0 insertions(+), 608 deletions(-)

lib/std/http.zig deleted-10
......@@ -1,10 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6test "std.http" {
7 _ = @import("http/headers.zig");
8}
9
10pub const Headers = @import("http/headers.zig").Headers;
lib/std/http/headers.zig deleted-597
......@@ -1,597 +0,0 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2020 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6// HTTP Header data structure/type
7// Based on lua-http's http.header module
8//
9// Design criteria:
10// - the same header field is allowed more than once
11// - must be able to fetch separate occurrences (important for some headers e.g. Set-Cookie)
12// - optionally available as comma separated list
13// - http2 adds flag to headers that they should never be indexed
14// - header order should be recoverable
15//
16// Headers are implemented as an array of entries.
17// An index of field name => array indices is kept.
18
19const std = @import("../std.zig");
20const debug = std.debug;
21const assert = debug.assert;
22const testing = std.testing;
23const mem = std.mem;
24const Allocator = mem.Allocator;
25
26fn never_index_default(name: []const u8) bool {
27 if (mem.eql(u8, "authorization", name)) return true;
28 if (mem.eql(u8, "proxy-authorization", name)) return true;
29 if (mem.eql(u8, "cookie", name)) return true;
30 if (mem.eql(u8, "set-cookie", name)) return true;
31 return false;
32}
33
34const HeaderEntry = struct {
35 name: []const u8,
36 value: []u8,
37 never_index: bool,
38
39 const Self = @This();
40
41 fn init(allocator: *Allocator, name: []const u8, value: []const u8, never_index: ?bool) !Self {
42 return Self{
43 .name = name, // takes reference
44 .value = try allocator.dupe(u8, value),
45 .never_index = never_index orelse never_index_default(name),
46 };
47 }
48
49 fn deinit(self: Self, allocator: *Allocator) void {
50 allocator.free(self.value);
51 }
52
53 pub fn modify(self: *Self, allocator: *Allocator, value: []const u8, never_index: ?bool) !void {
54 const old_len = self.value.len;
55 if (value.len > old_len) {
56 self.value = try allocator.realloc(self.value, value.len);
57 } else if (value.len < old_len) {
58 self.value = allocator.shrink(self.value, value.len);
59 }
60 mem.copy(u8, self.value, value);
61 self.never_index = never_index orelse never_index_default(self.name);
62 }
63
64 fn compare(context: void, a: HeaderEntry, b: HeaderEntry) bool {
65 if (a.name.ptr != b.name.ptr and a.name.len != b.name.len) {
66 // Things beginning with a colon *must* be before others
67 const a_is_colon = a.name[0] == ':';
68 const b_is_colon = b.name[0] == ':';
69 if (a_is_colon and !b_is_colon) {
70 return true;
71 } else if (!a_is_colon and b_is_colon) {
72 return false;
73 }
74
75 // Sort lexicographically on header name
76 return mem.order(u8, a.name, b.name) == .lt;
77 }
78
79 // Sort lexicographically on header value
80 if (!mem.eql(u8, a.value, b.value)) {
81 return mem.order(u8, a.value, b.value) == .lt;
82 }
83
84 // Doesn't matter here; need to pick something for sort consistency
85 return a.never_index;
86 }
87};
88
89test "HeaderEntry" {
90 var e = try HeaderEntry.init(testing.allocator, "foo", "bar", null);
91 defer e.deinit(testing.allocator);
92 testing.expectEqualSlices(u8, "foo", e.name);
93 testing.expectEqualSlices(u8, "bar", e.value);
94 testing.expectEqual(false, e.never_index);
95
96 try e.modify(testing.allocator, "longer value", null);
97 testing.expectEqualSlices(u8, "longer value", e.value);
98
99 // shorter value
100 try e.modify(testing.allocator, "x", null);
101 testing.expectEqualSlices(u8, "x", e.value);
102}
103
104const HeaderList = std.ArrayListUnmanaged(HeaderEntry);
105const HeaderIndexList = std.ArrayListUnmanaged(usize);
106const HeaderIndex = std.StringHashMapUnmanaged(HeaderIndexList);
107
108pub const Headers = struct {
109 // the owned header field name is stored in the index as part of the key
110 allocator: *Allocator,
111 data: HeaderList,
112 index: HeaderIndex,
113
114 const Self = @This();
115
116 pub fn init(allocator: *Allocator) Self {
117 return Self{
118 .allocator = allocator,
119 .data = HeaderList{},
120 .index = HeaderIndex{},
121 };
122 }
123
124 pub fn deinit(self: *Self) void {
125 {
126 var it = self.index.iterator();
127 while (it.next()) |entry| {
128 entry.value.deinit(self.allocator);
129 self.allocator.free(entry.key);
130 }
131 self.index.deinit(self.allocator);
132 }
133 {
134 for (self.data.items) |entry| {
135 entry.deinit(self.allocator);
136 }
137 self.data.deinit(self.allocator);
138 }
139 self.* = undefined;
140 }
141
142 pub fn clone(self: Self, allocator: *Allocator) !Self {
143 var other = Headers.init(allocator);
144 errdefer other.deinit();
145 try other.data.ensureCapacity(allocator, self.data.items.len);
146 try other.index.initCapacity(allocator, self.index.entries.len);
147 for (self.data.items) |entry| {
148 try other.append(entry.name, entry.value, entry.never_index);
149 }
150 return other;
151 }
152
153 pub fn toSlice(self: Self) []const HeaderEntry {
154 return self.data.items;
155 }
156
157 pub fn append(self: *Self, name: []const u8, value: []const u8, never_index: ?bool) !void {
158 const n = self.data.items.len + 1;
159 try self.data.ensureCapacity(self.allocator, n);
160 var entry: HeaderEntry = undefined;
161 if (self.index.getEntry(name)) |kv| {
162 entry = try HeaderEntry.init(self.allocator, kv.key, value, never_index);
163 errdefer entry.deinit(self.allocator);
164 const dex = &kv.value;
165 try dex.append(self.allocator, n - 1);
166 } else {
167 const name_dup = try self.allocator.dupe(u8, name);
168 errdefer self.allocator.free(name_dup);
169 entry = try HeaderEntry.init(self.allocator, name_dup, value, never_index);
170 errdefer entry.deinit(self.allocator);
171 var dex = HeaderIndexList{};
172 try dex.append(self.allocator, n - 1);
173 errdefer dex.deinit(self.allocator);
174 _ = try self.index.put(self.allocator, name_dup, dex);
175 }
176 self.data.appendAssumeCapacity(entry);
177 }
178
179 /// If the header already exists, replace the current value, otherwise append it to the list of headers.
180 /// If the header has multiple entries then returns an error.
181 pub fn upsert(self: *Self, name: []const u8, value: []const u8, never_index: ?bool) !void {
182 if (self.index.get(name)) |kv| {
183 const dex = kv.value;
184 if (dex.len != 1)
185 return error.CannotUpsertMultiValuedField;
186 var e = &self.data.at(dex.at(0));
187 try e.modify(value, never_index);
188 } else {
189 try self.append(name, value, never_index);
190 }
191 }
192
193 /// Returns boolean indicating if the field is present.
194 pub fn contains(self: Self, name: []const u8) bool {
195 return self.index.contains(name);
196 }
197
198 /// Returns boolean indicating if something was deleted.
199 pub fn delete(self: *Self, name: []const u8) bool {
200 if (self.index.remove(name)) |*kv| {
201 const dex = &kv.value;
202 // iterate backwards
203 var i = dex.items.len;
204 while (i > 0) {
205 i -= 1;
206 const data_index = dex.items[i];
207 const removed = self.data.orderedRemove(data_index);
208 assert(mem.eql(u8, removed.name, name));
209 removed.deinit(self.allocator);
210 }
211 dex.deinit(self.allocator);
212 self.allocator.free(kv.key);
213 self.rebuildIndex();
214 return true;
215 } else {
216 return false;
217 }
218 }
219
220 /// Removes the element at the specified index.
221 /// Moves items down to fill the empty space.
222 /// TODO this implementation can be replaced by adding
223 /// orderedRemove to the new hash table implementation as an
224 /// alternative to swapRemove.
225 pub fn orderedRemove(self: *Self, i: usize) void {
226 const removed = self.data.orderedRemove(i);
227 const kv = self.index.getEntry(removed.name).?;
228 const dex = &kv.value;
229 if (dex.items.len == 1) {
230 // was last item; delete the index
231 dex.deinit(self.allocator);
232 removed.deinit(self.allocator);
233 const key = kv.key;
234 _ = self.index.remove(key); // invalidates `kv` and `dex`
235 self.allocator.free(key);
236 } else {
237 dex.shrink(self.allocator, dex.items.len - 1);
238 removed.deinit(self.allocator);
239 }
240 // if it was the last item; no need to rebuild index
241 if (i != self.data.items.len) {
242 self.rebuildIndex();
243 }
244 }
245
246 /// Removes the element at the specified index.
247 /// The empty slot is filled from the end of the list.
248 /// TODO this implementation can be replaced by simply using the
249 /// new hash table which does swap removal.
250 pub fn swapRemove(self: *Self, i: usize) void {
251 const removed = self.data.swapRemove(i);
252 const kv = self.index.getEntry(removed.name).?;
253 const dex = &kv.value;
254 if (dex.items.len == 1) {
255 // was last item; delete the index
256 dex.deinit(self.allocator);
257 removed.deinit(self.allocator);
258 const key = kv.key;
259 _ = self.index.remove(key); // invalidates `kv` and `dex`
260 self.allocator.free(key);
261 } else {
262 dex.shrink(self.allocator, dex.items.len - 1);
263 removed.deinit(self.allocator);
264 }
265 // if it was the last item; no need to rebuild index
266 if (i != self.data.items.len) {
267 self.rebuildIndex();
268 }
269 }
270
271 /// Access the header at the specified index.
272 pub fn at(self: Self, i: usize) HeaderEntry {
273 return self.data.items[i];
274 }
275
276 /// Returns a list of indices containing headers with the given name.
277 /// The returned list should not be modified by the caller.
278 pub fn getIndices(self: Self, name: []const u8) ?HeaderIndexList {
279 return self.index.get(name);
280 }
281
282 /// Returns a slice containing each header with the given name.
283 pub fn get(self: Self, allocator: *Allocator, name: []const u8) !?[]const HeaderEntry {
284 const dex = self.getIndices(name) orelse return null;
285
286 const buf = try allocator.alloc(HeaderEntry, dex.items.len);
287 var n: usize = 0;
288 for (dex.items) |idx| {
289 buf[n] = self.data.items[idx];
290 n += 1;
291 }
292 return buf;
293 }
294
295 /// Returns all headers with the given name as a comma separated string.
296 ///
297 /// Useful for HTTP headers that follow RFC-7230 section 3.2.2:
298 /// A recipient MAY combine multiple header fields with the same field
299 /// name into one "field-name: field-value" pair, without changing the
300 /// semantics of the message, by appending each subsequent field value to
301 /// the combined field value in order, separated by a comma. The order
302 /// in which header fields with the same field name are received is
303 /// therefore significant to the interpretation of the combined field
304 /// value
305 pub fn getCommaSeparated(self: Self, allocator: *Allocator, name: []const u8) !?[]u8 {
306 const dex = self.getIndices(name) orelse return null;
307
308 // adapted from mem.join
309 const total_len = blk: {
310 var sum: usize = dex.items.len - 1; // space for separator(s)
311 for (dex.items) |idx|
312 sum += self.data.items[idx].value.len;
313 break :blk sum;
314 };
315
316 const buf = try allocator.alloc(u8, total_len);
317 errdefer allocator.free(buf);
318
319 const first_value = self.data.items[dex.items[0]].value;
320 mem.copy(u8, buf, first_value);
321 var buf_index: usize = first_value.len;
322 for (dex.items[1..]) |idx| {
323 const value = self.data.items[idx].value;
324 buf[buf_index] = ',';
325 buf_index += 1;
326 mem.copy(u8, buf[buf_index..], value);
327 buf_index += value.len;
328 }
329
330 // No need for shrink since buf is exactly the correct size.
331 return buf;
332 }
333
334 fn rebuildIndex(self: *Self) void {
335 // clear out the indexes
336 var it = self.index.iterator();
337 while (it.next()) |entry| {
338 entry.value.shrinkRetainingCapacity(0);
339 }
340 // fill up indexes again; we know capacity is fine from before
341 for (self.data.items) |entry, i| {
342 self.index.getEntry(entry.name).?.value.appendAssumeCapacity(i);
343 }
344 }
345
346 pub fn sort(self: *Self) void {
347 std.sort.sort(HeaderEntry, self.data.items, {}, HeaderEntry.compare);
348 self.rebuildIndex();
349 }
350
351 pub fn format(
352 self: Self,
353 comptime fmt: []const u8,
354 options: std.fmt.FormatOptions,
355 out_stream: anytype,
356 ) !void {
357 for (self.toSlice()) |entry| {
358 try out_stream.writeAll(entry.name);
359 try out_stream.writeAll(": ");
360 try out_stream.writeAll(entry.value);
361 try out_stream.writeAll("\n");
362 }
363 }
364};
365
366test "Headers.iterator" {
367 var h = Headers.init(testing.allocator);
368 defer h.deinit();
369 try h.append("foo", "bar", null);
370 try h.append("cookie", "somevalue", null);
371
372 var count: i32 = 0;
373 for (h.toSlice()) |e| {
374 if (count == 0) {
375 testing.expectEqualSlices(u8, "foo", e.name);
376 testing.expectEqualSlices(u8, "bar", e.value);
377 testing.expectEqual(false, e.never_index);
378 } else if (count == 1) {
379 testing.expectEqualSlices(u8, "cookie", e.name);
380 testing.expectEqualSlices(u8, "somevalue", e.value);
381 testing.expectEqual(true, e.never_index);
382 }
383 count += 1;
384 }
385 testing.expectEqual(@as(i32, 2), count);
386}
387
388test "Headers.contains" {
389 var h = Headers.init(testing.allocator);
390 defer h.deinit();
391 try h.append("foo", "bar", null);
392 try h.append("cookie", "somevalue", null);
393
394 testing.expectEqual(true, h.contains("foo"));
395 testing.expectEqual(false, h.contains("flooble"));
396}
397
398test "Headers.delete" {
399 var h = Headers.init(testing.allocator);
400 defer h.deinit();
401 try h.append("foo", "bar", null);
402 try h.append("baz", "qux", null);
403 try h.append("cookie", "somevalue", null);
404
405 testing.expectEqual(false, h.delete("not-present"));
406 testing.expectEqual(@as(usize, 3), h.toSlice().len);
407
408 testing.expectEqual(true, h.delete("foo"));
409 testing.expectEqual(@as(usize, 2), h.toSlice().len);
410 {
411 const e = h.at(0);
412 testing.expectEqualSlices(u8, "baz", e.name);
413 testing.expectEqualSlices(u8, "qux", e.value);
414 testing.expectEqual(false, e.never_index);
415 }
416 {
417 const e = h.at(1);
418 testing.expectEqualSlices(u8, "cookie", e.name);
419 testing.expectEqualSlices(u8, "somevalue", e.value);
420 testing.expectEqual(true, e.never_index);
421 }
422
423 testing.expectEqual(false, h.delete("foo"));
424}
425
426test "Headers.orderedRemove" {
427 var h = Headers.init(testing.allocator);
428 defer h.deinit();
429 try h.append("foo", "bar", null);
430 try h.append("baz", "qux", null);
431 try h.append("cookie", "somevalue", null);
432
433 h.orderedRemove(0);
434 testing.expectEqual(@as(usize, 2), h.toSlice().len);
435 {
436 const e = h.at(0);
437 testing.expectEqualSlices(u8, "baz", e.name);
438 testing.expectEqualSlices(u8, "qux", e.value);
439 testing.expectEqual(false, e.never_index);
440 }
441 {
442 const e = h.at(1);
443 testing.expectEqualSlices(u8, "cookie", e.name);
444 testing.expectEqualSlices(u8, "somevalue", e.value);
445 testing.expectEqual(true, e.never_index);
446 }
447}
448
449test "Headers.swapRemove" {
450 var h = Headers.init(testing.allocator);
451 defer h.deinit();
452 try h.append("foo", "bar", null);
453 try h.append("baz", "qux", null);
454 try h.append("cookie", "somevalue", null);
455
456 h.swapRemove(0);
457 testing.expectEqual(@as(usize, 2), h.toSlice().len);
458 {
459 const e = h.at(0);
460 testing.expectEqualSlices(u8, "cookie", e.name);
461 testing.expectEqualSlices(u8, "somevalue", e.value);
462 testing.expectEqual(true, e.never_index);
463 }
464 {
465 const e = h.at(1);
466 testing.expectEqualSlices(u8, "baz", e.name);
467 testing.expectEqualSlices(u8, "qux", e.value);
468 testing.expectEqual(false, e.never_index);
469 }
470}
471
472test "Headers.at" {
473 var h = Headers.init(testing.allocator);
474 defer h.deinit();
475 try h.append("foo", "bar", null);
476 try h.append("cookie", "somevalue", null);
477
478 {
479 const e = h.at(0);
480 testing.expectEqualSlices(u8, "foo", e.name);
481 testing.expectEqualSlices(u8, "bar", e.value);
482 testing.expectEqual(false, e.never_index);
483 }
484 {
485 const e = h.at(1);
486 testing.expectEqualSlices(u8, "cookie", e.name);
487 testing.expectEqualSlices(u8, "somevalue", e.value);
488 testing.expectEqual(true, e.never_index);
489 }
490}
491
492test "Headers.getIndices" {
493 var h = Headers.init(testing.allocator);
494 defer h.deinit();
495 try h.append("foo", "bar", null);
496 try h.append("set-cookie", "x=1", null);
497 try h.append("set-cookie", "y=2", null);
498
499 testing.expect(null == h.getIndices("not-present"));
500 testing.expectEqualSlices(usize, &[_]usize{0}, h.getIndices("foo").?.items);
501 testing.expectEqualSlices(usize, &[_]usize{ 1, 2 }, h.getIndices("set-cookie").?.items);
502}
503
504test "Headers.get" {
505 var h = Headers.init(testing.allocator);
506 defer h.deinit();
507 try h.append("foo", "bar", null);
508 try h.append("set-cookie", "x=1", null);
509 try h.append("set-cookie", "y=2", null);
510
511 {
512 const v = try h.get(testing.allocator, "not-present");
513 testing.expect(null == v);
514 }
515 {
516 const v = (try h.get(testing.allocator, "foo")).?;
517 defer testing.allocator.free(v);
518 const e = v[0];
519 testing.expectEqualSlices(u8, "foo", e.name);
520 testing.expectEqualSlices(u8, "bar", e.value);
521 testing.expectEqual(false, e.never_index);
522 }
523 {
524 const v = (try h.get(testing.allocator, "set-cookie")).?;
525 defer testing.allocator.free(v);
526 {
527 const e = v[0];
528 testing.expectEqualSlices(u8, "set-cookie", e.name);
529 testing.expectEqualSlices(u8, "x=1", e.value);
530 testing.expectEqual(true, e.never_index);
531 }
532 {
533 const e = v[1];
534 testing.expectEqualSlices(u8, "set-cookie", e.name);
535 testing.expectEqualSlices(u8, "y=2", e.value);
536 testing.expectEqual(true, e.never_index);
537 }
538 }
539}
540
541test "Headers.getCommaSeparated" {
542 var h = Headers.init(testing.allocator);
543 defer h.deinit();
544 try h.append("foo", "bar", null);
545 try h.append("set-cookie", "x=1", null);
546 try h.append("set-cookie", "y=2", null);
547
548 {
549 const v = try h.getCommaSeparated(testing.allocator, "not-present");
550 testing.expect(null == v);
551 }
552 {
553 const v = (try h.getCommaSeparated(testing.allocator, "foo")).?;
554 defer testing.allocator.free(v);
555 testing.expectEqualSlices(u8, "bar", v);
556 }
557 {
558 const v = (try h.getCommaSeparated(testing.allocator, "set-cookie")).?;
559 defer testing.allocator.free(v);
560 testing.expectEqualSlices(u8, "x=1,y=2", v);
561 }
562}
563
564test "Headers.sort" {
565 var h = Headers.init(testing.allocator);
566 defer h.deinit();
567 try h.append("foo", "bar", null);
568 try h.append("cookie", "somevalue", null);
569
570 h.sort();
571 {
572 const e = h.at(0);
573 testing.expectEqualSlices(u8, "cookie", e.name);
574 testing.expectEqualSlices(u8, "somevalue", e.value);
575 testing.expectEqual(true, e.never_index);
576 }
577 {
578 const e = h.at(1);
579 testing.expectEqualSlices(u8, "foo", e.name);
580 testing.expectEqualSlices(u8, "bar", e.value);
581 testing.expectEqual(false, e.never_index);
582 }
583}
584
585test "Headers.format" {
586 var h = Headers.init(testing.allocator);
587 defer h.deinit();
588 try h.append("foo", "bar", null);
589 try h.append("cookie", "somevalue", null);
590
591 var buf: [100]u8 = undefined;
592 testing.expectEqualSlices(u8,
593 \\foo: bar
594 \\cookie: somevalue
595 \\
596 , try std.fmt.bufPrint(buf[0..], "{}", .{h}));
597}
lib/std/std.zig-1
......@@ -62,7 +62,6 @@ pub const fs = @import("fs.zig");
6262pub const hash = @import("hash.zig");
6363pub const hash_map = @import("hash_map.zig");
6464pub const heap = @import("heap.zig");
65pub const http = @import("http.zig");
6665pub const io = @import("io.zig");
6766pub const json = @import("json.zig");
6867pub const log = @import("log.zig");