diff --git a/src/node_url.cc b/src/node_url.cc index 54a2944588071c..f9965d537b9abf 100644 --- a/src/node_url.cc +++ b/src/node_url.cc @@ -862,8 +862,10 @@ namespace url { } break; case kRelativeSlash: - if (ch == '/' || special_back_slash) { + if (IsSpecial(url->scheme) && (ch == '/' || ch == '\\')) { state = kSpecialAuthorityIgnoreSlashes; + } else if (ch == '/') { + state = kAuthority; } else { if (base->flags & URL_FLAGS_HAS_USERNAME) { url->flags |= URL_FLAGS_HAS_USERNAME; @@ -1145,9 +1147,25 @@ namespace url { } break; case kPathStart: - state = kPath; - if (ch != '/' && !special_back_slash) - continue; + if (IsSpecial(url->scheme)) { + state = kPath; + if (ch != '/' && ch != '\\') { + continue; + } + } else if (!has_state_override && ch == '?') { + url->flags |= URL_FLAGS_HAS_QUERY; + url->query.clear(); + state = kQuery; + } else if (!has_state_override && ch == '#') { + url->flags |= URL_FLAGS_HAS_FRAGMENT; + url->fragment.clear(); + state = kFragment; + } else if (ch != kEOL) { + state = kPath; + if (ch != '/') { + continue; + } + } break; case kPath: if (ch == kEOL || @@ -1165,7 +1183,7 @@ namespace url { url->flags |= URL_FLAGS_HAS_PATH; url->path.push_back(""); } - } else { + } else if (!IsSingleDotSegment(buffer)) { if (url->scheme == "file:" && url->path.empty() && buffer.size() == 2 && diff --git a/test/fixtures/url-setter-tests.json b/test/fixtures/url-setter-tests.js similarity index 71% rename from test/fixtures/url-setter-tests.json rename to test/fixtures/url-setter-tests.js index d0138204b3fe6c..8c15a3cc5ac885 100644 --- a/test/fixtures/url-setter-tests.json +++ b/test/fixtures/url-setter-tests.js @@ -1,6 +1,12 @@ +'use strict'; + +/* WPT Refs: + https://github.com/w3c/web-platform-tests/blob/e48dd15/url/setters_tests.json + License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html +*/ +module.exports = { "comment": [ - "License: http://www.w3.org/Consortium/Legal/2008/04-testsuite-copyright.html", "## Tests for setters of https://url.spec.whatwg.org/#urlutils-members", "", "This file contains a JSON object.", @@ -28,7 +34,7 @@ "href": "a://example.net", "new_value": "", "expected": { - "href": "a://example.net/", + "href": "a://example.net", "protocol": "a:" } }, @@ -36,16 +42,24 @@ "href": "a://example.net", "new_value": "b", "expected": { - "href": "b://example.net/", + "href": "b://example.net", "protocol": "b:" } }, + { + "href": "javascript:alert(1)", + "new_value": "defuse", + "expected": { + "href": "defuse:alert(1)", + "protocol": "defuse:" + } + }, { "comment": "Upper-case ASCII is lower-cased", "href": "a://example.net", "new_value": "B", "expected": { - "href": "b://example.net/", + "href": "b://example.net", "protocol": "b:" } }, @@ -54,7 +68,7 @@ "href": "a://example.net", "new_value": "é", "expected": { - "href": "a://example.net/", + "href": "a://example.net", "protocol": "a:" } }, @@ -63,7 +77,7 @@ "href": "a://example.net", "new_value": "0b", "expected": { - "href": "a://example.net/", + "href": "a://example.net", "protocol": "a:" } }, @@ -72,7 +86,7 @@ "href": "a://example.net", "new_value": "+b", "expected": { - "href": "a://example.net/", + "href": "a://example.net", "protocol": "a:" } }, @@ -80,7 +94,7 @@ "href": "a://example.net", "new_value": "bC0+-.", "expected": { - "href": "bc0+-.://example.net/", + "href": "bc0+-.://example.net", "protocol": "bc0+-.:" } }, @@ -89,7 +103,7 @@ "href": "a://example.net", "new_value": "b,c", "expected": { - "href": "a://example.net/", + "href": "a://example.net", "protocol": "a:" } }, @@ -98,16 +112,16 @@ "href": "a://example.net", "new_value": "bé", "expected": { - "href": "a://example.net/", + "href": "a://example.net", "protocol": "a:" } }, { - "comment": "Can’t switch from special scheme to non-special", - "href": "http://example.net", - "new_value": "b", + "comment": "Can’t switch from URL containing username/password/port to file", + "href": "http://test@example.net", + "new_value": "file", "expected": { - "href": "http://example.net/", + "href": "http://test@example.net/", "protocol": "http:" } }, @@ -152,6 +166,15 @@ "protocol": "file:" } }, + { + "comment": "Can’t switch from special scheme to non-special", + "href": "http://example.net", + "new_value": "b", + "expected": { + "href": "http://example.net/", + "protocol": "http:" + } + }, { "href": "file://hi/path", "new_value": "s", @@ -190,7 +213,7 @@ "href": "ssh://me@example.net", "new_value": "http", "expected": { - "href": "ssh://me@example.net/", + "href": "ssh://me@example.net", "protocol": "ssh:" } }, @@ -198,7 +221,7 @@ "href": "ssh://me@example.net", "new_value": "gopher", "expected": { - "href": "ssh://me@example.net/", + "href": "ssh://me@example.net", "protocol": "ssh:" } }, @@ -206,7 +229,15 @@ "href": "ssh://me@example.net", "new_value": "file", "expected": { - "href": "ssh://me@example.net/", + "href": "ssh://me@example.net", + "protocol": "ssh:" + } + }, + { + "href": "ssh://example.net", + "new_value": "file", + "expected": { + "href": "ssh://example.net", "protocol": "ssh:" } }, @@ -265,6 +296,14 @@ "username": "" } }, + { + "href": "javascript:alert(1)", + "new_value": "wario", + "expected": { + "href": "javascript:alert(1)", + "username": "" + } + }, { "href": "http://example.net", "new_value": "me", @@ -314,7 +353,31 @@ "href": "http://%c3%89t%C3%A9@example.net/", "username": "%c3%89t%C3%A9" } - } + }, + { + "href": "sc:///", + "new_value": "x", + "expected": { + "href": "sc:///", + "username": "" + } + }, + { + "href": "javascript://x/", + "new_value": "wario", + "expected": { + "href": "javascript://wario@x/", + "username": "wario" + } + }, + // { + // "href": "file://test/", + // "new_value": "test", + // "expected": { + // "href": "file://test/", + // "username": "" + // } + // } ], "password": [ { @@ -393,9 +456,134 @@ "href": "http://:%c3%89t%C3%A9@example.net/", "password": "%c3%89t%C3%A9" } - } + }, + { + "href": "sc:///", + "new_value": "x", + "expected": { + "href": "sc:///", + "password": "" + } + }, + { + "href": "javascript://x/", + "new_value": "bowser", + "expected": { + "href": "javascript://:bowser@x/", + "password": "bowser" + } + }, + // { + // "href": "file://test/", + // "new_value": "test", + // "expected": { + // "href": "file://test/", + // "password": "" + // } + // } ], "host": [ + { + "comment": "Non-special scheme", + "href": "sc://x/", + "new_value": "\u0000", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + // { + // "href": "sc://x/", + // "new_value": "\u0009", + // "expected": { + // "href": "sc:///", + // "host": "", + // "hostname": "" + // } + // }, + // { + // "href": "sc://x/", + // "new_value": "\u000A", + // "expected": { + // "href": "sc:///", + // "host": "", + // "hostname": "" + // } + // }, + // { + // "href": "sc://x/", + // "new_value": "\u000D", + // "expected": { + // "href": "sc:///", + // "host": "", + // "hostname": "" + // } + // }, + { + "href": "sc://x/", + "new_value": " ", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + // { + // "href": "sc://x/", + // "new_value": "#", + // "expected": { + // "href": "sc:///", + // "host": "", + // "hostname": "" + // } + // }, + // { + // "href": "sc://x/", + // "new_value": "/", + // "expected": { + // "href": "sc:///", + // "host": "", + // "hostname": "" + // } + // }, + // { + // "href": "sc://x/", + // "new_value": "?", + // "expected": { + // "href": "sc:///", + // "host": "", + // "hostname": "" + // } + // }, + { + "href": "sc://x/", + "new_value": "@", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + // { + // "href": "sc://x/", + // "new_value": "ß", + // "expected": { + // "href": "sc://%C3%9F/", + // "host": "%C3%9F", + // "hostname": "%C3%9F" + // } + // }, + { + "comment": "IDNA Nontransitional_Processing", + "href": "https://x/", + "new_value": "ß", + "expected": { + "href": "https://xn--zca/", + "host": "xn--zca", + "hostname": "xn--zca" + } + }, { "comment": "Cannot-be-a-base means no host", "href": "mailto:me@example.net", @@ -617,7 +805,7 @@ } }, { - "comment": "\\ is not a delimiter for non-special schemes, and it’s invalid in a domain", + "comment": "\\ is not a delimiter for non-special schemes, but still forbidden in hosts", "href": "view-source+http://example.net/path", "new_value": "example.com\\stuff", "expected": { @@ -681,9 +869,187 @@ "hostname": "example.com", "port": "" } - } + }, + { + "comment": "Broken IPv6", + "href": "http://example.net/", + "new_value": "[google.com]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + // { + // "href": "http://example.net/", + // "new_value": "[::1.2.3.4x]", + // "expected": { + // "href": "http://example.net/", + // "host": "example.net", + // "hostname": "example.net" + // } + // }, + // { + // "href": "http://example.net/", + // "new_value": "[::1.2.3.]", + // "expected": { + // "href": "http://example.net/", + // "host": "example.net", + // "hostname": "example.net" + // } + // }, + // { + // "href": "http://example.net/", + // "new_value": "[::1.2.]", + // "expected": { + // "href": "http://example.net/", + // "host": "example.net", + // "hostname": "example.net" + // } + // }, + // { + // "href": "http://example.net/", + // "new_value": "[::1.]", + // "expected": { + // "href": "http://example.net/", + // "host": "example.net", + // "hostname": "example.net" + // } + // }, + // { + // "href": "file://y/", + // "new_value": "x:123", + // "expected": { + // "href": "file://y/", + // "host": "y", + // "hostname": "y", + // "port": "" + // } + // }, + // { + // "href": "file://y/", + // "new_value": "loc%41lhost", + // "expected": { + // "href": "file:///", + // "host": "", + // "hostname": "", + // "port": "" + // } + // }, + // { + // "href": "file://hi/x", + // "new_value": "", + // "expected": { + // "href": "file:///x", + // "host": "", + // "hostname": "", + // "port": "" + // } + // }, + // { + // "href": "sc://test@test/", + // "new_value": "", + // "expected": { + // "href": "sc://test@test/", + // "host": "test", + // "hostname": "test", + // "username": "test" + // } + // }, + // { + // "href": "sc://test:12/", + // "new_value": "", + // "expected": { + // "href": "sc://test:12/", + // "host": "test:12", + // "hostname": "test", + // "port": "12" + // } + // } ], "hostname": [ + { + "comment": "Non-special scheme", + "href": "sc://x/", + "new_value": "\u0000", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + // { + // "href": "sc://x/", + // "new_value": "\u0009", + // "expected": { + // "href": "sc:///", + // "host": "", + // "hostname": "" + // } + // }, + // { + // "href": "sc://x/", + // "new_value": "\u000A", + // "expected": { + // "href": "sc:///", + // "host": "", + // "hostname": "" + // } + // }, + // { + // "href": "sc://x/", + // "new_value": "\u000D", + // "expected": { + // "href": "sc:///", + // "host": "", + // "hostname": "" + // } + // }, + { + "href": "sc://x/", + "new_value": " ", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, + // { + // "href": "sc://x/", + // "new_value": "#", + // "expected": { + // "href": "sc:///", + // "host": "", + // "hostname": "" + // } + // }, + // { + // "href": "sc://x/", + // "new_value": "/", + // "expected": { + // "href": "sc:///", + // "host": "", + // "hostname": "" + // } + // }, + // { + // "href": "sc://x/", + // "new_value": "?", + // "expected": { + // "href": "sc:///", + // "host": "", + // "hostname": "" + // } + // }, + { + "href": "sc://x/", + "new_value": "@", + "expected": { + "href": "sc://x/", + "host": "x", + "hostname": "x" + } + }, { "comment": "Cannot-be-a-base means no host", "href": "mailto:me@example.net", @@ -828,7 +1194,7 @@ } }, { - "comment": "\\ is not a delimiter for non-special schemes, and it’s invalid in a domain", + "comment": "\\ is not a delimiter for non-special schemes, but still forbidden in hosts", "href": "view-source+http://example.net/path", "new_value": "example.com\\stuff", "expected": { @@ -837,7 +1203,103 @@ "hostname": "example.net", "port": "" } - } + }, + { + "comment": "Broken IPv6", + "href": "http://example.net/", + "new_value": "[google.com]", + "expected": { + "href": "http://example.net/", + "host": "example.net", + "hostname": "example.net" + } + }, + // { + // "href": "http://example.net/", + // "new_value": "[::1.2.3.4x]", + // "expected": { + // "href": "http://example.net/", + // "host": "example.net", + // "hostname": "example.net" + // } + // }, + // { + // "href": "http://example.net/", + // "new_value": "[::1.2.3.]", + // "expected": { + // "href": "http://example.net/", + // "host": "example.net", + // "hostname": "example.net" + // } + // }, + // { + // "href": "http://example.net/", + // "new_value": "[::1.2.]", + // "expected": { + // "href": "http://example.net/", + // "host": "example.net", + // "hostname": "example.net" + // } + // }, + // { + // "href": "http://example.net/", + // "new_value": "[::1.]", + // "expected": { + // "href": "http://example.net/", + // "host": "example.net", + // "hostname": "example.net" + // } + // }, + // { + // "href": "file://y/", + // "new_value": "x:123", + // "expected": { + // "href": "file://y/", + // "host": "y", + // "hostname": "y", + // "port": "" + // } + // }, + // { + // "href": "file://y/", + // "new_value": "loc%41lhost", + // "expected": { + // "href": "file:///", + // "host": "", + // "hostname": "", + // "port": "" + // } + // }, + // { + // "href": "file://hi/x", + // "new_value": "", + // "expected": { + // "href": "file:///x", + // "host": "", + // "hostname": "", + // "port": "" + // } + // }, + // { + // "href": "sc://test@test/", + // "new_value": "", + // "expected": { + // "href": "sc://test@test/", + // "host": "test", + // "hostname": "test", + // "username": "test" + // } + // }, + // { + // "href": "sc://test:12/", + // "new_value": "", + // "expected": { + // "href": "sc://test:12/", + // "host": "test:12", + // "hostname": "test", + // "port": "12" + // } + // } ], "port": [ { @@ -992,6 +1454,65 @@ "hostname": "example.net", "port": "8080" } + }, + { + "comment": "Port numbers are 16 bit integers, overflowing is an error", + "href": "non-special://example.net:8080/path", + "new_value": "65536", + "expected": { + "href": "non-special://example.net:8080/path", + "host": "example.net:8080", + "hostname": "example.net", + "port": "8080" + } + }, + { + "href": "file://test/", + "new_value": "12", + "expected": { + "href": "file://test/", + "port": "" + } + }, + { + "href": "file://localhost/", + "new_value": "12", + "expected": { + "href": "file:///", + "port": "" + } + }, + { + "href": "non-base:value", + "new_value": "12", + "expected": { + "href": "non-base:value", + "port": "" + } + }, + { + "href": "sc:///", + "new_value": "12", + "expected": { + "href": "sc:///", + "port": "" + } + }, + { + "href": "sc://x/", + "new_value": "12", + "expected": { + "href": "sc://x:12/", + "port": "12" + } + }, + { + "href": "javascript://x/", + "new_value": "12", + "expected": { + "href": "javascript://x:12/", + "port": "12" + } } ], "pathname": [ @@ -1072,6 +1593,33 @@ "href": "http://example.net/%3F", "pathname": "/%3F" } + }, + { + "comment": "# needs to be encoded", + "href": "http://example.net", + "new_value": "#", + "expected": { + "href": "http://example.net/%23", + "pathname": "/%23" + } + }, + { + "comment": "? needs to be encoded, non-special scheme", + "href": "sc://example.net", + "new_value": "?", + "expected": { + "href": "sc://example.net/%3F", + "pathname": "/%3F" + } + }, + { + "comment": "# needs to be encoded, non-special scheme", + "href": "sc://example.net", + "new_value": "#", + "expected": { + "href": "sc://example.net/%23", + "pathname": "/%23" + } } ], "search": [ @@ -1224,6 +1772,14 @@ "href": "http://example.net/#%c3%89t%C3%A9", "hash": "#%c3%89t%C3%A9" } - } + }, + // { + // "href": "javascript:alert(1)", + // "new_value": "castle", + // "expected": { + // "href": "javascript:alert(1)#castle", + // "hash": "#castle" + // } + // } ] } diff --git a/test/fixtures/url-tests.js b/test/fixtures/url-tests.js index a4e7de9f26b199..c7e63f50331c3b 100644 --- a/test/fixtures/url-tests.js +++ b/test/fixtures/url-tests.js @@ -571,21 +571,21 @@ module.exports = "search": "", "hash": "" }, - // { - // "input": "foo://", - // "base": "http://example.org/foo/bar", - // "href": "foo://", - // "origin": "null", - // "protocol": "foo:", - // "username": "", - // "password": "", - // "host": "", - // "hostname": "", - // "port": "", - // "pathname": "", - // "search": "", - // "hash": "" - // }, + { + "input": "foo://", + "base": "http://example.org/foo/bar", + "href": "foo://", + "origin": "null", + "protocol": "foo:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "", + "search": "", + "hash": "" + }, { "input": "http://a:b@c:29/d", "base": "http://example.org/foo/bar", @@ -5338,34 +5338,34 @@ module.exports = "search": "", "hash": "" }, - // { - // "input": "////", - // "base": "sc://x/", - // "href": "sc:////", - // "protocol": "sc:", - // "username": "", - // "password": "", - // "host": "", - // "hostname": "", - // "port": "", - // "pathname": "//", - // "search": "", - // "hash": "" - // }, - // { - // "input": "////x/", - // "base": "sc://x/", - // "href": "sc:////x/", - // "protocol": "sc:", - // "username": "", - // "password": "", - // "host": "", - // "hostname": "", - // "port": "", - // "pathname": "//x/", - // "search": "", - // "hash": "" - // }, + { + "input": "////", + "base": "sc://x/", + "href": "sc:////", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//", + "search": "", + "hash": "" + }, + { + "input": "////x/", + "base": "sc://x/", + "href": "sc:////x/", + "protocol": "sc:", + "username": "", + "password": "", + "host": "", + "hostname": "", + "port": "", + "pathname": "//x/", + "search": "", + "hash": "" + }, { "input": "tftp://foobar.com/someconfig;mode=netascii", "base": "about:blank", diff --git a/test/parallel/test-whatwg-url-setters.js b/test/parallel/test-whatwg-url-setters.js index 66188e48158c9f..253415dad6e5a9 100644 --- a/test/parallel/test-whatwg-url-setters.js +++ b/test/parallel/test-whatwg-url-setters.js @@ -15,7 +15,7 @@ if (!common.hasIntl) { } const request = { - response: require(path.join(common.fixturesDir, 'url-setter-tests.json')) + response: require(path.join(common.fixturesDir, 'url-setter-tests')) }; /* eslint-disable */