Perform IDNA encoding on parameters for setHostAndPort and setHost
authorbfulgham@apple.com <bfulgham@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 9 Jun 2016 05:07:24 +0000 (05:07 +0000)
committerbfulgham@apple.com <bfulgham@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 9 Jun 2016 05:07:24 +0000 (05:07 +0000)
https://bugs.webkit.org/show_bug.cgi?id=158371
<rdar://problem/16869342>

Patch by John Wilander <wilander@apple.com> on 2016-06-08
Reviewed by Brent Fulgham.

Source/WebCore:

Tests: fast/dom/set-document-location-host-to-unaccepted-values.html
       fast/dom/set-document-location-hostname-to-unaccepted-values.html
       http/tests/dom/set-document-location-host-to-accepted-values.html
       http/tests/dom/set-document-location-hostname-to-accepted-values.html

* platform/URL.cpp:
(WebCore::containsOnlyASCII):
    Moved up to enable usage in URL::setHost and URL::setHostAndPort.
(WebCore::appendEncodedHostname):
    Moved up to enable usage in URL::setHost and URL::setHostAndPort.
(WebCore::URL::setHost):
    Now disallows the colon character, does IDNA encoding, and uses StringBuilder.
(WebCore::URL::setHostAndPort):
    Now disallows multiple colons, disallows non-numeric ports, disallows the empty
    string, does IDNA encoding, and uses StringBuilder.

LayoutTests:

* fast/dom/resources/set-document-location-iframe.html: Added.
* fast/dom/set-document-location-host-to-unaccepted-values-expected.txt: Added.
* fast/dom/set-document-location-host-to-unaccepted-values.html: Added.
* fast/dom/set-document-location-hostname-to-unaccepted-values-expected.txt: Added.
* fast/dom/set-document-location-hostname-to-unaccepted-values.html: Added.
* http/tests/dom/resources/set-document-location-iframe.html: Added.
* http/tests/dom/set-document-location-host-to-accepted-values-expected.txt: Added.
* http/tests/dom/set-document-location-host-to-accepted-values.html: Added.
* http/tests/dom/set-document-location-hostname-to-accepted-values-expected.txt: Added.
* http/tests/dom/set-document-location-hostname-to-accepted-values.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@201850 268f45cc-cd09-0410-ab3c-d52691b4dbfc

13 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/dom/resources/set-document-location-iframe.html [new file with mode: 0644]
LayoutTests/fast/dom/set-document-location-host-to-unaccepted-values-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/set-document-location-host-to-unaccepted-values.html [new file with mode: 0644]
LayoutTests/fast/dom/set-document-location-hostname-to-unaccepted-values-expected.txt [new file with mode: 0644]
LayoutTests/fast/dom/set-document-location-hostname-to-unaccepted-values.html [new file with mode: 0644]
LayoutTests/http/tests/dom/resources/set-document-location-iframe.html [new file with mode: 0644]
LayoutTests/http/tests/dom/set-document-location-host-to-accepted-values-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/dom/set-document-location-host-to-accepted-values.html [new file with mode: 0644]
LayoutTests/http/tests/dom/set-document-location-hostname-to-accepted-values-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/dom/set-document-location-hostname-to-accepted-values.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/platform/URL.cpp

index 7db9a9b..96eaa33 100644 (file)
@@ -1,3 +1,22 @@
+2016-06-08  John Wilander  <wilander@apple.com>
+
+        Perform IDNA encoding on parameters for setHostAndPort and setHost
+        https://bugs.webkit.org/show_bug.cgi?id=158371
+        <rdar://problem/16869342>
+
+        Reviewed by Brent Fulgham.
+
+        * fast/dom/resources/set-document-location-iframe.html: Added.
+        * fast/dom/set-document-location-host-to-unaccepted-values-expected.txt: Added.
+        * fast/dom/set-document-location-host-to-unaccepted-values.html: Added.
+        * fast/dom/set-document-location-hostname-to-unaccepted-values-expected.txt: Added.
+        * fast/dom/set-document-location-hostname-to-unaccepted-values.html: Added.
+        * http/tests/dom/resources/set-document-location-iframe.html: Added.
+        * http/tests/dom/set-document-location-host-to-accepted-values-expected.txt: Added.
+        * http/tests/dom/set-document-location-host-to-accepted-values.html: Added.
+        * http/tests/dom/set-document-location-hostname-to-accepted-values-expected.txt: Added.
+        * http/tests/dom/set-document-location-hostname-to-accepted-values.html: Added.
+
 2016-06-08  Ryan Haddad  <ryanhaddad@apple.com>
 
         Rebaseline js/dom/global-constructors-attributes.html for Mac after r201810
diff --git a/LayoutTests/fast/dom/resources/set-document-location-iframe.html b/LayoutTests/fast/dom/resources/set-document-location-iframe.html
new file mode 100644 (file)
index 0000000..01211a6
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Dummy document to load before document.location test cases</title>
+</head>
+<body>
+<p id="status">
+    Before
+</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/fast/dom/set-document-location-host-to-unaccepted-values-expected.txt b/LayoutTests/fast/dom/set-document-location-host-to-unaccepted-values-expected.txt
new file mode 100644 (file)
index 0000000..ddb361d
--- /dev/null
@@ -0,0 +1,11 @@
+PASS: 'icl' + '\uf877' + 'oud.com' was not accepted.
+PASS: icloud.com:-1 was not accepted.
+PASS: icloud.com:11c was not accepted.
+PASS: icloud.com:11 was not accepted.
+PASS: icloud.com:80: was not accepted.
+PASS: icloud.com:80:80 was not accepted.
+PASS: icloud.com: was not accepted.
+PASS: :80 was not accepted.
+PASS: : was not accepted.
+PASS: empty string was not accepted.
+
diff --git a/LayoutTests/fast/dom/set-document-location-host-to-unaccepted-values.html b/LayoutTests/fast/dom/set-document-location-host-to-unaccepted-values.html
new file mode 100644 (file)
index 0000000..5cb2ef0
--- /dev/null
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Test for setting document.location.host to unaccepted values</title>
+</head>
+<body>
+<pre id="pre"></pre>
+<script>
+    // This function will be redefined as the test cases get executed
+    var testCaseOnload = function() {
+        runOneTestThatShouldNotBeAccepted(0);
+    };
+
+    var iframeDocument;
+    function iframeOnload() {
+        iframeDocument = document.getElementById("targetFrame").contentDocument;
+        testCaseOnload();
+    }
+
+    function log(s) {
+        document.getElementById("pre").appendChild(document.createTextNode(s));
+    }
+
+    function runOneTestThatShouldNotBeAccepted(testIndex) {
+        var testCasesThatShouldNotBeAccepted = [
+            { hostString : 'icl' + '\uf877' + 'oud.com', description : "'icl' + '\\uf877' + 'oud.com'" }
+            ,{ hostString : 'icloud.com:-1', description : false }
+            ,{ hostString : 'icloud.com:11c', description : false }
+            ,{ hostString : 'icloud.com:11', description : false }
+            ,{ hostString : 'icloud.com:80:', description : false }
+            ,{ hostString : 'icloud.com:80:80', description : false }
+            ,{ hostString : 'icloud.com:', description : false }
+            ,{ hostString : ':80', description : false }
+            ,{ hostString : ':', description : false }
+            ,{ hostString : '', description : "empty string" }
+        ];
+
+        if (testIndex >= testCasesThatShouldNotBeAccepted.length) {
+            if (window.testRunner)
+                testRunner.notifyDone();
+        } else {
+            // Prepare for next test case by setting testCaseOnload to load the test case
+            // and then load the simple HTML file that says "Before"
+            testCaseOnload = function () {
+
+                // Prepare test case execution by first setting testCaseOnload to check
+                // the test outcome and then load set the iframe's document.location.host
+                testCaseOnload = function(currentTestIndex) {
+                    return function () {
+                        if (iframeDocument.location.host == document.location.host) {
+                            var currentCase = testCasesThatShouldNotBeAccepted[currentTestIndex],
+                                    description = (currentCase.description ? currentCase.description : currentCase.hostString);
+                            log("PASS: " + description + " was not accepted.\n");
+                            runOneTestThatShouldNotBeAccepted(currentTestIndex + 1);
+                        } else {
+                            // Fails may very well become 404s so this log message might not be triggered
+                            log("FAIL: " + description + " was accepted, or at least the iframeDocument.location.host had changed to " + iframeDocument.location.host + "\n");
+                        }
+                    };
+                }(testIndex);
+
+                iframeDocument.location.host = testCasesThatShouldNotBeAccepted[testIndex].hostString;
+            };
+
+            iframeDocument.location = "./resources/set-document-location-iframe.html";
+        }
+    }
+
+    if (window.testRunner) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+    }
+</script>
+<iframe id="targetFrame" src="resources/set-document-location-iframe.html" onload="iframeOnload()"></iframe>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/fast/dom/set-document-location-hostname-to-unaccepted-values-expected.txt b/LayoutTests/fast/dom/set-document-location-hostname-to-unaccepted-values-expected.txt
new file mode 100644 (file)
index 0000000..8c055ac
--- /dev/null
@@ -0,0 +1,10 @@
+PASS: 'icl' + '\uf877' + 'oud.com' was not accepted.
+PASS: icloud.com:-1 was not accepted.
+PASS: icloud.com:11c was not accepted.
+PASS: icloud.com:11 was not accepted.
+PASS: icloud.com:80: was not accepted.
+PASS: icloud.com:80:80 was not accepted.
+PASS: icloud.com: was not accepted.
+PASS: :80 was not accepted.
+PASS: : was not accepted.
+
diff --git a/LayoutTests/fast/dom/set-document-location-hostname-to-unaccepted-values.html b/LayoutTests/fast/dom/set-document-location-hostname-to-unaccepted-values.html
new file mode 100644 (file)
index 0000000..5740a96
--- /dev/null
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Test for setting document.location.hostname to unaccepted values</title>
+</head>
+<body>
+<pre id="pre"></pre>
+<script>
+    // This function will be redefined as the test cases get executed
+    var testCaseOnload = function() {
+        runOneTestThatShouldNotBeAccepted(0);
+    };
+
+    var iframeDocument;
+    function iframeOnload() {
+        iframeDocument = document.getElementById("targetFrame").contentDocument;
+        testCaseOnload();
+    }
+
+    function log(s) {
+        document.getElementById("pre").appendChild(document.createTextNode(s));
+    }
+
+    function runOneTestThatShouldNotBeAccepted(testIndex) {
+        var testCasesThatShouldNotBeAccepted = [
+            { hostString : 'icl' + '\uf877' + 'oud.com', description : "'icl' + '\\uf877' + 'oud.com'" }
+            ,{ hostString : 'icloud.com:-1', description : false }
+            ,{ hostString : 'icloud.com:11c', description : false }
+            ,{ hostString : 'icloud.com:11', description : false }
+            ,{ hostString : 'icloud.com:80:', description : false }
+            ,{ hostString : 'icloud.com:80:80', description : false }
+            ,{ hostString : 'icloud.com:', description : false }
+            ,{ hostString : ':80', description : false }
+            ,{ hostString : ':', description : false }
+        ];
+
+        if (testIndex >= testCasesThatShouldNotBeAccepted.length) {
+            if (window.testRunner)
+                testRunner.notifyDone();
+        } else {
+            // Prepare for next test case by setting testCaseOnload to load the test case
+            // and then load the simple HTML file that says "Before"
+            testCaseOnload = function () {
+
+                // Prepare test case execution by first setting testCaseOnload to check
+                // the test outcome and then load set the iframe's document.location.hostname
+                testCaseOnload = function(currentTestIndex) {
+                    return function () {
+                        if (iframeDocument.location.host == document.location.host) {
+                            var currentCase = testCasesThatShouldNotBeAccepted[currentTestIndex],
+                                    description = (currentCase.description ? currentCase.description : currentCase.hostString);
+                            log("PASS: " + description + " was not accepted.\n");
+                            runOneTestThatShouldNotBeAccepted(currentTestIndex + 1);
+                        } else {
+                            // Fails may very well become 404s so this log message might not be triggered
+                            log("FAIL: " + description + " was accepted, or at least the iframeDocument.location.host had changed to " + iframeDocument.location.host + "\n");
+                        }
+                    };
+                }(testIndex);
+
+                iframeDocument.location.hostname = testCasesThatShouldNotBeAccepted[testIndex].hostString;
+            };
+
+            iframeDocument.location = "./resources/set-document-location-iframe.html";
+        }
+    }
+
+    if (window.testRunner) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+    }
+</script>
+<iframe id="targetFrame" src="resources/set-document-location-iframe.html" onload="iframeOnload()"></iframe>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/http/tests/dom/resources/set-document-location-iframe.html b/LayoutTests/http/tests/dom/resources/set-document-location-iframe.html
new file mode 100644 (file)
index 0000000..01211a6
--- /dev/null
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Dummy document to load before document.location test cases</title>
+</head>
+<body>
+<p id="status">
+    Before
+</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/http/tests/dom/set-document-location-host-to-accepted-values-expected.txt b/LayoutTests/http/tests/dom/set-document-location-host-to-accepted-values-expected.txt
new file mode 100644 (file)
index 0000000..dfafdae
--- /dev/null
@@ -0,0 +1,4 @@
+CONSOLE MESSAGE: line 66: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
+PASS: localhost:8000 was accepted (at least the iframe contentDocument was null so it had changed origin).
+PASS: 127.0.0.1:8000 was accepted.
+
diff --git a/LayoutTests/http/tests/dom/set-document-location-host-to-accepted-values.html b/LayoutTests/http/tests/dom/set-document-location-host-to-accepted-values.html
new file mode 100644 (file)
index 0000000..8e3d91d
--- /dev/null
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Test for setting document.location.host to accepted values</title>
+</head>
+<body>
+<pre id="pre"></pre>
+<script>
+    // This function will be redefined as the test cases get executed
+    var testCaseOnload = function() {
+        runOneTestThatShouldBeAccepted(0);
+    };
+
+    var iframeDocument;
+    function iframeOnload() {
+        iframeDocument = document.getElementById("targetFrame").contentDocument;
+        testCaseOnload();
+    }
+
+    function log(s) {
+        document.getElementById("pre").appendChild(document.createTextNode(s));
+    }
+
+    function runOneTestThatShouldBeAccepted(testIndex) {
+        var testCasesThatShouldBeAccepted = [
+            { hostString : 'localhost:8000', description : false }
+            ,{ hostString : '127.0.0.1:8000', description : false }
+        ];
+
+        if (testIndex >= testCasesThatShouldBeAccepted.length) {
+            if (window.testRunner)
+                testRunner.notifyDone();
+        } else {
+            // Prepare for next test case by setting testCaseOnload to load the test case
+            // and then load the simple HTML file that says "Before"
+            testCaseOnload = function () {
+
+                // Prepare test case execution by first setting testCaseOnload to check
+                // the test outcome and then load set the iframe's document.location.host
+                testCaseOnload = function(currentTestIndex) {
+                    return function () {
+                        var currentCase = testCasesThatShouldBeAccepted[currentTestIndex],
+                                description = (currentCase.description ? currentCase.description : currentCase.hostString);
+                        if (iframeDocument === null || iframeDocument.location.host == currentCase.hostString) {
+                            log("PASS: " + description + " was accepted" + (iframeDocument === null ? " (at least the iframe contentDocument was null so it had changed origin)" : "") + ".\n");
+                        } else {
+                            log("FAIL: " + description + " was not accepted. The iframe contentDocument was not null and its location.host was not " + currentCase.hostString + ".\n");
+                        }
+                        runOneTestThatShouldBeAccepted(currentTestIndex + 1);
+                    };
+                }(testIndex);
+
+                iframeDocument.location.host = testCasesThatShouldBeAccepted[testIndex].hostString;
+            };
+
+            document.getElementById("targetFrame").setAttribute("src", "http://127.0.0.1:8000/dom/resources/set-document-location-iframe.html");
+        }
+    }
+
+    if (window.testRunner) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+    }
+</script>
+<iframe id="targetFrame" src="resources/set-document-location-iframe.html" onload="iframeOnload()"></iframe>
+</body>
+</html>
\ No newline at end of file
diff --git a/LayoutTests/http/tests/dom/set-document-location-hostname-to-accepted-values-expected.txt b/LayoutTests/http/tests/dom/set-document-location-hostname-to-accepted-values-expected.txt
new file mode 100644 (file)
index 0000000..dcb3b9b
--- /dev/null
@@ -0,0 +1,4 @@
+CONSOLE MESSAGE: line 67: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a frame with origin "http://localhost:8000". Protocols, domains, and ports must match.
+PASS: localhost was accepted (at least the iframe contentDocument was null so it had changed origin).
+PASS: 127.0.0.1 was accepted.
+
diff --git a/LayoutTests/http/tests/dom/set-document-location-hostname-to-accepted-values.html b/LayoutTests/http/tests/dom/set-document-location-hostname-to-accepted-values.html
new file mode 100644 (file)
index 0000000..f17a713
--- /dev/null
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Test for setting document.location.hostname to accepted values</title>
+</head>
+<body>
+<pre id="pre"></pre>
+<script>
+    // This function will be redefined as the test cases get executed
+    var testCaseOnload = function() {
+        runOneTestThatShouldBeAccepted(0);
+    };
+
+    var iframeDocument;
+    function iframeOnload() {
+        iframeDocument = document.getElementById("targetFrame").contentDocument;
+        testCaseOnload();
+    }
+
+    function log(s) {
+        document.getElementById("pre").appendChild(document.createTextNode(s));
+    }
+
+    function runOneTestThatShouldBeAccepted(testIndex) {
+        var testCasesThatShouldBeAccepted = [
+            { hostString : 'localhost', description : false }
+            ,{ hostString : '127.0.0.1', description : false }
+        ];
+
+        if (testIndex >= testCasesThatShouldBeAccepted.length) {
+            if (window.testRunner)
+                testRunner.notifyDone();
+        } else {
+            // Prepare for next test case by setting testCaseOnload to load the test case
+            // and then load the simple HTML file that says "Before"
+            testCaseOnload = function () {
+
+                // Prepare test case execution by first setting testCaseOnload to check
+                // the test outcome and then load set the iframe's document.location.hostname
+                testCaseOnload = function(currentTestIndex) {
+                    return function () {
+                        var currentCase = testCasesThatShouldBeAccepted[currentTestIndex],
+                                description = (currentCase.description ? currentCase.description : currentCase.hostString);
+                        if (iframeDocument === null || iframeDocument.location.hostname == currentCase.hostString) {
+                            log("PASS: " + description + " was accepted" + (iframeDocument === null ? " (at least the iframe contentDocument was null so it had changed origin)" : "") + ".\n");
+                        } else {
+                            log("FAIL: " + description + " was not accepted. The iframe contentDocument was not null and its location.hostname was not " + currentCase.hostString + ".\n");
+                        }
+                        runOneTestThatShouldBeAccepted(currentTestIndex + 1);
+                    };
+                }(testIndex);
+
+                iframeDocument.location.hostname = testCasesThatShouldBeAccepted[testIndex].hostString;
+            };
+
+            document.getElementById("targetFrame").setAttribute("src", "http://127.0.0.1:8000/dom/resources/set-document-location-iframe.html");
+        }
+    }
+
+    if (window.testRunner) {
+        testRunner.dumpAsText();
+        testRunner.waitUntilDone();
+    }
+
+</script>
+<iframe id="targetFrame" src="resources/set-document-location-iframe.html" onload="iframeOnload()"></iframe>
+</body>
+</html>
\ No newline at end of file
index d002d0b..64aa262 100644 (file)
@@ -1,3 +1,27 @@
+2016-06-08  John Wilander  <wilander@apple.com>
+
+        Perform IDNA encoding on parameters for setHostAndPort and setHost
+        https://bugs.webkit.org/show_bug.cgi?id=158371
+        <rdar://problem/16869342>
+
+        Reviewed by Brent Fulgham.
+
+        Tests: fast/dom/set-document-location-host-to-unaccepted-values.html
+               fast/dom/set-document-location-hostname-to-unaccepted-values.html
+               http/tests/dom/set-document-location-host-to-accepted-values.html
+               http/tests/dom/set-document-location-hostname-to-accepted-values.html
+
+        * platform/URL.cpp:
+        (WebCore::containsOnlyASCII):
+            Moved up to enable usage in URL::setHost and URL::setHostAndPort.
+        (WebCore::appendEncodedHostname):
+            Moved up to enable usage in URL::setHost and URL::setHostAndPort.
+        (WebCore::URL::setHost):
+            Now disallows the colon character, does IDNA encoding, and uses StringBuilder.
+        (WebCore::URL::setHostAndPort):
+            Now disallows multiple colons, disallows non-numeric ports, disallows the empty
+            string, does IDNA encoding, and uses StringBuilder.
+
 2016-06-08  Alex Christensen  <achristensen@webkit.org>
 
         Fix WinCairo build.
index 96e8cb8..f3725bc 100644 (file)
@@ -836,17 +836,70 @@ bool URL::setProtocol(const String& s)
     return true;
 }
 
+static bool containsOnlyASCII(StringView string)
+{
+    if (string.is8Bit())
+        return charactersAreAllASCII(string.characters8(), string.length());
+    return charactersAreAllASCII(string.characters16(), string.length());
+}
+    
+// Appends the punycoded hostname identified by the given string and length to
+// the output buffer. The result will not be null terminated.
+// Return value of false means error in encoding.
+static bool appendEncodedHostname(UCharBuffer& buffer, StringView string)
+{
+    // Needs to be big enough to hold an IDN-encoded name.
+    // For host names bigger than this, we won't do IDN encoding, which is almost certainly OK.
+    const unsigned hostnameBufferLength = 2048;
+    
+    if (string.length() > hostnameBufferLength || containsOnlyASCII(string)) {
+        append(buffer, string);
+        return true;
+    }
+    
+    UChar hostnameBuffer[hostnameBufferLength];
+    UErrorCode error = U_ZERO_ERROR;
+    
+#if COMPILER(GCC_OR_CLANG)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+    int32_t numCharactersConverted = uidna_IDNToASCII(string.upconvertedCharacters(), string.length(), hostnameBuffer,
+        hostnameBufferLength, UIDNA_ALLOW_UNASSIGNED, 0, &error);
+#if COMPILER(GCC_OR_CLANG)
+#pragma GCC diagnostic pop
+#endif
+    
+    if (error == U_ZERO_ERROR) {
+        buffer.append(hostnameBuffer, numCharactersConverted);
+        return true;
+    }
+    return false;
+}
+    
 void URL::setHost(const String& s)
 {
     if (!m_isValid)
         return;
 
-    // FIXME: Non-ASCII characters must be encoded and escaped to match parse() expectations,
-    // and to avoid changing more than just the host.
+    auto colonIndex = s.find(':');
+    if (colonIndex != notFound)
+        return;
 
+    UCharBuffer encodedHostName;
+    if (!appendEncodedHostname(encodedHostName, s))
+        return;
+    
     bool slashSlashNeeded = m_userStart == m_schemeEnd + 1;
-
-    parse(m_string.left(hostStart()) + (slashSlashNeeded ? "//" : "") + s + m_string.substring(m_hostEnd));
+    
+    StringBuilder builder;
+    builder.append(m_string.left(hostStart()));
+    if (slashSlashNeeded)
+        builder.append("//");
+    builder.append(StringView(encodedHostName.data(), encodedHostName.size()));
+    builder.append(m_string.substring(m_hostEnd));
+    
+    parse(builder.toString());
 }
 
 void URL::removePort()
@@ -872,12 +925,40 @@ void URL::setHostAndPort(const String& hostAndPort)
     if (!m_isValid)
         return;
 
-    // FIXME: Non-ASCII characters must be encoded and escaped to match parse() expectations,
-    // and to avoid changing more than just host and port.
+    StringView hostName(hostAndPort);
+    StringView port;
+    
+    auto colonIndex = hostName.find(':');
+    if (colonIndex != notFound) {
+        port = hostName.substring(colonIndex + 1);
+        bool ok;
+        int portInt = port.toIntStrict(ok);
+        if (!ok || portInt < 0)
+            return;
+        hostName = hostName.substring(0, colonIndex);
+    }
+
+    if (hostName.isEmpty())
+        return;
+
+    UCharBuffer encodedHostName;
+    if (!appendEncodedHostname(encodedHostName, hostName))
+        return;
 
     bool slashSlashNeeded = m_userStart == m_schemeEnd + 1;
 
-    parse(m_string.left(hostStart()) + (slashSlashNeeded ? "//" : "") + hostAndPort + m_string.substring(m_portEnd));
+    StringBuilder builder;
+    builder.append(m_string.left(hostStart()));
+    if (slashSlashNeeded)
+        builder.append("//");
+    builder.append(StringView(encodedHostName.data(), encodedHostName.size()));
+    if (!port.isEmpty()) {
+        builder.append(":");
+        builder.append(port);
+    }
+    builder.append(m_string.substring(m_portEnd));
+
+    parse(builder.toString());
 }
 
 void URL::setUser(const String& user)
@@ -1664,13 +1745,6 @@ String encodeWithURLEscapeSequences(const String& notEncodedString)
     return String(buffer.data(), p - buffer.data());
 }
 
-static bool containsOnlyASCII(StringView string)
-{
-    if (string.is8Bit())
-        return charactersAreAllASCII(string.characters8(), string.length());
-    return charactersAreAllASCII(string.characters16(), string.length());
-}
-
 static bool protocolIs(StringView stringURL, const char* protocol)
 {
     assertProtocolIsGood(protocol);
@@ -1684,40 +1758,6 @@ static bool protocolIs(StringView stringURL, const char* protocol)
     return false;
 }
 
-// Appends the punycoded hostname identified by the given string and length to
-// the output buffer. The result will not be null terminated.
-// Return value of false means error in encoding.
-static bool appendEncodedHostname(UCharBuffer& buffer, StringView string)
-{
-    // Needs to be big enough to hold an IDN-encoded name.
-    // For host names bigger than this, we won't do IDN encoding, which is almost certainly OK.
-    const unsigned hostnameBufferLength = 2048;
-
-    if (string.length() > hostnameBufferLength || containsOnlyASCII(string)) {
-        append(buffer, string);
-        return true;
-    }
-
-    UChar hostnameBuffer[hostnameBufferLength];
-    UErrorCode error = U_ZERO_ERROR;
-
-#if COMPILER(GCC_OR_CLANG)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
-#endif
-    int32_t numCharactersConverted = uidna_IDNToASCII(string.upconvertedCharacters(), string.length(), hostnameBuffer,
-        hostnameBufferLength, UIDNA_ALLOW_UNASSIGNED, 0, &error);
-#if COMPILER(GCC_OR_CLANG)
-#pragma GCC diagnostic pop
-#endif
-
-    if (error == U_ZERO_ERROR) {
-        buffer.append(hostnameBuffer, numCharactersConverted);
-        return true;
-    }
-    return false;
-}
-
 static void findHostnamesInMailToURL(StringView string, Vector<std::pair<int, int>>& nameRanges)
 {
     // In a mailto: URL, host names come after a '@' character and end with a '>' or ',' or '?' or end of string character.