diff --git a/src/changes/changes.xml b/src/changes/changes.xml index add66177e8..0754214a6f 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -8,6 +8,11 @@ + + History.pushState() and History.replaceState() throw a SecurityError if the url is not same origin + with the document; a page was able to change its url (and by this its origin for cookies, + XMLHttpRequest and local files) to any other url. + DomNode.querySelector(String) and DomNode.querySelectorAll(String) are available again (removed in 5.7.0 by mistake); like element.querySelector() in the browsers, the node itself is the scope diff --git a/src/main/java/org/htmlunit/javascript/host/History.java b/src/main/java/org/htmlunit/javascript/host/History.java index 570ed34a6f..b6d98c9e4c 100644 --- a/src/main/java/org/htmlunit/javascript/host/History.java +++ b/src/main/java/org/htmlunit/javascript/host/History.java @@ -27,7 +27,9 @@ import org.htmlunit.javascript.configuration.JsxFunction; import org.htmlunit.javascript.configuration.JsxGetter; import org.htmlunit.javascript.configuration.JsxSetter; +import org.htmlunit.javascript.host.dom.DOMException; import org.htmlunit.util.StringUtils; +import org.htmlunit.util.UrlUtils; /** * JavaScript host object for the client's browsing history. @@ -156,13 +158,24 @@ public void pushState(final Object object, final String title, final Object url) } } - private static URL buildNewStateUrl(final WebWindow webWindow, final Object url) throws MalformedURLException { + private URL buildNewStateUrl(final WebWindow webWindow, final Object url) throws MalformedURLException { URL newStateUrl = null; if (url != null && !JavaScriptEngine.isUndefined(url)) { final String urlString = JavaScriptEngine.toString(url); if (StringUtils.isNotBlank(urlString)) { final HtmlPage page = (HtmlPage) webWindow.getEnclosedPage(); newStateUrl = page.getFullyQualifiedUrl(urlString); + + // the document url can only be changed inside the origin of the document + // https://html.spec.whatwg.org/multipage/nav-history-apis.html#can-have-its-url-rewritten + if (!UrlUtils.isSameOrigin(page.getUrl(), newStateUrl)) { + throw JavaScriptEngine.asJavaScriptException( + getWindow(), + "A history state object with URL '" + newStateUrl.toExternalForm() + + "' cannot be created in a document with URL '" + + page.getUrl().toExternalForm() + "'.", + DOMException.SECURITY_ERR); + } } } return newStateUrl; diff --git a/src/test/java/org/htmlunit/javascript/host/History2Test.java b/src/test/java/org/htmlunit/javascript/host/History2Test.java index 0fee4fbfd3..745d6715fd 100644 --- a/src/test/java/org/htmlunit/javascript/host/History2Test.java +++ b/src/test/java/org/htmlunit/javascript/host/History2Test.java @@ -963,6 +963,87 @@ public void replaceStateChangeHashNoStore() throws Exception { verifyTitle2(driver, getExpectedAlerts()); } + /** + * @throws Exception if an error occurs + */ + @Test + @Alerts({"href=§§URL§§", "SecurityError", "href=§§URL§§"}) + public void pushStateCrossOrigin() throws Exception { + final String html = DOCTYPE_HTML + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + + expandExpectedAlertsVariables(URL_FIRST); + loadPageVerifyTitle2(html); + } + + /** + * @throws Exception if an error occurs + */ + @Test + @Alerts({"href=§§URL§§", "SecurityError", "href=§§URL§§"}) + public void replaceStateCrossOrigin() throws Exception { + final String html = DOCTYPE_HTML + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + + expandExpectedAlertsVariables(URL_FIRST); + loadPageVerifyTitle2(html); + } + + /** + * @throws Exception if an error occurs + */ + @Test + @Alerts({"href=§§URL§§", "SecurityError", "href=§§URL§§"}) + public void replaceStateFileUrl() throws Exception { + final String html = DOCTYPE_HTML + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + + expandExpectedAlertsVariables(URL_FIRST); + loadPageVerifyTitle2(html); + } + /** * @throws Exception if an error occurs */