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