| ... | ... | @@ -2,6 +2,7 @@ const std = @import("std"); |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | 3 | const extras = @import("extras"); |
| 4 | 4 | const unicode_idna = @import("unicode-idna"); |
| 5 | const nio = @import("nio"); |
| 5 | 6 | |
| 6 | 7 | pub const URL = struct { |
| 7 | 8 | href: []const u8, |
| ... | ... | @@ -47,13 +48,13 @@ pub const URL = struct { |
| 47 | 48 | // input is a scalar value string |
| 48 | 49 | if (!std.unicode.utf8ValidateSlice(input)) return error.InvalidURL; |
| 49 | 50 | |
| 50 | | var inputl = std.array_list.Managed(u8).init(alloc); |
| 51 | var inputl = nio.AllocatingWriter.init(alloc); |
| 51 | 52 | defer inputl.deinit(); |
| 52 | | try inputl.appendSlice(input); |
| 53 | try inputl.writeAll(input); |
| 53 | 54 | |
| 54 | 55 | // 1. |
| 55 | 56 | while (inputl.items.len > 0 and is_c0control_or_space(inputl.items[0])) _ = inputl.orderedRemove(0); |
| 56 | | while (inputl.items.len > 0 and is_c0control_or_space(inputl.getLast())) inputl.items.len -= 1; |
| 57 | while (inputl.items.len > 0 and is_c0control_or_space(inputl.last())) inputl.items.len -= 1; |
| 57 | 58 | |
| 58 | 59 | // 3. |
| 59 | 60 | while (std.mem.indexOfScalar(u8, inputl.items, '\t')) |i| _ = inputl.orderedRemove(i); |
| ... | ... | @@ -70,7 +71,7 @@ pub const URL = struct { |
| 70 | 71 | // we always do utf-8 |
| 71 | 72 | |
| 72 | 73 | // 6. |
| 73 | | var buffer = std.array_list.Managed(u8).init(alloc); |
| 74 | var buffer = nio.AllocatingWriter.init(alloc); |
| 74 | 75 | defer buffer.deinit(); |
| 75 | 76 | |
| 76 | 77 | // 7. |
| ... | ... | @@ -118,7 +119,7 @@ pub const URL = struct { |
| 118 | 119 | std.debug.assert(pointer == 0); |
| 119 | 120 | // 1. If c is an ASCII alpha, append c, lowercased, to buffer, and set state to scheme state. |
| 120 | 121 | if (i < length and std.ascii.isAlphabetic(c)) { |
| 121 | | try buffer.append(std.ascii.toLower(c)); |
| 122 | try buffer.writeAll(&.{std.ascii.toLower(c)}); |
| 122 | 123 | state = .scheme; |
| 123 | 124 | } |
| 124 | 125 | // 2. Otherwise, if state override is not given, set state to no scheme state and decrease pointer by 1. |
| ... | ... | @@ -134,7 +135,7 @@ pub const URL = struct { |
| 134 | 135 | .scheme => { |
| 135 | 136 | // 1. If c is an ASCII alphanumeric, U+002B (+), U+002D (-), or U+002E (.), append c, lowercased, to buffer. |
| 136 | 137 | if (std.ascii.isAlphanumeric(c) or c == '+' or c == '-' or c == '.') { |
| 137 | | try buffer.append(std.ascii.toLower(c)); |
| 138 | try buffer.writeAll(&.{std.ascii.toLower(c)}); |
| 138 | 139 | } |
| 139 | 140 | // 2. Otherwise, if c is U+003A (:), then: |
| 140 | 141 | else if (c == ':') { |
| ... | ... | @@ -156,7 +157,7 @@ pub const URL = struct { |
| 156 | 157 | // 2. Return. |
| 157 | 158 | } |
| 158 | 159 | // 4. Set buffer to the empty string. |
| 159 | | buffer.clearRetainingCapacity(); |
| 160 | buffer.items.len = 0; |
| 160 | 161 | // 5. If url’s scheme is "file", then: |
| 161 | 162 | if (std.mem.eql(u8, href.items(0), "file")) { |
| 162 | 163 | // 1. If remaining does not start with "//", special-scheme-missing-following-solidus validation error. |
| ... | ... | @@ -192,7 +193,7 @@ pub const URL = struct { |
| 192 | 193 | } |
| 193 | 194 | // 3. Otherwise, if state override is not given, set buffer to the empty string, state to no scheme state, and start over (from the first code point in input). |
| 194 | 195 | else if (state_override == null) { |
| 195 | | buffer.clearRetainingCapacity(); |
| 196 | buffer.items.len = 0; |
| 196 | 197 | state = .no_scheme; |
| 197 | 198 | pointer = 0; |
| 198 | 199 | i = 0; |
| ... | ... | @@ -380,7 +381,7 @@ pub const URL = struct { |
| 380 | 381 | // 1. Invalid-credentials validation error. |
| 381 | 382 | {} |
| 382 | 383 | // 2. If atSignSeen is true, then prepend "%40" to buffer. |
| 383 | | if (atSignSeen) try buffer.insertSlice(0, "%40"); |
| 384 | if (atSignSeen) try buffer.insertAt(0, "%40"); |
| 384 | 385 | // 3. Set atSignSeen to true. |
| 385 | 386 | atSignSeen = true; |
| 386 | 387 | // 4. For each codePoint in buffer: |
| ... | ... | @@ -401,7 +402,7 @@ pub const URL = struct { |
| 401 | 402 | } |
| 402 | 403 | } |
| 403 | 404 | // 5. Set buffer to the empty string. |
| 404 | | buffer.clearRetainingCapacity(); |
| 405 | buffer.items.len = 0; |
| 405 | 406 | } |
| 406 | 407 | // 2. Otherwise, if one of the following is true: |
| 407 | 408 | // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) |
| ... | ... | @@ -416,12 +417,12 @@ pub const URL = struct { |
| 416 | 417 | i = lastcpi(inputl.items[0..i]); |
| 417 | 418 | c = inputl.items[i]; |
| 418 | 419 | } |
| 419 | | buffer.clearRetainingCapacity(); |
| 420 | buffer.items.len = 0; |
| 420 | 421 | state = .host; |
| 421 | 422 | } |
| 422 | 423 | // 3. Otherwise, append c to buffer. |
| 423 | 424 | else { |
| 424 | | try buffer.appendSlice(inputl.items[i..][0..l(c)]); |
| 425 | try buffer.writeAll(inputl.items[i..][0..l(c)]); |
| 425 | 426 | } |
| 426 | 427 | }, |
| 427 | 428 | .host, .hostname => { |
| ... | ... | @@ -445,7 +446,7 @@ pub const URL = struct { |
| 445 | 446 | // 5. Set url’s host to host, buffer to the empty string, and state to port state. |
| 446 | 447 | try setHost(&href, h); |
| 447 | 448 | hostname_kind = h; |
| 448 | | buffer.clearRetainingCapacity(); |
| 449 | buffer.items.len = 0; |
| 449 | 450 | state = .port; |
| 450 | 451 | } |
| 451 | 452 | // 3. Otherwise, if one of the following is true: |
| ... | ... | @@ -467,7 +468,7 @@ pub const URL = struct { |
| 467 | 468 | // 5. Set url’s host to host, buffer to the empty string, and state to path start state. |
| 468 | 469 | try setHost(&href, h); |
| 469 | 470 | hostname_kind = h; |
| 470 | | buffer.clearRetainingCapacity(); |
| 471 | buffer.items.len = 0; |
| 471 | 472 | state = .path_start; |
| 472 | 473 | // 6. If state override is given, then return. |
| 473 | 474 | if (state_override != null) break; |
| ... | ... | @@ -479,13 +480,13 @@ pub const URL = struct { |
| 479 | 480 | // 2. If c is U+005D (]), then set insideBrackets to false. |
| 480 | 481 | if (c == ']') insideBrackets = false; |
| 481 | 482 | // 3. Append c to buffer. |
| 482 | | try buffer.appendSlice(inputl.items[i..][0..l(c)]); |
| 483 | try buffer.writeAll(inputl.items[i..][0..l(c)]); |
| 483 | 484 | } |
| 484 | 485 | }, |
| 485 | 486 | .port => { |
| 486 | 487 | // 1. If c is an ASCII digit, append c to buffer. |
| 487 | 488 | if (std.ascii.isDigit(c)) { |
| 488 | | try buffer.appendSlice(inputl.items[i..][0..l(c)]); |
| 489 | try buffer.writeAll(inputl.items[i..][0..l(c)]); |
| 489 | 490 | } |
| 490 | 491 | // 2. Otherwise, if one of the following is true: |
| 491 | 492 | // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#); |
| ... | ... | @@ -501,7 +502,7 @@ pub const URL = struct { |
| 501 | 502 | // 3. Set url’s port to null, if port is url’s scheme’s default port; otherwise to port. |
| 502 | 503 | if (schemeDefaultPort(href.items(0)) != p) try href.print(9, "{d}", .{p}); |
| 503 | 504 | // 4. Set buffer to the empty string. |
| 504 | | buffer.clearRetainingCapacity(); |
| 505 | buffer.items.len = 0; |
| 505 | 506 | // 5. If state override is given, then return. |
| 506 | 507 | if (state_override != null) break; |
| 507 | 508 | } |
| ... | ... | @@ -655,13 +656,13 @@ pub const URL = struct { |
| 655 | 656 | // 5. If state override is given, then return. |
| 656 | 657 | if (state_override != null) break; |
| 657 | 658 | // 6. Set buffer to the empty string and state to path start state. |
| 658 | | buffer.clearRetainingCapacity(); |
| 659 | buffer.items.len = 0; |
| 659 | 660 | state = .path_start; |
| 660 | 661 | } |
| 661 | 662 | } |
| 662 | 663 | // 2. Otherwise, append c to buffer. |
| 663 | 664 | else { |
| 664 | | try buffer.appendSlice(inputl.items[i..][0..l(c)]); |
| 665 | try buffer.writeAll(inputl.items[i..][0..l(c)]); |
| 665 | 666 | } |
| 666 | 667 | }, |
| 667 | 668 | .path_start => { |
| ... | ... | @@ -743,7 +744,7 @@ pub const URL = struct { |
| 743 | 744 | try href.appendSlice(10, buffer.items); |
| 744 | 745 | } |
| 745 | 746 | // 5. Set buffer to the empty string. |
| 746 | | buffer.clearRetainingCapacity(); |
| 747 | buffer.items.len = 0; |
| 747 | 748 | // 6. If c is U+003F (?), then set url’s query to the empty string and state to query state. |
| 748 | 749 | if (c == '?') { |
| 749 | 750 | href.clear(12); |
| ... | ... | @@ -767,9 +768,9 @@ pub const URL = struct { |
| 767 | 768 | if (is_path_percent_char(c)) { |
| 768 | 769 | const pe = try percentEncode(alloc, inputl.items[i..][0..l(c)], is_path_percent_char); |
| 769 | 770 | defer alloc.free(pe); |
| 770 | | try buffer.appendSlice(pe); |
| 771 | try buffer.writeAll(pe); |
| 771 | 772 | } else { |
| 772 | | try buffer.appendSlice(inputl.items[i..][0..l(c)]); |
| 773 | try buffer.writeAll(inputl.items[i..][0..l(c)]); |
| 773 | 774 | } |
| 774 | 775 | } |
| 775 | 776 | }, |
| ... | ... | @@ -828,7 +829,7 @@ pub const URL = struct { |
| 828 | 829 | try percentEncodeML(&href, 12, buffer.items, is_query_percent_char); |
| 829 | 830 | } |
| 830 | 831 | // 3. Set buffer to the empty string. |
| 831 | | buffer.clearRetainingCapacity(); |
| 832 | buffer.items.len = 0; |
| 832 | 833 | // 4. If c is U+0023 (#), then set url’s fragment to the empty string and state to fragment state. |
| 833 | 834 | if (c == '#') { |
| 834 | 835 | href.clear(14); |
| ... | ... | @@ -843,7 +844,7 @@ pub const URL = struct { |
| 843 | 844 | // 2. If c is U+0025 (%) and remaining does not start with two ASCII hex digits, invalid-URL-unit validation error. |
| 844 | 845 | {} |
| 845 | 846 | // 3. Append c to buffer. |
| 846 | | try buffer.appendSlice(inputl.items[i..][0..l(c)]); |
| 847 | try buffer.writeAll(inputl.items[i..][0..l(c)]); |
| 847 | 848 | } |
| 848 | 849 | }, |
| 849 | 850 | .fragment => { |
| ... | ... | @@ -1140,12 +1141,12 @@ pub const SearchParams = struct { |
| 1140 | 1141 | |
| 1141 | 1142 | pub fn encode(self: *const SearchParams) ![]u8 { |
| 1142 | 1143 | const alloc = self.allocator; |
| 1143 | | var list = std.array_list.Managed(u8).init(alloc); |
| 1144 | var list = nio.AllocatingWriter.init(alloc); |
| 1144 | 1145 | errdefer list.deinit(); |
| 1145 | 1146 | for (self.inner.items(.key), self.inner.items(.value), 0..) |k, v, i| { |
| 1146 | | if (i > 0) try list.writer().writeAll("&"); |
| 1147 | if (i > 0) try list.writeAll("&"); |
| 1147 | 1148 | try percentEncodeAL(&list, k, is_formurlencoded_percent_char); |
| 1148 | | try list.append('='); |
| 1149 | try list.writeAll("="); |
| 1149 | 1150 | try percentEncodeAL(&list, v, is_formurlencoded_percent_char); |
| 1150 | 1151 | } |
| 1151 | 1152 | list.items.len -= std.mem.replace(u8, list.items, "%20", "+", list.items) * 2; |
| ... | ... | @@ -1458,7 +1459,7 @@ fn parseHostOpaque(allocator: std.mem.Allocator, input: []const u8) ![]const u8 |
| 1458 | 1459 | /// https://url.spec.whatwg.org/#utf-8-percent-encode |
| 1459 | 1460 | fn percentEncode(allocator: std.mem.Allocator, input: []const u8, comptime set: fn (u8) bool) ![]u8 { |
| 1460 | 1461 | if (!extras.matchesAny(u8, input, set)) return allocator.dupe(u8, input); |
| 1461 | | var result = std.array_list.Managed(u8).init(allocator); |
| 1462 | var result = nio.AllocatingWriter.init(allocator); |
| 1462 | 1463 | errdefer result.deinit(); |
| 1463 | 1464 | try result.ensureUnusedCapacity(input.len); |
| 1464 | 1465 | try percentEncodeAL(&result, input, set); |
| ... | ... | @@ -1467,10 +1468,10 @@ fn percentEncode(allocator: std.mem.Allocator, input: []const u8, comptime set: |
| 1467 | 1468 | |
| 1468 | 1469 | /// https://url.spec.whatwg.org/#string-percent-decode |
| 1469 | 1470 | pub fn percentDecode(allocator: std.mem.Allocator, input: []const u8) ![]u8 { |
| 1470 | | var result = std.array_list.Managed(u8).init(allocator); |
| 1471 | var result = nio.AllocatingWriter.init(allocator); |
| 1471 | 1472 | errdefer result.deinit(); |
| 1472 | 1473 | try result.ensureUnusedCapacity(input.len); |
| 1473 | | try percentDecodeW(result.writer(), input); |
| 1474 | try percentDecodeW(&result, input); |
| 1474 | 1475 | return result.toOwnedSlice(); |
| 1475 | 1476 | } |
| 1476 | 1477 | pub fn percentDecodeW(writer: anytype, input: []const u8) !void { |
| ... | ... | @@ -1792,11 +1793,11 @@ fn lastcpi(haystack: []const u8) usize { |
| 1792 | 1793 | while (haystack[i] & 0xC0 == 0x80) : (i -= 1) {} |
| 1793 | 1794 | return i; |
| 1794 | 1795 | } |
| 1795 | | pub fn percentEncodeScalarAL(list: *std.array_list.Managed(u8), cp: []const u8, comptime set: fn (u8) bool) !void { |
| 1796 | pub fn percentEncodeScalarAL(list: *nio.AllocatingWriter, cp: []const u8, comptime set: fn (u8) bool) !void { |
| 1796 | 1797 | if (set(cp[0])) { |
| 1797 | 1798 | for (cp) |b| { |
| 1798 | 1799 | try list.append('%'); |
| 1799 | | try list.writer().print("{X:0>2}", .{b}); |
| 1800 | try list.print("{X:0>2}", .{b}); |
| 1800 | 1801 | } |
| 1801 | 1802 | } else { |
| 1802 | 1803 | try list.append(cp[0]); |
| ... | ... | @@ -1815,16 +1816,16 @@ pub fn percentEncodeW(writer: anytype, input: []const u8, comptime set: fn (u8) |
| 1815 | 1816 | } |
| 1816 | 1817 | } |
| 1817 | 1818 | } |
| 1818 | | pub fn percentEncodeAL(list: *std.array_list.Managed(u8), input: []const u8, comptime set: fn (u8) bool) !void { |
| 1819 | pub fn percentEncodeAL(list: *nio.AllocatingWriter, input: []const u8, comptime set: fn (u8) bool) !void { |
| 1819 | 1820 | var it = std.unicode.Utf8View.initUnchecked(input).iterator(); |
| 1820 | 1821 | while (it.nextCodepointSlice()) |sl| { |
| 1821 | 1822 | if (set(sl[0])) { |
| 1822 | 1823 | for (sl) |b| { |
| 1823 | | try list.append('%'); |
| 1824 | | try list.writer().print("{X:0>2}", .{b}); |
| 1824 | try list.writeAll("%"); |
| 1825 | try list.print("{X:0>2}", .{b}); |
| 1825 | 1826 | } |
| 1826 | 1827 | } else { |
| 1827 | | try list.appendSlice(sl); |
| 1828 | try list.writeAll(sl); |
| 1828 | 1829 | } |
| 1829 | 1830 | } |
| 1830 | 1831 | } |