From 6a0039c10310396ab9c38377e1ce8d7a93354847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20Desr=C3=A9?= Date: Tue, 14 May 2013 12:00:09 -0700 Subject: [PATCH] Bug 852848 - Add support for app-specific redirections r=bz,ferjm --- docshell/base/nsDocShell.cpp | 26 ++++++++++++++++++ dom/apps/src/AppsService.js | 37 ++++++++++++++++++++++++++ dom/apps/src/AppsUtils.jsm | 7 ++--- dom/apps/src/Webapps.jsm | 32 ++++++++++++++++++++-- dom/interfaces/apps/nsIAppsService.idl | 9 ++++++- 5 files changed, 105 insertions(+), 6 deletions(-) diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 5b22686f8e1..7ca5cd76cac 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -202,6 +202,8 @@ #include "nsIAppShellService.h" #include "nsAppShellCID.h" +#include "nsIAppsService.h" + static NS_DEFINE_CID(kDOMScriptObjectFactoryCID, NS_DOM_SCRIPT_OBJECT_FACTORY_CID); static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); @@ -6436,6 +6438,30 @@ nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel, if (!oldURI || !newURI) { return; } + + // Check if we have a redirect registered for this url. + uint32_t appId; + nsresult rv = GetAppId(&appId); + if (NS_FAILED(rv)) { + return; + } + + if (appId != nsIScriptSecurityManager::NO_APP_ID && + appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) { + nsCOMPtr appsService = + do_GetService(APPS_SERVICE_CONTRACTID); + NS_ASSERTION(appsService, "No AppsService available"); + nsCOMPtr redirect; + rv = appsService->GetRedirect(appId, newURI, getter_AddRefs(redirect)); + if (NS_SUCCEEDED(rv) && redirect) { + aNewChannel->Cancel(NS_BINDING_ABORTED); + rv = LoadURI(redirect, nullptr, 0, false); + if (NS_SUCCEEDED(rv)) { + return; + } + } + } + // On session restore we get a redirect from page to itself. Don't count it. bool equals = false; if (mTiming && diff --git a/dom/apps/src/AppsService.js b/dom/apps/src/AppsService.js index f4bade00ced..fe364235b07 100644 --- a/dom/apps/src/AppsService.js +++ b/dom/apps/src/AppsService.js @@ -13,6 +13,7 @@ const Ci = Components.interfaces; const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); const APPS_SERVICE_CID = Components.ID("{05072afa-92fe-45bf-ae22-39b69c117058}"); @@ -78,6 +79,42 @@ AppsService.prototype = { return DOMApplicationRegistry.getAppInfo(aAppId); }, + getRedirect: function getRedirect(aLocalId, aURI) { + debug("getRedirect for " + aLocalId + " " + aURI.spec); + if (aLocalId == Ci.nsIScriptSecurityManager.NO_APP_ID || + aLocalId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) { + return null; + } + + let app = DOMApplicationRegistry.getAppByLocalId(aLocalId); + if (app && app.redirects) { + let spec = aURI.spec; + for (let i = 0; i < app.redirects.length; i++) { + let redirect = app.redirects[i]; + if (spec.startsWith(redirect.from)) { + // Prepend the app origin to the redirection. We need that since + // the origin of packaged apps is a uuid created at install time. + let to = app.origin + redirect.to; + // If we have a ? or a # in the current URL, add this part to the + // redirection. + let index = -1; + index = spec.indexOf('?'); + if (index == -1) { + index = spec.indexOf('#'); + } + + if (index != -1) { + to += spec.substring(index); + } + debug('App specific redirection from ' + spec + ' to ' + to); + return Services.io.newURI(to, null, null); + } + } + } else { + return null; + } + }, + classID : APPS_SERVICE_CID, QueryInterface : XPCOMUtils.generateQI([Ci.nsIAppsService]) } diff --git a/dom/apps/src/AppsUtils.jsm b/dom/apps/src/AppsUtils.jsm index d88ebf0380e..6c9ea2d82df 100644 --- a/dom/apps/src/AppsUtils.jsm +++ b/dom/apps/src/AppsUtils.jsm @@ -20,13 +20,13 @@ XPCOMUtils.defineLazyGetter(this, "NetUtil", function() { // Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js -this.EXPORTED_SYMBOLS = ["AppsUtils", "ManifestHelper"]; +this.EXPORTED_SYMBOLS = ["AppsUtils", "ManifestHelper", "isAbsoluteURI"]; function debug(s) { //dump("-*- AppsUtils.jsm: " + s + "\n"); } -function isAbsoluteURI(aURI) { +this.isAbsoluteURI = function(aURI) { let foo = Services.io.newURI("http://foo", null, null); let bar = Services.io.newURI("http://bar", null, null); return Services.io.newURI(aURI, null, foo).prePath != foo.prePath || @@ -92,7 +92,8 @@ this.AppsUtils = { installerAppId: aApp.installerAppId || Ci.nsIScriptSecurityManager.NO_APP_ID, installerIsBrowser: !!aApp.installerIsBrowser, storeId: aApp.storeId || "", - storeVersion: aApp.storeVersion || 0 + storeVersion: aApp.storeVersion || 0, + redirects: aApp.redirects }; }, diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index 5898e294b94..8e1e25b0d44 100644 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -196,6 +196,24 @@ this.DOMApplicationRegistry = { this._saveApps(); }, + // Ensure that the .to property in redirects is a relative URL. + sanitizeRedirects: function sanitizeRedirects(aSource) { + if (!aSource) { + return null; + } + + let res = []; + for (let i = 0; i < aSource.length; i++) { + let redirect = aSource[i]; + if (redirect.from && redirect.to && + isAbsoluteURI(redirect.from) && + !isAbsoluteURI(redirect.to)) { + res.push(redirect); + } + } + return res.length > 0 ? res : null; + }, + // Registers all the activities and system messages. registerAppsHandlers: function registerAppsHandlers(aRunUpdate) { this.notifyAppsRegistryStart(); @@ -211,7 +229,11 @@ this.DOMApplicationRegistry = { // twice this._readManifests(ids, (function readCSPs(aResults) { aResults.forEach(function registerManifest(aResult) { - this.webapps[aResult.id].csp = aResult.manifest.csp || ""; + let app = this.webapps[aResult.id]; + app.csp = aResult.manifest.csp || ""; + if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) { + app.redirects = this.sanitizeRedirects(aResult.redirects); + } }, this); }).bind(this)); @@ -673,6 +695,9 @@ this.DOMApplicationRegistry = { let manifest = aResult.manifest; app.name = manifest.name; app.csp = manifest.csp || ""; + if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) { + app.redirects = this.sanitizeRedirects(manifest.redirects); + } this._registerSystemMessages(manifest, app); appsToRegister.push({ manifest: manifest, app: app }); }, this); @@ -1327,11 +1352,14 @@ this.DOMApplicationRegistry = { return [toHexString(hash.charCodeAt(i)) for (i in hash)].join(""); }, - // Updates the activities and system message handlers. + // Updates the redirect mapping, activities and system message handlers. // aOldManifest can be null if we don't have any handler to unregister. updateAppHandlers: function(aOldManifest, aNewManifest, aApp) { debug("updateAppHandlers: old=" + aOldManifest + " new=" + aNewManifest); this.notifyAppsRegistryStart(); + if (aApp.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) { + aApp.redirects = this.sanitizeRedirects(aNewManifest.redirects); + } if (supportSystemMessages()) { if (aOldManifest) { diff --git a/dom/interfaces/apps/nsIAppsService.idl b/dom/interfaces/apps/nsIAppsService.idl index f820ba225cd..63287ff3c9e 100644 --- a/dom/interfaces/apps/nsIAppsService.idl +++ b/dom/interfaces/apps/nsIAppsService.idl @@ -6,6 +6,7 @@ interface mozIDOMApplication; interface mozIApplication; +interface nsIURI; %{C++ #define APPS_SERVICE_CID { 0x05072afa, 0x92fe, 0x45bf, { 0xae, 0x22, 0x39, 0xb6, 0x9c, 0x11, 0x70, 0x58 } } @@ -16,7 +17,7 @@ interface mozIApplication; * This service allows accessing some DOMApplicationRegistry methods from * non-javascript code. */ -[scriptable, uuid(1113c6e3-28a2-4315-be10-8b3230eecc0f)] +[scriptable, uuid(27b995cf-bec8-47de-aa48-6117c950fce3)] interface nsIAppsService : nsISupports { mozIDOMApplication getAppByManifestURL(in DOMString manifestURL); @@ -63,6 +64,12 @@ interface nsIAppsService : nsISupports jsval getAppInfo(in DOMString appId); + /** + * Returns a URI to redirect to when we get a redirection to 'uri'. + * Returns null if no redirection is declared for this uri. + */ + nsIURI getRedirect(in unsigned long localId, in nsIURI uri); + /** * Returns the localId if the app was installed from a store */