diff --git a/Source/WTF/wtf/URL.cpp b/Source/WTF/wtf/URL.cpp index 66b50763c5810..a83584fe55cda 100644 --- a/Source/WTF/wtf/URL.cpp +++ b/Source/WTF/wtf/URL.cpp @@ -133,9 +133,13 @@ bool URL::protocolIsSecure() const unsigned URL::pathStart() const { unsigned start = m_hostEnd + m_portLength; + // URLParser::addNonSpecialDotSlash inserts "/." before a path that starts with "//" in a URL + // without a host, so that the path is not parsed back as an authority. It is not part of the + // path. "/." followed by anything else is the path's first segment (e.g. "foo:/.well-known"): + // the parser removes single-dot segments, so a real segment is never just ".". if (start == m_schemeEnd + 1U - && start + 1 < m_string.length() - && m_string[start] == '/' && m_string[start + 1] == '.') + && start + 2 < m_string.length() + && m_string[start] == '/' && m_string[start + 1] == '.' && m_string[start + 2] == '/') start += 2; return start; } diff --git a/Tools/TestWebKitAPI/Tests/WTF/URL.cpp b/Tools/TestWebKitAPI/Tests/WTF/URL.cpp index 77ac6dad15752..0f4d56917dd11 100644 --- a/Tools/TestWebKitAPI/Tests/WTF/URL.cpp +++ b/Tools/TestWebKitAPI/Tests/WTF/URL.cpp @@ -120,6 +120,82 @@ TEST_F(WTF_URL, URLProtocolHostAndPort) url = createURL("asdf:///a/b/c"_s); EXPECT_EQ(String("asdf://"_s), url.protocolHostAndPort()); + + url = createURL("asdf:/a/b/c"_s); + EXPECT_EQ(String("asdf:"_s), url.protocolHostAndPort()); + + url = createURL("asdf:/.a/b/c"_s); + EXPECT_EQ(String("asdf:"_s), url.protocolHostAndPort()); +} + +// A URL without a host whose path starts with "//" is serialized with "/." in front of the path so that it does not +// parse back as an authority. That prefix is not part of the path; a first segment that starts with "." is. +TEST_F(WTF_URL, PathStartingWithDotWithoutHost) +{ + auto url = createURL("asdf:/.a/b"_s); + EXPECT_TRUE(url.isValid()); + EXPECT_TRUE(url.hasPath()); + EXPECT_EQ(String("asdf:/.a/b"_s), url.string()); + EXPECT_EQ(String("/.a/b"_s), url.path().toString()); + EXPECT_EQ(String("b"_s), url.lastPathComponent().toString()); + + url = createURL("asdf:/.a"_s); + EXPECT_EQ(String("/.a"_s), url.path().toString()); + EXPECT_EQ(String(".a"_s), url.lastPathComponent().toString()); + + url = createURL("asdf:/..a"_s); + EXPECT_EQ(String("/..a"_s), url.path().toString()); + EXPECT_EQ(String("..a"_s), url.lastPathComponent().toString()); + + url = createURL("asdf:/.well-known/x?q#f"_s); + EXPECT_EQ(String("/.well-known/x"_s), url.path().toString()); + EXPECT_EQ(String("x"_s), url.lastPathComponent().toString()); + + url = createURL("asdf:/.//b"_s); + EXPECT_EQ(String("asdf:/.//b"_s), url.string()); + EXPECT_EQ(String("//b"_s), url.path().toString()); + EXPECT_EQ(String("b"_s), url.lastPathComponent().toString()); + + url = createURL("asdf:/.//.a"_s); + EXPECT_EQ(String("//.a"_s), url.path().toString()); + EXPECT_EQ(String(".a"_s), url.lastPathComponent().toString()); + + url = createURL("asdf://host/.a"_s); + EXPECT_EQ(String("/.a"_s), url.path().toString()); + + url = createURL("asdf:///.a"_s); + EXPECT_EQ(String("/.a"_s), url.path().toString()); + + url = createURL("asdf:/x"_s); + url.setPath("/.a/b"_s); + EXPECT_EQ(String("asdf:/.a/b"_s), url.string()); + EXPECT_EQ(String("/.a/b"_s), url.path().toString()); + + url = createURL("asdf:/.a"_s); + url.setPath("/b"_s); + EXPECT_EQ(String("asdf:/b"_s), url.string()); + EXPECT_EQ(String("/b"_s), url.path().toString()); + + url = createURL("asdf:/.a"_s); + url.setPath("//b"_s); + EXPECT_EQ(String("asdf:/.//b"_s), url.string()); + EXPECT_EQ(String("//b"_s), url.path().toString()); + + url = createURL("asdf:/.//b"_s); + url.setPath("/.c"_s); + EXPECT_EQ(String("asdf:/.c"_s), url.string()); + EXPECT_EQ(String("/.c"_s), url.path().toString()); + + url = createURL("asdf:/.a"_s); + url.setHostAndPort("host:1"_s); + EXPECT_TRUE(url.isValid()); + EXPECT_EQ(String("asdf://host:1/.a"_s), url.string()); + EXPECT_EQ(String("/.a"_s), url.path().toString()); + + url = createURL("asdf:/.//b"_s); + url.setHostAndPort("host:1"_s); + EXPECT_EQ(String("asdf://host:1//b"_s), url.string()); + EXPECT_EQ(String("//b"_s), url.path().toString()); } TEST_F(WTF_URL, URLDataURIStringSharing) diff --git a/Tools/TestWebKitAPI/Tests/WTF/URLParser.cpp b/Tools/TestWebKitAPI/Tests/WTF/URLParser.cpp index 3d51e6899d679..cc5da27d114c7 100644 --- a/Tools/TestWebKitAPI/Tests/WTF/URLParser.cpp +++ b/Tools/TestWebKitAPI/Tests/WTF/URLParser.cpp @@ -235,6 +235,23 @@ TEST_F(WTF_URLParser, Idempotence) checkURL("v:/.//.."_s, { "v"_s, ""_s, ""_s, ""_s, 0, "/"_s, ""_s, ""_s, "v:/"_s }); checkURL("w:/.//..//"_s, { "w"_s, ""_s, ""_s, ""_s, 0, "//"_s, ""_s, ""_s, "w:/.//"_s }); checkURL("x:/.//../a"_s, { "x"_s, ""_s, ""_s, ""_s, 0, "/a"_s, ""_s, ""_s, "x:/a"_s }); + // "/." is only a serialization artifact in front of a path starting with "//". A first path segment that + // merely starts with "." is part of the path. + checkURL("y:/.a"_s, { "y"_s, ""_s, ""_s, ""_s, 0, "/.a"_s, ""_s, ""_s, "y:/.a"_s }); + checkURL("z:/.a/b"_s, { "z"_s, ""_s, ""_s, ""_s, 0, "/.a/b"_s, ""_s, ""_s, "z:/.a/b"_s }); + checkURL("aa:/..a"_s, { "aa"_s, ""_s, ""_s, ""_s, 0, "/..a"_s, ""_s, ""_s, "aa:/..a"_s }); + checkURL("ab:/.a?q#f"_s, { "ab"_s, ""_s, ""_s, ""_s, 0, "/.a"_s, "q"_s, "f"_s, "ab:/.a?q#f"_s }); + checkURL("ac:/.a//b"_s, { "ac"_s, ""_s, ""_s, ""_s, 0, "/.a//b"_s, ""_s, ""_s, "ac:/.a//b"_s }); + checkURL("ad:/.%2Fa"_s, { "ad"_s, ""_s, ""_s, ""_s, 0, "/.%2Fa"_s, ""_s, ""_s, "ad:/.%2Fa"_s }); + checkURL("ae:/.//.a"_s, { "ae"_s, ""_s, ""_s, ""_s, 0, "//.a"_s, ""_s, ""_s, "ae:/.//.a"_s }); + checkURL("af:/./.a"_s, { "af"_s, ""_s, ""_s, ""_s, 0, "/.a"_s, ""_s, ""_s, "af:/.a"_s }); + checkURL("ag:/.well-known/x"_s, { "ag"_s, ""_s, ""_s, ""_s, 0, "/.well-known/x"_s, ""_s, ""_s, "ag:/.well-known/x"_s }); + checkURL("ah://host/.a"_s, { "ah"_s, ""_s, ""_s, "host"_s, 0, "/.a"_s, ""_s, ""_s, "ah://host/.a"_s }); + checkURL("ai:///.a"_s, { "ai"_s, ""_s, ""_s, ""_s, 0, "/.a"_s, ""_s, ""_s, "ai:///.a"_s }); + checkRelativeURL("x"_s, "aj:/.a/b"_s, { "aj"_s, ""_s, ""_s, ""_s, 0, "/.a/x"_s, ""_s, ""_s, "aj:/.a/x"_s }); + checkRelativeURL("../y"_s, "ak:/.a/b/c"_s, { "ak"_s, ""_s, ""_s, ""_s, 0, "/.a/y"_s, ""_s, ""_s, "ak:/.a/y"_s }); + checkRelativeURL("/.a"_s, "al:/.//b"_s, { "al"_s, ""_s, ""_s, ""_s, 0, "/.a"_s, ""_s, ""_s, "al:/.a"_s }); + checkRelativeURL("//b"_s, "am:/.a"_s, { "am"_s, ""_s, ""_s, "b"_s, 0, ""_s, ""_s, ""_s, "am://b"_s }); checkURL("http://host/./"_s, { "http"_s, ""_s, ""_s, "host"_s, 0, "/"_s, ""_s, ""_s, "http://host/"_s }); checkURL("http://host/../"_s, { "http"_s, ""_s, ""_s, "host"_s, 0, "/"_s, ""_s, ""_s, "http://host/"_s }); checkURL("http://host/.../"_s, { "http"_s, ""_s, ""_s, "host"_s, 0, "/.../"_s, ""_s, ""_s, "http://host/.../"_s });