/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim:set ts=2 sts=2 sw=2 et cin: * * 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 "nsIURI.h" #include "nsIURL.h" #include "nsExternalProtocolHandler.h" #include "nsXPIDLString.h" #include "nsReadableUtils.h" #include "nsCOMPtr.h" #include "nsContentUtils.h" #include "nsIServiceManager.h" #include "nsServiceManagerUtils.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIStringBundle.h" #include "nsIPrefService.h" #include "nsIPrompt.h" #include "nsNetUtil.h" #include "nsContentSecurityManager.h" #include "nsExternalHelperAppService.h" // used to dispatch urls to default protocol handlers #include "nsCExternalHandlerService.h" #include "nsIExternalProtocolService.h" class nsILoadInfo; //////////////////////////////////////////////////////////////////////// // a stub channel implemenation which will map calls to AsyncRead and OpenInputStream // to calls in the OS for loading the url. //////////////////////////////////////////////////////////////////////// class nsExtProtocolChannel : public nsIChannel { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSICHANNEL NS_DECL_NSIREQUEST nsExtProtocolChannel(); nsresult SetURI(nsIURI*); private: virtual ~nsExtProtocolChannel(); nsresult OpenURL(); void Finish(nsresult aResult); nsCOMPtr mUrl; nsCOMPtr mOriginalURI; nsresult mStatus; nsLoadFlags mLoadFlags; bool mWasOpened; nsCOMPtr mCallbacks; nsCOMPtr mLoadGroup; nsCOMPtr mLoadInfo; }; NS_IMPL_ADDREF(nsExtProtocolChannel) NS_IMPL_RELEASE(nsExtProtocolChannel) NS_INTERFACE_MAP_BEGIN(nsExtProtocolChannel) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIChannel) NS_INTERFACE_MAP_ENTRY(nsIChannel) NS_INTERFACE_MAP_ENTRY(nsIRequest) NS_INTERFACE_MAP_END_THREADSAFE nsExtProtocolChannel::nsExtProtocolChannel() : mStatus(NS_OK), mWasOpened(false) { } nsExtProtocolChannel::~nsExtProtocolChannel() {} NS_IMETHODIMP nsExtProtocolChannel::GetLoadGroup(nsILoadGroup * *aLoadGroup) { NS_IF_ADDREF(*aLoadGroup = mLoadGroup); return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::SetLoadGroup(nsILoadGroup * aLoadGroup) { mLoadGroup = aLoadGroup; return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aCallbacks) { NS_IF_ADDREF(*aCallbacks = mCallbacks); return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) { mCallbacks = aCallbacks; return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::GetSecurityInfo(nsISupports * *aSecurityInfo) { *aSecurityInfo = nullptr; return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::GetOriginalURI(nsIURI* *aURI) { NS_ADDREF(*aURI = mOriginalURI); return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::SetOriginalURI(nsIURI* aURI) { NS_ENSURE_ARG_POINTER(aURI); mOriginalURI = aURI; return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::GetURI(nsIURI* *aURI) { *aURI = mUrl; NS_IF_ADDREF(*aURI); return NS_OK; } nsresult nsExtProtocolChannel::SetURI(nsIURI* aURI) { mUrl = aURI; return NS_OK; } nsresult nsExtProtocolChannel::OpenURL() { nsresult rv = NS_ERROR_FAILURE; nsCOMPtr extProtService (do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID)); if (extProtService) { #ifdef DEBUG nsAutoCString urlScheme; mUrl->GetScheme(urlScheme); bool haveHandler = false; extProtService->ExternalProtocolHandlerExists(urlScheme.get(), &haveHandler); NS_ASSERTION(haveHandler, "Why do we have a channel for this url if we don't support the protocol?"); #endif nsCOMPtr aggCallbacks; rv = NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup, getter_AddRefs(aggCallbacks)); if (NS_FAILED(rv)) { goto finish; } rv = extProtService->LoadURI(mUrl, aggCallbacks); if (NS_SUCCEEDED(rv)) { // despite success, we need to abort this channel, at the very least // to make it clear to the caller that no on{Start,Stop}Request // should be expected. rv = NS_ERROR_NO_CONTENT; } } finish: mCallbacks = 0; return rv; } NS_IMETHODIMP nsExtProtocolChannel::Open(nsIInputStream **_retval) { return OpenURL(); } NS_IMETHODIMP nsExtProtocolChannel::Open2(nsIInputStream** aStream) { nsCOMPtr listener; nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); NS_ENSURE_SUCCESS(rv, rv); return Open(aStream); } NS_IMETHODIMP nsExtProtocolChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt) { MOZ_ASSERT(!mLoadInfo || mLoadInfo->GetSecurityMode() == 0 || mLoadInfo->GetInitialSecurityCheckDone() || (mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL && nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())), "security flags in loadInfo but asyncOpen2() not called"); NS_ENSURE_ARG_POINTER(listener); NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); mWasOpened = true; return OpenURL(); } NS_IMETHODIMP nsExtProtocolChannel::AsyncOpen2(nsIStreamListener *aListener) { nsCOMPtr listener = aListener; nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); NS_ENSURE_SUCCESS(rv, rv); return AsyncOpen(listener, nullptr); } NS_IMETHODIMP nsExtProtocolChannel::GetLoadFlags(nsLoadFlags *aLoadFlags) { *aLoadFlags = mLoadFlags; return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::SetLoadFlags(nsLoadFlags aLoadFlags) { mLoadFlags = aLoadFlags; return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::GetContentType(nsACString &aContentType) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsExtProtocolChannel::SetContentType(const nsACString &aContentType) { return NS_ERROR_FAILURE; } NS_IMETHODIMP nsExtProtocolChannel::GetContentCharset(nsACString &aContentCharset) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsExtProtocolChannel::SetContentCharset(const nsACString &aContentCharset) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsExtProtocolChannel::GetContentDisposition(uint32_t *aContentDisposition) { return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP nsExtProtocolChannel::SetContentDisposition(uint32_t aContentDisposition) { return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP nsExtProtocolChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename) { return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP nsExtProtocolChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename) { return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP nsExtProtocolChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader) { return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP nsExtProtocolChannel::GetContentLength(int64_t * aContentLength) { *aContentLength = -1; return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::SetContentLength(int64_t aContentLength) { NS_NOTREACHED("SetContentLength"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsExtProtocolChannel::GetOwner(nsISupports * *aPrincipal) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsExtProtocolChannel::SetOwner(nsISupports * aPrincipal) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsExtProtocolChannel::GetLoadInfo(nsILoadInfo **aLoadInfo) { NS_IF_ADDREF(*aLoadInfo = mLoadInfo); return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::SetLoadInfo(nsILoadInfo *aLoadInfo) { mLoadInfo = aLoadInfo; return NS_OK; } //////////////////////////////////////////////////////////////////////////////// // From nsIRequest //////////////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsExtProtocolChannel::GetName(nsACString &result) { return mUrl->GetSpec(result); } NS_IMETHODIMP nsExtProtocolChannel::IsPending(bool *result) { *result = false; return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::GetStatus(nsresult *status) { *status = mStatus; return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::Cancel(nsresult status) { mStatus = status; return NS_OK; } NS_IMETHODIMP nsExtProtocolChannel::Suspend() { NS_NOTREACHED("Suspend"); return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsExtProtocolChannel::Resume() { NS_NOTREACHED("Resume"); return NS_ERROR_NOT_IMPLEMENTED; } /////////////////////////////////////////////////////////////////////// // the default protocol handler implementation ////////////////////////////////////////////////////////////////////// nsExternalProtocolHandler::nsExternalProtocolHandler() { m_schemeName = "default"; } nsExternalProtocolHandler::~nsExternalProtocolHandler() {} NS_IMPL_ADDREF(nsExternalProtocolHandler) NS_IMPL_RELEASE(nsExternalProtocolHandler) NS_INTERFACE_MAP_BEGIN(nsExternalProtocolHandler) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIProtocolHandler) NS_INTERFACE_MAP_ENTRY(nsIProtocolHandler) NS_INTERFACE_MAP_ENTRY(nsIExternalProtocolHandler) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_END_THREADSAFE NS_IMETHODIMP nsExternalProtocolHandler::GetScheme(nsACString &aScheme) { aScheme = m_schemeName; return NS_OK; } NS_IMETHODIMP nsExternalProtocolHandler::GetDefaultPort(int32_t *aDefaultPort) { *aDefaultPort = 0; return NS_OK; } NS_IMETHODIMP nsExternalProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) { // don't override anything. *_retval = false; return NS_OK; } // returns TRUE if the OS can handle this protocol scheme and false otherwise. bool nsExternalProtocolHandler::HaveExternalProtocolHandler(nsIURI * aURI) { bool haveHandler = false; if (aURI) { nsAutoCString scheme; aURI->GetScheme(scheme); nsCOMPtr extProtSvc(do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID)); if (extProtSvc) extProtSvc->ExternalProtocolHandlerExists(scheme.get(), &haveHandler); } return haveHandler; } NS_IMETHODIMP nsExternalProtocolHandler::GetProtocolFlags(uint32_t *aUritype) { // Make it norelative since it is a simple uri *aUritype = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_ANYONE | URI_NON_PERSISTABLE | URI_DOES_NOT_RETURN_DATA; return NS_OK; } NS_IMETHODIMP nsExternalProtocolHandler::NewURI(const nsACString &aSpec, const char *aCharset, // ignore charset info nsIURI *aBaseURI, nsIURI **_retval) { nsresult rv; nsCOMPtr uri = do_CreateInstance(NS_SIMPLEURI_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = uri->SetSpec(aSpec); NS_ENSURE_SUCCESS(rv, rv); NS_ADDREF(*_retval = uri); return NS_OK; } NS_IMETHODIMP nsExternalProtocolHandler::NewChannel2(nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** _retval) { // Only try to return a channel if we have a protocol handler for the url. // nsOSHelperAppService::LoadUriInternal relies on this to check trustedness // for some platforms at least. (win uses ::ShellExecute and unix uses // gnome_url_show.) bool haveExternalHandler = HaveExternalProtocolHandler(aURI); if (haveExternalHandler) { nsCOMPtr channel = new nsExtProtocolChannel(); if (!channel) return NS_ERROR_OUT_OF_MEMORY; ((nsExtProtocolChannel*) channel.get())->SetURI(aURI); channel->SetOriginalURI(aURI); // set the loadInfo on the new channel ((nsExtProtocolChannel*) channel.get())->SetLoadInfo(aLoadInfo); if (_retval) { *_retval = channel; NS_IF_ADDREF(*_retval); return NS_OK; } } return NS_ERROR_UNKNOWN_PROTOCOL; } NS_IMETHODIMP nsExternalProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval) { return NewChannel2(aURI, nullptr, _retval); } /////////////////////////////////////////////////////////////////////// // External protocol handler interface implementation ////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsExternalProtocolHandler::ExternalAppExistsForScheme(const nsACString& aScheme, bool *_retval) { nsCOMPtr extProtSvc(do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID)); if (extProtSvc) return extProtSvc->ExternalProtocolHandlerExists( PromiseFlatCString(aScheme).get(), _retval); // In case we don't have external protocol service. *_retval = false; return NS_OK; }