michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set expandtab ts=2 sw=2 sts=2 cin: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "AppProtocolHandler.h" michael@0: #include "nsBaseChannel.h" michael@0: #include "nsJARChannel.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsIAppsService.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsXULAppAPI.h" michael@0: michael@0: /** michael@0: * This dummy channel implementation only provides enough functionality michael@0: * to return a fake 404 error when the caller asks for an app:// URL michael@0: * containing an unknown appId. michael@0: */ michael@0: class DummyChannel : public nsIJARChannel michael@0: , nsRunnable michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIREQUEST michael@0: NS_DECL_NSICHANNEL michael@0: NS_DECL_NSIJARCHANNEL michael@0: michael@0: DummyChannel(); michael@0: michael@0: NS_IMETHODIMP Run(); michael@0: michael@0: private: michael@0: bool mPending; michael@0: uint32_t mSuspendCount; michael@0: nsCOMPtr mListenerContext; michael@0: nsCOMPtr mListener; michael@0: nsCOMPtr mLoadGroup; michael@0: nsLoadFlags mLoadFlags; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(DummyChannel, nsIRequest, nsIChannel, nsIJARChannel) michael@0: michael@0: DummyChannel::DummyChannel() : mPending(false) michael@0: , mSuspendCount(0) michael@0: , mLoadFlags(LOAD_NORMAL) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetName(nsACString &result) michael@0: { michael@0: result = "dummy_app_channel"; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetStatus(nsresult *aStatus) michael@0: { michael@0: *aStatus = NS_ERROR_FILE_NOT_FOUND; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::IsPending(bool *aResult) michael@0: { michael@0: *aResult = mPending; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::Suspend() michael@0: { michael@0: mSuspendCount++; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::Resume() michael@0: { michael@0: if (mSuspendCount <= 0) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: if (--mSuspendCount == 0) { michael@0: NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::Open(nsIInputStream**) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::AsyncOpen(nsIStreamListener* aListener, nsISupports* aContext) michael@0: { michael@0: mListener = aListener; michael@0: mListenerContext = aContext; michael@0: mPending = true; michael@0: michael@0: if (mLoadGroup) { michael@0: mLoadGroup->AddRequest(this, aContext); michael@0: } michael@0: michael@0: if (mSuspendCount == 0) { michael@0: NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsIJarChannel, needed for XHR to turn NS_ERROR_FILE_NOT_FOUND into michael@0: // a 404 error. michael@0: NS_IMETHODIMP DummyChannel::GetIsUnsafe(bool *aResult) michael@0: { michael@0: *aResult = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::SetAppURI(nsIURI *aURI) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::Run() michael@0: { michael@0: nsresult rv = mListener->OnStartRequest(this, mListenerContext); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: mPending = false; michael@0: rv = mListener->OnStopRequest(this, mListenerContext, NS_ERROR_FILE_NOT_FOUND); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (mLoadGroup) { michael@0: mLoadGroup->RemoveRequest(this, mListenerContext, NS_ERROR_FILE_NOT_FOUND); michael@0: } michael@0: michael@0: mListener = nullptr; michael@0: mListenerContext = nullptr; michael@0: rv = SetNotificationCallbacks(nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::Cancel(nsresult) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup) michael@0: { michael@0: *aLoadGroup = mLoadGroup; michael@0: NS_IF_ADDREF(*aLoadGroup); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) michael@0: { michael@0: mLoadGroup = aLoadGroup; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetLoadFlags(nsLoadFlags *aLoadFlags) michael@0: { michael@0: *aLoadFlags = mLoadFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::SetLoadFlags(nsLoadFlags aLoadFlags) michael@0: { michael@0: mLoadFlags = aLoadFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetOriginalURI(nsIURI**) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::SetOriginalURI(nsIURI*) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetOwner(nsISupports**) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::SetOwner(nsISupports*) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetNotificationCallbacks(nsIInterfaceRequestor**) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::SetNotificationCallbacks(nsIInterfaceRequestor*) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetSecurityInfo(nsISupports**) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetContentType(nsACString&) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::SetContentType(const nsACString&) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetContentCharset(nsACString&) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::SetContentCharset(const nsACString&) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetContentLength(int64_t*) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::SetContentLength(int64_t) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetContentDisposition(uint32_t*) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::SetContentDisposition(uint32_t) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetURI(nsIURI**) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetContentDispositionFilename(nsAString&) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::SetContentDispositionFilename(nsAString const &) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP DummyChannel::GetContentDispositionHeader(nsACString&) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: /** michael@0: * app:// protocol implementation. michael@0: */ michael@0: michael@0: AppProtocolHandler::AppProtocolHandler() { michael@0: } michael@0: michael@0: AppProtocolHandler::~AppProtocolHandler() { michael@0: mAppInfoCache.Clear(); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(AppProtocolHandler, nsIProtocolHandler) michael@0: michael@0: /* static */ michael@0: nsresult michael@0: AppProtocolHandler::Create(nsISupports* aOuter, michael@0: const nsIID& aIID, michael@0: void* *aResult) michael@0: { michael@0: // Instantiate the service here since that intializes gJarHandler, which we michael@0: // use indirectly (via our new JarChannel) in NewChannel. michael@0: nsCOMPtr jarInitializer( michael@0: do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "jar")); michael@0: AppProtocolHandler* ph = new AppProtocolHandler(); michael@0: if (ph == nullptr) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: NS_ADDREF(ph); michael@0: nsresult rv = ph->QueryInterface(aIID, aResult); michael@0: NS_RELEASE(ph); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AppProtocolHandler::GetScheme(nsACString &aResult) michael@0: { michael@0: aResult.AssignLiteral("app"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AppProtocolHandler::GetDefaultPort(int32_t *aResult) michael@0: { michael@0: // No ports for the app protocol. michael@0: *aResult = -1; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AppProtocolHandler::GetProtocolFlags(uint32_t *aResult) michael@0: { michael@0: *aResult = URI_NOAUTH | michael@0: URI_DANGEROUS_TO_LOAD | michael@0: URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AppProtocolHandler::NewURI(const nsACString &aSpec, michael@0: const char *aCharset, // ignore charset info michael@0: nsIURI *aBaseURI, michael@0: nsIURI **result) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr surl(do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = surl->Init(nsIStandardURL::URLTYPE_STANDARD, -1, aSpec, aCharset, aBaseURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr url(do_QueryInterface(surl, &rv)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: url.forget(result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We map app://ABCDEF/path/to/file.ext to michael@0: // jar:file:///path/to/profile/webapps/ABCDEF/application.zip!/path/to/file.ext michael@0: NS_IMETHODIMP michael@0: AppProtocolHandler::NewChannel(nsIURI* aUri, nsIChannel* *aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aUri); michael@0: nsRefPtr channel = new nsJARChannel(); michael@0: michael@0: nsAutoCString host; michael@0: nsresult rv = aUri->GetHost(host); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString fileSpec; michael@0: nsCOMPtr url = do_QueryInterface(aUri); michael@0: rv = url->GetFilePath(fileSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mozilla::dom::AppInfo *appInfo; michael@0: michael@0: if (!mAppInfoCache.Get(host, &appInfo)) { michael@0: nsCOMPtr appsService = do_GetService(APPS_SERVICE_CONTRACTID); michael@0: if (!appsService) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mozilla::AutoSafeJSContext cx; michael@0: JS::RootedValue jsInfo(cx); michael@0: rv = appsService->GetAppInfo(NS_ConvertUTF8toUTF16(host), &jsInfo); michael@0: if (NS_FAILED(rv) || !jsInfo.isObject()) { michael@0: // Return a DummyChannel. michael@0: printf_stderr("!! Creating a dummy channel for %s (no appInfo)\n", host.get()); michael@0: NS_IF_ADDREF(*aResult = new DummyChannel()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: appInfo = new mozilla::dom::AppInfo(); michael@0: JSAutoCompartment ac(cx, &jsInfo.toObject()); michael@0: if (!appInfo->Init(cx, jsInfo) || appInfo->mPath.IsEmpty()) { michael@0: // Return a DummyChannel. michael@0: printf_stderr("!! Creating a dummy channel for %s (invalid appInfo)\n", host.get()); michael@0: NS_IF_ADDREF(*aResult = new DummyChannel()); michael@0: return NS_OK; michael@0: } michael@0: mAppInfoCache.Put(host, appInfo); michael@0: } michael@0: michael@0: bool noRemote = (appInfo->mIsCoreApp || michael@0: XRE_GetProcessType() == GeckoProcessType_Default); michael@0: michael@0: // In-parent and CoreApps can directly access files, so use jar:file:// michael@0: nsAutoCString jarSpec(noRemote ? "jar:file://" michael@0: : "jar:remoteopenfile://"); michael@0: jarSpec += NS_ConvertUTF16toUTF8(appInfo->mPath) + michael@0: NS_LITERAL_CSTRING("/application.zip!") + michael@0: fileSpec; michael@0: michael@0: nsCOMPtr jarURI; michael@0: rv = NS_NewURI(getter_AddRefs(jarURI), michael@0: jarSpec, nullptr, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = channel->Init(jarURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = channel->SetAppURI(aUri); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = channel->SetOriginalURI(aUri); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: channel.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: AppProtocolHandler::AllowPort(int32_t aPort, const char *aScheme, bool *aRetval) michael@0: { michael@0: // No port allowed for this scheme. michael@0: *aRetval = false; michael@0: return NS_OK; michael@0: } michael@0: