diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 6fe44d82685..d82d86dc443 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -3813,9 +3813,85 @@ String Document::domain() const return effective_domain->serialize(); } -void Document::set_domain(String const& domain) +// https://html.spec.whatwg.org/multipage/browsers.html#is-a-registrable-domain-suffix-of-or-is-equal-to +static bool is_a_registrable_domain_suffix_of_or_is_equal_to(StringView host_suffix_string, URL::Host const& original_host) { + // 1. If hostSuffixString is the empty string, then return false. + if (host_suffix_string.is_empty()) + return false; + + // 2. Let hostSuffix be the result of parsing hostSuffixString. + auto host_suffix = URL::Parser::parse_host(host_suffix_string); + + // 3. If hostSuffix is failure, then return false. + if (!host_suffix.has_value()) + return false; + + // 4. If hostSuffix does not equal originalHost, then: + if (host_suffix.value() != original_host) { + // 1. If hostSuffix or originalHost is not a domain, then return false. + // NOTE: This excludes hosts that are IP addresses. + if (!host_suffix->has() || !original_host.has()) + return false; + auto const& host_suffix_string = host_suffix->get(); + auto const& original_host_string = original_host.get(); + + // 2. If hostSuffix, prefixed by U+002E (.), does not match the end of originalHost, then return false. + auto prefixed_host_suffix = MUST(String::formatted(".{}", host_suffix_string)); + if (!original_host_string.ends_with_bytes(prefixed_host_suffix)) + return false; + + // 3. If any of the following are true: + // * hostSuffix equals hostSuffix's public suffix; or + // * hostSuffix, prefixed by U+002E (.), matches the end of originalHost's public suffix, + // then return false. [URL] + if (host_suffix_string == URL::get_public_suffix(host_suffix_string)) + return false; + + auto original_host_public_suffix = URL::get_public_suffix(original_host_string); + VERIFY(original_host_public_suffix.has_value()); + + if (original_host_public_suffix->ends_with_bytes(prefixed_host_suffix)) + return false; + + // 4. Assert: originalHost's public suffix, prefixed by U+002E (.), matches the end of hostSuffix. + VERIFY(host_suffix_string.ends_with_bytes(MUST(String::formatted(".{}", *original_host_public_suffix)))); + } + + // 5. Return true. + return true; +} + +// https://html.spec.whatwg.org/multipage/browsers.html#dom-document-domain +WebIDL::ExceptionOr Document::set_domain(String const& domain) +{ + auto& realm = this->realm(); + + // 1. If this's browsing context is null, then throw a "SecurityError" DOMException. + if (!m_browsing_context) + return WebIDL::SecurityError::create(realm, "Document.domain setter requires a browsing context"_string); + + // 2. If this's active sandboxing flag set has its sandboxed document.domain browsing context flag set, then throw a "SecurityError" DOMException. + if (has_flag(active_sandboxing_flag_set(), HTML::SandboxingFlagSet::SandboxedDocumentDomain)) + return WebIDL::SecurityError::create(realm, "Document.domain setter is sandboxed"_string); + + // 3. Let effectiveDomain be this's origin's effective domain. + auto effective_domain = origin().effective_domain(); + + // 4. If effectiveDomain is null, then throw a "SecurityError" DOMException. + if (!effective_domain.has_value()) + return WebIDL::SecurityError::create(realm, "Document.domain setter called on a Document with a null effective domain"_string); + + // 5. If the given value is not a registrable domain suffix of and is not equal to effectiveDomain, then throw a "SecurityError" DOMException. + if (!is_a_registrable_domain_suffix_of_or_is_equal_to(domain, effective_domain.value())) + return WebIDL::SecurityError::create(realm, "Document.domain setter called for an invalid domain"_string); + + // FIXME: 6. If the surrounding agent's agent cluster's is origin-keyed is true, then return. + + // FIXME: 7. Set this's origin's domain to the result of parsing the given value. + dbgln("(STUBBED) Document::set_domain(domain='{}')", domain); + return {}; } void Document::set_navigation_id(Optional navigation_id) diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index b8bd4ac4220..c999ba30f08 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -606,7 +606,7 @@ public: void set_about_base_url(Optional url) { m_about_base_url = url; } String domain() const; - void set_domain(String const&); + WebIDL::ExceptionOr set_domain(String const&); auto& pending_scroll_event_targets() { return m_pending_scroll_event_targets; } auto& pending_scrollend_event_targets() { return m_pending_scrollend_event_targets; } diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.txt b/Tests/LibWeb/Text/expected/wpt-import/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.txt new file mode 100644 index 00000000000..b46bf38e4ae --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.txt @@ -0,0 +1,9 @@ +Harness status: OK + +Found 3 tests + +2 Pass +1 Fail +Pass failed setting of document.domain +Fail same-origin-domain iframe +Pass failed setting of document.domain for documents without browsing context \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.txt b/Tests/LibWeb/Text/expected/wpt-import/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.txt new file mode 100644 index 00000000000..8b33372fecc --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.txt @@ -0,0 +1,10 @@ +Harness status: OK + +Found 5 tests + +5 Pass +Pass Sandboxed document.domain +Pass Sandboxed document.domain 1 +Pass Sandboxed document.domain 2 +Pass Sandboxed document.domain 3 +Pass Sandboxed document.domain 4 \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/common/get-host-info.sub.js b/Tests/LibWeb/Text/input/wpt-import/common/get-host-info.sub.js new file mode 100644 index 00000000000..c9055236888 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/common/get-host-info.sub.js @@ -0,0 +1,64 @@ +/** + * Host information for cross-origin tests. + * @returns {Object} with properties for different host information. + */ +function get_host_info() { + + var HTTP_PORT = '80'; + var HTTP_PORT2 = '8000'; + var HTTPS_PORT = '443'; + var HTTPS_PORT2 = '8443'; + var PROTOCOL = self.location.protocol; + var IS_HTTPS = (PROTOCOL == "https:"); + var PORT = IS_HTTPS ? HTTPS_PORT : HTTP_PORT; + var PORT2 = IS_HTTPS ? HTTPS_PORT2 : HTTP_PORT2; + var HTTP_PORT_ELIDED = HTTP_PORT == "80" ? "" : (":" + HTTP_PORT); + var HTTP_PORT2_ELIDED = HTTP_PORT2 == "80" ? "" : (":" + HTTP_PORT2); + var HTTPS_PORT_ELIDED = HTTPS_PORT == "443" ? "" : (":" + HTTPS_PORT); + var PORT_ELIDED = IS_HTTPS ? HTTPS_PORT_ELIDED : HTTP_PORT_ELIDED; + var ORIGINAL_HOST = 'wpt.live'; + var REMOTE_HOST = (ORIGINAL_HOST === 'localhost') ? '127.0.0.1' : ('www1.' + ORIGINAL_HOST); + var OTHER_HOST = 'www2.wpt.live'; + var NOTSAMESITE_HOST = (ORIGINAL_HOST === 'localhost') ? '127.0.0.1' : ('not-wpt.live'); + + return { + HTTP_PORT: HTTP_PORT, + HTTP_PORT2: HTTP_PORT2, + HTTPS_PORT: HTTPS_PORT, + HTTPS_PORT2: HTTPS_PORT2, + PORT: PORT, + PORT2: PORT2, + ORIGINAL_HOST: ORIGINAL_HOST, + REMOTE_HOST: REMOTE_HOST, + NOTSAMESITE_HOST, + + ORIGIN: PROTOCOL + "//" + ORIGINAL_HOST + PORT_ELIDED, + HTTP_ORIGIN: 'http://' + ORIGINAL_HOST + HTTP_PORT_ELIDED, + HTTPS_ORIGIN: 'https://' + ORIGINAL_HOST + HTTPS_PORT_ELIDED, + HTTPS_ORIGIN_WITH_CREDS: 'https://foo:bar@' + ORIGINAL_HOST + HTTPS_PORT_ELIDED, + HTTP_ORIGIN_WITH_DIFFERENT_PORT: 'http://' + ORIGINAL_HOST + HTTP_PORT2_ELIDED, + REMOTE_ORIGIN: PROTOCOL + "//" + REMOTE_HOST + PORT_ELIDED, + OTHER_ORIGIN: PROTOCOL + "//" + OTHER_HOST + PORT_ELIDED, + HTTP_REMOTE_ORIGIN: 'http://' + REMOTE_HOST + HTTP_PORT_ELIDED, + HTTP_NOTSAMESITE_ORIGIN: 'http://' + NOTSAMESITE_HOST + HTTP_PORT_ELIDED, + HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT: 'http://' + REMOTE_HOST + HTTP_PORT2_ELIDED, + HTTPS_REMOTE_ORIGIN: 'https://' + REMOTE_HOST + HTTPS_PORT_ELIDED, + HTTPS_REMOTE_ORIGIN_WITH_CREDS: 'https://foo:bar@' + REMOTE_HOST + HTTPS_PORT_ELIDED, + HTTPS_NOTSAMESITE_ORIGIN: 'https://' + NOTSAMESITE_HOST + HTTPS_PORT_ELIDED, + UNAUTHENTICATED_ORIGIN: 'http://' + OTHER_HOST + HTTP_PORT_ELIDED, + AUTHENTICATED_ORIGIN: 'https://' + OTHER_HOST + HTTPS_PORT_ELIDED + }; +} + +/** + * When a default port is used, location.port returns the empty string. + * This function attempts to provide an exact port, assuming we are running under wptserve. + * @param {*} loc - can be Location///URL, but assumes http/https only. + * @returns {string} The port number. + */ +function get_port(loc) { + if (loc.port) { + return loc.port; + } + return loc.protocol === 'https:' ? '443' : '80'; +} diff --git a/Tests/LibWeb/Text/input/wpt-import/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html b/Tests/LibWeb/Text/input/wpt-import/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html new file mode 100644 index 00000000000..f35ac790c20 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html @@ -0,0 +1,76 @@ + + + + document.domain's setter + + + + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html b/Tests/LibWeb/Text/input/wpt-import/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html new file mode 100644 index 00000000000..7718861d320 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/browsers/origin/relaxing-the-same-origin-restriction/sandboxed-document_domain.html @@ -0,0 +1,21 @@ + +Sandboxed document.domain + + +