Bug 815523 - Remote the app: and jar: protocols. r=fabrice,mwu,jdm

This commit is contained in:
Jason Duell 2012-12-22 05:56:21 -08:00
parent fccec36a4b
commit e7f09d1290
28 changed files with 1398 additions and 121 deletions

View File

@ -58,6 +58,16 @@ AppsService.prototype = {
return DOMApplicationRegistry.getAppFromObserverMessage(aMessage);
},
getCoreAppsBasePath: function getCoreAppsBasePath() {
debug("getCoreAppsBasePath()");
return DOMApplicationRegistry.getCoreAppsBasePath();
},
getWebAppsBasePath: function getWebAppsBasePath() {
debug("getWebAppsBasePath()");
return DOMApplicationRegistry.getWebAppsBasePath();
},
classID : APPS_SERVICE_CID,
QueryInterface : XPCOMUtils.generateQI([Ci.nsIAppsService])
}

View File

@ -86,6 +86,15 @@ this.DOMApplicationRegistry = {
getAppFromObserverMessage: function getAppFromObserverMessage(aMessage) {
debug("getAppFromObserverMessage " + aMessage);
return AppsUtils.getAppFromObserverMessage(this.webapps. aMessage);
},
getCoreAppsBasePath: function getCoreAppsBasePath() {
debug("getCoreAppsBasePath() not yet supported on child!");
return null;
},
getWebAppsBasePath: function getWebAppsBasePath() {
debug("getWebAppsBasePath() not yet supported on child!");
return null;
}
}

View File

@ -38,7 +38,9 @@ this.AppsUtils = {
manifestURL: aApp.manifestURL,
appStatus: aApp.appStatus,
removable: aApp.removable,
id: aApp.id,
localId: aApp.localId,
basePath: aApp.basePath,
progress: aApp.progress || 0.0,
installState: aApp.installState || "installed",
downloadAvailable: aApp.downloadAvailable,

View File

@ -75,7 +75,7 @@ this.DOMApplicationRegistry = {
"Webapps:GetSelf", "Webapps:CheckInstalled",
"Webapps:GetInstalled", "Webapps:GetNotInstalled",
"Webapps:Launch", "Webapps:GetAll",
"Webapps:InstallPackage", "Webapps:GetBasePath",
"Webapps:InstallPackage", "Webapps:GetAppInfo",
"Webapps:GetList", "Webapps:RegisterForMessages",
"Webapps:UnregisterForMessages",
"Webapps:CancelDownload", "Webapps:CheckForUpdate",
@ -110,6 +110,9 @@ this.DOMApplicationRegistry = {
this.webapps = aData;
let appDir = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], false);
for (let id in this.webapps) {
this.webapps[id].id = id;
// Make sure we have a localId
if (this.webapps[id].localId === undefined) {
this.webapps[id].localId = this._nextLocalId();
@ -215,7 +218,7 @@ this.DOMApplicationRegistry = {
let app = this.webapps[aId];
let baseDir;
try {
baseDir = FileUtils.getDir("coreAppsDir", ["webapps", aId], true, true);
baseDir = FileUtils.getDir("coreAppsDir", ["webapps", aId], false);
} catch(e) {
// In ENG builds, we don't have apps in coreAppsDir.
return;
@ -320,6 +323,8 @@ this.DOMApplicationRegistry = {
this.webapps[id] = aData[id];
this.webapps[id].basePath = appDir.path;
this.webapps[id].id = id;
// Create a new localId.
this.webapps[id].localId = this._nextLocalId();
@ -779,12 +784,13 @@ this.DOMApplicationRegistry = {
case "Webapps:InstallPackage":
this.doInstallPackage(msg, mm);
break;
case "Webapps:GetBasePath":
case "Webapps:GetAppInfo":
if (!this.webapps[msg.id]) {
debug("No webapp for " + msg.id);
return null;
}
return this.webapps[msg.id].basePath;
return { "basePath": this.webapps[msg.id].basePath + "/",
"isCoreApp": !this.webapps[msg.id].removable };
break;
case "Webapps:RegisterForMessages":
this.addMessageListener(msg, mm);
@ -1479,9 +1485,9 @@ this.DOMApplicationRegistry = {
}
} else {
id = this.makeAppId();
app.id = id;
localId = this._nextLocalId();
}
app.id = id;
let manifestName = "manifest.webapp";
if (aData.isPackage) {
@ -1506,6 +1512,7 @@ this.DOMApplicationRegistry = {
let appNote = JSON.stringify(appObject);
appNote.id = id;
appObject.id = id;
appObject.localId = localId;
appObject.basePath = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true).path;
let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
@ -2189,6 +2196,14 @@ this.DOMApplicationRegistry = {
return AppsUtils.getAppFromObserverMessage(this.webapps, aMessage);
},
getCoreAppsBasePath: function() {
return FileUtils.getDir("coreAppsDir", ["webapps"], false).path;
},
getWebAppsBasePath: function getWebAppsBasePath() {
return FileUtils.getDir(DIRECTORY_NAME, ["webapps"], false).path;
},
getAllWithoutManifests: function(aCallback) {
let result = {};
for (let id in this.webapps) {

View File

@ -11,7 +11,7 @@
* We expose Gecko-internal helpers related to "web apps" through this
* sub-interface.
*/
[scriptable, uuid(8ac7827f-f982-40fb-be11-ba16dd665635)]
[scriptable, uuid(cfa75628-4d31-481f-b51e-fe0ce18fa98f)]
interface mozIApplication: mozIDOMApplication
{
/* Return true if this app has |permission|. */
@ -20,9 +20,15 @@ interface mozIApplication: mozIDOMApplication
/* Application status as defined in nsIPrincipal. */
readonly attribute unsigned short appStatus;
/* Returns the local id of the app (not the uuid used for sync). */
/* Returns the uuid of the app. */
readonly attribute DOMString id;
/* Returns the local id of the app. */
readonly attribute unsigned long localId;
/* Returns the base directory for the app */
readonly attribute DOMString basePath;
/* Name copied from the manifest */
readonly attribute DOMString name;

View File

@ -16,7 +16,7 @@ interface mozIApplication;
* This service allows accessing some DOMApplicationRegistry methods from
* non-javascript code.
*/
[scriptable, uuid(4a182c18-dbdf-4f9c-93a0-0f0cffb88ed0)]
[scriptable, uuid(e65f9397-e191-4273-aa5f-f13c185ce63b)]
interface nsIAppsService : nsISupports
{
mozIDOMApplication getAppByManifestURL(in DOMString manifestURL);
@ -50,4 +50,14 @@ interface nsIAppsService : nsISupports
* Returns the CSP associated to this localId.
*/
DOMString getCSPByLocalId(in unsigned long localId);
/**
* Returns the basepath for core apps
*/
DOMString getCoreAppsBasePath();
/**
* Returns the basepath for regular packaged apps
*/
DOMString getWebAppsBasePath();
};

View File

@ -185,7 +185,7 @@ interface nsIZipReader : nsISupports
////////////////////////////////////////////////////////////////////////////////
// nsIZipReaderCache
[scriptable, uuid(72fc56e5-3e6e-4d11-8967-26ab96071032)]
[scriptable, uuid(748050ac-3ab6-4472-bc2a-cb1564ac6a81)]
interface nsIZipReaderCache : nsISupports
{
/**
@ -210,6 +210,11 @@ interface nsIZipReaderCache : nsISupports
*/
nsIZipReader getZip(in nsIFile zipFile);
/**
* returns true if this zipreader already has this file cached
*/
bool isCached(in nsIFile zipFile);
/**
* Returns a (possibly shared) nsIZipReader for a zip inside another zip
*

View File

@ -1062,6 +1062,27 @@ nsZipReaderCache::~nsZipReaderCache()
#endif
}
NS_IMETHODIMP
nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult)
{
NS_ENSURE_ARG_POINTER(zipFile);
nsresult rv;
nsCOMPtr<nsIZipReader> antiLockZipGrip;
MutexAutoLock lock(mLock);
nsAutoCString uri;
rv = zipFile->GetNativePath(uri);
if (NS_FAILED(rv))
return rv;
uri.Insert(NS_LITERAL_CSTRING("file:"), 0);
nsCStringKey key(uri);
*aResult = mZips.Exists(&key);
return NS_OK;
}
NS_IMETHODIMP
nsZipReaderCache::GetZip(nsIFile* zipFile, nsIZipReader* *result)
{

View File

@ -1,6 +1,6 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 ts=8 et tw=80 : */
/* 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/. */
@ -18,10 +18,14 @@
#include "nsIScriptSecurityManager.h"
#include "nsIPrincipal.h"
#include "nsIFileURL.h"
#include "nsXULAppAPI.h"
#include "mozilla/Preferences.h"
#include "mozilla/net/RemoteOpenFileChild.h"
#include "nsITabChild.h"
using namespace mozilla;
using namespace mozilla::net;
static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
@ -188,6 +192,7 @@ nsJARChannel::nsJARChannel()
, mStatus(NS_OK)
, mIsPending(false)
, mIsUnsafe(true)
, mOpeningRemote(false)
{
#if defined(PR_LOGGING)
if (!gJarProtocolLog)
@ -205,13 +210,14 @@ nsJARChannel::~nsJARChannel()
NS_RELEASE(handler); // NULL parameter
}
NS_IMPL_ISUPPORTS_INHERITED6(nsJARChannel,
NS_IMPL_ISUPPORTS_INHERITED7(nsJARChannel,
nsHashPropertyBag,
nsIRequest,
nsIChannel,
nsIStreamListener,
nsIRequestObserver,
nsIDownloadObserver,
nsIRemoteOpenFileListener,
nsIJARChannel)
nsresult
@ -263,9 +269,9 @@ nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache, nsJARInputThunk **resu
nsCOMPtr<nsIZipReader> reader;
if (jarCache) {
if (mInnerJarEntry.IsEmpty())
rv = jarCache->GetZip(mJarFile, getter_AddRefs(reader));
rv = jarCache->GetZip(clonedFile, getter_AddRefs(reader));
else
rv = jarCache->GetInnerZip(mJarFile, mInnerJarEntry,
rv = jarCache->GetInnerZip(clonedFile, mInnerJarEntry,
getter_AddRefs(reader));
} else {
// create an uncached jar reader
@ -273,7 +279,7 @@ nsJARChannel::CreateJarInput(nsIZipReaderCache *jarCache, nsJARInputThunk **resu
if (NS_FAILED(rv))
return rv;
rv = outerReader->Open(mJarFile);
rv = outerReader->Open(clonedFile);
if (NS_FAILED(rv))
return rv;
@ -334,6 +340,37 @@ nsJARChannel::LookupFile()
if (fileURL)
fileURL->GetFile(getter_AddRefs(mJarFile));
}
// if we're in child process and have special "remoteopenfile:://" scheme,
// create special nsIFile that gets file handle from parent when opened.
if (!mJarFile && XRE_GetProcessType() != GeckoProcessType_Default) {
nsAutoCString scheme;
nsresult rv = mJarBaseURI->GetScheme(scheme);
if (NS_SUCCEEDED(rv) && scheme.EqualsLiteral("remoteopenfile")) {
nsRefPtr<RemoteOpenFileChild> remoteFile = new RemoteOpenFileChild();
rv = remoteFile->Init(mJarBaseURI);
NS_ENSURE_SUCCESS(rv, rv);
mJarFile = remoteFile;
nsIZipReaderCache *jarCache = gJarHandler->JarCache();
if (jarCache) {
bool cached = false;
rv = jarCache->IsCached(mJarFile, &cached);
if (NS_SUCCEEDED(rv) && cached) {
// zipcache already has file mmapped: don't open on parent,
// just return and proceed to cache hit in CreateJarInput()
return NS_OK;
}
}
// Open file on parent: OnRemoteFileOpenComplete called when done
nsCOMPtr<nsITabChild> tabChild;
NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, tabChild);
rv = remoteFile->AsyncRemoteFileOpen(PR_RDONLY, this, tabChild.get());
NS_ENSURE_SUCCESS(rv, rv);
mOpeningRemote = true;
}
}
// try to handle a nested jar
if (!mJarFile) {
nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(mJarBaseURI);
@ -655,7 +692,7 @@ nsJARChannel::Open(nsIInputStream **stream)
return NS_ERROR_NOT_IMPLEMENTED;
}
nsCOMPtr<nsJARInputThunk> input;
nsRefPtr<nsJARInputThunk> input;
rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input));
if (NS_FAILED(rv))
return rv;
@ -702,12 +739,13 @@ nsJARChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctx)
rv = NS_OpenURI(mDownloader, nullptr, mJarBaseURI, nullptr,
mLoadGroup, mCallbacks,
mLoadFlags & ~(LOAD_DOCUMENT_URI | LOAD_CALL_CONTENT_SNIFFERS));
}
else {
} else if (mOpeningRemote) {
// nothing to do: already asked parent to open file.
} else {
// local files are always considered safe
mIsUnsafe = false;
nsCOMPtr<nsJARInputThunk> input;
nsRefPtr<nsJARInputThunk> input;
rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input));
if (NS_SUCCEEDED(rv)) {
// create input stream pump and call AsyncRead as a block
@ -845,7 +883,7 @@ nsJARChannel::OnDownloadComplete(nsIDownloader *downloader,
if (NS_SUCCEEDED(status)) {
mJarFile = file;
nsCOMPtr<nsJARInputThunk> input;
nsRefPtr<nsJARInputThunk> input;
rv = CreateJarInput(nullptr, getter_AddRefs(input));
if (NS_SUCCEEDED(rv)) {
// create input stream pump
@ -865,6 +903,38 @@ nsJARChannel::OnDownloadComplete(nsIDownloader *downloader,
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsIRemoteOpenFileListener
//-----------------------------------------------------------------------------
nsresult
nsJARChannel::OnRemoteFileOpenComplete(nsresult aOpenStatus)
{
nsresult rv = aOpenStatus;
if (NS_SUCCEEDED(rv)) {
// files on parent are always considered safe
mIsUnsafe = false;
nsRefPtr<nsJARInputThunk> input;
rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input));
if (NS_SUCCEEDED(rv)) {
// create input stream pump and call AsyncRead as a block
rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input);
if (NS_SUCCEEDED(rv))
rv = mPump->AsyncRead(this, nullptr);
}
}
if (NS_FAILED(rv)) {
mStatus = rv;
OnStartRequest(nullptr, nullptr);
OnStopRequest(nullptr, nullptr, mStatus);
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsIStreamListener
//-----------------------------------------------------------------------------

View File

@ -12,6 +12,7 @@
#include "nsIInterfaceRequestor.h"
#include "nsIProgressEventSink.h"
#include "nsIStreamListener.h"
#include "nsIRemoteOpenFileListener.h"
#include "nsIZipReader.h"
#include "nsIDownloader.h"
#include "nsILoadGroup.h"
@ -29,6 +30,7 @@ class nsJARInputThunk;
class nsJARChannel : public nsIJARChannel
, public nsIDownloadObserver
, public nsIStreamListener
, public nsIRemoteOpenFileListener
, public nsHashPropertyBag
{
public:
@ -39,6 +41,7 @@ public:
NS_DECL_NSIDOWNLOADOBSERVER
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIREMOTEOPENFILELISTENER
nsJARChannel();
virtual ~nsJARChannel();
@ -76,6 +79,7 @@ private:
nsresult mStatus;
bool mIsPending;
bool mIsUnsafe;
bool mOpeningRemote;
nsCOMPtr<nsIStreamListener> mDownloader;
nsCOMPtr<nsIInputStreamPump> mPump;

View File

@ -0,0 +1,17 @@
/*
* If we're running in e10s, determines whether we're in child directory or
* not.
*/
var inChild = false;
var filePrefix = "";
try {
inChild = Components.classes["@mozilla.org/xre/runtime;1"].
getService(Components.interfaces.nsIXULRuntime).processType
!= Components.interfaces.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
if (inChild) {
// use "jar:remoteopenfile://" in child instead of "jar:file://"
filePrefix = "remoteopen";
}
}
catch (e) { }

View File

@ -27,7 +27,8 @@ const nsIBinaryInputStream = ctor("@mozilla.org/binaryinputstream;1",
const fileBase = "test_bug637286.zip";
const file = do_get_file("data/" + fileBase);
const jarBase = "jar:" + ios.newFileURI(file).spec + "!";
// on child we'll test with jar:remoteopenfile:// instead of jar:file://
const jarBase = "jar:" + filePrefix + ios.newFileURI(file).spec + "!";
const tmpDir = dirSvc.get("TmpD", Ci.nsIFile);
function Listener(callback) {
@ -65,25 +66,10 @@ Listener.prototype = {
}
};
/**
* Basic reading test for synchronously opened jar channels
*/
add_test(function testSync() {
var uri = jarBase + "/inner40.zip";
var chan = ios.newChannel(uri, null, null);
var stream = chan.open();
do_check_true(chan.contentLength > 0);
do_check_eq(stream.available(), chan.contentLength);
stream.close();
stream.close(); // should still not throw
run_next_test();
});
/**
* Basic reading test for asynchronously opened jar channel
*/
add_test(function testAsync() {
function testAsync() {
var uri = jarBase + "/inner40.zip";
var chan = ios.newChannel(uri, null, null);
do_check_true(chan.contentLength < 0);
@ -95,93 +81,122 @@ add_test(function testAsync() {
run_next_test();
}), null);
});
}
/**
* Basic reading test for synchronously opened, nested jar channels
*/
add_test(function testSyncNested() {
var uri = "jar:" + jarBase + "/inner40.zip!/foo";
var chan = ios.newChannel(uri, null, null);
var stream = chan.open();
do_check_true(chan.contentLength > 0);
do_check_eq(stream.available(), chan.contentLength);
stream.close();
stream.close(); // should still not throw
add_test(testAsync);
// Run same test again so we test the codepath for a zipcache hit
add_test(testAsync);
run_next_test();
});
/**
* Basic reading test for asynchronously opened, nested jar channels
*/
add_test(function testAsyncNested(next) {
var uri = "jar:" + jarBase + "/inner40.zip!/foo";
var chan = ios.newChannel(uri, null, null);
chan.asyncOpen(new Listener(function(l) {
do_check_true(chan.contentLength > 0);
do_check_true(l.gotStartRequest);
do_check_true(l.gotStopRequest);
do_check_eq(l.available, chan.contentLength);
// In e10s child processes we don't currently support
// 1) synchronously opening jar files on parent
// 2) nested jar channels in e10s: (app:// doesn't use them).
// 3) we can't do file lock checks on android, so skip those tests too.
if (!inChild) {
run_next_test();
}), null);
});
/**
* Basic reading test for synchronously opened jar channels
*/
add_test(function testSync() {
var uri = jarBase + "/inner40.zip";
var chan = ios.newChannel(uri, null, null);
var stream = chan.open();
do_check_true(chan.contentLength > 0);
do_check_eq(stream.available(), chan.contentLength);
stream.close();
stream.close(); // should still not throw
/**
* Verify that file locks are released when closing a synchronously
* opened jar channel stream
*/
add_test(function testSyncCloseUnlocks() {
var copy = tmpDir.clone();
copy.append(fileBase);
file.copyTo(copy.parent, copy.leafName);
run_next_test();
});
var uri = "jar:" + ios.newFileURI(copy).spec + "!/inner40.zip";
var chan = ios.newChannel(uri, null, null);
var stream = chan.open();
do_check_true(chan.contentLength > 0);
stream.close();
// Drop any jar caches
obs.notifyObservers(null, "chrome-flush-caches", null);
/**
* Basic reading test for synchronously opened, nested jar channels
*/
add_test(function testSyncNested() {
var uri = "jar:" + jarBase + "/inner40.zip!/foo";
var chan = ios.newChannel(uri, null, null);
var stream = chan.open();
do_check_true(chan.contentLength > 0);
do_check_eq(stream.available(), chan.contentLength);
stream.close();
stream.close(); // should still not throw
try {
copy.remove(false);
}
catch (ex) {
do_throw(ex);
}
run_next_test();
});
run_next_test();
});
/**
* Basic reading test for asynchronously opened, nested jar channels
*/
add_test(function testAsyncNested(next) {
var uri = "jar:" + jarBase + "/inner40.zip!/foo";
var chan = ios.newChannel(uri, null, null);
chan.asyncOpen(new Listener(function(l) {
do_check_true(chan.contentLength > 0);
do_check_true(l.gotStartRequest);
do_check_true(l.gotStopRequest);
do_check_eq(l.available, chan.contentLength);
/**
* Verify that file locks are released when closing an asynchronously
* opened jar channel stream
*/
add_test(function testAsyncCloseUnlocks() {
var copy = tmpDir.clone();
copy.append(fileBase);
file.copyTo(copy.parent, copy.leafName);
run_next_test();
}), null);
});
var uri = "jar:" + ios.newFileURI(copy).spec + "!/inner40.zip";
var chan = ios.newChannel(uri, null, null);
chan.asyncOpen(new Listener(function (l) {
do_check_true(chan.contentLength > 0);
/**
* Verify that file locks are released when closing a synchronously
* opened jar channel stream
*/
add_test(function testSyncCloseUnlocks() {
var copy = tmpDir.clone();
copy.append(fileBase);
file.copyTo(copy.parent, copy.leafName);
// Drop any jar caches
obs.notifyObservers(null, "chrome-flush-caches", null);
var uri = "jar:" + ios.newFileURI(copy).spec + "!/inner40.zip";
var chan = ios.newChannel(uri, null, null);
var stream = chan.open();
do_check_true(chan.contentLength > 0);
stream.close();
try {
copy.remove(false);
}
catch (ex) {
do_throw(ex);
}
// Drop any jar caches
obs.notifyObservers(null, "chrome-flush-caches", null);
run_next_test();
}), null);
});
try {
copy.remove(false);
}
catch (ex) {
do_throw(ex);
}
run_next_test();
});
/**
* Verify that file locks are released when closing an asynchronously
* opened jar channel stream
*/
add_test(function testAsyncCloseUnlocks() {
var copy = tmpDir.clone();
copy.append(fileBase);
file.copyTo(copy.parent, copy.leafName);
var uri = "jar:" + ios.newFileURI(copy).spec + "!/inner40.zip";
var chan = ios.newChannel(uri, null, null);
chan.asyncOpen(new Listener(function (l) {
do_check_true(chan.contentLength > 0);
// Drop any jar caches
obs.notifyObservers(null, "chrome-flush-caches", null);
try {
copy.remove(false);
}
catch (ex) {
do_throw(ex);
}
run_next_test();
}), null);
});
} // if !inChild
function run_test() run_next_test();

View File

@ -0,0 +1,7 @@
//
// Run test script in content process instead of chrome (xpcshell's default)
//
function run_test() {
run_test_in_child("../unit/test_jarchannel.js");
}

View File

@ -1,8 +1,10 @@
[DEFAULT]
head =
head = head_ipc.js
tail =
[test_jarchannel.js]
[test_jarchannel_e10s.js]
skip-if = os == "mac" || os == "windows"
[test_bug278262.js]
[test_bug333423.js]
[test_bug336691.js]

View File

@ -16,6 +16,11 @@ LIBRARY_NAME = neckoipc_s
LIBXUL_LIBRARY = 1
FORCE_STATIC_LIB = 1
EXPORT_LIBRARY = 1
XPIDL_MODULE = necko_ipc
XPIDLSRCS = \
nsIRemoteOpenFileListener.idl \
$(NULL)
EXPORTS_NAMESPACES = mozilla/net
@ -25,12 +30,16 @@ EXPORTS_mozilla/net = \
NeckoCommon.h \
NeckoMessageUtils.h \
ChannelEventQueue.h \
RemoteOpenFileParent.h \
RemoteOpenFileChild.h \
$(NULL)
CPPSRCS = \
NeckoChild.cpp \
NeckoParent.cpp \
ChannelEventQueue.cpp \
RemoteOpenFileParent.cpp \
RemoteOpenFileChild.cpp \
$(NULL)
LOCAL_INCLUDES += \

View File

@ -13,6 +13,7 @@
#include "mozilla/net/WyciwygChannelChild.h"
#include "mozilla/net/FTPChannelChild.h"
#include "mozilla/net/WebSocketChannelChild.h"
#include "mozilla/net/RemoteOpenFileChild.h"
#include "mozilla/dom/network/TCPSocketChild.h"
#include "mozilla/Preferences.h"
@ -172,5 +173,22 @@ NeckoChild::DeallocPTCPSocket(PTCPSocketChild* child)
return true;
}
PRemoteOpenFileChild*
NeckoChild::AllocPRemoteOpenFile(const URIParams&, PBrowserChild*)
{
// We don't allocate here: instead we always use IPDL constructor that takes
// an existing RemoteOpenFileChild
NS_NOTREACHED("AllocPRemoteOpenFile should not be called on child");
return nullptr;
}
bool
NeckoChild::DeallocPRemoteOpenFile(PRemoteOpenFileChild* aChild)
{
RemoteOpenFileChild *p = static_cast<RemoteOpenFileChild*>(aChild);
p->ReleaseIPDLReference();
return true;
}
}} // mozilla::net

View File

@ -43,6 +43,9 @@ protected:
const nsString& aBinaryType,
PBrowserChild* aBrowser);
virtual bool DeallocPTCPSocket(PTCPSocketChild*);
virtual PRemoteOpenFileChild* AllocPRemoteOpenFile(const URIParams&,
PBrowserChild*);
virtual bool DeallocPRemoteOpenFile(PRemoteOpenFileChild*);
};
/**

View File

@ -12,11 +12,15 @@
#include "mozilla/net/WyciwygChannelParent.h"
#include "mozilla/net/FTPChannelParent.h"
#include "mozilla/net/WebSocketChannelParent.h"
#include "mozilla/net/RemoteOpenFileParent.h"
#include "mozilla/dom/TabParent.h"
#include "mozilla/dom/network/TCPSocketParent.h"
#include "mozilla/ipc/URIUtils.h"
#include "mozilla/Preferences.h"
#include "nsHTMLDNSPrefetch.h"
#include "nsIAppsService.h"
#include "nsEscape.h"
using mozilla::dom::TabParent;
using mozilla::net::PTCPSocketParent;
@ -32,6 +36,21 @@ static const char kPrefDisableIPCSecurity[] = "network.disable.ipc.security";
NeckoParent::NeckoParent()
{
Preferences::AddBoolVarCache(&gDisableIPCSecurity, kPrefDisableIPCSecurity);
if (!gDisableIPCSecurity) {
// cache values for core/packaged apps basepaths
nsAutoString corePath, webPath;
nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
if (appsService) {
appsService->GetCoreAppsBasePath(corePath);
appsService->GetWebAppsBasePath(webPath);
}
// corePath may be empty: we don't use it for all build types
MOZ_ASSERT(!webPath.IsEmpty());
LossyCopyUTF16toASCII(corePath, mCoreAppsBasePath);
LossyCopyUTF16toASCII(webPath, mWebAppsBasePath);
}
}
NeckoParent::~NeckoParent()
@ -149,6 +168,116 @@ NeckoParent::DeallocPTCPSocket(PTCPSocketParent* actor)
return true;
}
PRemoteOpenFileParent*
NeckoParent::AllocPRemoteOpenFile(const URIParams& aURI,
PBrowserParent* aBrowser)
{
nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
if (!fileURL) {
return nullptr;
}
// security checks
if (!gDisableIPCSecurity) {
if (!aBrowser) {
NS_WARNING("NeckoParent::AllocPRemoteOpenFile: "
"FATAL error: missing TabParent: KILLING CHILD PROCESS\n");
return nullptr;
}
nsRefPtr<TabParent> tabParent = static_cast<TabParent*>(aBrowser);
uint32_t appId = tabParent->OwnOrContainingAppId();
nsCOMPtr<nsIAppsService> appsService =
do_GetService(APPS_SERVICE_CONTRACTID);
if (!appsService) {
return nullptr;
}
nsCOMPtr<mozIDOMApplication> domApp;
nsresult rv = appsService->GetAppByLocalId(appId, getter_AddRefs(domApp));
if (!domApp) {
return nullptr;
}
nsCOMPtr<mozIApplication> mozApp = do_QueryInterface(domApp);
if (!mozApp) {
return nullptr;
}
bool hasManage = false;
rv = mozApp->HasPermission("webapps-manage", &hasManage);
if (NS_FAILED(rv)) {
return nullptr;
}
nsAutoCString requestedPath;
fileURL->GetPath(requestedPath);
NS_UnescapeURL(requestedPath);
if (hasManage) {
// webapps-manage permission means allow reading any application.zip file
// in either the regular webapps directory, or the core apps directory (if
// we're using one).
NS_NAMED_LITERAL_CSTRING(appzip, "/application.zip");
nsAutoCString pathEnd;
requestedPath.Right(pathEnd, appzip.Length());
if (!pathEnd.Equals(appzip)) {
return nullptr;
}
nsAutoCString pathStart;
requestedPath.Left(pathStart, mWebAppsBasePath.Length());
if (!pathStart.Equals(mWebAppsBasePath)) {
if (mCoreAppsBasePath.IsEmpty()) {
return nullptr;
}
requestedPath.Left(pathStart, mCoreAppsBasePath.Length());
if (!pathStart.Equals(mCoreAppsBasePath)) {
return nullptr;
}
}
// Finally: make sure there are no "../" in URI.
// Note: not checking for symlinks (would cause I/O for each path
// component). So it's up to us to avoid creating symlinks that could
// provide attack vectors.
if (PL_strnstr(requestedPath.BeginReading(), "/../",
requestedPath.Length())) {
NS_WARNING("NeckoParent::AllocPRemoteOpenFile: "
"FATAL error: requested file URI contains '/../' "
"KILLING CHILD PROCESS\n");
return nullptr;
}
} else {
// regular packaged apps can only access their own application.zip file
nsAutoString basePath;
rv = mozApp->GetBasePath(basePath);
if (NS_FAILED(rv)) {
return nullptr;
}
nsAutoString uuid;
rv = mozApp->GetId(uuid);
if (NS_FAILED(rv)) {
return nullptr;
}
nsPrintfCString mustMatch("%s/%s/application.zip",
NS_LossyConvertUTF16toASCII(basePath).get(),
NS_LossyConvertUTF16toASCII(uuid).get());
if (!requestedPath.Equals(mustMatch)) {
NS_WARNING("NeckoParent::AllocPRemoteOpenFile: "
"FATAL error: requesting file other than application.zip: "
"KILLING CHILD PROCESS\n");
return nullptr;
}
}
}
RemoteOpenFileParent* parent = new RemoteOpenFileParent(fileURL);
return parent;
}
bool
NeckoParent::DeallocPRemoteOpenFile(PRemoteOpenFileParent* actor)
{
delete actor;
return true;
}
bool
NeckoParent::RecvHTMLDNSPrefetch(const nsString& hostname,
const uint16_t& flags)

View File

@ -39,6 +39,11 @@ protected:
const bool& useSSL,
const nsString& aBinaryType,
PBrowserParent* aBrowser);
virtual PRemoteOpenFileParent* AllocPRemoteOpenFile(
const URIParams& fileuri,
PBrowserParent* browser);
virtual bool DeallocPRemoteOpenFile(PRemoteOpenFileParent* actor);
virtual bool RecvPTCPSocketConstructor(PTCPSocketParent*,
const nsString& aHost,
const uint16_t& aPort,
@ -52,6 +57,9 @@ protected:
const uint16_t& flags,
const nsresult& reason);
private:
nsCString mCoreAppsBasePath;
nsCString mWebAppsBasePath;
};
} // namespace net

View File

@ -13,6 +13,8 @@ include protocol PWyciwygChannel;
include protocol PFTPChannel;
include protocol PWebSocket;
include protocol PTCPSocket;
include protocol PRemoteOpenFile;
include URIParams;
include "SerializedLoadContext.h";
@ -32,6 +34,7 @@ sync protocol PNecko
manages PFTPChannel;
manages PWebSocket;
manages PTCPSocket;
manages PRemoteOpenFile;
parent:
__delete__();
@ -44,6 +47,7 @@ parent:
PWebSocket(PBrowser browser);
PTCPSocket(nsString host, uint16_t port, bool useSSL, nsString binaryType,
nullable PBrowser browser);
PRemoteOpenFile(URIParams fileuri, nullable PBrowser browser);
HTMLDNSPrefetch(nsString hostname, uint16_t flags);
CancelHTMLDNSPrefetch(nsString hostname, uint16_t flags, nsresult reason);

View File

@ -0,0 +1,35 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
/* 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 protocol PNecko;
namespace mozilla {
namespace net {
/**
* Protocol to support RemoteOpenFile, an nsIFile that opens it's file handle on
* the parent instead of the child (since child lacks permission to do so).
*/
protocol PRemoteOpenFile
{
manager PNecko;
parent:
// Tell parent to open file. URI to open was passed and vetted for security in
// IPDL constructor: see NeckoParent::AllocPRemoteOpenFile()
AsyncOpenFile();
__delete__();
child:
// success/failure code, and if NS_SUCCEEDED(rv), an open file descriptor
FileOpened(FileDescriptor fd, nsresult rv);
};
} // namespace net
} // namespace mozilla

View File

@ -0,0 +1,644 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* 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 "mozilla/net/NeckoChild.h"
#include "mozilla/net/RemoteOpenFileChild.h"
#include "nsIRemoteOpenFileListener.h"
#include "mozilla/ipc/URIUtils.h"
// needed to alloc/free NSPR file descriptors
#include "private/pprio.h"
using namespace mozilla::ipc;
namespace mozilla {
namespace net {
NS_IMPL_THREADSAFE_ISUPPORTS2(RemoteOpenFileChild,
nsIFile,
nsIHashable)
RemoteOpenFileChild::RemoteOpenFileChild(const RemoteOpenFileChild& other)
: mNSPRFileDesc(other.mNSPRFileDesc)
, mAsyncOpenCalled(other.mAsyncOpenCalled)
, mNSPROpenCalled(other.mNSPROpenCalled)
{
// Note: don't clone mListener or we'll have a refcount leak.
other.mURI->Clone(getter_AddRefs(mURI));
other.mFile->Clone(getter_AddRefs(mFile));
}
RemoteOpenFileChild::~RemoteOpenFileChild()
{
if (mNSPRFileDesc) {
// If we handed out fd we shouldn't have pointer to it any more.
MOZ_ASSERT(!mNSPROpenCalled);
// PR_Close both closes the file and deallocates the PRFileDesc
PR_Close(mNSPRFileDesc);
}
}
nsresult
RemoteOpenFileChild::Init(nsIURI* aRemoteOpenUri)
{
if (!aRemoteOpenUri) {
return NS_ERROR_INVALID_ARG;
}
nsAutoCString scheme;
nsresult rv = aRemoteOpenUri->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
if (!scheme.EqualsLiteral("remoteopenfile")) {
return NS_ERROR_INVALID_ARG;
}
// scheme of URI is not file:// so this is not a nsIFileURL. Convert to one.
nsCOMPtr<nsIURI> clonedURI;
rv = aRemoteOpenUri->Clone(getter_AddRefs(clonedURI));
NS_ENSURE_SUCCESS(rv, rv);
clonedURI->SetScheme(NS_LITERAL_CSTRING("file"));
nsAutoCString spec;
clonedURI->GetSpec(spec);
rv = NS_NewURI(getter_AddRefs(mURI), spec);
NS_ENSURE_SUCCESS(rv, rv);
// Get nsIFile
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mURI);
if (!fileURL) {
return NS_ERROR_UNEXPECTED;
}
rv = fileURL->GetFile(getter_AddRefs(mFile));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
RemoteOpenFileChild::AsyncRemoteFileOpen(int32_t aFlags,
nsIRemoteOpenFileListener* aListener,
nsITabChild* aTabChild)
{
if (!mFile) {
return NS_ERROR_NOT_INITIALIZED;
}
if (!aListener) {
return NS_ERROR_INVALID_ARG;
}
if (mAsyncOpenCalled) {
return NS_ERROR_ALREADY_OPENED;
}
if (aFlags != PR_RDONLY) {
return NS_ERROR_NOT_AVAILABLE;
}
mozilla::dom::TabChild* tabChild = nullptr;
if (aTabChild) {
tabChild = static_cast<mozilla::dom::TabChild*>(aTabChild);
}
#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
// we do nothing on these platforms: we'll just open file locally when asked
// for NSPR handle
mListener->OnRemoteFileOpenComplete(NS_OK);
mListener = nullptr;
mAsyncOpenCalled = true;
return NS_OK;
#else
URIParams uri;
SerializeURI(mURI, uri);
gNeckoChild->SendPRemoteOpenFileConstructor(this, uri, tabChild);
// Can't seem to reply from within IPDL Parent constructor, so send open as
// separate message
SendAsyncOpenFile();
// The chrome process now has a logical ref to us until we call Send__delete
AddIPDLReference();
mListener = aListener;
mAsyncOpenCalled = true;
return NS_OK;
#endif
}
//-----------------------------------------------------------------------------
// RemoteOpenFileChild::PRemoteOpenFileChild
//-----------------------------------------------------------------------------
bool
RemoteOpenFileChild::RecvFileOpened(const FileDescriptor& aFD,
const nsresult& aRV)
{
#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
NS_NOTREACHED("osX and Windows shouldn't be doing IPDL here");
#else
if (NS_SUCCEEDED(aRV)) {
mNSPRFileDesc = PR_AllocFileDesc(aFD.PlatformHandle(), PR_GetFileMethods());
}
MOZ_ASSERT(mListener);
mListener->OnRemoteFileOpenComplete(aRV);
mListener = nullptr; // release ref to listener
// This calls NeckoChild::DeallocPRemoteOpenFile(), which deletes |this| if
// IPDL holds the last reference. Don't rely on |this| existing after here!
Send__delete__(this);
#endif
return true;
}
void
RemoteOpenFileChild::AddIPDLReference()
{
AddRef();
}
void
RemoteOpenFileChild::ReleaseIPDLReference()
{
// if we haven't gotten fd from parent yet, we're not going to.
if (mListener) {
mListener->OnRemoteFileOpenComplete(NS_ERROR_UNEXPECTED);
mListener = nullptr;
}
Release();
}
//-----------------------------------------------------------------------------
// RemoteOpenFileChild::nsIFile functions that we override logic for
//-----------------------------------------------------------------------------
NS_IMETHODIMP
RemoteOpenFileChild::Clone(nsIFile **file)
{
*file = new RemoteOpenFileChild(*this);
NS_ADDREF(*file);
// if we transferred ownership of file to clone, forget our pointer.
if (mNSPRFileDesc) {
mNSPRFileDesc = nullptr;
}
return NS_OK;
}
/* The main event: get file descriptor from parent process
*/
NS_IMETHODIMP
RemoteOpenFileChild::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode,
PRFileDesc **aRetval)
{
#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
// Windows and OSX builds: just open nsIFile locally.
return mFile->OpenNSPRFileDesc(aFlags, aMode, aRetval);
#else
if (aFlags != PR_RDONLY) {
return NS_ERROR_NOT_AVAILABLE;
}
// Unlike regular nsIFile we can't (easily) support multiple open()s.
if (mNSPROpenCalled) {
return NS_ERROR_ALREADY_OPENED;
}
if (!mNSPRFileDesc) {
// client skipped AsyncRemoteFileOpen() or didn't wait for result, or this
// object has been cloned
return NS_ERROR_NOT_AVAILABLE;
}
// hand off ownership (i.e responsibility to PR_Close() file handle) to caller
*aRetval = mNSPRFileDesc;
mNSPRFileDesc = nullptr;
mNSPROpenCalled = true;
return NS_OK;
#endif
}
//-----------------------------------------------------------------------------
// RemoteOpenFileChild::nsIFile functions that we delegate to underlying nsIFile
//-----------------------------------------------------------------------------
nsresult
RemoteOpenFileChild::GetLeafName(nsAString &aLeafName)
{
return mFile->GetLeafName(aLeafName);
}
NS_IMETHODIMP
RemoteOpenFileChild::GetNativeLeafName(nsACString &aLeafName)
{
return mFile->GetNativeLeafName(aLeafName);
}
nsresult
RemoteOpenFileChild::GetTarget(nsAString &_retval)
{
return mFile->GetTarget(_retval);
}
NS_IMETHODIMP
RemoteOpenFileChild::GetNativeTarget(nsACString &_retval)
{
return mFile->GetNativeTarget(_retval);
}
nsresult
RemoteOpenFileChild::GetPath(nsAString &_retval)
{
return mFile->GetPath(_retval);
}
NS_IMETHODIMP
RemoteOpenFileChild::GetNativePath(nsACString &_retval)
{
return mFile->GetNativePath(_retval);
}
NS_IMETHODIMP
RemoteOpenFileChild::Equals(nsIFile *inFile, bool *_retval)
{
return mFile->Equals(inFile, _retval);
}
NS_IMETHODIMP
RemoteOpenFileChild::Contains(nsIFile *inFile, bool recur, bool *_retval)
{
return mFile->Contains(inFile, recur, _retval);
}
NS_IMETHODIMP
RemoteOpenFileChild::GetParent(nsIFile **aParent)
{
return mFile->GetParent(aParent);
}
NS_IMETHODIMP
RemoteOpenFileChild::GetFollowLinks(bool *aFollowLinks)
{
return mFile->GetFollowLinks(aFollowLinks);
}
//-----------------------------------------------------------------------------
// RemoteOpenFileChild::nsIFile functions that are not currently supported
//-----------------------------------------------------------------------------
nsresult
RemoteOpenFileChild::Append(const nsAString &node)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::AppendNative(const nsACString &fragment)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::Normalize()
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::Create(uint32_t type, uint32_t permissions)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
RemoteOpenFileChild::SetLeafName(const nsAString &aLeafName)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::SetNativeLeafName(const nsACString &aLeafName)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
RemoteOpenFileChild::InitWithPath(const nsAString &filePath)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::InitWithNativePath(const nsACString &filePath)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::InitWithFile(nsIFile *aFile)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::SetFollowLinks(bool aFollowLinks)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
RemoteOpenFileChild::AppendRelativePath(const nsAString &node)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::AppendRelativeNativePath(const nsACString &fragment)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::GetPersistentDescriptor(nsACString &aPersistentDescriptor)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::SetPersistentDescriptor(const nsACString &aPersistentDescriptor)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::GetRelativeDescriptor(nsIFile *fromFile, nsACString& _retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::SetRelativeDescriptor(nsIFile *fromFile,
const nsACString& relativeDesc)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
RemoteOpenFileChild::CopyTo(nsIFile *newParentDir, const nsAString &newName)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::CopyToNative(nsIFile *newParent, const nsACString &newName)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
RemoteOpenFileChild::CopyToFollowingLinks(nsIFile *newParentDir,
const nsAString &newName)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::CopyToFollowingLinksNative(nsIFile *newParent,
const nsACString &newName)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
RemoteOpenFileChild::MoveTo(nsIFile *newParentDir, const nsAString &newName)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::MoveToNative(nsIFile *newParent, const nsACString &newName)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::Remove(bool recursive)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::GetPermissions(uint32_t *aPermissions)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::SetPermissions(uint32_t aPermissions)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::GetPermissionsOfLink(uint32_t *aPermissionsOfLink)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::SetPermissionsOfLink(uint32_t aPermissions)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::GetLastModifiedTime(PRTime *aLastModTime)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::SetLastModifiedTime(PRTime aLastModTime)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::GetLastModifiedTimeOfLink(PRTime *aLastModTimeOfLink)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::GetFileSize(int64_t *aFileSize)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::SetFileSize(int64_t aFileSize)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::GetFileSizeOfLink(int64_t *aFileSize)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::Exists(bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::IsWritable(bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::IsReadable(bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::IsExecutable(bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::IsHidden(bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::IsDirectory(bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::IsFile(bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::IsSymlink(bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::IsSpecial(bool *_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::CreateUnique(uint32_t type, uint32_t attributes)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::GetDirectoryEntries(nsISimpleEnumerator **entries)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::OpenANSIFileDesc(const char *mode, FILE **_retval)
{
// TODO: can implement using fdopen()?
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::Load(PRLibrary **_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::GetDiskSpaceAvailable(int64_t *aDiskSpaceAvailable)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::Reveal()
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::Launch()
{
return NS_ERROR_NOT_IMPLEMENTED;
}
//-----------------------------------------------------------------------------
// RemoteOpenFileChild::nsIHashable functions that we delegate to underlying nsIFile
//-----------------------------------------------------------------------------
NS_IMETHODIMP
RemoteOpenFileChild::Equals(nsIHashable* aOther, bool *aResult)
{
nsCOMPtr<nsIHashable> hashable = do_QueryInterface(mFile);
MOZ_ASSERT(hashable);
if (hashable) {
return hashable->Equals(aOther, aResult);
}
return NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP
RemoteOpenFileChild::GetHashCode(uint32_t *aResult)
{
nsCOMPtr<nsIHashable> hashable = do_QueryInterface(mFile);
MOZ_ASSERT(hashable);
if (hashable) {
return hashable->GetHashCode(aResult);
}
return NS_ERROR_UNEXPECTED;
}
} // namespace net
} // namespace mozilla

View File

@ -0,0 +1,86 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* 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/. */
#ifndef _RemoteOpenFileChild_h
#define _RemoteOpenFileChild_h
#include "mozilla/dom/TabChild.h"
#include "mozilla/net/PRemoteOpenFileChild.h"
#include "nsILocalFile.h"
#include "nsIRemoteOpenFileListener.h"
namespace mozilla {
namespace net {
/**
* RemoteOpenFileChild: a thin wrapper around regular nsIFile classes that does
* IPC to open a file handle on parent instead of child. Used when we can't
* open file handle on child (don't have permission), but we don't want the
* overhead of shipping all I/O traffic across IPDL. Example: JAR files.
*
* To open a file handle with this class, AsyncRemoteFileOpen() must be called
* first. After the listener's OnRemoteFileOpenComplete() is called, if the
* result is NS_OK, nsIFile.OpenNSPRFileDesc() may be called--once--to get the
* file handle.
*
* Note that calling Clone() on this class results in the filehandle ownership
* being passed on to the new RemoteOpenFileChild. I.e. if
* OnRemoteFileOpenComplete is called and then Clone(), OpenNSPRFileDesc() will
* work in the cloned object, but not in the original.
*
* This class should only be instantiated in a child process.
*
*/
class RemoteOpenFileChild MOZ_FINAL
: public PRemoteOpenFileChild
, public nsIFile
, public nsIHashable
{
public:
RemoteOpenFileChild()
: mNSPRFileDesc(nullptr)
, mAsyncOpenCalled(false)
, mNSPROpenCalled(false)
{}
virtual ~RemoteOpenFileChild();
NS_DECL_ISUPPORTS
NS_DECL_NSIFILE
NS_DECL_NSIHASHABLE
// URI must be scheme 'remoteopenfile://': otherwise looks like a file:// uri.
nsresult Init(nsIURI* aRemoteOpenUri);
void AddIPDLReference();
void ReleaseIPDLReference();
// Send message to parent to tell it to open file handle for file.
// TabChild is required, for IPC security.
// Note: currently only PR_RDONLY is supported for 'flags'
nsresult AsyncRemoteFileOpen(int32_t aFlags,
nsIRemoteOpenFileListener* aListener,
nsITabChild* aTabChild);
private:
RemoteOpenFileChild(const RemoteOpenFileChild& other);
protected:
virtual bool RecvFileOpened(const FileDescriptor&, const nsresult&);
// regular nsIFile object, that we forward most calls to.
nsCOMPtr<nsIFile> mFile;
nsCOMPtr<nsIURI> mURI;
nsCOMPtr<nsIRemoteOpenFileListener> mListener;
PRFileDesc* mNSPRFileDesc;
bool mAsyncOpenCalled;
bool mNSPROpenCalled;
};
} // namespace net
} // namespace mozilla
#endif // _RemoteOpenFileChild_h

View File

@ -0,0 +1,69 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* 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 "mozilla/net/RemoteOpenFileParent.h"
#include "mozilla/unused.h"
#include "nsEscape.h"
#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA)
#include <fcntl.h>
#endif
namespace mozilla {
namespace net {
RemoteOpenFileParent::RemoteOpenFileParent(nsIFileURL *aURI)
: mURI(aURI)
#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA)
, mFd(-1)
#endif
{}
RemoteOpenFileParent::~RemoteOpenFileParent()
{
#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA)
if (mFd != -1) {
// close file handle now that other process has it open, else we'll leak
// file handles in parent process
close(mFd);
}
#endif
}
bool
RemoteOpenFileParent::RecvAsyncOpenFile()
{
#if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA)
NS_NOTREACHED("osX and Windows shouldn't be doing IPDL here");
#else
// TODO: make this async!
nsAutoCString path;
nsresult rv = mURI->GetFilePath(path);
NS_UnescapeURL(path);
if (NS_SUCCEEDED(rv)) {
int fd = open(path.get(), O_RDONLY);
if (fd != -1) {
unused << SendFileOpened(FileDescriptor(fd), NS_OK);
// file handle needs to stay open until it's shared with child (and IPDL
// is async, so hasn't happened yet). Close in destructor.
mFd = fd;
return true;
}
}
// Note: sending an invalid file descriptor currently kills the child process:
// but that's ok for our use case (failing to open application.jar).
unused << SendFileOpened(FileDescriptor(mFd), NS_ERROR_NOT_AVAILABLE);
#endif // OS_TYPE
return true;
}
} // namespace net
} // namespace mozilla

View File

@ -0,0 +1,38 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* 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/. */
#ifndef mozilla_net_RemoteOpenFileParent_h
#define mozilla_net_RemoteOpenFileParent_h
#include "mozilla/net/PRemoteOpenFileParent.h"
#include "mozilla/net/NeckoCommon.h"
#include "nsIFileURL.h"
namespace mozilla {
namespace net {
class RemoteOpenFileParent : public PRemoteOpenFileParent
{
public:
RemoteOpenFileParent(nsIFileURL* aURI);
~RemoteOpenFileParent();
virtual bool RecvAsyncOpenFile();
private:
nsCOMPtr<nsIFileURL> mURI;
#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA)
int mFd;
#endif
};
} // namespace net
} // namespace mozilla
#endif // mozilla_net_RemoteOpenFileParent_h

View File

@ -4,5 +4,6 @@
IPDLSRCS = \
PNecko.ipdl \
PRemoteOpenFile.ipdl \
$(NULL)

View File

@ -0,0 +1,30 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 ts=4 et tw=80 : */
/* 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 "nsISupports.idl"
/**
* nsIRemoteOpenFileListener: passed to RemoteOpenFileChild::AsyncRemoteFileOpen.
*
* Interface for notifying when the file has been opened and is available in
* child.
*/
[uuid(5c89208c-fe2b-4e04-9783-93bcf5c3b783)]
interface nsIRemoteOpenFileListener : nsISupports
{
/**
* Called when result of opening RemoteOpenFileChild:AsyncRemoteFileOpen()
* is available in child.
*
* @param aOpenStatus: nsresult from opening file in parent. If NS_OK,
* then a following call to RemoteOpenFileChild::OpenNSPRFileDesc that
* passes the same flags as were passed to
* RemoteOpenFileChild::AsyncRemoteFileOpen is guaranteed to succeed. If
* !NS_OK or if different flags were passed, the call will fail.
*/
void onRemoteFileOpenComplete(in nsresult aOpenStatus);
};

View File

@ -16,7 +16,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"nsISyncMessageSender");
function AppProtocolHandler() {
this._basePath = [];
this._appInfo = [];
this._runningInParent = Cc["@mozilla.org/xre/runtime;1"]
.getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
}
AppProtocolHandler.prototype = {
@ -30,14 +33,13 @@ AppProtocolHandler.prototype = {
Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
Ci.nsIProtocolHandler.URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM,
getBasePath: function app_phGetBasePath(aId) {
getAppInfo: function app_phGetAppInfo(aId) {
if (!this._basePath[aId]) {
this._basePath[aId] = cpmm.sendSyncMessage("Webapps:GetBasePath",
{ id: aId })[0] + "/";
if (!this._appInfo[aId]) {
let reply = cpmm.sendSyncMessage("Webapps:GetAppInfo", { id: aId });
this._appInfo[aId] = reply[0];
}
return this._basePath[aId];
return this._appInfo[aId];
},
newURI: function app_phNewURI(aSpec, aOriginCharset, aBaseURI) {
@ -62,7 +64,15 @@ AppProtocolHandler.prototype = {
}
// Build a jar channel and masquerade as an app:// URI.
let uri = "jar:file://" + this.getBasePath(appId) + appId + "/application.zip!" + fileSpec;
let appInfo = this.getAppInfo(appId);
let uri;
if (this._runningInParent || appInfo.isCoreApp) {
// In-parent and CoreApps can directly access files, so use jar:file://
uri = "jar:file://" + appInfo.basePath + appId + "/application.zip!" + fileSpec;
} else {
// non-CoreApps in child need to ask parent for file handle, use jar:ipcfile://
uri = "jar:remoteopenfile://" + appInfo.basePath + appId + "/application.zip!" + fileSpec;
}
let channel = Services.io.newChannel(uri, null, null);
channel.QueryInterface(Ci.nsIJARChannel).setAppURI(aURI);
channel.QueryInterface(Ci.nsIChannel).originalURI = aURI;