Bug 1342258 - Refactor SetDomain to IsRegistrableDomainSuffixOfOrEqualTo r=smaug
This commit refactors the SetDomain method in a Document to call a new function
IsRegistrableDomainSuffixOfOrEqualTo(), defined in HTML [1]. This commit tries
not to rename anything except input variables, so as to remain as clear as
possible. It likely should have various variables renamed, but given the
author's unfamiliarity with this module, review seems a good time to do that.
It's also duplicating comments a little bit; let me know which one(s) you'd like
to keep!
Note: Commentary on the HTML change is available in the PR [2], and the
rationale for this behavior in Web Auentication, where this algorithm will be
used, is also recorded [3].
Update 1: Refactored two new protected methods to avoid code duplication.
Update 2: Bugfix: Be sure to use CreateInheritingURIForHost for the
provided domain so as to catch internationalized domains.
Update 3: Nit-fix and rebase
[1] https://html.spec.whatwg.org/multipage/browsers.html#is-a-registrable-domain-suffix-of-or-is-equal-to
[2] https://github.com/whatwg/html/pull/2365
[3] https://github.com/w3ctag/spec-reviews/issues/97#issuecomment-175766580
MozReview-Commit-ID: 4Dr8yOMdhez
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -901,16 +901,127 @@ nsHTMLDocument::GetDomain(nsAString& aDo
} else {
// If we can't get the host from the URI (e.g. about:, javascript:,
// etc), just return an null string.
SetDOMStringToNull(aDomain);
}
return NS_OK;
}
+already_AddRefed<nsIURI>
+nsHTMLDocument::CreateInheritingURIForHost(const nsACString& aHostString)
+{
+ if (aHostString.IsEmpty()) {
+ return nullptr;
+ }
+
+ // Create new URI
+ nsCOMPtr<nsIURI> uri = GetDomainURI();
+ if (!uri) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = uri->Clone(getter_AddRefs(newURI));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ rv = newURI->SetUserPass(EmptyCString());
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ // We use SetHostAndPort because we want to reset the port number if needed.
+ rv = newURI->SetHostAndPort(aHostString);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ return newURI.forget();
+}
+
+already_AddRefed<nsIURI>
+nsHTMLDocument::RegistrableDomainSuffixOfInternal(const nsAString& aNewDomain,
+ nsIURI* aOrigHost)
+{
+ if (NS_WARN_IF(!aOrigHost)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> newURI = CreateInheritingURIForHost(NS_ConvertUTF16toUTF8(aNewDomain));
+ if (!newURI) {
+ // Error: failed to parse input domain
+ return nullptr;
+ }
+
+ // Check new domain - must be a superdomain of the current host
+ // For example, a page from foo.bar.com may set domain to bar.com,
+ // but not to ar.com, baz.com, or fi.foo.bar.com.
+ nsAutoCString current;
+ nsAutoCString domain;
+ if (NS_FAILED(aOrigHost->GetAsciiHost(current))) {
+ current.Truncate();
+ }
+ if (NS_FAILED(newURI->GetAsciiHost(domain))) {
+ domain.Truncate();
+ }
+
+ bool ok = current.Equals(domain);
+ if (current.Length() > domain.Length() &&
+ StringEndsWith(current, domain) &&
+ current.CharAt(current.Length() - domain.Length() - 1) == '.') {
+ // We're golden if the new domain is the current page's base domain or a
+ // subdomain of it.
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ return nullptr;
+ }
+
+ nsAutoCString currentBaseDomain;
+ ok = NS_SUCCEEDED(tldService->GetBaseDomain(aOrigHost, 0, currentBaseDomain));
+ NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
+ (domain.Length() >= currentBaseDomain.Length()),
+ "uh-oh! slight optimization wasn't valid somehow!");
+ ok = ok && domain.Length() >= currentBaseDomain.Length();
+ }
+
+ if (!ok) {
+ // Error: illegal domain
+ return nullptr;
+ }
+
+ return CreateInheritingURIForHost(domain);
+}
+
+bool
+nsHTMLDocument::IsRegistrableDomainSuffixOfOrEqualTo(const nsAString& aHostSuffixString,
+ const nsACString& aOrigHost)
+{
+ // https://html.spec.whatwg.org/multipage/browsers.html#is-a-registrable-domain-suffix-of-or-is-equal-to
+ if (aHostSuffixString.IsEmpty()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> origURI = CreateInheritingURIForHost(aOrigHost);
+ if (!origURI) {
+ // Error: failed to parse input domain
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aHostSuffixString, origURI);
+ if (!newURI) {
+ // Error: illegal domain
+ return false;
+ }
+ return true;
+}
+
+
NS_IMETHODIMP
nsHTMLDocument::SetDomain(const nsAString& aDomain)
{
ErrorResult rv;
SetDomain(aDomain, rv);
return rv.StealNSResult();
}
@@ -923,74 +1034,28 @@ nsHTMLDocument::SetDomain(const nsAStrin
return;
}
if (aDomain.IsEmpty()) {
rv.Throw(NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN);
return;
}
- // Create new URI
nsCOMPtr<nsIURI> uri = GetDomainURI();
-
if (!uri) {
rv.Throw(NS_ERROR_FAILURE);
return;
}
- nsCOMPtr<nsIURI> newURI;
- nsresult rv2 = uri->Clone(getter_AddRefs(newURI));
- if (NS_FAILED(rv2)) {
- rv.Throw(rv2);
- return;
- }
-
- rv2 = newURI->SetUserPass(EmptyCString());
- if (NS_FAILED(rv2)) {
- rv.Throw(rv2);
- return;
- }
-
- // We use SetHostAndPort because we want to reset the port number if needed.
- rv2 = newURI->SetHostAndPort(NS_ConvertUTF16toUTF8(aDomain));
- if (NS_FAILED(rv2)) {
- rv.Throw(rv2);
- return;
- }
-
// Check new domain - must be a superdomain of the current host
// For example, a page from foo.bar.com may set domain to bar.com,
// but not to ar.com, baz.com, or fi.foo.bar.com.
- nsAutoCString current, domain;
- if (NS_FAILED(uri->GetAsciiHost(current)))
- current.Truncate();
- if (NS_FAILED(newURI->GetAsciiHost(domain)))
- domain.Truncate();
-
- bool ok = current.Equals(domain);
- if (current.Length() > domain.Length() &&
- StringEndsWith(current, domain) &&
- current.CharAt(current.Length() - domain.Length() - 1) == '.') {
- // We're golden if the new domain is the current page's base domain or a
- // subdomain of it.
- nsCOMPtr<nsIEffectiveTLDService> tldService =
- do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
- if (!tldService) {
- rv.Throw(NS_ERROR_NOT_AVAILABLE);
- return;
- }
-
- nsAutoCString currentBaseDomain;
- ok = NS_SUCCEEDED(tldService->GetBaseDomain(uri, 0, currentBaseDomain));
- NS_ASSERTION(StringEndsWith(domain, currentBaseDomain) ==
- (domain.Length() >= currentBaseDomain.Length()),
- "uh-oh! slight optimization wasn't valid somehow!");
- ok = ok && domain.Length() >= currentBaseDomain.Length();
- }
- if (!ok) {
+
+ nsCOMPtr<nsIURI> newURI = RegistrableDomainSuffixOfInternal(aDomain, uri);
+ if (!newURI) {
// Error: illegal domain
rv.Throw(NS_ERROR_DOM_BAD_DOCUMENT_DOMAIN);
return;
}
NS_TryToSetImmutable(newURI);
rv = NodePrincipal()->SetDomain(newURI);
}
--- a/dom/html/nsHTMLDocument.h
+++ b/dom/html/nsHTMLDocument.h
@@ -161,16 +161,18 @@ public:
// DocAddSizeOfIncludingThis is inherited from nsIDocument.
virtual bool WillIgnoreCharsetOverride() override;
// WebIDL API
virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
override;
void SetDomain(const nsAString& aDomain, mozilla::ErrorResult& rv);
+ bool IsRegistrableDomainSuffixOfOrEqualTo(const nsAString& aHostSuffixString,
+ const nsACString& aOrigHost);
void GetCookie(nsAString& aCookie, mozilla::ErrorResult& rv);
void SetCookie(const nsAString& aCookie, mozilla::ErrorResult& rv);
void NamedGetter(JSContext* cx, const nsAString& aName, bool& aFound,
JS::MutableHandle<JSObject*> aRetval,
mozilla::ErrorResult& rv);
void GetSupportedNames(nsTArray<nsString>& aNames);
nsGenericHTMLElement *GetBody();
void SetBody(nsGenericHTMLElement* aBody, mozilla::ErrorResult& rv);
@@ -269,16 +271,20 @@ protected:
static bool MatchNameAttribute(mozilla::dom::Element* aElement,
int32_t aNamespaceID,
nsIAtom* aAtom, void* aData);
static void* UseExistingNameString(nsINode* aRootNode, const nsString* aName);
static void DocumentWriteTerminationFunc(nsISupports *aRef);
already_AddRefed<nsIURI> GetDomainURI();
+ already_AddRefed<nsIURI> CreateInheritingURIForHost(const nsACString& aHostString);
+ already_AddRefed<nsIURI> RegistrableDomainSuffixOfInternal(const nsAString& aHostSuffixString,
+ nsIURI* aOrigHost);
+
nsresult WriteCommon(JSContext *cx, const nsAString& aText,
bool aNewlineTerminate);
// A version of WriteCommon used by WebIDL bindings
void WriteCommon(JSContext *cx,
const mozilla::dom::Sequence<nsString>& aText,
bool aNewlineTerminate,
mozilla::ErrorResult& rv);