mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 19:59:17 +00:00
LibWeb/HTML: Implement the exception checks for Document.domain setter
This commit is contained in:
parent
68b57daf84
commit
20d369b96d
Notes:
github-actions[bot]
2025-06-27 06:46:56 +00:00
Author: https://github.com/shannonbooth
Commit: 20d369b96d
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5221
7 changed files with 258 additions and 2 deletions
|
@ -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<String>() || !original_host.has<String>())
|
||||
return false;
|
||||
auto const& host_suffix_string = host_suffix->get<String>();
|
||||
auto const& original_host_string = original_host.get<String>();
|
||||
|
||||
// 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<void> 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<String> navigation_id)
|
||||
|
|
|
@ -606,7 +606,7 @@ public:
|
|||
void set_about_base_url(Optional<URL::URL> url) { m_about_base_url = url; }
|
||||
|
||||
String domain() const;
|
||||
void set_domain(String const&);
|
||||
WebIDL::ExceptionOr<void> 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; }
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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/<a>/<area>/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';
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>document.domain's setter</title>
|
||||
<script src="../../../../resources/testharness.js"></script>
|
||||
<script src="../../../../resources/testharnessreport.js"></script>
|
||||
<script src="../../../../common/get-host-info.sub.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="iframe"></iframe>
|
||||
<script>
|
||||
var host_info = get_host_info();
|
||||
var HTTP_PORT = host_info.HTTP_PORT;
|
||||
var ORIGINAL_HOST = host_info.ORIGINAL_HOST;
|
||||
var SUFFIX_HOST = ORIGINAL_HOST.substring(ORIGINAL_HOST.lastIndexOf('.') + 1); // e.g. "test"
|
||||
var REMOTE_HOST = host_info.REMOTE_HOST;
|
||||
var iframe = document.getElementById("iframe");
|
||||
var iframe_url = new URL("support/document_domain_setter_iframe.html", document.location);
|
||||
iframe_url.hostname = REMOTE_HOST;
|
||||
iframe.src = iframe_url;
|
||||
|
||||
test(function() {
|
||||
assert_throws_dom("SecurityError", function() { document.domain = SUFFIX_HOST; });
|
||||
assert_throws_dom("SecurityError", function() { document.domain = "." + SUFFIX_HOST; });
|
||||
assert_throws_dom("SecurityError", function() { document.domain = REMOTE_HOST; });
|
||||
assert_throws_dom("SecurityError", function() { document.domain = "example.com"; });
|
||||
}, "failed setting of document.domain");
|
||||
|
||||
async_test(function(t) {
|
||||
iframe.addEventListener("load", t.step_func_done(function() {
|
||||
// Before setting document.domain, the iframe is not
|
||||
// same-origin-domain, so security checks fail.
|
||||
assert_equals(iframe.contentDocument, null);
|
||||
assert_throws_dom("SecurityError", () => iframe.contentWindow.frameElement);
|
||||
assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.origin; });
|
||||
assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.href; });
|
||||
assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.protocol; });
|
||||
assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.host; });
|
||||
assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.port; });
|
||||
assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.hostname; });
|
||||
assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.pathname; });
|
||||
assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.hash; });
|
||||
assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.search; });
|
||||
assert_throws_dom("SecurityError", function() { iframe.contentWindow.location.toString(); });
|
||||
// Set document.domain
|
||||
document.domain = ORIGINAL_HOST;
|
||||
// After setting document.domain, the iframe is
|
||||
// same-origin-domain, so security checks pass.
|
||||
assert_equals(iframe.contentDocument.domain, document.domain);
|
||||
assert_equals(iframe.contentWindow.frameElement, iframe);
|
||||
assert_equals(iframe.contentWindow.origin, iframe_url.origin);
|
||||
assert_equals(iframe.contentWindow.location.href, iframe_url.href);
|
||||
assert_equals(iframe.contentWindow.location.protocol, iframe_url.protocol);
|
||||
assert_equals(iframe.contentWindow.location.host, iframe_url.host);
|
||||
assert_equals(iframe.contentWindow.location.port, iframe_url.port);
|
||||
assert_equals(iframe.contentWindow.location.hostname, iframe_url.hostname);
|
||||
assert_equals(iframe.contentWindow.location.pathname, iframe_url.pathname);
|
||||
assert_equals(iframe.contentWindow.location.hash, iframe_url.hash);
|
||||
assert_equals(iframe.contentWindow.location.search, iframe_url.search);
|
||||
assert_equals(iframe.contentWindow.location.search, iframe_url.search);
|
||||
assert_equals(iframe.contentWindow.location.toString(), iframe_url.toString());
|
||||
// document.open checks for same-origin, not same-origin-domain,
|
||||
// https://github.com/whatwg/html/issues/2282
|
||||
assert_throws_dom("SecurityError", iframe.contentWindow.DOMException,
|
||||
function() { iframe.contentDocument.open(); });
|
||||
}));
|
||||
}, "same-origin-domain iframe");
|
||||
|
||||
test(() => {
|
||||
assert_throws_dom("SecurityError", () => { (new Document).domain = document.domain });
|
||||
assert_throws_dom("SecurityError", () => { document.implementation.createHTMLDocument().domain = document.domain });
|
||||
assert_throws_dom("SecurityError", () => { document.implementation.createDocument(null, "").domain = document.domain });
|
||||
}, "failed setting of document.domain for documents without browsing context");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,21 @@
|
|||
<!doctype html>
|
||||
<title>Sandboxed document.domain</title>
|
||||
<script src="../../../../resources/testharness.js"></script>
|
||||
<script src="../../../../resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
assert_throws_dom("SecurityError", () => { document.domain = document.domain });
|
||||
});
|
||||
test(() => {
|
||||
assert_throws_dom("SecurityError", () => { (new Document).domain = document.domain });
|
||||
});
|
||||
test(() => {
|
||||
assert_throws_dom("SecurityError", () => { document.implementation.createHTMLDocument().domain = document.domain });
|
||||
});
|
||||
test(() => {
|
||||
assert_throws_dom("SecurityError", () => { document.implementation.createDocument(null, "").domain = document.domain });
|
||||
});
|
||||
test(() => {
|
||||
assert_throws_dom("SecurityError", () => { document.createElement("template").content.ownerDocument.domain = document.domain });
|
||||
});
|
||||
</script>
|
Loading…
Add table
Add a link
Reference in a new issue