diff --git a/src/jsc/bindings/URLDecomposition.cpp b/src/jsc/bindings/URLDecomposition.cpp index a59c4747e242..6829023f64ec 100644 --- a/src/jsc/bindings/URLDecomposition.cpp +++ b/src/jsc/bindings/URLDecomposition.cpp @@ -26,7 +26,6 @@ #include "URLDecomposition.h" #include "NodeURLHelpers.h" -#include namespace WebCore { @@ -38,19 +37,31 @@ static bool hasAcceptableHost(const WTF::URL& url) return Bun::hasValidPunycodeHost(url.host()) || !url.hasSpecialScheme(); } +static bool setHostChecked(WTF::URL& url, StringView host) +{ + return url.setHost(host) && hasAcceptableHost(url); +} + +// https://infra.spec.whatwg.org/#ascii-tab-or-newline; the URL parser removes these before parsing. +static bool isASCIITabOrNewline(char16_t c) +{ + return c == 0x0009 || c == 0x000A || c == 0x000D; +} + +// https://url.spec.whatwg.org/#concept-url-origin String URLDecomposition::origin() const { auto fullURL = this->fullURL(); - if (fullURL.protocolIsInHTTPFamily() or fullURL.protocolIsInFTPFamily() or fullURL.protocolIs("ws"_s) or fullURL.protocolIs("wss"_s)) + // Not protocolIsInFTPFamily(): "ftps" is not a special scheme, so its origin is opaque. + if (fullURL.protocolIsInHTTPFamily() or fullURL.protocolIs("ftp"_s) or fullURL.protocolIs("ws"_s) or fullURL.protocolIs("wss"_s)) return fullURL.protocolHostAndPort(); if (fullURL.protocolIsBlob()) { const String& path = fullURL.path().toString(); const URL subUrl { URL {}, path }; - if (subUrl.isValid()) { - if (subUrl.protocolIsInHTTPFamily() or subUrl.protocolIsInFTPFamily() or subUrl.protocolIs("ws"_s) or subUrl.protocolIs("wss"_s) or subUrl.protocolIsFile()) - return subUrl.protocolHostAndPort(); - } + // The spec also lists "file" here, but a file URL's own origin is opaque anyway. + if (subUrl.isValid() && subUrl.protocolIsInHTTPFamily()) + return subUrl.protocolHostAndPort(); } return "null"_s; } @@ -103,49 +114,61 @@ String URLDecomposition::host() const return fullURL().hostAndPort(); } -static unsigned countASCIIDigits(StringView string) +// Index of the ':' where the spec's host state would enter the port state, or notFound. +static size_t findHostPortSeparator(StringView value, bool isSpecial) { - unsigned length = string.length(); - for (unsigned count = 0; count < length; ++count) { - if (!isASCIIDigit(string[count])) - return count; + bool insideBrackets = false; + for (unsigned i = 0; i < value.length(); ++i) { + auto c = value[i]; + if (c == ':' && !insideBrackets) + return i; + if (c == '/' || c == '?' || c == '#' || (isSpecial && c == '\\')) + return notFound; + if (c == '[') + insideBrackets = true; + else if (c == ']') + insideBrackets = false; } - return length; + return notFound; } +// https://url.spec.whatwg.org/#dom-url-host void URLDecomposition::setHost(StringView value) { auto fullURL = this->fullURL(); - if (value.isEmpty() && !fullURL.protocolIsFile() && fullURL.hasSpecialScheme()) + if (fullURL.hasOpaquePath()) return; - size_t separator = value.reverseFind(':'); - if (!separator) + // The file host state has no port state, so any ':' in the value fails the whole assignment. + if (fullURL.protocolIsFile()) { + if (setHostChecked(fullURL, value)) + setFullURL(fullURL); return; + } - if (fullURL.hasOpaquePath()) + // The host state fails on an empty host for special schemes. + if (value.isEmpty() && fullURL.hasSpecialScheme()) return; - // No port if no colon or rightmost colon is within the IPv6 section. - size_t ipv6Separator = value.reverseFind(']'); - if (separator == notFound || (ipv6Separator != notFound && ipv6Separator > separator)) - fullURL.setHost(value); - else { - // Multiple colons are acceptable only in case of IPv6. - if (value.find(':') != separator && ipv6Separator == notFound) - return; - unsigned portLength = countASCIIDigits(value.substring(separator + 1)); - if (!portLength) { - fullURL.setHost(value.left(separator)); - } else { - auto portNumber = parseInteger(value.substring(separator + 1, portLength)); - if (portNumber && WTF::isDefaultPortForProtocol(*portNumber, fullURL.protocol())) - fullURL.setHostAndPort(value.left(separator)); - else - fullURL.setHostAndPort(value.left(separator + 1 + portLength)); - } + size_t separator = findHostPortSeparator(value, fullURL.hasSpecialScheme()); + if (separator == notFound) { + // No port part. URL::setHost truncates the value at the terminator itself. + if (setHostChecked(fullURL, value)) + setFullURL(fullURL); + return; } - if (fullURL.isValid() && hasAcceptableHost(fullURL)) + + // A ':' with nothing before it fails the whole parse. + auto hostPart = value.left(separator); + if (hostPart.containsOnly()) + return; + + // The host is committed before the port is parsed, so an invalid port keeps the old one. + if (!setHostChecked(fullURL, hostPart)) + return; + if (auto port = parsePort(value.substring(separator + 1), fullURL.protocol())) + fullURL.setPort(*port); + if (fullURL.isValid()) setFullURL(fullURL); } @@ -154,15 +177,27 @@ String URLDecomposition::hostname() const return fullURL().host().toString(); } -void URLDecomposition::setHostname(StringView host) +// https://url.spec.whatwg.org/#dom-url-hostname +void URLDecomposition::setHostname(StringView value) { auto fullURL = this->fullURL(); - if (host.isEmpty() && !fullURL.protocolIsFile() && fullURL.hasSpecialScheme()) - return; if (fullURL.hasOpaquePath()) return; - fullURL.setHost(host); - if (fullURL.isValid() && hasAcceptableHost(fullURL)) + + if (fullURL.protocolIsFile()) { + if (setHostChecked(fullURL, value)) + setFullURL(fullURL); + return; + } + + if (value.isEmpty() && fullURL.hasSpecialScheme()) + return; + + // Unlike the host state, the hostname state fails on a ':' instead of parsing a port. + if (findHostPortSeparator(value, fullURL.hasSpecialScheme()) != notFound) + return; + + if (setHostChecked(fullURL, value)) setFullURL(fullURL); } @@ -174,7 +209,6 @@ String URLDecomposition::port() const return String::number(*port); } -// Outer optional is whether we could parse at all. Inner optional is "no port specified". std::optional> URLDecomposition::parsePort(StringView string, StringView protocol) { // https://url.spec.whatwg.org/#port-state with state override given. @@ -182,8 +216,7 @@ std::optional> URLDecomposition::parsePort(StringView st bool foundDigit = false; for (size_t i = 0; i < string.length(); ++i) { auto c = string[i]; - // https://infra.spec.whatwg.org/#ascii-tab-or-newline - if (c == 0x0009 || c == 0x000A || c == 0x000D) + if (isASCIITabOrNewline(c)) continue; if (isASCIIDigit(c)) { port = port * 10 + c - '0'; @@ -196,16 +229,26 @@ std::optional> URLDecomposition::parsePort(StringView st return std::nullopt; break; } - if (!foundDigit || WTF::isDefaultPortForProtocol(static_cast(port), protocol)) + // With a state override, an empty buffer (e.g. the input was all tab/newline) is a failure. + if (!foundDigit) + return std::nullopt; + if (WTF::isDefaultPortForProtocol(static_cast(port), protocol)) return std::optional { std::nullopt }; return { { static_cast(port) } }; } +// https://url.spec.whatwg.org/#dom-url-port void URLDecomposition::setPort(StringView value) { auto fullURL = this->fullURL(); if (fullURL.host().isEmpty() || fullURL.protocolIsFile()) return; + // Only a literally empty value clears the port; "\t\n" reaches parsePort and fails instead. + if (value.isEmpty()) { + fullURL.setPort(std::nullopt); + setFullURL(fullURL); + return; + } auto port = parsePort(value, fullURL.protocol()); if (!port) return; diff --git a/src/jsc/bindings/URLDecomposition.h b/src/jsc/bindings/URLDecomposition.h index 47f88b5f41dd..1e41609614e7 100644 --- a/src/jsc/bindings/URLDecomposition.h +++ b/src/jsc/bindings/URLDecomposition.h @@ -35,8 +35,7 @@ namespace WebCore { class URLDecomposition { public: - // Parse a port string with optional protocol for default port detection - // Returns nullopt on parse error, or optional (nullopt means empty/default port) + // Outer nullopt: parse failure, leave the URL's port alone. Inner nullopt: the scheme's default port. static std::optional> parsePort(StringView port, StringView protocol); String origin() const; diff --git a/src/jsc/bindings/webcore/URLPatternComponent.cpp b/src/jsc/bindings/webcore/URLPatternComponent.cpp index 81b9d4ced70b..07c654c236fe 100644 --- a/src/jsc/bindings/webcore/URLPatternComponent.cpp +++ b/src/jsc/bindings/webcore/URLPatternComponent.cpp @@ -95,11 +95,12 @@ std::optional URLPatternComponent::componentMatch(JSC if (position < 0) return std::nullopt; - unsigned numSubpatterns = regExp->numSubpatterns(); + // A user regexp like (?...) adds capture groups that have no name; those are not exposed. + size_t groupCount = std::min(regExp->numSubpatterns(), m_groupNameList.size()); URLPatternComponentResult::GroupsRecord groups; - groups.reserveInitialCapacity(numSubpatterns); - for (unsigned i = 1; i <= numSubpatterns; ++i) { + groups.reserveInitialCapacity(groupCount); + for (size_t i = 1; i <= groupCount; ++i) { int start = ovector[i * 2]; int end = ovector[i * 2 + 1]; @@ -107,9 +108,7 @@ std::optional URLPatternComponent::componentMatch(JSC if (start >= 0) value = input.substring(start, end - start); - size_t groupIndex = i - 1; - String groupName = groupIndex < m_groupNameList.size() ? m_groupNameList[groupIndex] : emptyString(); - groups.append(URLPatternComponentResult::NameMatchPair { WTF::move(groupName), WTF::move(value) }); + groups.append(URLPatternComponentResult::NameMatchPair { m_groupNameList[i - 1], WTF::move(value) }); } return URLPatternComponentResult { !input.isEmpty() ? WTF::move(input) : emptyString(), WTF::move(groups) }; diff --git a/test/js/web/url/url.test.ts b/test/js/web/url/url.test.ts index 3857ed3e9ac0..561dbd880325 100755 --- a/test/js/web/url/url.test.ts +++ b/test/js/web/url/url.test.ts @@ -66,6 +66,13 @@ describe("url", () => { url = new URL("ftp://example.com"); expect(url.protocol).toBe("ftp:"); expect(url.origin).toBe("ftp://example.com"); + // "ftps" is not a special scheme, so its origin is opaque. + url = new URL("ftps://example.com"); + expect(url.protocol).toBe("ftps:"); + expect(url.origin).toBe("null"); + url = new URL("ftps:/a"); + expect(url.protocol).toBe("ftps:"); + expect(url.origin).toBe("null"); url = new URL("file://example.com"); expect(url.protocol).toBe("file:"); expect(url.origin).toBe("null"); @@ -92,27 +99,35 @@ describe("url", () => { expect(url.origin).toBe("null"); }); it("blob urls", () => { + // https://url.spec.whatwg.org/#concept-url-origin: only an inner http(s) URL yields + // a tuple origin. Everything else, including file (whose own origin is opaque), is "null". var url = new URL("blob:https://example.com/1234-5678"); expect(url.protocol).toBe("blob:"); expect(url.origin).toBe("https://example.com"); + url = new URL("blob:http://example.com/x"); + expect(url.protocol).toBe("blob:"); + expect(url.origin).toBe("http://example.com"); url = new URL("blob:file://text.txt"); expect(url.protocol).toBe("blob:"); - expect(url.origin).toBe("file://text.txt"); + expect(url.origin).toBe("null"); url = new URL("blob:kjka://example.com"); expect(url.protocol).toBe("blob:"); expect(url.origin).toBe("null"); url = new URL("blob:blob://example.com"); expect(url.protocol).toBe("blob:"); expect(url.origin).toBe("null"); - url = new URL("blob:blob://example.com"); + url = new URL("blob:ftp://example.com/x"); expect(url.protocol).toBe("blob:"); expect(url.origin).toBe("null"); url = new URL("blob:ws://example.com"); expect(url.protocol).toBe("blob:"); - expect(url.origin).toBe("ws://example.com"); + expect(url.origin).toBe("null"); + url = new URL("blob:wss://example.com/x"); + expect(url.protocol).toBe("blob:"); + expect(url.origin).toBe("null"); url = new URL("blob:file:///folder/else/text.txt"); expect(url.protocol).toBe("blob:"); - expect(url.origin).toBe("file://"); + expect(url.origin).toBe("null"); }); it("leaves opaque (non-special-scheme) hosts unchanged", () => { expect(new URL("foo://\u1E9E.com/").href).toBe("foo://%E1%BA%9E.com/"); @@ -386,6 +401,214 @@ describe("url", () => { expect(params.get("second")).toBe("replaced"); expect(params.get("third")).toBeNull(); }); + + describe("setters", () => { + // Each case assigns `url[prop] = value` to a URL parsed from `href` and checks the + // listed getters afterward. Expected values follow the URL Standard's "basic URL + // parse with a state override" setter steps (https://url.spec.whatwg.org/#api) and + // agree with https://github.com/web-platform-tests/wpt/blob/master/url/resources/setters_tests.json + const cases = [ + // The host setter clears the port when the new port equals the scheme's default. + // https://github.com/oven-sh/bun/issues/28661 + { + href: "http://example.net:8080", + prop: "host", + value: "example.com:80", + expected: { href: "http://example.com/", host: "example.com", hostname: "example.com", port: "" }, + }, + { + href: "https://example.net:3000/", + prop: "host", + value: "example.com:443", + expected: { href: "https://example.com/", host: "example.com", port: "" }, + }, + { + href: "ws://example.net:3000/", + prop: "host", + value: "example.com:80", + expected: { href: "ws://example.com/", host: "example.com", port: "" }, + }, + // An out-of-range port fails the port state, but the host was already committed. + { + href: "http://example.net:8080", + prop: "host", + value: "newhost:99999", + expected: { href: "http://newhost:8080/", host: "newhost:8080", hostname: "newhost", port: "8080" }, + }, + // The file host state never splits off a port, and ":" is a forbidden host code point. + { + href: "file:///a/b", + prop: "host", + value: "h2:x", + expected: { href: "file:///a/b", host: "", hostname: "", port: "" }, + }, + // Only the empty string clears the port. A value that is entirely ASCII tab/newline + // is stripped to nothing by the parser, which is a no-op, not a clear. + { + href: "http://example.net:123/", + prop: "port", + value: "\t\n", + expected: { href: "http://example.net:123/", port: "123" }, + }, + // The hostname state fails on a ":" outside an IPv6 literal. + { + href: "http://example.net/", + prop: "hostname", + value: "[::1]:80", + expected: { href: "http://example.net/", host: "example.net", port: "" }, + }, + { + href: "http://example.net/path", + prop: "hostname", + value: "example.com:8080", + expected: { href: "http://example.net/path", hostname: "example.net" }, + }, + { + href: "file:///a/b", + prop: "hostname", + value: "h2:x", + expected: { href: "file:///a/b", hostname: "" }, + }, + // An empty port part leaves the existing port alone. + { + href: "http://example.net:8080", + prop: "host", + value: "example.com:", + expected: { href: "http://example.com:8080/", host: "example.com:8080", port: "8080" }, + }, + // A non-numeric port part leaves the existing port alone. + { + href: "http://example.net:8080", + prop: "host", + value: "example.com:invalid", + expected: { href: "http://example.com:8080/", host: "example.com:8080", port: "8080" }, + }, + { + href: "http://example.net", + prop: "host", + value: "example.com:8080", + expected: { href: "http://example.com:8080/", host: "example.com:8080", port: "8080" }, + }, + // 80 is not the default for https, so it stays. + { + href: "https://example.net", + prop: "host", + value: "example.com:80", + expected: { href: "https://example.com:80/", host: "example.com:80", port: "80" }, + }, + // A ":" inside an IPv6 literal does not start the port. + { + href: "http://example.net:8080/test", + prop: "host", + value: "[::1]:invalid", + expected: { href: "http://[::1]:8080/test", host: "[::1]:8080", port: "8080" }, + }, + { + href: "http://example.net", + prop: "host", + value: "[::0:01]:2", + expected: { href: "http://[::1]:2/", host: "[::1]:2", port: "2" }, + }, + // A terminator (? / # \) before the ":" ends the host, so the port part is never reached. + { + href: "http://example.net/path", + prop: "host", + value: "example.com?stuff:8080", + expected: { href: "http://example.com/path", host: "example.com", port: "" }, + }, + { + href: "https://test.invalid/", + prop: "host", + value: "test/:aaa", + expected: { href: "https://test/", host: "test" }, + }, + // A "\" after the ":" is a terminator for special schemes, so the port still parses. + { + href: "http://example.net/path", + prop: "host", + value: "example.com:8080\\stuff", + expected: { href: "http://example.com:8080/path", host: "example.com:8080", port: "8080" }, + }, + // A ":" with an empty host before it fails the whole assignment. + { + href: "foo://path/to", + prop: "host", + value: ":80", + expected: { href: "foo://path/to", host: "path", port: "" }, + }, + // The parser strips ASCII tab/newline first, so this is the same empty host as above. + { + href: "foo://path/to", + prop: "host", + value: "\t\n:", + expected: { href: "foo://path/to", host: "path", port: "" }, + }, + { + href: "file://y/", + prop: "host", + value: "x:123", + expected: { href: "file://y/", host: "y", port: "" }, + }, + { + href: "file://hi/x", + prop: "host", + value: "", + expected: { href: "file:///x", host: "" }, + }, + { + href: "file://y/", + prop: "host", + value: "loc%41lhost", + expected: { href: "file:///", host: "" }, + }, + // Special schemes cannot have an empty host. + { + href: "http://example.net", + prop: "host", + value: "", + expected: { href: "http://example.net/", host: "example.net" }, + }, + { + href: "view-source+http://example.net/foo", + prop: "host", + value: "", + expected: { href: "view-source+http:///foo", host: "" }, + }, + { + href: "sc://test:12/", + prop: "host", + value: "", + expected: { href: "sc://test:12/", host: "test:12" }, + }, + { + href: "http://example.net:8080", + prop: "port", + value: "", + expected: { href: "http://example.net/", port: "" }, + }, + { + href: "http://example.net:8080/path", + prop: "port", + value: "randomstring", + expected: { href: "http://example.net:8080/path", port: "8080" }, + }, + { + href: "http://example.net/path", + prop: "port", + value: "8080stuff2", + expected: { href: "http://example.net:8080/path", port: "8080" }, + }, + ] as const; + + for (const { href, prop, value, expected } of cases) { + it(`new URL(${JSON.stringify(href)}).${prop} = ${JSON.stringify(value)}`, () => { + const url = new URL(href); + (url as any)[prop] = value; + const actual = Object.fromEntries(Object.keys(expected).map(key => [key, (url as any)[key]])); + expect(actual).toEqual(expected); + }); + } + }); }); describe("url.searchParams lazy href sync", () => { diff --git a/test/js/web/urlpattern/urlpattern.test.ts b/test/js/web/urlpattern/urlpattern.test.ts index c2a727ab5b15..871724430095 100644 --- a/test/js/web/urlpattern/urlpattern.test.ts +++ b/test/js/web/urlpattern/urlpattern.test.ts @@ -206,4 +206,18 @@ describe("URLPattern", () => { expect(new URLPattern({ pathname: "/a/:foo/:baz([a-z]+)?/b/*" }).hasRegExpGroups).toBe(true); }); }); + + describe("nested regexp capture groups", () => { + // https://urlpattern.spec.whatwg.org/#create-a-component-match-result: the result's + // `groups` record holds one entry per group name the compiled pattern produced. + // Capture groups that a user-supplied regexp nests inside a part, like (?...), add + // captures to the compiled RegExp but no names, and must not leak into `groups`. + test("a nested named regexp group is not exposed as an extra group", () => { + // :a and :b compile to capture indices 1 and 4 because the nested (?1)(?2) + // occupy 2 and 3, so "b" gets index 2's value ("1"), matching Node and upstream + // WebKit. The clamp only stops the two unnamed captures leaking in under a "" key. + const result = new URLPattern({ pathname: "/:a((?1)(?2)):b(3)" }).exec({ pathname: "/123" }); + expect(result?.pathname.groups).toEqual({ a: "12", b: "1" }); + }); + }); }); diff --git a/test/js/web/urlpattern/urlpatterntestdata.json b/test/js/web/urlpattern/urlpatterntestdata.json index 75f1cf869452..233d2c567e13 100644 --- a/test/js/web/urlpattern/urlpatterntestdata.json +++ b/test/js/web/urlpattern/urlpatterntestdata.json @@ -23,7 +23,7 @@ }, { "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [ "https://example.com/foo/bar" ], + "inputs": ["https://example.com/foo/bar"], "expected_match": { "hostname": { "input": "example.com", "groups": { "0": "example.com" } }, "pathname": { "input": "/foo/bar", "groups": {} }, @@ -32,7 +32,7 @@ }, { "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [ "https://example.com/foo/bar/baz" ], + "inputs": ["https://example.com/foo/bar/baz"], "expected_match": null }, { @@ -59,28 +59,23 @@ }, { "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [{ "pathname": "/foo/bar/baz", - "baseURL": "https://example.com" }], + "inputs": [{ "pathname": "/foo/bar/baz", "baseURL": "https://example.com" }], "expected_match": null }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], "inputs": [{ "pathname": "/foo/bar" }], "expected_match": null }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], "inputs": [{ "hostname": "example.com", "pathname": "/foo/bar" }], "expected_match": null }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "protocol": "https", "hostname": "example.com", - "pathname": "/foo/bar" }], - "exactly_empty_components": [ "port" ], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], + "inputs": [{ "protocol": "https", "hostname": "example.com", "pathname": "/foo/bar" }], + "exactly_empty_components": ["port"], "expected_match": { "hostname": { "input": "example.com", "groups": {} }, "pathname": { "input": "/foo/bar", "groups": {} }, @@ -88,11 +83,9 @@ } }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com" }], - "inputs": [{ "protocol": "https", "hostname": "example.com", - "pathname": "/foo/bar" }], - "exactly_empty_components": [ "port" ], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com" }], + "inputs": [{ "protocol": "https", "hostname": "example.com", "pathname": "/foo/bar" }], + "exactly_empty_components": ["port"], "expected_match": { "hostname": { "input": "example.com", "groups": {} }, "pathname": { "input": "/foo/bar", "groups": {} }, @@ -100,19 +93,22 @@ } }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com" }], - "inputs": [{ "protocol": "https", "hostname": "example.com", - "pathname": "/foo/bar/baz" }], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com" }], + "inputs": [{ "protocol": "https", "hostname": "example.com", "pathname": "/foo/bar/baz" }], "expected_match": null }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "protocol": "https", "hostname": "example.com", - "pathname": "/foo/bar", "search": "otherquery", - "hash": "otherhash" }], - "exactly_empty_components": [ "port" ], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], + "inputs": [ + { + "protocol": "https", + "hostname": "example.com", + "pathname": "/foo/bar", + "search": "otherquery", + "hash": "otherhash" + } + ], + "exactly_empty_components": ["port"], "expected_match": { "hash": { "input": "otherhash", "groups": { "0": "otherhash" } }, "hostname": { "input": "example.com", "groups": {} }, @@ -122,12 +118,17 @@ } }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com" }], - "inputs": [{ "protocol": "https", "hostname": "example.com", - "pathname": "/foo/bar", "search": "otherquery", - "hash": "otherhash" }], - "exactly_empty_components": [ "port" ], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com" }], + "inputs": [ + { + "protocol": "https", + "hostname": "example.com", + "pathname": "/foo/bar", + "search": "otherquery", + "hash": "otherhash" + } + ], + "exactly_empty_components": ["port"], "expected_match": { "hash": { "input": "otherhash", "groups": { "0": "otherhash" } }, "hostname": { "input": "example.com", "groups": {} }, @@ -137,12 +138,17 @@ } }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?otherquery#otherhash" }], - "inputs": [{ "protocol": "https", "hostname": "example.com", - "pathname": "/foo/bar", "search": "otherquery", - "hash": "otherhash" }], - "exactly_empty_components": [ "port" ], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?otherquery#otherhash" }], + "inputs": [ + { + "protocol": "https", + "hostname": "example.com", + "pathname": "/foo/bar", + "search": "otherquery", + "hash": "otherhash" + } + ], + "exactly_empty_components": ["port"], "expected_match": { "hash": { "input": "otherhash", "groups": { "0": "otherhash" } }, "hostname": { "input": "example.com", "groups": {} }, @@ -152,10 +158,9 @@ } }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [ "https://example.com/foo/bar" ], - "exactly_empty_components": [ "port" ], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], + "inputs": ["https://example.com/foo/bar"], + "exactly_empty_components": ["port"], "expected_match": { "hostname": { "input": "example.com", "groups": {} }, "pathname": { "input": "/foo/bar", "groups": {} }, @@ -163,10 +168,9 @@ } }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [ "https://example.com/foo/bar?otherquery#otherhash" ], - "exactly_empty_components": [ "port" ], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], + "inputs": ["https://example.com/foo/bar?otherquery#otherhash"], + "exactly_empty_components": ["port"], "expected_match": { "hash": { "input": "otherhash", "groups": { "0": "otherhash" } }, "hostname": { "input": "example.com", "groups": {} }, @@ -176,10 +180,9 @@ } }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [ "https://example.com/foo/bar?query#hash" ], - "exactly_empty_components": [ "port" ], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], + "inputs": ["https://example.com/foo/bar?query#hash"], + "exactly_empty_components": ["port"], "expected_match": { "hash": { "input": "hash", "groups": { "0": "hash" } }, "hostname": { "input": "example.com", "groups": {} }, @@ -189,28 +192,24 @@ } }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [ "https://example.com/foo/bar/baz" ], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], + "inputs": ["https://example.com/foo/bar/baz"], "expected_match": null }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [ "https://other.com/foo/bar" ], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], + "inputs": ["https://other.com/foo/bar"], "expected_match": null }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [ "http://other.com/foo/bar" ], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], + "inputs": ["http://other.com/foo/bar"], "expected_match": null }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], "inputs": [{ "pathname": "/foo/bar", "baseURL": "https://example.com" }], - "exactly_empty_components": [ "port" ], + "exactly_empty_components": ["port"], "expected_match": { "hostname": { "input": "example.com", "groups": {} }, "pathname": { "input": "/foo/bar", "groups": {} }, @@ -218,11 +217,9 @@ } }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "exactly_empty_components": [ "port" ], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], + "inputs": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], + "exactly_empty_components": ["port"], "expected_match": { "hostname": { "input": "example.com", "groups": {} }, "pathname": { "input": "/foo/bar", "groups": {} }, @@ -230,21 +227,17 @@ } }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], - "inputs": [{ "pathname": "/foo/bar/baz", - "baseURL": "https://example.com" }], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], + "inputs": [{ "pathname": "/foo/bar/baz", "baseURL": "https://example.com" }], "expected_match": null }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], "inputs": [{ "pathname": "/foo/bar", "baseURL": "https://other.com" }], "expected_match": null }, { - "pattern": [{ "pathname": "/foo/bar", - "baseURL": "https://example.com?query#hash" }], + "pattern": [{ "pathname": "/foo/bar", "baseURL": "https://example.com?query#hash" }], "inputs": [{ "pathname": "/foo/bar", "baseURL": "http://example.com" }], "expected_match": null }, @@ -1037,7 +1030,7 @@ }, { "pattern": [{ "protocol": "(.*)" }], - "inputs": [{ "protocol" : "café" }], + "inputs": [{ "protocol": "café" }], "expected_obj": { "protocol": "*" }, @@ -1050,7 +1043,7 @@ "protocol": "*" }, "expected_match": { - "protocol": { "input": "cafe", "groups": { "0": "cafe" }} + "protocol": { "input": "cafe", "groups": { "0": "cafe" } } } }, { @@ -1062,78 +1055,78 @@ }, { "pattern": [{ "username": "caf%C3%A9" }], - "inputs": [{ "username" : "café" }], + "inputs": [{ "username": "café" }], "expected_match": { - "username": { "input": "caf%C3%A9", "groups": {}} + "username": { "input": "caf%C3%A9", "groups": {} } } }, { "pattern": [{ "username": "café" }], - "inputs": [{ "username" : "café" }], + "inputs": [{ "username": "café" }], "expected_obj": { "username": "caf%C3%A9" }, "expected_match": { - "username": { "input": "caf%C3%A9", "groups": {}} + "username": { "input": "caf%C3%A9", "groups": {} } } }, { "pattern": [{ "username": "caf%c3%a9" }], - "inputs": [{ "username" : "café" }], + "inputs": [{ "username": "café" }], "expected_match": null }, { "pattern": [{ "password": "caf%C3%A9" }], - "inputs": [{ "password" : "café" }], + "inputs": [{ "password": "café" }], "expected_match": { - "password": { "input": "caf%C3%A9", "groups": {}} + "password": { "input": "caf%C3%A9", "groups": {} } } }, { "pattern": [{ "password": "café" }], - "inputs": [{ "password" : "café" }], + "inputs": [{ "password": "café" }], "expected_obj": { "password": "caf%C3%A9" }, "expected_match": { - "password": { "input": "caf%C3%A9", "groups": {}} + "password": { "input": "caf%C3%A9", "groups": {} } } }, { "pattern": [{ "password": "caf%c3%a9" }], - "inputs": [{ "password" : "café" }], + "inputs": [{ "password": "café" }], "expected_match": null }, { "pattern": [{ "hostname": "xn--caf-dma.com" }], - "inputs": [{ "hostname" : "café.com" }], + "inputs": [{ "hostname": "café.com" }], "expected_match": { - "hostname": { "input": "xn--caf-dma.com", "groups": {}} + "hostname": { "input": "xn--caf-dma.com", "groups": {} } } }, { "pattern": [{ "hostname": "café.com" }], - "inputs": [{ "hostname" : "café.com" }], + "inputs": [{ "hostname": "café.com" }], "expected_obj": { "hostname": "xn--caf-dma.com" }, "expected_match": { - "hostname": { "input": "xn--caf-dma.com", "groups": {}} + "hostname": { "input": "xn--caf-dma.com", "groups": {} } } }, { "pattern": ["http://\uD83D\uDEB2.com/"], "inputs": ["http://\uD83D\uDEB2.com/"], - "exactly_empty_components": [ "port" ], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "http", "hostname": "xn--h78h.com", "pathname": "/" }, "expected_match": { - "protocol": { "input": "http", "groups": {}}, - "hostname": { "input": "xn--h78h.com", "groups": {}}, - "pathname": { "input": "/", "groups": {}} + "protocol": { "input": "http", "groups": {} }, + "hostname": { "input": "xn--h78h.com", "groups": {} }, + "pathname": { "input": "/", "groups": {} } } }, { @@ -1141,11 +1134,11 @@ "expected_obj": "error" }, { - "pattern": [{"hostname":"\uD83D \uDEB2"}], + "pattern": [{ "hostname": "\uD83D \uDEB2" }], "expected_obj": "error" }, { - "pattern": [{"pathname":"\uD83D \uDEB2"}], + "pattern": [{ "pathname": "\uD83D \uDEB2" }], "inputs": [], "expected_obj": { "pathname": "%EF%BF%BD%20%EF%BF%BD" @@ -1153,11 +1146,11 @@ "expected_match": null }, { - "pattern": [{"pathname":":\uD83D \uDEB2"}], + "pattern": [{ "pathname": ":\uD83D \uDEB2" }], "expected_obj": "error" }, { - "pattern": [{"pathname":":a\uDB40\uDD00b"}], + "pattern": [{ "pathname": ":a\uDB40\uDD00b" }], "inputs": [], "expected_obj": { "pathname": ":a\uDB40\uDD00b" @@ -1165,33 +1158,33 @@ "expected_match": null }, { - "pattern": [{"pathname":"test/:a\uD801\uDC50b"}], - "inputs": [{"pathname":"test/foo"}], + "pattern": [{ "pathname": "test/:a\uD801\uDC50b" }], + "inputs": [{ "pathname": "test/foo" }], "expected_obj": { "pathname": "test/:a\uD801\uDC50b" }, "expected_match": { - "pathname": { "input": "test/foo", "groups": { "a\uD801\uDC50b": "foo" }} + "pathname": { "input": "test/foo", "groups": { "a\uD801\uDC50b": "foo" } } } }, { - "pattern": [{"pathname":":\uD83D\uDEB2"}], + "pattern": [{ "pathname": ":\uD83D\uDEB2" }], "expected_obj": "error" }, { "pattern": [{ "port": "" }], "inputs": [{ "protocol": "http", "port": "80" }], - "exactly_empty_components": [ "port" ], + "exactly_empty_components": ["port"], "expected_match": { - "protocol": { "input": "http", "groups": { "0": "http" }} + "protocol": { "input": "http", "groups": { "0": "http" } } } }, { "pattern": [{ "protocol": "http", "port": "80" }], "inputs": [{ "protocol": "http", "port": "80" }], - "exactly_empty_components": [ "port" ], + "exactly_empty_components": ["port"], "expected_match": { - "protocol": { "input": "http", "groups": {}} + "protocol": { "input": "http", "groups": {} } } }, { @@ -1227,7 +1220,35 @@ "pattern": [{ "port": "80" }], "inputs": [{ "port": "80" }], "expected_match": { - "port": { "input": "80", "groups": {}} + "port": { "input": "80", "groups": {} } + } + }, + { + "pattern": [{ "port": "80" }], + "inputs": [{ "port": "8\t0" }], + "expected_match": { + "port": { "input": "80", "groups": {} } + } + }, + { + "pattern": [{ "port": "80" }], + "inputs": [{ "port": "80x" }], + "expected_match": { + "port": { "input": "80", "groups": {} } + } + }, + { + "pattern": [{ "port": "80" }], + "inputs": [{ "port": "80?x" }], + "expected_match": { + "port": { "input": "80", "groups": {} } + } + }, + { + "pattern": [{ "port": "80" }], + "inputs": [{ "port": "80\\x" }], + "expected_match": { + "port": { "input": "80", "groups": {} } } }, { @@ -1238,25 +1259,41 @@ }, "expected_match": null }, + { + "pattern": [{ "protocol": "https", "port": "443*" }], + "inputs": [{ "protocol": "https", "port": "4430" }], + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "port": { "input": "4430", "groups": { "0": "0" } } + } + }, + { + "pattern": [{ "protocol": "https", "port": "*443" }], + "inputs": [{ "protocol": "https", "port": "1443" }], + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "port": { "input": "1443", "groups": { "0": "1" } } + } + }, { "pattern": [{ "pathname": "/foo/bar" }], "inputs": [{ "pathname": "/foo/./bar" }], "expected_match": { - "pathname": { "input": "/foo/bar", "groups": {}} + "pathname": { "input": "/foo/bar", "groups": {} } } }, { "pattern": [{ "pathname": "/foo/baz" }], "inputs": [{ "pathname": "/foo/bar/../baz" }], "expected_match": { - "pathname": { "input": "/foo/baz", "groups": {}} + "pathname": { "input": "/foo/baz", "groups": {} } } }, { "pattern": [{ "pathname": "/caf%C3%A9" }], "inputs": [{ "pathname": "/café" }], "expected_match": { - "pathname": { "input": "/caf%C3%A9", "groups": {}} + "pathname": { "input": "/caf%C3%A9", "groups": {} } } }, { @@ -1266,7 +1303,7 @@ "pathname": "/caf%C3%A9" }, "expected_match": { - "pathname": { "input": "/caf%C3%A9", "groups": {}} + "pathname": { "input": "/caf%C3%A9", "groups": {} } } }, { @@ -1283,9 +1320,9 @@ "pattern": [{ "pathname": "/foo/bar" }], "inputs": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }], "expected_match": { - "protocol": { "input": "https", "groups": { "0": "https" }}, - "hostname": { "input": "example.com", "groups": { "0": "example.com" }}, - "pathname": { "input": "/foo/bar", "groups": {}} + "protocol": { "input": "https", "groups": { "0": "https" } }, + "hostname": { "input": "example.com", "groups": { "0": "example.com" } }, + "pathname": { "input": "/foo/bar", "groups": {} } } }, { @@ -1295,39 +1332,39 @@ "pathname": "/bar" }, "expected_match": { - "pathname": { "input": "/bar", "groups": {}} + "pathname": { "input": "/bar", "groups": {} } } }, { "pattern": [{ "pathname": "./foo/bar", "baseURL": "https://example.com" }], "inputs": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }], - "exactly_empty_components": [ "port" ], + "exactly_empty_components": ["port"], "expected_obj": { "pathname": "/foo/bar" }, "expected_match": { - "protocol": { "input": "https", "groups": {}}, - "hostname": { "input": "example.com", "groups": {}}, - "pathname": { "input": "/foo/bar", "groups": {}} + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/foo/bar", "groups": {} } } }, { "pattern": [{ "pathname": "", "baseURL": "https://example.com" }], "inputs": [{ "pathname": "/", "baseURL": "https://example.com" }], - "exactly_empty_components": [ "port" ], + "exactly_empty_components": ["port"], "expected_obj": { "pathname": "/" }, "expected_match": { - "protocol": { "input": "https", "groups": {}}, - "hostname": { "input": "example.com", "groups": {}}, - "pathname": { "input": "/", "groups": {}} + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/", "groups": {} } } }, { "pattern": [{ "pathname": "{/bar}", "baseURL": "https://example.com/foo/" }], "inputs": [{ "pathname": "./bar", "baseURL": "https://example.com/foo/" }], - "exactly_empty_components": [ "port" ], + "exactly_empty_components": ["port"], "expected_obj": { "pathname": "/bar" }, @@ -1336,7 +1373,7 @@ { "pattern": [{ "pathname": "\\/bar", "baseURL": "https://example.com/foo/" }], "inputs": [{ "pathname": "./bar", "baseURL": "https://example.com/foo/" }], - "exactly_empty_components": [ "port" ], + "exactly_empty_components": ["port"], "expected_obj": { "pathname": "/bar" }, @@ -1345,52 +1382,52 @@ { "pattern": [{ "pathname": "b", "baseURL": "https://example.com/foo/" }], "inputs": [{ "pathname": "./b", "baseURL": "https://example.com/foo/" }], - "exactly_empty_components": [ "port" ], + "exactly_empty_components": ["port"], "expected_obj": { "pathname": "/foo/b" }, "expected_match": { - "protocol": { "input": "https", "groups": {}}, - "hostname": { "input": "example.com", "groups": {}}, - "pathname": { "input": "/foo/b", "groups": {}} + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/foo/b", "groups": {} } } }, { "pattern": [{ "pathname": "foo/bar" }], - "inputs": [ "https://example.com/foo/bar" ], + "inputs": ["https://example.com/foo/bar"], "expected_match": null }, { "pattern": [{ "pathname": "foo/bar", "baseURL": "https://example.com" }], - "inputs": [ "https://example.com/foo/bar" ], - "exactly_empty_components": [ "port" ], + "inputs": ["https://example.com/foo/bar"], + "exactly_empty_components": ["port"], "expected_obj": { "pathname": "/foo/bar" }, "expected_match": { - "protocol": { "input": "https", "groups": {}}, - "hostname": { "input": "example.com", "groups": {}}, - "pathname": { "input": "/foo/bar", "groups": {}} + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/foo/bar", "groups": {} } } }, { "pattern": [{ "pathname": ":name.html", "baseURL": "https://example.com" }], - "inputs": [ "https://example.com/foo.html"] , - "exactly_empty_components": [ "port" ], + "inputs": ["https://example.com/foo.html"], + "exactly_empty_components": ["port"], "expected_obj": { "pathname": "/:name.html" }, "expected_match": { - "protocol": { "input": "https", "groups": {}}, - "hostname": { "input": "example.com", "groups": {}}, - "pathname": { "input": "/foo.html", "groups": { "name": "foo" }} + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/foo.html", "groups": { "name": "foo" } } } }, { "pattern": [{ "search": "q=caf%C3%A9" }], "inputs": [{ "search": "q=café" }], "expected_match": { - "search": { "input": "q=caf%C3%A9", "groups": {}} + "search": { "input": "q=caf%C3%A9", "groups": {} } } }, { @@ -1400,7 +1437,7 @@ "search": "q=caf%C3%A9" }, "expected_match": { - "search": { "input": "q=caf%C3%A9", "groups": {}} + "search": { "input": "q=caf%C3%A9", "groups": {} } } }, { @@ -1412,7 +1449,7 @@ "pattern": [{ "hash": "caf%C3%A9" }], "inputs": [{ "hash": "café" }], "expected_match": { - "hash": { "input": "caf%C3%A9", "groups": {}} + "hash": { "input": "caf%C3%A9", "groups": {} } } }, { @@ -1422,7 +1459,7 @@ "hash": "caf%C3%A9" }, "expected_match": { - "hash": { "input": "caf%C3%A9", "groups": {}} + "hash": { "input": "caf%C3%A9", "groups": {} } } }, { @@ -1432,18 +1469,18 @@ }, { "pattern": [{ "protocol": "about", "pathname": "(blank|sourcedoc)" }], - "inputs": [ "about:blank" ], + "inputs": ["about:blank"], "expected_match": { - "protocol": { "input": "about", "groups": {}}, - "pathname": { "input": "blank", "groups": { "0": "blank" }} + "protocol": { "input": "about", "groups": {} }, + "pathname": { "input": "blank", "groups": { "0": "blank" } } } }, { "pattern": [{ "protocol": "data", "pathname": ":number([0-9]+)" }], - "inputs": [ "data:8675309" ], + "inputs": ["data:8675309"], "expected_match": { - "protocol": { "input": "data", "groups": {}}, - "pathname": { "input": "8675309", "groups": { "number": "8675309" }} + "protocol": { "input": "data", "groups": {} }, + "pathname": { "input": "8675309", "groups": { "number": "8675309" } } } }, { @@ -1454,14 +1491,14 @@ "pattern": [{ "pathname": "/foo!" }], "inputs": [{ "pathname": "/foo!" }], "expected_match": { - "pathname": { "input": "/foo!", "groups": {}} + "pathname": { "input": "/foo!", "groups": {} } } }, { "pattern": [{ "pathname": "/foo\\:" }], "inputs": [{ "pathname": "/foo:" }], "expected_match": { - "pathname": { "input": "/foo:", "groups": {}} + "pathname": { "input": "/foo:", "groups": {} } } }, { @@ -1471,22 +1508,22 @@ "pathname": "/foo%7B" }, "expected_match": { - "pathname": { "input": "/foo%7B", "groups": {}} + "pathname": { "input": "/foo%7B", "groups": {} } } }, { "pattern": [{ "pathname": "/foo\\(" }], "inputs": [{ "pathname": "/foo(" }], "expected_match": { - "pathname": { "input": "/foo(", "groups": {}} + "pathname": { "input": "/foo(", "groups": {} } } }, { "pattern": [{ "protocol": "javascript", "pathname": "var x = 1;" }], "inputs": [{ "protocol": "javascript", "pathname": "var x = 1;" }], "expected_match": { - "protocol": { "input": "javascript", "groups": {}}, - "pathname": { "input": "var x = 1;", "groups": {}} + "protocol": { "input": "javascript", "groups": {} }, + "pathname": { "input": "var x = 1;", "groups": {} } } }, { @@ -1501,16 +1538,16 @@ "pattern": [{ "protocol": "javascript", "pathname": "var x = 1;" }], "inputs": [{ "baseURL": "javascript:var x = 1;" }], "expected_match": { - "protocol": { "input": "javascript", "groups": {}}, - "pathname": { "input": "var x = 1;", "groups": {}} + "protocol": { "input": "javascript", "groups": {} }, + "pathname": { "input": "var x = 1;", "groups": {} } } }, { "pattern": [{ "protocol": "(data|javascript)", "pathname": "var x = 1;" }], "inputs": [{ "protocol": "javascript", "pathname": "var x = 1;" }], "expected_match": { - "protocol": { "input": "javascript", "groups": {"0": "javascript"}}, - "pathname": { "input": "var x = 1;", "groups": {}} + "protocol": { "input": "javascript", "groups": { "0": "javascript" } }, + "pathname": { "input": "var x = 1;", "groups": {} } } }, { @@ -1528,12 +1565,12 @@ "pathname": "var%20x%20=%201;" }, "expected_match": { - "pathname": { "input": "var%20x%20=%201;", "groups": {}} + "pathname": { "input": "var%20x%20=%201;", "groups": {} } } }, { "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [ "./foo/bar", "https://example.com" ], + "inputs": ["./foo/bar", "https://example.com"], "expected_match": { "hostname": { "input": "example.com", "groups": { "0": "example.com" } }, "pathname": { "input": "/foo/bar", "groups": {} }, @@ -1542,13 +1579,12 @@ }, { "pattern": [{ "pathname": "/foo/bar" }], - "inputs": [ { "pathname": "/foo/bar" }, "https://example.com" ], + "inputs": [{ "pathname": "/foo/bar" }, "https://example.com"], "expected_match": "error" }, { - "pattern": [ "https://example.com:8080/foo?bar#baz" ], - "inputs": [{ "pathname": "/foo", "search": "bar", "hash": "baz", - "baseURL": "https://example.com:8080" }], + "pattern": ["https://example.com:8080/foo?bar#baz"], + "inputs": [{ "pathname": "/foo", "search": "bar", "hash": "baz", "baseURL": "https://example.com:8080" }], "expected_obj": { "protocol": "https", "username": "*", @@ -1569,9 +1605,8 @@ } }, { - "pattern": [ "/foo?bar#baz", "https://example.com:8080" ], - "inputs": [{ "pathname": "/foo", "search": "bar", "hash": "baz", - "baseURL": "https://example.com:8080" }], + "pattern": ["/foo?bar#baz", "https://example.com:8080"], + "inputs": [{ "pathname": "/foo", "search": "bar", "hash": "baz", "baseURL": "https://example.com:8080" }], "expected_obj": { "pathname": "/foo", "search": "bar", @@ -1587,17 +1622,17 @@ } }, { - "pattern": [ "/foo" ], + "pattern": ["/foo"], "expected_obj": "error" }, { - "pattern": [ "example.com/foo" ], + "pattern": ["example.com/foo"], "expected_obj": "error" }, { - "pattern": [ "http{s}?://{*.}?example.com/:product/:endpoint" ], - "inputs": [ "https://sub.example.com/foo/bar" ], - "exactly_empty_components": [ "port" ], + "pattern": ["http{s}?://{*.}?example.com/:product/:endpoint"], + "inputs": ["https://sub.example.com/foo/bar"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "http{s}?", "hostname": "{*.}?example.com", @@ -1606,14 +1641,13 @@ "expected_match": { "protocol": { "input": "https", "groups": {} }, "hostname": { "input": "sub.example.com", "groups": { "0": "sub" } }, - "pathname": { "input": "/foo/bar", "groups": { "product": "foo", - "endpoint": "bar" } } + "pathname": { "input": "/foo/bar", "groups": { "product": "foo", "endpoint": "bar" } } } }, { - "pattern": [ "https://example.com?foo" ], - "inputs": [ "https://example.com/?foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://example.com?foo"], + "inputs": ["https://example.com/?foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1628,9 +1662,9 @@ } }, { - "pattern": [ "https://example.com#foo" ], - "inputs": [ "https://example.com/#foo" ], - "exactly_empty_components": [ "port", "search" ], + "pattern": ["https://example.com#foo"], + "inputs": ["https://example.com/#foo"], + "exactly_empty_components": ["port", "search"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1645,8 +1679,8 @@ } }, { - "pattern": [ "https://example.com:8080?foo" ], - "inputs": [ "https://example.com:8080/?foo" ], + "pattern": ["https://example.com:8080?foo"], + "inputs": ["https://example.com:8080/?foo"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1663,9 +1697,9 @@ } }, { - "pattern": [ "https://example.com:8080#foo" ], - "inputs": [ "https://example.com:8080/#foo" ], - "exactly_empty_components": [ "search" ], + "pattern": ["https://example.com:8080#foo"], + "inputs": ["https://example.com:8080/#foo"], + "exactly_empty_components": ["search"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1682,9 +1716,9 @@ } }, { - "pattern": [ "https://example.com/?foo" ], - "inputs": [ "https://example.com/?foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://example.com/?foo"], + "inputs": ["https://example.com/?foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1699,9 +1733,9 @@ } }, { - "pattern": [ "https://example.com/#foo" ], - "inputs": [ "https://example.com/#foo" ], - "exactly_empty_components": [ "port", "search" ], + "pattern": ["https://example.com/#foo"], + "inputs": ["https://example.com/#foo"], + "exactly_empty_components": ["port", "search"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1716,9 +1750,9 @@ } }, { - "pattern": [ "https://example.com/*?foo" ], - "inputs": [ "https://example.com/?foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://example.com/*?foo"], + "inputs": ["https://example.com/?foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1727,9 +1761,9 @@ "expected_match": null }, { - "pattern": [ "https://example.com/*\\?foo" ], - "inputs": [ "https://example.com/?foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://example.com/*\\?foo"], + "inputs": ["https://example.com/?foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1744,9 +1778,9 @@ } }, { - "pattern": [ "https://example.com/:name?foo" ], - "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://example.com/:name?foo"], + "inputs": ["https://example.com/bar?foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1755,9 +1789,9 @@ "expected_match": null }, { - "pattern": [ "https://example.com/:name\\?foo" ], - "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://example.com/:name\\?foo"], + "inputs": ["https://example.com/bar?foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1772,9 +1806,9 @@ } }, { - "pattern": [ "https://example.com/(bar)?foo" ], - "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://example.com/(bar)?foo"], + "inputs": ["https://example.com/bar?foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1783,9 +1817,9 @@ "expected_match": null }, { - "pattern": [ "https://example.com/(bar)\\?foo" ], - "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://example.com/(bar)\\?foo"], + "inputs": ["https://example.com/bar?foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1800,9 +1834,9 @@ } }, { - "pattern": [ "https://example.com/{bar}?foo" ], - "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://example.com/{bar}?foo"], + "inputs": ["https://example.com/bar?foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1811,9 +1845,9 @@ "expected_match": null }, { - "pattern": [ "https://example.com/{bar}\\?foo" ], - "inputs": [ "https://example.com/bar?foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://example.com/{bar}\\?foo"], + "inputs": ["https://example.com/bar?foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1828,9 +1862,9 @@ } }, { - "pattern": [ "https://example.com/" ], - "inputs": [ "https://example.com:8080/" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://example.com/"], + "inputs": ["https://example.com:8080/"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -1840,14 +1874,14 @@ "expected_match": null }, { - "pattern": [ "data:foobar" ], - "inputs": [ "data:foobar" ], + "pattern": ["data:foobar"], + "inputs": ["data:foobar"], "expected_obj": "error" }, { - "pattern": [ "data\\:foobar" ], - "inputs": [ "data:foobar" ], - "exactly_empty_components": [ "hostname", "port" ], + "pattern": ["data\\:foobar"], + "inputs": ["data:foobar"], + "exactly_empty_components": ["hostname", "port"], "expected_obj": { "protocol": "data", "pathname": "foobar" @@ -1858,9 +1892,9 @@ } }, { - "pattern": [ "https://{sub.}?example.com/foo" ], - "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://{sub.}?example.com/foo"], + "inputs": ["https://example.com/foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "{sub.}?example.com", @@ -1873,9 +1907,9 @@ } }, { - "pattern": [ "https://{sub.}?example{.com/}foo" ], - "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://{sub.}?example{.com/}foo"], + "inputs": ["https://example.com/foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "{sub.}?example.com", @@ -1888,14 +1922,14 @@ } }, { - "pattern": [ "{https://}example.com/foo" ], - "inputs": [ "https://example.com/foo" ], + "pattern": ["{https://}example.com/foo"], + "inputs": ["https://example.com/foo"], "expected_obj": "error" }, { - "pattern": [ "https://(sub.)?example.com/foo" ], - "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://(sub.)?example.com/foo"], + "inputs": ["https://example.com/foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "(sub.)?example.com", @@ -1909,9 +1943,9 @@ } }, { - "pattern": [ "https://(sub.)?example(.com/)foo" ], - "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://(sub.)?example(.com/)foo"], + "inputs": ["https://example.com/foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "(sub.)?example(.com/)foo", @@ -1920,19 +1954,19 @@ "expected_match": null }, { - "pattern": [ "(https://)example.com/foo" ], - "inputs": [ "https://example.com/foo" ], + "pattern": ["(https://)example.com/foo"], + "inputs": ["https://example.com/foo"], "expected_obj": "error" }, { - "pattern": [ "https://{sub{.}}example.com/foo" ], - "inputs": [ "https://example.com/foo" ], + "pattern": ["https://{sub{.}}example.com/foo"], + "inputs": ["https://example.com/foo"], "expected_obj": "error" }, { - "pattern": [ "https://(sub(?:.))?example.com/foo" ], - "inputs": [ "https://example.com/foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://(sub(?:.))?example.com/foo"], + "inputs": ["https://example.com/foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "(sub(?:.))?example.com", @@ -1946,9 +1980,9 @@ } }, { - "pattern": [ "file:///foo/bar" ], - "inputs": [ "file:///foo/bar" ], - "exactly_empty_components": [ "hostname", "port" ], + "pattern": ["file:///foo/bar"], + "inputs": ["file:///foo/bar"], + "exactly_empty_components": ["hostname", "port"], "expected_obj": { "protocol": "file", "pathname": "/foo/bar" @@ -1959,9 +1993,9 @@ } }, { - "pattern": [ "data:" ], - "inputs": [ "data:" ], - "exactly_empty_components": [ "hostname", "port", "pathname" ], + "pattern": ["data:"], + "inputs": ["data:"], + "exactly_empty_components": ["hostname", "port", "pathname"], "expected_obj": { "protocol": "data" }, @@ -1970,9 +2004,9 @@ } }, { - "pattern": [ "foo://bar" ], - "inputs": [ "foo://bad_url_browser_interop" ], - "exactly_empty_components": [ "port" ], + "pattern": ["foo://bar"], + "inputs": ["foo://bad_url_browser_interop"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "foo", "hostname": "bar" @@ -1980,16 +2014,13 @@ "expected_match": null }, { - "pattern": [ "(café)://foo" ], + "pattern": ["(café)://foo"], "expected_obj": "error" }, { - "pattern": [ "https://example.com/foo?bar#baz" ], - "inputs": [{ "protocol": "https:", - "search": "?bar", - "hash": "#baz", - "baseURL": "http://example.com/foo" }], - "exactly_empty_components": [ "port" ], + "pattern": ["https://example.com/foo?bar#baz"], + "inputs": [{ "protocol": "https:", "search": "?bar", "hash": "#baz", "baseURL": "http://example.com/foo" }], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -2000,10 +2031,8 @@ "expected_match": null }, { - "pattern": [{ "protocol": "http{s}?:", - "search": "?bar", - "hash": "#baz" }], - "inputs": [ "http://example.com/foo?bar#baz" ], + "pattern": [{ "protocol": "http{s}?:", "search": "?bar", "hash": "#baz" }], + "inputs": ["http://example.com/foo?bar#baz"], "expected_obj": { "protocol": "http{s}?", "search": "bar", @@ -2011,16 +2040,16 @@ }, "expected_match": { "protocol": { "input": "http", "groups": {} }, - "hostname": { "input": "example.com", "groups": { "0": "example.com" }}, - "pathname": { "input": "/foo", "groups": { "0": "/foo" }}, + "hostname": { "input": "example.com", "groups": { "0": "example.com" } }, + "pathname": { "input": "/foo", "groups": { "0": "/foo" } }, "search": { "input": "bar", "groups": {} }, "hash": { "input": "baz", "groups": {} } } }, { - "pattern": [ "?bar#baz", "https://example.com/foo" ], - "inputs": [ "?bar#baz", "https://example.com/foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["?bar#baz", "https://example.com/foo"], + "inputs": ["?bar#baz", "https://example.com/foo"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -2037,9 +2066,9 @@ } }, { - "pattern": [ "?bar", "https://example.com/foo#baz" ], - "inputs": [ "?bar", "https://example.com/foo#snafu" ], - "exactly_empty_components": [ "port" ], + "pattern": ["?bar", "https://example.com/foo#baz"], + "inputs": ["?bar", "https://example.com/foo#snafu"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -2055,9 +2084,9 @@ } }, { - "pattern": [ "#baz", "https://example.com/foo?bar" ], - "inputs": [ "#baz", "https://example.com/foo?bar" ], - "exactly_empty_components": [ "port" ], + "pattern": ["#baz", "https://example.com/foo?bar"], + "inputs": ["#baz", "https://example.com/foo?bar"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -2074,9 +2103,9 @@ } }, { - "pattern": [ "#baz", "https://example.com/foo" ], - "inputs": [ "#baz", "https://example.com/foo" ], - "exactly_empty_components": [ "port", "search" ], + "pattern": ["#baz", "https://example.com/foo"], + "inputs": ["#baz", "https://example.com/foo"], + "exactly_empty_components": ["port", "search"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -2092,18 +2121,18 @@ }, { "pattern": [{ "pathname": "*" }], - "inputs": [ "foo", "data:data-urls-cannot-be-base-urls" ], + "inputs": ["foo", "data:data-urls-cannot-be-base-urls"], "expected_match": null }, { "pattern": [{ "pathname": "*" }], - "inputs": [ "foo", "not|a|valid|url" ], + "inputs": ["foo", "not|a|valid|url"], "expected_match": null }, { - "pattern": [ "https://foo\\:bar@example.com" ], - "inputs": [ "https://foo:bar@example.com" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://foo\\:bar@example.com"], + "inputs": ["https://foo:bar@example.com"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "username": "foo", @@ -2120,9 +2149,9 @@ } }, { - "pattern": [ "https://foo@example.com" ], - "inputs": [ "https://foo@example.com" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://foo@example.com"], + "inputs": ["https://foo@example.com"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "username": "foo", @@ -2137,9 +2166,9 @@ } }, { - "pattern": [ "https://\\:bar@example.com" ], - "inputs": [ "https://:bar@example.com" ], - "exactly_empty_components": [ "username", "port" ], + "pattern": ["https://\\:bar@example.com"], + "inputs": ["https://:bar@example.com"], + "exactly_empty_components": ["username", "port"], "expected_obj": { "protocol": "https", "password": "bar", @@ -2154,9 +2183,9 @@ } }, { - "pattern": [ "https://:user::pass@example.com" ], - "inputs": [ "https://foo:bar@example.com" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://:user::pass@example.com"], + "inputs": ["https://foo:bar@example.com"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "username": ":user", @@ -2173,9 +2202,9 @@ } }, { - "pattern": [ "https\\:foo\\:bar@example.com" ], - "inputs": [ "https:foo:bar@example.com" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https\\:foo\\:bar@example.com"], + "inputs": ["https:foo:bar@example.com"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "username": "foo", @@ -2192,9 +2221,9 @@ } }, { - "pattern": [ "data\\:foo\\:bar@example.com" ], - "inputs": [ "data:foo:bar@example.com" ], - "exactly_empty_components": [ "hostname", "port" ], + "pattern": ["data\\:foo\\:bar@example.com"], + "inputs": ["data:foo:bar@example.com"], + "exactly_empty_components": ["hostname", "port"], "expected_obj": { "protocol": "data", "pathname": "foo\\:bar@example.com" @@ -2205,9 +2234,9 @@ } }, { - "pattern": [ "https://foo{\\:}bar@example.com" ], - "inputs": [ "https://foo:bar@example.com" ], - "exactly_empty_components": [ "port" ], + "pattern": ["https://foo{\\:}bar@example.com"], + "inputs": ["https://foo:bar@example.com"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "username": "foo%3Abar", @@ -2216,9 +2245,9 @@ "expected_match": null }, { - "pattern": [ "data{\\:}channel.html", "https://example.com" ], - "inputs": [ "https://example.com/data:channel.html" ], - "exactly_empty_components": [ "port" ], + "pattern": ["data{\\:}channel.html", "https://example.com"], + "inputs": ["https://example.com/data:channel.html"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -2233,9 +2262,9 @@ } }, { - "pattern": [ "http://[\\:\\:1]/" ], - "inputs": [ "http://[::1]/" ], - "exactly_empty_components": [ "port" ], + "pattern": ["http://[\\:\\:1]/"], + "inputs": ["http://[::1]/"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "http", "hostname": "[\\:\\:1]", @@ -2248,8 +2277,8 @@ } }, { - "pattern": [ "http://[\\:\\:1]:8080/" ], - "inputs": [ "http://[::1]:8080/" ], + "pattern": ["http://[\\:\\:1]:8080/"], + "inputs": ["http://[::1]:8080/"], "expected_obj": { "protocol": "http", "hostname": "[\\:\\:1]", @@ -2264,9 +2293,9 @@ } }, { - "pattern": [ "http://[\\:\\:a]/" ], - "inputs": [ "http://[::a]/" ], - "exactly_empty_components": [ "port" ], + "pattern": ["http://[\\:\\:a]/"], + "inputs": ["http://[::a]/"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "http", "hostname": "[\\:\\:a]", @@ -2279,9 +2308,9 @@ } }, { - "pattern": [ "http://[:address]/" ], - "inputs": [ "http://[::1]/" ], - "exactly_empty_components": [ "port" ], + "pattern": ["http://[:address]/"], + "inputs": ["http://[::1]/"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "http", "hostname": "[:address]", @@ -2289,14 +2318,14 @@ }, "expected_match": { "protocol": { "input": "http", "groups": {} }, - "hostname": { "input": "[::1]", "groups": { "address": "::1" }}, + "hostname": { "input": "[::1]", "groups": { "address": "::1" } }, "pathname": { "input": "/", "groups": {} } } }, { - "pattern": [ "http://[\\:\\:AB\\::num]/" ], - "inputs": [ "http://[::ab:1]/" ], - "exactly_empty_components": [ "port" ], + "pattern": ["http://[\\:\\:AB\\::num]/"], + "inputs": ["http://[::ab:1]/"], + "exactly_empty_components": ["port"], "expected_obj": { "protocol": "http", "hostname": "[\\:\\:ab\\::num]", @@ -2304,7 +2333,7 @@ }, "expected_match": { "protocol": { "input": "http", "groups": {} }, - "hostname": { "input": "[::ab:1]", "groups": { "num": "1" }}, + "hostname": { "input": "[::ab:1]", "groups": { "num": "1" } }, "pathname": { "input": "/", "groups": {} } } }, @@ -2315,7 +2344,7 @@ "hostname": "[\\:\\:ab\\::num]" }, "expected_match": { - "hostname": { "input": "[::ab:1]", "groups": { "num": "1" }} + "hostname": { "input": "[::ab:1]", "groups": { "num": "1" } } } }, { @@ -2326,7 +2355,7 @@ "pattern": [{ "hostname": "{[\\:\\:ab\\::num]}" }], "inputs": [{ "hostname": "[::ab:1]" }], "expected_match": { - "hostname": { "input": "[::ab:1]", "groups": { "num": "1" }} + "hostname": { "input": "[::ab:1]", "groups": { "num": "1" } } } }, { @@ -2337,7 +2366,7 @@ "pattern": [{ "hostname": "{[\\:\\::num\\:1]}" }], "inputs": [{ "hostname": "[::ab:1]" }], "expected_match": { - "hostname": { "input": "[::ab:1]", "groups": { "num": "ab" }} + "hostname": { "input": "[::ab:1]", "groups": { "num": "ab" } } } }, { @@ -2348,7 +2377,7 @@ "pattern": [{ "hostname": "[*\\:1]" }], "inputs": [{ "hostname": "[::ab:1]" }], "expected_match": { - "hostname": { "input": "[::ab:1]", "groups": { "0": "::ab" }} + "hostname": { "input": "[::ab:1]", "groups": { "0": "::ab" } } } }, { @@ -2356,19 +2385,19 @@ "expected_obj": "error" }, { - "pattern": [ "https://foo{{@}}example.com" ], - "inputs": [ "https://foo@example.com" ], + "pattern": ["https://foo{{@}}example.com"], + "inputs": ["https://foo@example.com"], "expected_obj": "error" }, { - "pattern": [ "https://foo{@example.com" ], - "inputs": [ "https://foo@example.com" ], + "pattern": ["https://foo{@example.com"], + "inputs": ["https://foo@example.com"], "expected_obj": "error" }, { - "pattern": [ "data\\:text/javascript,let x = 100/:tens?5;" ], - "inputs": [ "data:text/javascript,let x = 100/5;" ], - "exactly_empty_components": [ "hostname", "port" ], + "pattern": ["data\\:text/javascript,let x = 100/:tens?5;"], + "inputs": ["data:text/javascript,let x = 100/5;"], + "exactly_empty_components": ["hostname", "port"], "expected_obj": { "protocol": "data", "pathname": "text/javascript,let x = 100/:tens?5;" @@ -2388,53 +2417,53 @@ "expected_obj": "error" }, { - "pattern": [ "/foo", "" ], + "pattern": ["/foo", ""], "expected_obj": "error" }, { - "pattern": [{ "pathname": "/foo" }, "https://example.com" ], + "pattern": [{ "pathname": "/foo" }, "https://example.com"], "expected_obj": "error" }, { "pattern": [{ "pathname": ":name*" }], "inputs": [{ "pathname": "foobar" }], "expected_match": { - "pathname": { "input": "foobar", "groups": { "name": "foobar" }} + "pathname": { "input": "foobar", "groups": { "name": "foobar" } } } }, { "pattern": [{ "pathname": ":name+" }], "inputs": [{ "pathname": "foobar" }], "expected_match": { - "pathname": { "input": "foobar", "groups": { "name": "foobar" }} + "pathname": { "input": "foobar", "groups": { "name": "foobar" } } } }, { "pattern": [{ "pathname": ":name" }], "inputs": [{ "pathname": "foobar" }], "expected_match": { - "pathname": { "input": "foobar", "groups": { "name": "foobar" }} + "pathname": { "input": "foobar", "groups": { "name": "foobar" } } } }, { "pattern": [{ "protocol": ":name*" }], "inputs": [{ "protocol": "foobar" }], "expected_match": { - "protocol": { "input": "foobar", "groups": { "name": "foobar" }} + "protocol": { "input": "foobar", "groups": { "name": "foobar" } } } }, { "pattern": [{ "protocol": ":name+" }], "inputs": [{ "protocol": "foobar" }], "expected_match": { - "protocol": { "input": "foobar", "groups": { "name": "foobar" }} + "protocol": { "input": "foobar", "groups": { "name": "foobar" } } } }, { "pattern": [{ "protocol": ":name" }], "inputs": [{ "protocol": "foobar" }], "expected_match": { - "protocol": { "input": "foobar", "groups": { "name": "foobar" }} + "protocol": { "input": "foobar", "groups": { "name": "foobar" } } } }, { @@ -2443,7 +2472,7 @@ }, { "pattern": [{ "hostname": "bad#hostname" }], - "inputs": [{ "hostname": "bad" }], + "inputs": [{ "hostname": "bad" }], "expected_obj": { "hostname": "bad" }, @@ -2543,18 +2572,18 @@ "pattern": [{}], "inputs": ["https://example.com/"], "expected_match": { - "protocol": { "input": "https", "groups": { "0": "https" }}, - "hostname": { "input": "example.com", "groups": { "0": "example.com" }}, - "pathname": { "input": "/", "groups": { "0": "/" }} + "protocol": { "input": "https", "groups": { "0": "https" } }, + "hostname": { "input": "example.com", "groups": { "0": "example.com" } }, + "pathname": { "input": "/", "groups": { "0": "/" } } } }, { "pattern": [], "inputs": ["https://example.com/"], "expected_match": { - "protocol": { "input": "https", "groups": { "0": "https" }}, - "hostname": { "input": "example.com", "groups": { "0": "example.com" }}, - "pathname": { "input": "/", "groups": { "0": "/" }} + "protocol": { "input": "https", "groups": { "0": "https" } }, + "hostname": { "input": "example.com", "groups": { "0": "example.com" } }, + "pathname": { "input": "/", "groups": { "0": "/" } } } }, { @@ -2571,14 +2600,14 @@ "pattern": [{ "pathname": "(foo)(.*)" }], "inputs": [{ "pathname": "foobarbaz" }], "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "0": "foo", "1": "barbaz" }} + "pathname": { "input": "foobarbaz", "groups": { "0": "foo", "1": "barbaz" } } } }, { "pattern": [{ "pathname": "{(foo)bar}(.*)" }], "inputs": [{ "pathname": "foobarbaz" }], "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "0": "foo", "1": "baz" }} + "pathname": { "input": "foobarbaz", "groups": { "0": "foo", "1": "baz" } } } }, { @@ -2588,21 +2617,21 @@ "pathname": "(foo)?*" }, "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "0": "foo", "1": "barbaz" }} + "pathname": { "input": "foobarbaz", "groups": { "0": "foo", "1": "barbaz" } } } }, { "pattern": [{ "pathname": "{:foo}(.*)" }], "inputs": [{ "pathname": "foobarbaz" }], "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "foo": "f", "0": "oobarbaz" }} + "pathname": { "input": "foobarbaz", "groups": { "foo": "f", "0": "oobarbaz" } } } }, { "pattern": [{ "pathname": "{:foo}(barbaz)" }], "inputs": [{ "pathname": "foobarbaz" }], "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "foo": "foo", "0": "barbaz" }} + "pathname": { "input": "foobarbaz", "groups": { "foo": "foo", "0": "barbaz" } } } }, { @@ -2612,7 +2641,7 @@ "pathname": "{:foo}(.*)" }, "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "foo": "f", "0": "oobarbaz" }} + "pathname": { "input": "foobarbaz", "groups": { "foo": "f", "0": "oobarbaz" } } } }, { @@ -2630,7 +2659,7 @@ "pathname": ":foo{bar*}" }, "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "foo": "foo", "0": "baz" }} + "pathname": { "input": "foobarbaz", "groups": { "foo": "foo", "0": "baz" } } } }, { @@ -2640,7 +2669,7 @@ "pathname": ":foo:bar(.*)" }, "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "foo": "f", "bar": "oobarbaz" }} + "pathname": { "input": "foobarbaz", "groups": { "foo": "f", "bar": "oobarbaz" } } } }, { @@ -2650,14 +2679,14 @@ "pathname": ":foo?*" }, "expected_match": { - "pathname": { "input": "foobarbaz", "groups": { "foo": "f", "0": "oobarbaz" }} + "pathname": { "input": "foobarbaz", "groups": { "foo": "f", "0": "oobarbaz" } } } }, { "pattern": [{ "pathname": "{:foo\\bar}" }], "inputs": [{ "pathname": "foobar" }], "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "foo" }} + "pathname": { "input": "foobar", "groups": { "foo": "foo" } } } }, { @@ -2667,21 +2696,21 @@ "pathname": "{:foo.bar}" }, "expected_match": { - "pathname": { "input": "foo.bar", "groups": { "foo": "foo" }} + "pathname": { "input": "foo.bar", "groups": { "foo": "foo" } } } }, { "pattern": [{ "pathname": "{:foo(foo)bar}" }], "inputs": [{ "pathname": "foobar" }], "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "foo" }} + "pathname": { "input": "foobar", "groups": { "foo": "foo" } } } }, { "pattern": [{ "pathname": "{:foo}bar" }], "inputs": [{ "pathname": "foobar" }], "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "foo" }} + "pathname": { "input": "foobar", "groups": { "foo": "foo" } } } }, { @@ -2691,7 +2720,7 @@ "pathname": "{:foo}bar" }, "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "foo" }} + "pathname": { "input": "foobar", "groups": { "foo": "foo" } } } }, { @@ -2701,7 +2730,7 @@ "pathname": "{:foo}(.*)" }, "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "f", "0": "oobar" }} + "pathname": { "input": "foobar", "groups": { "foo": "f", "0": "oobar" } } } }, { @@ -2711,7 +2740,7 @@ "pathname": "{:foo}bar" }, "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "foo" }} + "pathname": { "input": "foobar", "groups": { "foo": "foo" } } } }, { @@ -2721,7 +2750,7 @@ "pathname": "{:foo}bar" }, "expected_match": { - "pathname": { "input": "foobar", "groups": { "foo": "foo" }} + "pathname": { "input": "foobar", "groups": { "foo": "foo" } } } }, { @@ -2732,28 +2761,28 @@ }, "//": "The `null` below is translated to undefined in the test harness.", "expected_match": { - "pathname": { "input": "foobar", "groups": { "0": "foobar", "1": null }} + "pathname": { "input": "foobar", "groups": { "0": "foobar", "1": null } } } }, { "pattern": [{ "pathname": ":foo(baz)(.*)" }], "inputs": [{ "pathname": "bazbar" }], "expected_match": { - "pathname": { "input": "bazbar", "groups": { "foo": "baz", "0": "bar" }} + "pathname": { "input": "bazbar", "groups": { "foo": "baz", "0": "bar" } } } }, { "pattern": [{ "pathname": ":foo(baz)bar" }], "inputs": [{ "pathname": "bazbar" }], "expected_match": { - "pathname": { "input": "bazbar", "groups": { "foo": "baz" }} + "pathname": { "input": "bazbar", "groups": { "foo": "baz" } } } }, { "pattern": [{ "pathname": "*/*" }], "inputs": [{ "pathname": "foo/bar" }], "expected_match": { - "pathname": { "input": "foo/bar", "groups": { "0": "foo", "1": "bar" }} + "pathname": { "input": "foo/bar", "groups": { "0": "foo", "1": "bar" } } } }, { @@ -2763,14 +2792,14 @@ "pathname": "*/{*}" }, "expected_match": { - "pathname": { "input": "foo/bar", "groups": { "0": "foo", "1": "bar" }} + "pathname": { "input": "foo/bar", "groups": { "0": "foo", "1": "bar" } } } }, { "pattern": [{ "pathname": "*/{*}" }], "inputs": [{ "pathname": "foo/bar" }], "expected_match": { - "pathname": { "input": "foo/bar", "groups": { "0": "foo", "1": "bar" }} + "pathname": { "input": "foo/bar", "groups": { "0": "foo", "1": "bar" } } } }, { @@ -2796,28 +2825,28 @@ "pattern": [{ "pathname": "./foo" }], "inputs": [{ "pathname": "./foo" }], "expected_match": { - "pathname": { "input": "./foo", "groups": {}} + "pathname": { "input": "./foo", "groups": {} } } }, { "pattern": [{ "pathname": "../foo" }], "inputs": [{ "pathname": "../foo" }], "expected_match": { - "pathname": { "input": "../foo", "groups": {}} + "pathname": { "input": "../foo", "groups": {} } } }, { "pattern": [{ "pathname": ":foo./" }], "inputs": [{ "pathname": "bar./" }], "expected_match": { - "pathname": { "input": "bar./", "groups": { "foo": "bar" }} + "pathname": { "input": "bar./", "groups": { "foo": "bar" } } } }, { "pattern": [{ "pathname": ":foo../" }], "inputs": [{ "pathname": "bar../" }], "expected_match": { - "pathname": { "input": "bar../", "groups": { "foo": "bar" }} + "pathname": { "input": "bar../", "groups": { "foo": "bar" } } } }, { @@ -2827,7 +2856,7 @@ "pathname": "{/:foo}bar" }, "expected_match": { - "pathname": { "input": "/bazbar", "groups": { "foo": "baz" }} + "pathname": { "input": "/bazbar", "groups": { "foo": "baz" } } } }, { @@ -2845,10 +2874,8 @@ } }, { - "pattern": [ "https://example.com:8080/foo?bar#baz", - { "ignoreCase": true }], - "inputs": [{ "pathname": "/FOO", "search": "BAR", "hash": "BAZ", - "baseURL": "https://example.com:8080" }], + "pattern": ["https://example.com:8080/foo?bar#baz", { "ignoreCase": true }], + "inputs": [{ "pathname": "/FOO", "search": "BAR", "hash": "BAZ", "baseURL": "https://example.com:8080" }], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -2867,10 +2894,8 @@ } }, { - "pattern": [ "/foo?bar#baz", "https://example.com:8080", - { "ignoreCase": true }], - "inputs": [{ "pathname": "/FOO", "search": "BAR", "hash": "BAZ", - "baseURL": "https://example.com:8080" }], + "pattern": ["/foo?bar#baz", "https://example.com:8080", { "ignoreCase": true }], + "inputs": [{ "pathname": "/FOO", "search": "BAR", "hash": "BAZ", "baseURL": "https://example.com:8080" }], "expected_obj": { "protocol": "https", "hostname": "example.com", @@ -2889,16 +2914,14 @@ } }, { - "pattern": [ "/foo?bar#baz", { "ignoreCase": true }, - "https://example.com:8080" ], - "inputs": [{ "pathname": "/FOO", "search": "BAR", "hash": "BAZ", - "baseURL": "https://example.com:8080" }], + "pattern": ["/foo?bar#baz", { "ignoreCase": true }, "https://example.com:8080"], + "inputs": [{ "pathname": "/FOO", "search": "BAR", "hash": "BAZ", "baseURL": "https://example.com:8080" }], "expected_obj": "error" }, { "pattern": [{ "search": "foo", "baseURL": "https://example.com/a/+/b" }], "inputs": [{ "search": "foo", "baseURL": "https://example.com/a/+/b" }], - "exactly_empty_components": [ "port" ], + "exactly_empty_components": ["port"], "expected_obj": { "pathname": "/a/\\+/b" }, @@ -2912,7 +2935,7 @@ { "pattern": [{ "hash": "foo", "baseURL": "https://example.com/?q=*&v=?&hmm={}&umm=()" }], "inputs": [{ "hash": "foo", "baseURL": "https://example.com/?q=*&v=?&hmm={}&umm=()" }], - "exactly_empty_components": [ "port" ], + "exactly_empty_components": ["port"], "expected_obj": { "search": "q=\\*&v=\\?&hmm=\\{\\}&umm=\\(\\)" }, @@ -2925,9 +2948,9 @@ } }, { - "pattern": [ "#foo", "https://example.com/?q=*&v=?&hmm={}&umm=()" ], - "inputs": [ "https://example.com/?q=*&v=?&hmm={}&umm=()#foo" ], - "exactly_empty_components": [ "port" ], + "pattern": ["#foo", "https://example.com/?q=*&v=?&hmm={}&umm=()"], + "inputs": ["https://example.com/?q=*&v=?&hmm={}&umm=()#foo"], + "exactly_empty_components": ["port"], "expected_obj": { "search": "q=\\*&v=\\?&hmm=\\{\\}&umm=\\(\\)", "hash": "foo" @@ -2952,7 +2975,7 @@ "pathname": { "input": "/z", "groups": { "0": "z" } } } }, - { + { "pattern": [{ "pathname": "/([\\d&&[0-1]])" }], "inputs": [{ "pathname": "/0" }], "expected_match": { @@ -2963,5 +2986,167 @@ "pattern": [{ "pathname": "/([\\d&&[0-1]])" }], "inputs": [{ "pathname": "/3" }], "expected_match": null + }, + { + "pattern": [{ "protocol": "http", "hostname": "example.com/ignoredpath" }], + "inputs": ["http://example.com/"], + "expected_obj": { + "protocol": "http", + "hostname": "example.com", + "pathname": "*" + }, + "expected_match": { + "protocol": { "input": "http", "groups": {} }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/", "groups": { "0": "/" } } + } + }, + { + "pattern": [{ "protocol": "http", "hostname": "example.com\\?ignoredsearch" }], + "inputs": ["http://example.com/"], + "expected_obj": { + "protocol": "http", + "hostname": "example.com", + "search": "*" + }, + "expected_match": { + "protocol": { "input": "http", "groups": {} }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/", "groups": { "0": "/" } } + } + }, + { + "pattern": [{ "protocol": "http", "hostname": "example.com#ignoredhash" }], + "inputs": ["http://example.com/"], + "expected_obj": { + "protocol": "http", + "hostname": "example.com", + "hash": "*" + }, + "expected_match": { + "protocol": { "input": "http", "groups": {} }, + "hostname": { "input": "example.com", "groups": {} }, + "pathname": { "input": "/", "groups": { "0": "/" } } + } + }, + { + "pattern": ["https://www.example.com/*"], + "inputs": ["https://www.example.com/x"], + "exactly_empty_components": ["port"], + "expected_obj": { + "protocol": "https", + "hostname": "www.example.com", + "pathname": "/*" + }, + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "www.example.com", "groups": {} }, + "pathname": { "input": "/x", "groups": { "0": "x" } } + } + }, + { + "pattern": ["https://www.example.com/*"], + "inputs": ["https://www.example.com/xyz"], + "exactly_empty_components": ["port"], + "expected_obj": { + "protocol": "https", + "hostname": "www.example.com", + "pathname": "/*" + }, + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "www.example.com", "groups": {} }, + "pathname": { "input": "/xyz", "groups": { "0": "xyz" } } + } + }, + { + "pattern": ["https://www.example.com/*"], + "inputs": ["https://www.example.com/example"], + "exactly_empty_components": ["port"], + "expected_obj": { + "protocol": "https", + "hostname": "www.example.com", + "pathname": "/*" + }, + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "www.example.com", "groups": {} }, + "pathname": { "input": "/example", "groups": { "0": "example" } } + } + }, + { + "pattern": ["https://www.example.com/*"], + "inputs": ["https://www.example.com/text"], + "exactly_empty_components": ["port"], + "expected_obj": { + "protocol": "https", + "hostname": "www.example.com", + "pathname": "/*" + }, + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "www.example.com", "groups": {} }, + "pathname": { "input": "/text", "groups": { "0": "text" } } + } + }, + { + "pattern": ["https://www.example.com/*"], + "inputs": ["https://www.example.com/path/with/x"], + "exactly_empty_components": ["port"], + "expected_obj": { + "protocol": "https", + "hostname": "www.example.com", + "pathname": "/*" + }, + "expected_match": { + "protocol": { "input": "https", "groups": {} }, + "hostname": { "input": "www.example.com", "groups": {} }, + "pathname": { "input": "/path/with/x", "groups": { "0": "path/with/x" } } + } + }, + { + "pattern": [{ "hostname": ":domain(.*)" }], + "inputs": [{ "hostname": "localhost" }], + "expected_obj": { + "hostname": ":domain(.*)" + }, + "expected_match": { + "hostname": { "input": "localhost", "groups": { "domain": "localhost" } } + } + }, + { + "pattern": ["((?R)):"], + "expected_obj": "error" + }, + { + "pattern": ["(\\H):"], + "expected_obj": "error" + }, + { + "pattern": [{ "pathname": "/:foo((?a))" }], + "inputs": [{ "pathname": "/a" }], + "expected_match": { + "pathname": { + "input": "/a", + "groups": { "foo": "a" } + } + } + }, + { + "pattern": [{ "pathname": "/foo/(bar(?baz))" }], + "inputs": [{ "pathname": "/foo/barbaz" }], + "expected_match": { + "pathname": { + "input": "/foo/barbaz", + "groups": { "0": "barbaz" } + } + } + }, + { + "pattern": [{ "pathname": "/:𠀀" }], + "inputs": [{ "pathname": "/foo" }], + "expected_match": { + "pathname": { "input": "/foo", "groups": { "𠀀": "foo" } } + } } ]