/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "PresentationRequest.h" #include "ControllerConnectionCollection.h" #include "mozilla/BasePrincipal.h" #include "mozilla/dom/PresentationRequestBinding.h" #include "mozilla/dom/PresentationConnectionAvailableEvent.h" #include "mozilla/dom/Promise.h" #include "mozIThirdPartyUtil.h" #include "nsContentSecurityManager.h" #include "nsCycleCollectionParticipant.h" #include "nsIDocument.h" #include "nsIPresentationService.h" #include "nsIURI.h" #include "nsIUUIDGenerator.h" #include "nsNetUtil.h" #include "nsSandboxFlags.h" #include "nsServiceManagerUtils.h" #include "PresentationAvailability.h" #include "PresentationCallbacks.h" using namespace mozilla; using namespace mozilla::dom; NS_IMPL_CYCLE_COLLECTION_INHERITED(PresentationRequest, DOMEventTargetHelper, mAvailability) NS_IMPL_ADDREF_INHERITED(PresentationRequest, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(PresentationRequest, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PresentationRequest) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) static nsresult GetAbsoluteURL(const nsAString& aUrl, nsIURI* aBaseUri, nsIDocument* aDocument, nsAString& aAbsoluteUrl) { nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), aUrl, aDocument ? aDocument->GetDocumentCharacterSet().get() : nullptr, aBaseUri); if (NS_FAILED(rv)) { return rv; } nsAutoCString spec; uri->GetSpec(spec); aAbsoluteUrl = NS_ConvertUTF8toUTF16(spec); return NS_OK; } /* static */ already_AddRefed PresentationRequest::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl, ErrorResult& aRv) { nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); if (!window) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } // Ensure the URL is not empty. if (NS_WARN_IF(aUrl.IsEmpty())) { aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); return nullptr; } // Resolve relative URL to absolute URL nsCOMPtr baseUri = window->GetDocBaseURI(); nsAutoString absoluteUrl; nsresult rv = GetAbsoluteURL(aUrl, baseUri, window->GetExtantDoc(), absoluteUrl); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } RefPtr request = new PresentationRequest(window, absoluteUrl); return NS_WARN_IF(!request->Init()) ? nullptr : request.forget(); } PresentationRequest::PresentationRequest(nsPIDOMWindowInner* aWindow, const nsAString& aUrl) : DOMEventTargetHelper(aWindow) , mUrl(aUrl) { } PresentationRequest::~PresentationRequest() { } bool PresentationRequest::Init() { mAvailability = PresentationAvailability::Create(GetOwner()); if (NS_WARN_IF(!mAvailability)) { return false; } return true; } /* virtual */ JSObject* PresentationRequest::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return PresentationRequestBinding::Wrap(aCx, this, aGivenProto); } already_AddRefed PresentationRequest::Start(ErrorResult& aRv) { return StartWithDevice(NullString(), aRv); } already_AddRefed PresentationRequest::StartWithDevice(const nsAString& aDeviceId, ErrorResult& aRv) { nsCOMPtr global = do_QueryInterface(GetOwner()); if (NS_WARN_IF(!global)) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } // Get the origin. nsAutoString origin; nsresult rv = nsContentUtils::GetUTFOrigin(global->PrincipalOrNull(), origin); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return nullptr; } nsCOMPtr doc = GetOwner()->GetExtantDoc(); if (NS_WARN_IF(!doc)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } RefPtr promise = Promise::Create(global, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } if (IsProhibitMixedSecurityContexts(doc) && !IsPrioriAuthenticatedURL(mUrl)) { promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); return promise.forget(); } if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) { promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); return promise.forget(); } // Generate a session ID. nsCOMPtr uuidgen = do_GetService("@mozilla.org/uuid-generator;1"); if(NS_WARN_IF(!uuidgen)) { promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR); return promise.forget(); } nsID uuid; uuidgen->GenerateUUIDInPlace(&uuid); char buffer[NSID_LENGTH]; uuid.ToProvidedString(buffer); nsAutoString id; CopyASCIItoUTF16(buffer, id); nsCOMPtr service = do_GetService(PRESENTATION_SERVICE_CONTRACTID); if(NS_WARN_IF(!service)) { promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR); return promise.forget(); } nsCOMPtr callback = new PresentationRequesterCallback(this, mUrl, id, promise); rv = service->StartSession(mUrl, id, origin, aDeviceId, GetOwner()->WindowID(), callback); if (NS_WARN_IF(NS_FAILED(rv))) { promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR); } return promise.forget(); } already_AddRefed PresentationRequest::Reconnect(const nsAString& aPresentationId, ErrorResult& aRv) { nsCOMPtr global = do_QueryInterface(GetOwner()); if (NS_WARN_IF(!global)) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsCOMPtr doc = GetOwner()->GetExtantDoc(); if (NS_WARN_IF(!doc)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } RefPtr promise = Promise::Create(global, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } if (IsProhibitMixedSecurityContexts(doc) && !IsPrioriAuthenticatedURL(mUrl)) { promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); return promise.forget(); } if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) { promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); return promise.forget(); } nsString presentationId = nsString(aPresentationId); nsCOMPtr r = NewRunnableMethod>( this, &PresentationRequest::FindOrCreatePresentationConnection, presentationId, promise); if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)))) { promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR); } return promise.forget(); } void PresentationRequest::FindOrCreatePresentationConnection( const nsAString& aPresentationId, Promise* aPromise) { MOZ_ASSERT(aPromise); if (NS_WARN_IF(!GetOwner())) { aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR); return; } RefPtr connection = ControllerConnectionCollection::GetSingleton()->FindConnection( GetOwner()->WindowID(), aPresentationId, nsIPresentationService::ROLE_CONTROLLER); if (connection) { nsAutoString url; connection->GetUrl(url); if (url.Equals(mUrl)) { switch (connection->State()) { case PresentationConnectionState::Closed: // We found the matched connection. break; case PresentationConnectionState::Connecting: case PresentationConnectionState::Connected: aPromise->MaybeResolve(connection); return; case PresentationConnectionState::Terminated: // A terminated connection cannot be reused. connection = nullptr; break; default: MOZ_CRASH("Unknown presentation session state."); return; } } else { connection = nullptr; } } nsCOMPtr service = do_GetService(PRESENTATION_SERVICE_CONTRACTID); if(NS_WARN_IF(!service)) { aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR); return; } nsCOMPtr callback = new PresentationReconnectCallback(this, mUrl, aPresentationId, aPromise, connection); nsresult rv = service->ReconnectSession(mUrl, aPresentationId, nsIPresentationService::ROLE_CONTROLLER, callback); if (NS_WARN_IF(NS_FAILED(rv))) { aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR); } } already_AddRefed PresentationRequest::GetAvailability(ErrorResult& aRv) { nsCOMPtr global = do_QueryInterface(GetOwner()); if (NS_WARN_IF(!global)) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } nsCOMPtr doc = GetOwner()->GetExtantDoc(); if (NS_WARN_IF(!doc)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } RefPtr promise = Promise::Create(global, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } if (IsProhibitMixedSecurityContexts(doc) && !IsPrioriAuthenticatedURL(mUrl)) { promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); return promise.forget(); } if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) { promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); return promise.forget(); } promise->MaybeResolve(mAvailability); return promise.forget(); } nsresult PresentationRequest::DispatchConnectionAvailableEvent(PresentationConnection* aConnection) { PresentationConnectionAvailableEventInit init; init.mConnection = aConnection; RefPtr event = PresentationConnectionAvailableEvent::Constructor(this, NS_LITERAL_STRING("connectionavailable"), init); if (NS_WARN_IF(!event)) { return NS_ERROR_FAILURE; } event->SetTrusted(true); RefPtr asyncDispatcher = new AsyncEventDispatcher(this, event); return asyncDispatcher->PostDOMEvent(); } bool PresentationRequest::IsProhibitMixedSecurityContexts(nsIDocument* aDocument) { MOZ_ASSERT(aDocument); if (nsContentUtils::IsChromeDoc(aDocument)) { return true; } nsCOMPtr doc = aDocument; while (doc && !nsContentUtils::IsChromeDoc(doc)) { if (nsContentUtils::HttpsStateIsModern(doc)) { return true; } doc = doc->GetParentDocument(); } return false; } bool PresentationRequest::IsPrioriAuthenticatedURL(const nsAString& aUrl) { nsCOMPtr uri; if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), aUrl))) { return false; } nsAutoCString scheme; nsresult rv = uri->GetScheme(scheme); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } if (scheme.EqualsLiteral("data")) { return true; } nsAutoCString uriSpec; rv = uri->GetSpec(uriSpec); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } if (uriSpec.EqualsLiteral("about:blank") || uriSpec.EqualsLiteral("about:srcdoc")) { return true; } PrincipalOriginAttributes attrs; nsCOMPtr principal = BasePrincipal::CreateCodebasePrincipal(uri, attrs); if (NS_WARN_IF(!principal)) { return false; } nsCOMPtr csm = do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID); if (NS_WARN_IF(!csm)) { return false; } bool isTrustworthyOrigin = false; csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin); return isTrustworthyOrigin; }