Skip to content

Commit 8449fc1

Browse files
committed
Fix encodeHostName() for inputs containing %
https://bugs.webkit.org/show_bug.cgi?id=310982 Reviewed by Alex Christensen. There were two issues with encodeHostName(): 1. mapHostNames() would return early for ASCII-only input, even when that input contains %. 2. mapHostName() checked the wrong variable for %. 3. Fixing (1) meant URLs containing % could now reach the host-range detection code, which doesn't understand bracketed IPv6 syntax: for http://[::1]/path%20x it would pick '[' as the host, which then failed UIDNA. Skip IDN processing for host ranges starting with '[' in collectRangesThatNeedMapping(). We also fix various trivial issues in the URL code: 1. protocolIsFile() is redundant with hasSpecialScheme(). 2. Some type mismatches. 3. A redundant call to codePointAt(i). Test: Tools/TestWebKitAPI/Tests/WTF/cocoa/URLExtras.mm Canonical link: https://commits.webkit.org/314167@main
1 parent 3bdc8f1 commit 8449fc1

5 files changed

Lines changed: 38 additions & 10 deletions

File tree

Source/WTF/wtf/URL.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@ void URL::setPath(StringView path)
766766

767767
parseAllowingC0AtEnd(makeString(
768768
StringView(m_string).left(pathStart()),
769-
path.startsWith('/') || (path.startsWith('\\') && (hasSpecialScheme() || protocolIsFile())) || (!hasSpecialScheme() && path.isEmpty() && m_schemeEnd + 1U < pathStart()) ? ""_s : "/"_s,
769+
path.startsWith('/') || (path.startsWith('\\') && hasSpecialScheme()) || (!hasSpecialScheme() && path.isEmpty() && m_schemeEnd + 1U < pathStart()) ? ""_s : "/"_s,
770770
!hasSpecialScheme() && host().isEmpty() && path.startsWith("//"_s) && path.length() > 2 ? "/."_s : ""_s,
771771
escapePathWithoutCopying(path),
772772
StringView(m_string).substring(m_pathEnd)

Source/WTF/wtf/URL.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ class URL {
138138
bool isValid() const;
139139

140140
// Since we overload operator NSURL * we have this to prevent accidentally using that operator
141-
// when placing a URL in an if statment.
141+
// when placing a URL in an if statement.
142142
operator bool() const = delete;
143143

144144
const String& string() const LIFETIME_BOUND { return m_string; }

Source/WTF/wtf/URLHelpers.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ std::optional<String> mapHostName(const String& hostName, URLDecodeFunction deco
643643
return String();
644644

645645
String string;
646-
if (decodeFunction && string.contains('%'))
646+
if (decodeFunction && hostName.contains('%'))
647647
string = (*decodeFunction)(hostName);
648648
else
649649
string = hostName;
@@ -680,6 +680,10 @@ static void collectRangesThatNeedMapping(const String& string, unsigned location
680680
// Generally, we want to optimize for the case where there is one host name that does not need mapping.
681681
// Therefore, we use null to indicate no mapping here and an empty array to indicate error.
682682

683+
// IPv6 addresses are bracketed and don't need IDN processing.
684+
if (length && string[location] == '[')
685+
return;
686+
683687
String substring = string.substringSharingImpl(location, length);
684688
std::optional<String> host = mapHostName(substring, decodeFunction);
685689

@@ -731,7 +735,7 @@ static void applyHostNameFunctionToMailToURLString(const String& string, URLDeco
731735
current = hostNameEnd;
732736
done = false;
733737
}
734-
738+
735739
// Process host name range.
736740
collectRangesThatNeedMapping(string, hostNameStart, hostNameEnd - hostNameStart, array, decodeFunction);
737741

@@ -812,7 +816,7 @@ String mapHostNames(const String& string, URLDecodeFunction decodeFunction)
812816
{
813817
// Generally, we want to optimize for the case where there is one host name that does not need mapping.
814818

815-
if (decodeFunction && string.containsOnlyASCII())
819+
if (decodeFunction && string.containsOnlyASCII() && !string.contains('%'))
816820
return string;
817821

818822
// Make a list of ranges that actually need mapping.
@@ -842,7 +846,7 @@ static String escapeUnsafeCharacters(const String& sourceBuffer)
842846
unsigned i;
843847
for (i = 0; i < length; ) {
844848
char32_t c = sourceBuffer.codePointAt(i);
845-
if (isLookalikeCharacter(previousCodePoint, sourceBuffer.codePointAt(i)))
849+
if (isLookalikeCharacter(previousCodePoint, c))
846850
break;
847851
previousCodePoint = c;
848852
i += U16_LENGTH(c);
@@ -888,7 +892,7 @@ static String escapeUnsafeCharacters(const String& sourceBuffer)
888892
String userVisibleURL(const CString& url)
889893
{
890894
auto before = url.span();
891-
int length = url.length();
895+
size_t length = url.length();
892896

893897
if (!length)
894898
return { };
@@ -904,7 +908,7 @@ String userVisibleURL(const CString& url)
904908
size_t afterIndex = 0;
905909
{
906910
auto p = before;
907-
for (int i = 0; i < length; i++) {
911+
for (size_t i = 0; i < length; i++) {
908912
unsigned char c = p[i];
909913
// unescape escape sequences that indicate bytes greater than 0x7f
910914
if (c == '%' && i + 2 < length && isASCIIHexDigit(p[i + 1]) && isASCIIHexDigit(p[i + 2])) {
@@ -938,7 +942,7 @@ String userVisibleURL(const CString& url)
938942
// Shift current string to the end of the buffer
939943
// then we will copy back bytes to the start of the buffer
940944
// as we convert.
941-
int afterlength = afterIndex;
945+
size_t afterlength = afterIndex;
942946
auto p = after.mutableSpan().subspan(bufferLength.value() - afterlength - 1);
943947
memmoveSpan(p, after.span().first(afterlength + 1)); // copies trailing '\0'
944948
afterIndex = 0;

Source/WTF/wtf/URLParser.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ ALWAYS_INLINE bool URLParser::isForbiddenDomainCodePoint(CharacterType character
350350
return character <= 0x7F && characterClassTable[character] & ForbiddenDomain;
351351
}
352352

353-
ALWAYS_INLINE static bool shouldPercentEncodeQueryByte(uint8_t byte, const bool& urlIsSpecial)
353+
ALWAYS_INLINE static bool shouldPercentEncodeQueryByte(uint8_t byte, bool urlIsSpecial)
354354
{
355355
if (characterClassTable[byte] & QueryEncode)
356356
return true;

Tools/TestWebKitAPI/Tests/WTF/cocoa/URLExtras.mm

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,30 @@
294294
EXPECT_WK_STREQ("site.com\xE3\x80\x80othersite.org", [@"site.com\xE3\x80\x80othersite.org" _wk_decodeHostName]);
295295
}
296296

297+
TEST(URLExtras, URLExtras_PercentEncodedIDN)
298+
{
299+
// Percent-encoded IDN hostnames should be decoded before UIDNA processing.
300+
// m%C3%BCnchen.de should decode to münchen.de, then encode to xn--mnchen-3ya.de.
301+
EXPECT_STREQ("xn--mnchen-3ya.de", [WTF::encodeHostName(@"m%C3%BCnchen.de") UTF8String]);
302+
EXPECT_STREQ("http://xn--mnchen-3ya.de/", originalDataAsString(WTF::URLWithUserTypedString(@"http://m%C3%BCnchen.de/", nil)));
303+
}
304+
305+
TEST(URLExtras, URLExtras_IPv6)
306+
{
307+
// IPv6 hosts pass through unchanged.
308+
EXPECT_STREQ("http://[::1]/", originalDataAsString(WTF::URLWithUserTypedString(@"http://[::1]/", nil)));
309+
EXPECT_STREQ("http://[::1]:8080/", originalDataAsString(WTF::URLWithUserTypedString(@"http://[::1]:8080/", nil)));
310+
311+
// IPv6 hosts in URLs containing '%' must skip IDN processing rather than be
312+
// mistaken for a hostname starting with '['.
313+
EXPECT_STREQ("http://[::1]/path%20x", originalDataAsString(WTF::URLWithUserTypedString(@"http://[::1]/path%20x", nil)));
314+
EXPECT_STREQ("http://[::1]:8080/?q=%20", originalDataAsString(WTF::URLWithUserTypedString(@"http://[::1]:8080/?q=%20", nil)));
315+
EXPECT_STREQ("http://user@[::1]/path%20x", originalDataAsString(WTF::URLWithUserTypedString(@"http://user@[::1]/path%20x", nil)));
316+
317+
// Same for mailto: URLs whose address is an IPv6 literal.
318+
EXPECT_STREQ("mailto:user@[::1]?subject=hi%20there", originalDataAsString(WTF::URLWithUserTypedString(@"mailto:user@[::1]?subject=hi%20there", nil)));
319+
}
320+
297321
TEST(URLExtras, URLExtras_File)
298322
{
299323
EXPECT_STREQ("file:///%E2%98%83", [[WTF::URLWithUserTypedString(@"file:///☃", nil) absoluteString] UTF8String]);

0 commit comments

Comments
 (0)