diff --git a/dom/apps/src/AppsService.js b/dom/apps/src/AppsService.js index 2baacb1c2fe4..bc6f2863c9b3 100644 --- a/dom/apps/src/AppsService.js +++ b/dom/apps/src/AppsService.js @@ -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]) } diff --git a/dom/apps/src/AppsServiceChild.jsm b/dom/apps/src/AppsServiceChild.jsm index 950c83acfbe0..7cbf4a56b1f5 100644 --- a/dom/apps/src/AppsServiceChild.jsm +++ b/dom/apps/src/AppsServiceChild.jsm @@ -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; } } diff --git a/dom/apps/src/AppsUtils.jsm b/dom/apps/src/AppsUtils.jsm index 5530820a782b..eb7fcd65c3d0 100644 --- a/dom/apps/src/AppsUtils.jsm +++ b/dom/apps/src/AppsUtils.jsm @@ -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, diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index 127d4152d326..5ca18421dd49 100644 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -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) { diff --git a/dom/interfaces/apps/mozIApplication.idl b/dom/interfaces/apps/mozIApplication.idl index d284f0ab439e..737f5e622ed3 100644 --- a/dom/interfaces/apps/mozIApplication.idl +++ b/dom/interfaces/apps/mozIApplication.idl @@ -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; diff --git a/dom/interfaces/apps/nsIAppsService.idl b/dom/interfaces/apps/nsIAppsService.idl index f6ca6797e438..8349e4b6e332 100644 --- a/dom/interfaces/apps/nsIAppsService.idl +++ b/dom/interfaces/apps/nsIAppsService.idl @@ -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(); }; diff --git a/modules/libjar/nsIZipReader.idl b/modules/libjar/nsIZipReader.idl index 2aa89eddf5e5..1df6e32875c0 100644 --- a/modules/libjar/nsIZipReader.idl +++ b/modules/libjar/nsIZipReader.idl @@ -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 * diff --git a/modules/libjar/nsJAR.cpp b/modules/libjar/nsJAR.cpp index 97c5f347c1d7..38f4b1ce849c 100644 --- a/modules/libjar/nsJAR.cpp +++ b/modules/libjar/nsJAR.cpp @@ -1062,6 +1062,27 @@ nsZipReaderCache::~nsZipReaderCache() #endif } +NS_IMETHODIMP +nsZipReaderCache::IsCached(nsIFile* zipFile, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(zipFile); + nsresult rv; + nsCOMPtr 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) { diff --git a/modules/libjar/nsJARChannel.cpp b/modules/libjar/nsJARChannel.cpp index 1d704d383f59..348ebbc7a38d 100644 --- a/modules/libjar/nsJARChannel.cpp +++ b/modules/libjar/nsJARChannel.cpp @@ -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 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 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 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 jarURI = do_QueryInterface(mJarBaseURI); @@ -655,7 +692,7 @@ nsJARChannel::Open(nsIInputStream **stream) return NS_ERROR_NOT_IMPLEMENTED; } - nsCOMPtr input; + nsRefPtr 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 input; + nsRefPtr 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 input; + nsRefPtr 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 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 //----------------------------------------------------------------------------- diff --git a/modules/libjar/nsJARChannel.h b/modules/libjar/nsJARChannel.h index 96d4f3b90ace..baddcd54f77e 100644 --- a/modules/libjar/nsJARChannel.h +++ b/modules/libjar/nsJARChannel.h @@ -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 mDownloader; nsCOMPtr mPump; diff --git a/modules/libjar/test/unit/head_ipc.js b/modules/libjar/test/unit/head_ipc.js new file mode 100644 index 000000000000..5b752316a83c --- /dev/null +++ b/modules/libjar/test/unit/head_ipc.js @@ -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) { } diff --git a/modules/libjar/test/unit/test_jarchannel.js b/modules/libjar/test/unit/test_jarchannel.js index 9f0c82204460..1720e881cfe6 100644 --- a/modules/libjar/test/unit/test_jarchannel.js +++ b/modules/libjar/test/unit/test_jarchannel.js @@ -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(); diff --git a/modules/libjar/test/unit/test_jarchannel_e10s.js b/modules/libjar/test/unit/test_jarchannel_e10s.js new file mode 100644 index 000000000000..b77ebc5c2df8 --- /dev/null +++ b/modules/libjar/test/unit/test_jarchannel_e10s.js @@ -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"); +} diff --git a/modules/libjar/test/unit/xpcshell.ini b/modules/libjar/test/unit/xpcshell.ini index 864bfe7d6005..e46b01058883 100644 --- a/modules/libjar/test/unit/xpcshell.ini +++ b/modules/libjar/test/unit/xpcshell.ini @@ -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] diff --git a/netwerk/ipc/Makefile.in b/netwerk/ipc/Makefile.in index 3c312dd436ed..1f46764c8a4f 100644 --- a/netwerk/ipc/Makefile.in +++ b/netwerk/ipc/Makefile.in @@ -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 += \ diff --git a/netwerk/ipc/NeckoChild.cpp b/netwerk/ipc/NeckoChild.cpp index 74fcc5a0ee4b..2eaa36849e11 100644 --- a/netwerk/ipc/NeckoChild.cpp +++ b/netwerk/ipc/NeckoChild.cpp @@ -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(aChild); + p->ReleaseIPDLReference(); + return true; +} + }} // mozilla::net diff --git a/netwerk/ipc/NeckoChild.h b/netwerk/ipc/NeckoChild.h index 8006c4c86937..1b3455a4c72d 100644 --- a/netwerk/ipc/NeckoChild.h +++ b/netwerk/ipc/NeckoChild.h @@ -43,6 +43,9 @@ protected: const nsString& aBinaryType, PBrowserChild* aBrowser); virtual bool DeallocPTCPSocket(PTCPSocketChild*); + virtual PRemoteOpenFileChild* AllocPRemoteOpenFile(const URIParams&, + PBrowserChild*); + virtual bool DeallocPRemoteOpenFile(PRemoteOpenFileChild*); }; /** diff --git a/netwerk/ipc/NeckoParent.cpp b/netwerk/ipc/NeckoParent.cpp index e62d5b9a702b..9c5e80739a7f 100644 --- a/netwerk/ipc/NeckoParent.cpp +++ b/netwerk/ipc/NeckoParent.cpp @@ -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 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 uri = DeserializeURI(aURI); + nsCOMPtr 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 = static_cast(aBrowser); + uint32_t appId = tabParent->OwnOrContainingAppId(); + nsCOMPtr appsService = + do_GetService(APPS_SERVICE_CONTRACTID); + if (!appsService) { + return nullptr; + } + nsCOMPtr domApp; + nsresult rv = appsService->GetAppByLocalId(appId, getter_AddRefs(domApp)); + if (!domApp) { + return nullptr; + } + nsCOMPtr 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) diff --git a/netwerk/ipc/NeckoParent.h b/netwerk/ipc/NeckoParent.h index 1112e1046a10..df22267e8e96 100644 --- a/netwerk/ipc/NeckoParent.h +++ b/netwerk/ipc/NeckoParent.h @@ -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 diff --git a/netwerk/ipc/PNecko.ipdl b/netwerk/ipc/PNecko.ipdl index 9330fb01fe3b..a1c12ba47b1d 100644 --- a/netwerk/ipc/PNecko.ipdl +++ b/netwerk/ipc/PNecko.ipdl @@ -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); diff --git a/netwerk/ipc/PRemoteOpenFile.ipdl b/netwerk/ipc/PRemoteOpenFile.ipdl new file mode 100644 index 000000000000..89d22079d72a --- /dev/null +++ b/netwerk/ipc/PRemoteOpenFile.ipdl @@ -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 diff --git a/netwerk/ipc/RemoteOpenFileChild.cpp b/netwerk/ipc/RemoteOpenFileChild.cpp new file mode 100644 index 000000000000..9cddaf82ab4e --- /dev/null +++ b/netwerk/ipc/RemoteOpenFileChild.cpp @@ -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 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 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(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 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 hashable = do_QueryInterface(mFile); + + MOZ_ASSERT(hashable); + + if (hashable) { + return hashable->GetHashCode(aResult); + } + return NS_ERROR_UNEXPECTED; +} + +} // namespace net +} // namespace mozilla + diff --git a/netwerk/ipc/RemoteOpenFileChild.h b/netwerk/ipc/RemoteOpenFileChild.h new file mode 100644 index 000000000000..260718572d91 --- /dev/null +++ b/netwerk/ipc/RemoteOpenFileChild.h @@ -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 mFile; + nsCOMPtr mURI; + nsCOMPtr mListener; + PRFileDesc* mNSPRFileDesc; + bool mAsyncOpenCalled; + bool mNSPROpenCalled; +}; + +} // namespace net +} // namespace mozilla + +#endif // _RemoteOpenFileChild_h + diff --git a/netwerk/ipc/RemoteOpenFileParent.cpp b/netwerk/ipc/RemoteOpenFileParent.cpp new file mode 100644 index 000000000000..2f4311c0c3b0 --- /dev/null +++ b/netwerk/ipc/RemoteOpenFileParent.cpp @@ -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 +#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 diff --git a/netwerk/ipc/RemoteOpenFileParent.h b/netwerk/ipc/RemoteOpenFileParent.h new file mode 100644 index 000000000000..e56f1a081270 --- /dev/null +++ b/netwerk/ipc/RemoteOpenFileParent.h @@ -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 mURI; + +#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA) + int mFd; +#endif +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_RemoteOpenFileParent_h diff --git a/netwerk/ipc/ipdl.mk b/netwerk/ipc/ipdl.mk index bdc4d60dd16d..8b1da9f059ff 100644 --- a/netwerk/ipc/ipdl.mk +++ b/netwerk/ipc/ipdl.mk @@ -4,5 +4,6 @@ IPDLSRCS = \ PNecko.ipdl \ + PRemoteOpenFile.ipdl \ $(NULL) diff --git a/netwerk/ipc/nsIRemoteOpenFileListener.idl b/netwerk/ipc/nsIRemoteOpenFileListener.idl new file mode 100644 index 000000000000..1ecf53a08c62 --- /dev/null +++ b/netwerk/ipc/nsIRemoteOpenFileListener.idl @@ -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); +}; + diff --git a/netwerk/protocol/app/AppProtocolHandler.js b/netwerk/protocol/app/AppProtocolHandler.js index 089a549218ec..8a9504122d90 100644 --- a/netwerk/protocol/app/AppProtocolHandler.js +++ b/netwerk/protocol/app/AppProtocolHandler.js @@ -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;