| ... | ... | @@ -7,6 +7,7 @@ const unicode_idna = @import("unicode-idna"); |
| 7 | 7 | pub const URL = struct { |
| 8 | 8 | href: []const u8, |
| 9 | 9 | scheme: []const u8 = "", |
| 10 | protocol: []const u8, |
| 10 | 11 | |
| 11 | 12 | pub const HostKind = enum { |
| 12 | 13 | name, |
| ... | ... | @@ -75,23 +76,27 @@ pub const URL = struct { |
| 75 | 76 | // inputl[i], must change in tandem with i |
| 76 | 77 | var c: u8 = if (length > 0) inputl.items[i] else 0; |
| 77 | 78 | |
| 78 | | var href = std.ArrayList(u8).init(alloc); |
| 79 | var href = ManyArrayList(8 + 7, u8).init(alloc); |
| 79 | 80 | defer href.deinit(); |
| 80 | | |
| 81 | | var scheme = std.ArrayList(u8).init(alloc); |
| 82 | | defer scheme.deinit(); |
| 81 | // : |
| 82 | // // |
| 83 | 83 | var username = std.ArrayList(u8).init(alloc); |
| 84 | 84 | defer username.deinit(); |
| 85 | // : |
| 85 | 86 | var password = std.ArrayList(u8).init(alloc); |
| 86 | 87 | defer password.deinit(); |
| 88 | // @ |
| 87 | 89 | var host: Host = .{ .name = "" }; |
| 88 | 90 | defer if (host == .name) alloc.free(host.name); |
| 91 | // : |
| 89 | 92 | var port = std.ArrayList(u8).init(alloc); |
| 90 | 93 | defer port.deinit(); |
| 91 | 94 | var path = std.ArrayList(u8).init(alloc); |
| 92 | 95 | defer path.deinit(); |
| 96 | // ? |
| 93 | 97 | var query = std.ArrayList(u8).init(alloc); |
| 94 | 98 | defer query.deinit(); |
| 99 | // # |
| 95 | 100 | var fragment = std.ArrayList(u8).init(alloc); |
| 96 | 101 | defer fragment.deinit(); |
| 97 | 102 | |
| ... | ... | @@ -122,6 +127,7 @@ pub const URL = struct { |
| 122 | 127 | } |
| 123 | 128 | // 2. Otherwise, if c is U+003A (:), then: |
| 124 | 129 | else if (c == ':') { |
| 130 | try href.set(1, ":"); |
| 125 | 131 | // 1. If state override is given, then: |
| 126 | 132 | if (state_override != null) { |
| 127 | 133 | @panic("TODO"); |
| ... | ... | @@ -131,8 +137,7 @@ pub const URL = struct { |
| 131 | 137 | // 4. If url’s scheme is "file" and its host is an empty host, then return. |
| 132 | 138 | } |
| 133 | 139 | // 2. Set url’s scheme to buffer. |
| 134 | | scheme.clearRetainingCapacity(); |
| 135 | | try scheme.appendSlice(buffer.items); |
| 140 | try href.set(0, buffer.items); |
| 136 | 141 | // 3. If state override is given, then: |
| 137 | 142 | if (state_override != null) { |
| 138 | 143 | @panic("TODO"); |
| ... | ... | @@ -142,20 +147,20 @@ pub const URL = struct { |
| 142 | 147 | // 4. Set buffer to the empty string. |
| 143 | 148 | buffer.clearRetainingCapacity(); |
| 144 | 149 | // 5. If url’s scheme is "file", then: |
| 145 | | if (std.mem.eql(u8, scheme.items, "file")) { |
| 150 | if (std.mem.eql(u8, href.items(0), "file")) { |
| 146 | 151 | // 1. If remaining does not start with "//", special-scheme-missing-following-solidus validation error. |
| 147 | 152 | {} |
| 148 | 153 | // 2. Set state to file state. |
| 149 | 154 | state = .file; |
| 150 | 155 | } |
| 151 | 156 | // 6. Otherwise, if url is special, base is non-null, and base’s scheme is url’s scheme: |
| 152 | | else if (isSchemeSpecial(scheme.items) and base != null and std.mem.eql(u8, base.?.scheme, scheme.items)) { |
| 157 | else if (isSchemeSpecial(href.items(0)) and base != null and std.mem.eql(u8, base.?.scheme, href.items(0))) { |
| 153 | 158 | @panic("TODO"); |
| 154 | 159 | // 1. Assert: base is special (and therefore does not have an opaque path). |
| 155 | 160 | // 2. Set state to special relative or authority state. |
| 156 | 161 | } |
| 157 | 162 | // 7. Otherwise, if url is special, set state to special authority slashes state. |
| 158 | | else if (isSchemeSpecial(scheme.items)) { |
| 163 | else if (isSchemeSpecial(href.items(0))) { |
| 159 | 164 | state = .special_authority_slashes; |
| 160 | 165 | } |
| 161 | 166 | // 8. Otherwise, if remaining starts with an U+002F (/), set state to path or authority state and increase pointer by 1. |
| ... | ... | @@ -223,13 +228,13 @@ pub const URL = struct { |
| 223 | 228 | // 1. Assert: base’s scheme is not "file". |
| 224 | 229 | std.debug.assert(!std.mem.eql(u8, base.?.scheme, "file")); |
| 225 | 230 | // 2. Set url’s scheme to base’s scheme. |
| 226 | | try scheme.appendSlice(base.?.scheme); |
| 231 | try href.set(0, base.?.scheme); |
| 227 | 232 | // 3. If c is U+002F (/), then set state to relative slash state. |
| 228 | 233 | if (c == '/') { |
| 229 | 234 | state = .relative_slash; |
| 230 | 235 | } |
| 231 | 236 | // 4. Otherwise, if url is special and c is U+005C (\), invalid-reverse-solidus validation error, set state to relative slash state. |
| 232 | | else if (isSchemeSpecial(scheme.items) and c == '\\') { |
| 237 | else if (isSchemeSpecial(href.items(0)) and c == '\\') { |
| 233 | 238 | state = .relative_slash; |
| 234 | 239 | } |
| 235 | 240 | // 5. Otherwise: |
| ... | ... | @@ -267,7 +272,7 @@ pub const URL = struct { |
| 267 | 272 | }, |
| 268 | 273 | .relative_slash => { |
| 269 | 274 | // 1. If url is special and c is U+002F (/) or U+005C (\), then: |
| 270 | | if (isSchemeSpecial(scheme.items) and (c == '/' or c == '\\')) { |
| 275 | if (isSchemeSpecial(href.items(0)) and (c == '/' or c == '\\')) { |
| 271 | 276 | // 1. If c is U+005C (\), invalid-reverse-solidus validation error. |
| 272 | 277 | // 2. Set state to special authority ignore slashes state. |
| 273 | 278 | state = .special_authority_ignore_slashes; |
| ... | ... | @@ -340,7 +345,7 @@ pub const URL = struct { |
| 340 | 345 | // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) |
| 341 | 346 | // - url is special and c is U+005C (\) |
| 342 | 347 | // then: |
| 343 | | else if ((i == length or c == '/' or c == '?' or c == '#') or (isSchemeSpecial(scheme.items) and c == '\\')) { |
| 348 | else if ((i == length or c == '/' or c == '?' or c == '#') or (isSchemeSpecial(href.items(0)) and c == '\\')) { |
| 344 | 349 | // 1. If atSignSeen is true and buffer is the empty string, host-missing validation error, return failure. |
| 345 | 350 | if (atSignSeen and buffer.items.len == 0) return error.InvalidURL; |
| 346 | 351 | // 2. Decrease pointer by buffer’s code point length + 1, set buffer to the empty string, and set state to host state. |
| ... | ... | @@ -359,7 +364,7 @@ pub const URL = struct { |
| 359 | 364 | }, |
| 360 | 365 | .host, .hostname => { |
| 361 | 366 | // 1. If state override is given and url’s scheme is "file", then decrease pointer by 1 and set state to file host state. |
| 362 | | if (state_override != null and std.mem.eql(u8, scheme.items, "file")) { |
| 367 | if (state_override != null and std.mem.eql(u8, href.items(0), "file")) { |
| 363 | 368 | pointer -= 1; |
| 364 | 369 | i = lastcpi(inputl.items[0..i]); |
| 365 | 370 | c = inputl.items[i]; |
| ... | ... | @@ -373,7 +378,7 @@ pub const URL = struct { |
| 373 | 378 | if (state_override != null and state_override.? == .hostname) return error.InvalidURL; |
| 374 | 379 | // 3. Let host be the result of host parsing buffer with url is not special. |
| 375 | 380 | // 4. If host is failure, then return failure. |
| 376 | | const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(scheme.items)); |
| 381 | const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0))); |
| 377 | 382 | // 5. Set url’s host to host, buffer to the empty string, and state to port state. |
| 378 | 383 | host = h; |
| 379 | 384 | buffer.clearRetainingCapacity(); |
| ... | ... | @@ -383,17 +388,17 @@ pub const URL = struct { |
| 383 | 388 | // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) |
| 384 | 389 | // - url is special and c is U+005C (\) |
| 385 | 390 | // then decrease pointer by 1, and: |
| 386 | | else if ((i == length or c == '/' or c == '?' or c == '#') or (isSchemeSpecial(scheme.items) and c == '\\')) { |
| 391 | else if ((i == length or c == '/' or c == '?' or c == '#') or (isSchemeSpecial(href.items(0)) and c == '\\')) { |
| 387 | 392 | pointer -= 1; |
| 388 | 393 | i = lastcpi(inputl.items[0..i]); |
| 389 | 394 | c = inputl.items[i]; |
| 390 | 395 | // 1. If url is special and buffer is the empty string, host-missing validation error, return failure. |
| 391 | | if (isSchemeSpecial(scheme.items) and buffer.items.len == 0) return error.InvalidURL |
| 396 | if (isSchemeSpecial(href.items(0)) and buffer.items.len == 0) return error.InvalidURL |
| 392 | 397 | // 2. Otherwise, if state override is given, buffer is the empty string, and either url includes credentials or url’s port is non-null, then return failure. |
| 393 | 398 | else if (state_override != null and buffer.items.len == 0 and (username.items.len > 0 or password.items.len > 0)) return error.InvalidURL; |
| 394 | 399 | // 3. Let host be the result of host parsing buffer with url is not special. |
| 395 | 400 | // 4. If host is failure, then return failure. |
| 396 | | const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(scheme.items)); |
| 401 | const h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0))); |
| 397 | 402 | // 5. Set url’s host to host, buffer to the empty string, and state to path start state. |
| 398 | 403 | host = h; |
| 399 | 404 | buffer.clearRetainingCapacity(); |
| ... | ... | @@ -421,14 +426,14 @@ pub const URL = struct { |
| 421 | 426 | // - url is special and c is U+005C (\); or |
| 422 | 427 | // - state override is given, |
| 423 | 428 | // then: |
| 424 | | else if ((i == length or c == '/' or c == '?' or c == '#') or (isSchemeSpecial(scheme.items) and c == '\\') or (state_override != null)) { |
| 429 | else if ((i == length or c == '/' or c == '?' or c == '#') or (isSchemeSpecial(href.items(0)) and c == '\\') or (state_override != null)) { |
| 425 | 430 | // 1. If buffer is not the empty string: |
| 426 | 431 | if (buffer.items.len > 0) { |
| 427 | 432 | // 1. Let port be the mathematical integer value that is represented by buffer in radix-10 using ASCII digits for digits with values 0 through 9. |
| 428 | 433 | // 2. If port is not a 16-bit unsigned integer, port-out-of-range validation error, return failure. |
| 429 | 434 | const p = std.fmt.parseInt(u16, buffer.items, 10) catch return error.InvalidURL; |
| 430 | 435 | // 3. Set url’s port to null, if port is url’s scheme’s default port; otherwise to port. |
| 431 | | if (schemeDefaultPort(scheme.items) != p) try port.writer().print("{d}", .{p}); |
| 436 | if (schemeDefaultPort(href.items(0)) != p) try port.writer().print("{d}", .{p}); |
| 432 | 437 | // 4. Set buffer to the empty string. |
| 433 | 438 | buffer.clearRetainingCapacity(); |
| 434 | 439 | // 5. If state override is given, then return. |
| ... | ... | @@ -449,8 +454,7 @@ pub const URL = struct { |
| 449 | 454 | }, |
| 450 | 455 | .file => { |
| 451 | 456 | // 1. Set url’s scheme to "file". |
| 452 | | scheme.clearRetainingCapacity(); |
| 453 | | try scheme.appendSlice("file"); |
| 457 | try href.set(0, "file"); |
| 454 | 458 | // 2. Set url’s host to the empty string. |
| 455 | 459 | host = .{ .name = "" }; |
| 456 | 460 | // 3. If c is U+002F (/) or U+005C (\), then: |
| ... | ... | @@ -552,7 +556,7 @@ pub const URL = struct { |
| 552 | 556 | else { |
| 553 | 557 | // 1. Let host be the result of host parsing buffer with url is not special. |
| 554 | 558 | // 2. If host is failure, then return failure. |
| 555 | | var h = try parseHost(alloc, buffer.items, !isSchemeSpecial(scheme.items)); |
| 559 | var h = try parseHost(alloc, buffer.items, !isSchemeSpecial(href.items(0))); |
| 556 | 560 | // 3. If host is "localhost", then set host to the empty string. |
| 557 | 561 | if (h == .name and std.mem.eql(u8, h.name, "localhost")) { |
| 558 | 562 | alloc.free(h.name); |
| ... | ... | @@ -572,7 +576,7 @@ pub const URL = struct { |
| 572 | 576 | }, |
| 573 | 577 | .path_start => { |
| 574 | 578 | // 1. If url is special, then: |
| 575 | | if (isSchemeSpecial(scheme.items)) { |
| 579 | if (isSchemeSpecial(href.items(0))) { |
| 576 | 580 | // 1. If c is U+005C (\), invalid-reverse-solidus validation error. |
| 577 | 581 | {} |
| 578 | 582 | // 2. Set state to path state. |
| ... | ... | @@ -616,7 +620,7 @@ pub const URL = struct { |
| 616 | 620 | // - url is special and c is U+005C (\) |
| 617 | 621 | // - state override is not given and c is U+003F (?) or U+0023 (#) |
| 618 | 622 | // then: |
| 619 | | if ((i == length or c == '/') or (isSchemeSpecial(scheme.items) and c == '\\') or (state_override == null and (c == '?' or c == '#'))) { |
| 623 | if ((i == length or c == '/') or (isSchemeSpecial(href.items(0)) and c == '\\') or (state_override == null and (c == '?' or c == '#'))) { |
| 620 | 624 | // 1. If url is special and c is U+005C (\), invalid-reverse-solidus validation error. |
| 621 | 625 | {} |
| 622 | 626 | // 2. If buffer is a double-dot URL path segment, then: |
| ... | ... | @@ -627,14 +631,14 @@ pub const URL = struct { |
| 627 | 631 | // > This means that for input /usr/.. the result is / and not a lack of a path. |
| 628 | 632 | } |
| 629 | 633 | // 3. Otherwise, if buffer is a single-dot URL path segment and if neither c is U+002F (/), nor url is special and c is U+005C (\), append the empty string to url’s path. |
| 630 | | else if (isSingleDotPathSeg(buffer.items) and (c != '/' or !(isSchemeSpecial(scheme.items) and c == '\\'))) { |
| 634 | else if (isSingleDotPathSeg(buffer.items) and (c != '/' or !(isSchemeSpecial(href.items(0)) and c == '\\'))) { |
| 631 | 635 | // @panic("TODO"); |
| 632 | 636 | } |
| 633 | 637 | // 4. Otherwise, if buffer is not a single-dot URL path segment, then: |
| 634 | 638 | else if (!isSingleDotPathSeg(buffer.items)) { |
| 635 | 639 | // 1. If url’s scheme is "file", url’s path is empty, and buffer is a Windows drive letter, then replace the second code point in buffer with U+003A (:). |
| 636 | 640 | // > This is a (platform-independent) Windows drive letter quirk. |
| 637 | | if (std.mem.eql(u8, scheme.items, "file") and path.items.len == 0 and isWindowsDriveLetter(buffer.items)) { |
| 641 | if (std.mem.eql(u8, href.items(0), "file") and path.items.len == 0 and isWindowsDriveLetter(buffer.items)) { |
| 638 | 642 | buffer.items[1] = ':'; |
| 639 | 643 | } |
| 640 | 644 | // 2. Append buffer to url’s path. |
| ... | ... | @@ -715,7 +719,7 @@ pub const URL = struct { |
| 715 | 719 | // 1. Let queryPercentEncodeSet be the special-query percent-encode set if url is special; otherwise the query percent-encode set. |
| 716 | 720 | // 2. Percent-encode after encoding, with encoding, buffer, and queryPercentEncodeSet, and append the result to url’s query. |
| 717 | 721 | // > This operation cannot be invoked code-point-for-code-point due to the stateful ISO-2022-JP encoder. |
| 718 | | if (isSchemeSpecial(scheme.items)) { |
| 722 | if (isSchemeSpecial(href.items(0))) { |
| 719 | 723 | try percentEncodeAL(&query, buffer.items, is_special_query_percent_char); |
| 720 | 724 | } else { |
| 721 | 725 | try percentEncodeAL(&query, buffer.items, is_query_percent_char); |
| ... | ... | @@ -762,10 +766,11 @@ pub const URL = struct { |
| 762 | 766 | } |
| 763 | 767 | } |
| 764 | 768 | |
| 765 | | const _href = try href.toOwnedSlice(); |
| 769 | const _href = try href.list.toOwnedSlice(); |
| 766 | 770 | |
| 767 | 771 | const url: URL = .{ |
| 768 | 772 | .href = _href, |
| 773 | .protocol = _href[0..extras.sum(usize, href.lengths[0..2])], |
| 769 | 774 | }; |
| 770 | 775 | return url; |
| 771 | 776 | } |
| ... | ... | @@ -1376,3 +1381,33 @@ fn percentEncodeAL(list: *std.ArrayList(u8), input: []const u8, comptime set: fn |
| 1376 | 1381 | } |
| 1377 | 1382 | } |
| 1378 | 1383 | } |
| 1384 | |
| 1385 | pub fn ManyArrayList(N: usize, T: type) type { |
| 1386 | return struct { |
| 1387 | list: std.ArrayList(T), |
| 1388 | lengths: [N]usize, |
| 1389 | |
| 1390 | pub fn init(allocator: std.mem.Allocator) @This() { |
| 1391 | return .{ |
| 1392 | .list = .init(allocator), |
| 1393 | .lengths = @splat(0), |
| 1394 | }; |
| 1395 | } |
| 1396 | |
| 1397 | pub fn deinit(self: *@This()) void { |
| 1398 | self.list.deinit(); |
| 1399 | } |
| 1400 | |
| 1401 | pub fn set(self: *@This(), n: usize, slice: []const T) !void { |
| 1402 | const real_n = extras.sum(usize, self.lengths[0..n]); |
| 1403 | try self.list.replaceRange(real_n, self.lengths[n], slice); |
| 1404 | self.lengths[n] = slice.len; |
| 1405 | } |
| 1406 | |
| 1407 | pub fn items(self: *@This(), n: usize) []T { |
| 1408 | const real_n = extras.sum(usize, self.lengths[0..n]); |
| 1409 | const len = self.lengths[n]; |
| 1410 | return self.list.items[real_n..][0..len]; |
| 1411 | } |
| 1412 | }; |
| 1413 | } |