author | Jason Duell <jduell.mcbugs@gmail.com> |
Sat, 22 Dec 2012 05:56:21 -0800 | |
changeset 116889 | 108eb99a250db187c9bfa6c5f383f8375d261c9f |
parent 116888 | 4ff2c4b415f8dfa9fd2be3908808268c87c2186e |
child 116890 | 443df7602f6ef3f7736fc687c0e62a0dc15caf8b |
push id | 24076 |
push user | ryanvm@gmail.com |
push date | Sun, 23 Dec 2012 20:50:19 +0000 |
treeherder | mozilla-central@4f74d77d6d8b [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | fabrice, mwu, jdm |
bugs | 815523 |
milestone | 20.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/apps/src/AppsService.js +++ b/dom/apps/src/AppsService.js @@ -53,13 +53,23 @@ AppsService.prototype = { return DOMApplicationRegistry.getManifestURLByLocalId(aLocalId); }, getAppFromObserverMessage: function getAppFromObserverMessage(aMessage) { debug("getAppFromObserverMessage( " + aMessage + " )"); 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]) } this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AppsService])
--- a/dom/apps/src/AppsServiceChild.jsm +++ b/dom/apps/src/AppsServiceChild.jsm @@ -81,12 +81,21 @@ this.DOMApplicationRegistry = { getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) { debug("getManifestURLByLocalId " + aLocalId); return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId); }, 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; } } DOMApplicationRegistry.init();
--- a/dom/apps/src/AppsUtils.jsm +++ b/dom/apps/src/AppsUtils.jsm @@ -33,17 +33,19 @@ this.AppsUtils = { csp: aApp.csp, installOrigin: aApp.installOrigin, origin: aApp.origin, receipts: aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null, installTime: aApp.installTime, 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, downloading: aApp.downloading, readyToApplyDownload: aApp.readyToApplyDownload, downloadSize: aApp.downloadSize || 0, lastUpdateCheck: aApp.lastUpdateCheck, updateTime: aApp.updateTime,
--- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -70,17 +70,17 @@ this.DOMApplicationRegistry = { children: [ ], allAppsLaunchable: false, init: function() { this.messages = ["Webapps:Install", "Webapps:Uninstall", "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", "Webapps:Download", "Webapps:ApplyDownload", "child-process-shutdown"]; this.frameMessages = ["Webapps:ClearBrowserData"]; @@ -105,16 +105,19 @@ this.DOMApplicationRegistry = { loadCurrentRegistry: function loadCurrentRegistry(aNext) { let file = FileUtils.getFile(DIRECTORY_NAME, ["webapps", "webapps.json"], false); if (file && file.exists()) { this._loadJSONAsync(file, (function loadRegistry(aData) { if (aData) { 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(); } if (this.webapps[id].basePath === undefined) { this.webapps[id].basePath = appDir.path; } @@ -210,17 +213,17 @@ this.DOMApplicationRegistry = { }, // Installs a 3rd party packaged app. installPreinstalledPackage: function installPreinstalledPackage(aId) { #ifdef MOZ_WIDGET_GONK 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; } let updateFile = baseDir.clone(); updateFile.append("update.webapp"); if (!updateFile.exists()) { @@ -315,16 +318,18 @@ this.DOMApplicationRegistry = { // c for (let id in aData) { // Core apps have ids matching their domain name (eg: dialer.gaiamobile.org) // Use that property to check if they are new or not. if (!(id in this.webapps)) { 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(); // Core apps are not removable. if (this.webapps[id].removable === undefined) { this.webapps[id].removable = false; } } @@ -774,22 +779,23 @@ this.DOMApplicationRegistry = { if (msg.hasPrivileges) this.getAll(msg, mm); else mm.sendAsyncMessage("Webapps:GetAll:Return:KO", msg); break; 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); break; case "Webapps:UnregisterForMessages": this.removeMessageListener(msg, mm); break; case "child-process-shutdown": @@ -1474,19 +1480,19 @@ this.DOMApplicationRegistry = { isReinstall = true; let dir = this._getAppDir(id); try { dir.remove(true); } catch(e) { } } else { id = this.makeAppId(); - app.id = id; localId = this._nextLocalId(); } + app.id = id; let manifestName = "manifest.webapp"; if (aData.isPackage) { // Override the origin with the correct id. app.origin = "app://" + id; // For packaged apps, keep the update manifest distinct from the app // manifest. @@ -1501,16 +1507,17 @@ this.DOMApplicationRegistry = { appObject.appStatus = AppsUtils.getAppManifestStatus(app.manifest); } appObject.installTime = app.installTime = Date.now(); appObject.lastUpdateCheck = app.lastUpdateCheck = Date.now(); 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); dir.permissions = FileUtils.PERMS_DIRECTORY; let manFile = dir.clone(); manFile.append(manifestName); let jsonManifest = aData.isPackage ? app.updateManifest : app.manifest; this._writeFile(manFile, JSON.stringify(jsonManifest), function() { }); @@ -2184,16 +2191,24 @@ this.DOMApplicationRegistry = { getAppLocalIdByManifestURL: function(aManifestURL) { return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL); }, getAppFromObserverMessage: function(aMessage) { 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) { let app = AppsUtils.cloneAppObject(this.webapps[id]); result[id] = app; } aCallback(result); },
--- a/dom/interfaces/apps/mozIApplication.idl +++ b/dom/interfaces/apps/mozIApplication.idl @@ -6,26 +6,32 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsIDOMApplicationRegistry.idl" /** * 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|. */ boolean hasPermission(in string permission); /* 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; /* CSP copied from the manifest */ readonly attribute DOMString csp; };
--- a/dom/interfaces/apps/nsIAppsService.idl +++ b/dom/interfaces/apps/nsIAppsService.idl @@ -11,17 +11,17 @@ interface mozIApplication; #define APPS_SERVICE_CID { 0x05072afa, 0x92fe, 0x45bf, { 0xae, 0x22, 0x39, 0xb6, 0x9c, 0x11, 0x70, 0x58 } } #define APPS_SERVICE_CONTRACTID "@mozilla.org/AppsService;1" %} /* * 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); /** * Returns the |localId| of the app associated with the |manifestURL| passed * in parameter. * Returns nsIScriptSecurityManager::NO_APP_ID if |manifestURL| isn't a valid @@ -45,9 +45,19 @@ interface nsIAppsService : nsISupports * of the message when listening to one. */ mozIApplication getAppFromObserverMessage(in DOMString message); /** * 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(); };
--- a/modules/libjar/nsIZipReader.idl +++ b/modules/libjar/nsIZipReader.idl @@ -180,17 +180,17 @@ interface nsIZipReader : nsISupports nsICertificatePrincipal getCertificatePrincipal(in AUTF8String aEntryName); readonly attribute uint32_t manifestEntriesCount; }; //////////////////////////////////////////////////////////////////////////////// // nsIZipReaderCache -[scriptable, uuid(72fc56e5-3e6e-4d11-8967-26ab96071032)] +[scriptable, uuid(748050ac-3ab6-4472-bc2a-cb1564ac6a81)] interface nsIZipReaderCache : nsISupports { /** * Initializes a new zip reader cache. * @param cacheSize - the number of released entries to maintain before * beginning to throw some out (note that the number of outstanding * entries can be much greater than this number -- this is the count * for those otherwise unused entries) @@ -206,16 +206,21 @@ interface nsIZipReaderCache : nsISupport * returned. * * @note If someone called close() on the shared nsIZipReader, this method * will return the closed zip reader. */ 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 * * See getZip */ nsIZipReader getInnerZip(in nsIFile zipFile, in AUTF8String zipEntry); }; ////////////////////////////////////////////////////////////////////////////////
--- a/modules/libjar/nsJAR.cpp +++ b/modules/libjar/nsJAR.cpp @@ -1058,16 +1058,37 @@ nsZipReaderCache::~nsZipReaderCache() printf("nsZipReaderCache size=%d hits=%d lookups=%d rate=%f%% flushes=%d missed %d\n", mCacheSize, mZipCacheHits, mZipCacheLookups, (float)mZipCacheHits / mZipCacheLookups, mZipCacheFlushes, mZipSyncMisses); #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) { NS_ENSURE_ARG_POINTER(zipFile); nsresult rv; nsCOMPtr<nsIZipReader> antiLockZipGrip; MutexAutoLock lock(mLock); #ifdef ZIP_CACHE_HIT_RATE
--- a/modules/libjar/nsJARChannel.cpp +++ b/modules/libjar/nsJARChannel.cpp @@ -1,11 +1,11 @@ -/* -*- 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/. */ #include "nsJAR.h" #include "nsJARChannel.h" #include "nsJARProtocolHandler.h" #include "nsMimeTypes.h" #include "nsNetUtil.h" @@ -13,20 +13,24 @@ #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsIViewSourceChannel.h" #include "nsChannelProperties.h" #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); // the entry for a directory will either be empty (in the case of the // top-level directory) or will end with a slash #define ENTRY_IS_DIRECTORY(_entry) \ ((_entry).IsEmpty() || '/' == (_entry).Last()) @@ -183,16 +187,17 @@ nsJARInputThunk::IsNonBlocking(bool *non nsJARChannel::nsJARChannel() : mOpened(false) , mAppURI(nullptr) , mContentLength(-1) , mLoadFlags(LOAD_NORMAL) , mStatus(NS_OK) , mIsPending(false) , mIsUnsafe(true) + , mOpeningRemote(false) { #if defined(PR_LOGGING) if (!gJarProtocolLog) gJarProtocolLog = PR_NewLogModule("nsJarProtocol"); #endif // hold an owning reference to the jar handler NS_ADDREF(gJarHandler); @@ -200,23 +205,24 @@ nsJARChannel::nsJARChannel() nsJARChannel::~nsJARChannel() { // release owning reference to the jar handler nsJARProtocolHandler *handler = gJarHandler; NS_RELEASE(handler); // NULL parameter } -NS_IMPL_ISUPPORTS_INHERITED6(nsJARChannel, +NS_IMPL_ISUPPORTS_INHERITED7(nsJARChannel, nsHashPropertyBag, nsIRequest, nsIChannel, nsIStreamListener, nsIRequestObserver, nsIDownloadObserver, + nsIRemoteOpenFileListener, nsIJARChannel) nsresult nsJARChannel::Init(nsIURI *uri) { nsresult rv; rv = nsHashPropertyBag::Init(); if (NS_FAILED(rv)) @@ -258,27 +264,27 @@ nsJARChannel::CreateJarInput(nsIZipReade nsCOMPtr<nsIFile> clonedFile; nsresult rv = mJarFile->Clone(getter_AddRefs(clonedFile)); if (NS_FAILED(rv)) return rv; 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 nsCOMPtr<nsIZipReader> outerReader = do_CreateInstance(kZipReaderCID, &rv); if (NS_FAILED(rv)) return rv; - rv = outerReader->Open(mJarFile); + rv = outerReader->Open(clonedFile); if (NS_FAILED(rv)) return rv; if (mInnerJarEntry.IsEmpty()) reader = outerReader; else { reader = do_CreateInstance(kZipReaderCID, &rv); if (NS_FAILED(rv)) @@ -329,16 +335,47 @@ nsJARChannel::LookupFile() NS_UnescapeURL(mJarEntry); // try to get a nsIFile directly from the url, which will often succeed. { nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(mJarBaseURI); 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); if (jarURI) { nsCOMPtr<nsIFileURL> fileURL; nsCOMPtr<nsIURI> innerJarURI; rv = jarURI->GetJARFile(getter_AddRefs(innerJarURI)); if (NS_SUCCEEDED(rv)) @@ -650,17 +687,17 @@ nsJARChannel::Open(nsIInputStream **stre return rv; // If mJarInput was not set by LookupFile, the JAR is a remote jar. if (!mJarFile) { NS_NOTREACHED("need sync downloader"); return NS_ERROR_NOT_IMPLEMENTED; } - nsCOMPtr<nsJARInputThunk> input; + nsRefPtr<nsJARInputThunk> input; rv = CreateJarInput(gJarHandler->JarCache(), getter_AddRefs(input)); if (NS_FAILED(rv)) return rv; input.forget(stream); mOpened = true; // local files are always considered safe mIsUnsafe = false; @@ -697,22 +734,23 @@ nsJARChannel::AsyncOpen(nsIStreamListene if (!mJarFile) { // Not a local file... // kick off an async download of the base URI... rv = NS_NewDownloader(getter_AddRefs(mDownloader), this); if (NS_SUCCEEDED(rv)) 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 rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input); if (NS_SUCCEEDED(rv)) rv = mPump->AsyncRead(this, nullptr); } } @@ -840,17 +878,17 @@ nsJARChannel::OnDownloadComplete(nsIDown if (viewSource) { status = NS_ERROR_UNSAFE_CONTENT_TYPE; } } 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 rv = NS_NewInputStreamPump(getter_AddRefs(mPump), input); if (NS_SUCCEEDED(rv)) rv = mPump->AsyncRead(this, nullptr); } status = rv; @@ -861,16 +899,48 @@ nsJARChannel::OnDownloadComplete(nsIDown OnStartRequest(nullptr, nullptr); OnStopRequest(nullptr, nullptr, status); } 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 //----------------------------------------------------------------------------- NS_IMETHODIMP nsJARChannel::OnStartRequest(nsIRequest *req, nsISupports *ctx) { LOG(("nsJARChannel::OnStartRequest [this=%x %s]\n", this, mSpec.get()));
--- a/modules/libjar/nsJARChannel.h +++ b/modules/libjar/nsJARChannel.h @@ -7,16 +7,17 @@ #define nsJARChannel_h__ #include "nsIJARChannel.h" #include "nsIJARURI.h" #include "nsIInputStreamPump.h" #include "nsIInterfaceRequestor.h" #include "nsIProgressEventSink.h" #include "nsIStreamListener.h" +#include "nsIRemoteOpenFileListener.h" #include "nsIZipReader.h" #include "nsIDownloader.h" #include "nsILoadGroup.h" #include "nsHashPropertyBag.h" #include "nsIFile.h" #include "nsIURI.h" #include "nsCOMPtr.h" #include "nsString.h" @@ -24,26 +25,28 @@ class nsJARInputThunk; //----------------------------------------------------------------------------- class nsJARChannel : public nsIJARChannel , public nsIDownloadObserver , public nsIStreamListener + , public nsIRemoteOpenFileListener , public nsHashPropertyBag { public: NS_DECL_ISUPPORTS NS_DECL_NSIREQUEST NS_DECL_NSICHANNEL NS_DECL_NSIJARCHANNEL NS_DECL_NSIDOWNLOADOBSERVER NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREMOTEOPENFILELISTENER nsJARChannel(); virtual ~nsJARChannel(); nsresult Init(nsIURI *uri); private: nsresult CreateJarInput(nsIZipReaderCache *, nsJARInputThunk **); @@ -71,16 +74,17 @@ private: /* mContentDisposition is uninitialized if mContentDispositionHeader is * empty */ uint32_t mContentDisposition; int64_t mContentLength; uint32_t mLoadFlags; nsresult mStatus; bool mIsPending; bool mIsUnsafe; + bool mOpeningRemote; nsCOMPtr<nsIStreamListener> mDownloader; nsCOMPtr<nsIInputStreamPump> mPump; nsCOMPtr<nsIFile> mJarFile; nsCOMPtr<nsIURI> mJarBaseURI; nsCString mJarEntry; nsCString mInnerJarEntry; };
new file mode 100644 --- /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) { }
--- a/modules/libjar/test/unit/test_jarchannel.js +++ b/modules/libjar/test/unit/test_jarchannel.js @@ -22,17 +22,18 @@ const obs = Cc["@mozilla.org/observer-se const nsIBinaryInputStream = ctor("@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream" ); 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) { this._callback = callback; } Listener.prototype = { gotStartRequest: false, available: -1, @@ -61,127 +62,141 @@ Listener.prototype = { this.gotStopRequest = true; if (this._callback) { this._callback.call(null, this); } } }; /** - * 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); 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); run_next_test(); }), null); -}); +} + +add_test(testAsync); +// Run same test again so we test the codepath for a zipcache hit +add_test(testAsync); + + +// 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) { -/** - * 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 + /** + * 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(); -}); + 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); + + /** + * 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 - run_next_test(); - }), null); -}); + run_next_test(); + }); -/** - * 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); + /** + * 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); - 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(); + run_next_test(); + }), null); + }); - // Drop any jar caches - obs.notifyObservers(null, "chrome-flush-caches", null); + /** + * 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); - try { - copy.remove(false); - } - catch (ex) { - do_throw(ex); - } + 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(); - run_next_test(); -}); + // Drop any jar caches + obs.notifyObservers(null, "chrome-flush-caches", null); + + try { + copy.remove(false); + } + catch (ex) { + do_throw(ex); + } -/** - * 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(); + }); - 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 an asynchronously + * opened jar channel stream + */ + add_test(function testAsyncCloseUnlocks() { + 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); + 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); - } + try { + copy.remove(false); + } + catch (ex) { + do_throw(ex); + } - run_next_test(); - }), null); -}); + run_next_test(); + }), null); + }); + +} // if !inChild function run_test() run_next_test();
new file mode 100644 --- /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"); +}
--- a/modules/libjar/test/unit/xpcshell.ini +++ b/modules/libjar/test/unit/xpcshell.ini @@ -1,13 +1,15 @@ [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] [test_bug370103.js] [test_bug379841.js] [test_bug407303.js] [test_bug453254.js] [test_bug458158.js]
--- a/netwerk/ipc/Makefile.in +++ b/netwerk/ipc/Makefile.in @@ -11,31 +11,40 @@ FAIL_ON_WARNINGS := 1 include $(DEPTH)/config/autoconf.mk MODULE = necko 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 EXPORTS_mozilla/net = \ NeckoParent.h \ NeckoChild.h \ 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 += \ -I$(srcdir)/../protocol/http \ -I$(srcdir)/../base/src \ $(NULL) include $(topsrcdir)/config/config.mk
--- a/netwerk/ipc/NeckoChild.cpp +++ b/netwerk/ipc/NeckoChild.cpp @@ -8,16 +8,17 @@ #include "nsHttp.h" #include "mozilla/net/NeckoChild.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/net/HttpChannelChild.h" #include "mozilla/net/CookieServiceChild.h" #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" using mozilla::dom::TCPSocketChild; namespace mozilla { namespace net { @@ -167,10 +168,27 @@ NeckoChild::AllocPTCPSocket(const nsStri bool NeckoChild::DeallocPTCPSocket(PTCPSocketChild* child) { TCPSocketChild* p = static_cast<TCPSocketChild*>(child); p->ReleaseIPDLReference(); 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
--- a/netwerk/ipc/NeckoChild.h +++ b/netwerk/ipc/NeckoChild.h @@ -38,16 +38,19 @@ protected: virtual PWebSocketChild* AllocPWebSocket(PBrowserChild*); virtual bool DeallocPWebSocket(PWebSocketChild*); virtual PTCPSocketChild* AllocPTCPSocket(const nsString& aHost, const uint16_t& aPort, const bool& useSSL, const nsString& aBinaryType, PBrowserChild* aBrowser); virtual bool DeallocPTCPSocket(PTCPSocketChild*); + virtual PRemoteOpenFileChild* AllocPRemoteOpenFile(const URIParams&, + PBrowserChild*); + virtual bool DeallocPRemoteOpenFile(PRemoteOpenFileChild*); }; /** * Reference to the PNecko Child protocol. * Null if this is not a content process. */ extern PNeckoChild *gNeckoChild;
--- a/netwerk/ipc/NeckoParent.cpp +++ b/netwerk/ipc/NeckoParent.cpp @@ -7,36 +7,55 @@ #include "nsHttp.h" #include "mozilla/net/NeckoParent.h" #include "mozilla/net/HttpChannelParent.h" #include "mozilla/net/CookieServiceParent.h" #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; using mozilla::dom::TCPSocketParent; namespace mozilla { namespace net { static bool gDisableIPCSecurity = false; static const char kPrefDisableIPCSecurity[] = "network.disable.ipc.security"; // C++ file contents 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() { } PHttpChannelParent* NeckoParent::AllocPHttpChannel(PBrowserParent* browser, @@ -144,16 +163,126 @@ NeckoParent::RecvPTCPSocketConstructor(P bool NeckoParent::DeallocPTCPSocket(PTCPSocketParent* actor) { TCPSocketParent* p = static_cast<TCPSocketParent*>(actor); p->Release(); 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) { nsHTMLDNSPrefetch::Prefetch(hostname, flags); return true; }
--- a/netwerk/ipc/NeckoParent.h +++ b/netwerk/ipc/NeckoParent.h @@ -34,27 +34,35 @@ protected: virtual bool DeallocPFTPChannel(PFTPChannelParent*); virtual PWebSocketParent* AllocPWebSocket(PBrowserParent* browser); virtual bool DeallocPWebSocket(PWebSocketParent*); virtual PTCPSocketParent* AllocPTCPSocket(const nsString& aHost, const uint16_t& aPort, 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, const bool& useSSL, const nsString& aBinaryType, PBrowserParent* aBrowser); virtual bool DeallocPTCPSocket(PTCPSocketParent*); virtual bool RecvHTMLDNSPrefetch(const nsString& hostname, const uint16_t& flags); virtual bool RecvCancelHTMLDNSPrefetch(const nsString& hostname, const uint16_t& flags, const nsresult& reason); +private: + nsCString mCoreAppsBasePath; + nsCString mWebAppsBasePath; }; } // namespace net } // namespace mozilla #endif // mozilla_net_NeckoParent_h
--- a/netwerk/ipc/PNecko.ipdl +++ b/netwerk/ipc/PNecko.ipdl @@ -8,16 +8,18 @@ include protocol PContent; include protocol PHttpChannel; include protocol PCookieService; include protocol PBrowser; include protocol PWyciwygChannel; include protocol PFTPChannel; include protocol PWebSocket; include protocol PTCPSocket; +include protocol PRemoteOpenFile; +include URIParams; include "SerializedLoadContext.h"; using IPC::SerializedLoadContext; namespace mozilla { namespace net { @@ -27,28 +29,30 @@ sync protocol PNecko { manager PContent; manages PHttpChannel; manages PCookieService; manages PWyciwygChannel; manages PFTPChannel; manages PWebSocket; manages PTCPSocket; + manages PRemoteOpenFile; parent: __delete__(); PCookieService(); PHttpChannel(nullable PBrowser browser, SerializedLoadContext loadContext); PWyciwygChannel(); PFTPChannel(); 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); }; } // namespace net
new file mode 100644 --- /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
new file mode 100644 --- /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<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 +
new file mode 100644 --- /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<nsIFile> mFile; + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIRemoteOpenFileListener> mListener; + PRFileDesc* mNSPRFileDesc; + bool mAsyncOpenCalled; + bool mNSPROpenCalled; +}; + +} // namespace net +} // namespace mozilla + +#endif // _RemoteOpenFileChild_h +
new file mode 100644 --- /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 <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
new file mode 100644 --- /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<nsIFileURL> mURI; + +#if !defined(XP_WIN) && !defined(MOZ_WIDGET_COCOA) + int mFd; +#endif +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_RemoteOpenFileParent_h
--- a/netwerk/ipc/ipdl.mk +++ b/netwerk/ipc/ipdl.mk @@ -1,8 +1,9 @@ # 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/. IPDLSRCS = \ PNecko.ipdl \ + PRemoteOpenFile.ipdl \ $(NULL)
new file mode 100644 --- /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); +}; +
--- a/netwerk/protocol/app/AppProtocolHandler.js +++ b/netwerk/protocol/app/AppProtocolHandler.js @@ -11,38 +11,40 @@ const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyServiceGetter(this, "cpmm", "@mozilla.org/childprocessmessagemanager;1", "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 = { classID: Components.ID("{b7ad6144-d344-4687-b2d0-b6b9dce1f07f}"), QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]), scheme: "app", defaultPort: -1, // Don't allow loading from other protocols, and only from app:// if webapps is granted protocolFlags: Ci.nsIProtocolHandler.URI_NOAUTH | 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) { let uri = Cc["@mozilla.org/network/standard-url;1"] .createInstance(Ci.nsIStandardURL); uri.init(Ci.nsIStandardURL.URLTYPE_STANDARD, -1, aSpec, aOriginCharset, aBaseURI); return uri.QueryInterface(Ci.nsIURI); @@ -57,17 +59,25 @@ AppProtocolHandler.prototype = { let appId = noScheme; let fileSpec = aURI.path; if (firstSlash) { appId = noScheme.substring(0, firstSlash); } // 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; return channel; }, allowPort: function app_phAllowPort(aPort, aScheme) {