Bug 744867 - Fire INSTALL_FROM_DENIED for app installation failure due to installs_allowed_from. original-patch=ianb r=fabrice
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Sun, 18 Nov 2012 14:43:38 -0800
changeset 113648 4fddb9923ef08fbd94ed76c950f49eb6125ff9ec
parent 113647 e72aeaefb691a9bb1cfb7f6143cdd7918f0a4630
child 113649 e10975ff4a070889dc8ed763de51f2f94ef6bbd5
child 113673 be3a0b4edebdbf2cf4c3c089671f0189bc53c6c9
push id18281
push usermozilla@noorenberghe.ca
push dateSun, 18 Nov 2012 22:56:40 +0000
treeherdermozilla-inbound@4fddb9923ef0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs744867
milestone19.0a1
first release with
nightly linux32
4fddb9923ef0 / 19.0a1 / 20121119030725 / files
nightly linux64
4fddb9923ef0 / 19.0a1 / 20121119030725 / files
nightly mac
4fddb9923ef0 / 19.0a1 / 20121119030725 / files
nightly win32
4fddb9923ef0 / 19.0a1 / 20121119030725 / files
nightly win64
4fddb9923ef0 / 19.0a1 / 20121119030725 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 744867 - Fire INSTALL_FROM_DENIED for app installation failure due to installs_allowed_from. original-patch=ianb r=fabrice
dom/apps/src/AppsUtils.jsm
dom/apps/src/Webapps.js
dom/apps/src/Webapps.jsm
dom/tests/mochitest/webapps/apps/Makefile.in
dom/tests/mochitest/webapps/apps/installs_allowed_from_chrome_mochitests.webapp
dom/tests/mochitest/webapps/apps/installs_allowed_from_example.com.webapp
dom/tests/mochitest/webapps/test_install_errors.xul
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -142,27 +142,20 @@ this.AppsUtils = {
 
     return null;
   },
 
   /**
    * from https://developer.mozilla.org/en/OpenWebApps/The_Manifest
    * only the name property is mandatory
    */
-  checkManifest: function(aManifest, aInstallOrigin) {
+  checkManifest: function(aManifest) {
     if (aManifest.name == undefined)
       return false;
 
-    function cbCheckAllowedOrigin(aOrigin) {
-      return aOrigin == "*" || aOrigin == aInstallOrigin;
-    }
-
-    if (aManifest.installs_allowed_from && !aManifest.installs_allowed_from.some(cbCheckAllowedOrigin))
-      return false;
-
     function isAbsolute(uri) {
       // See bug 810551
       let foo = Services.io.newURI("http://foo", null, null);
       let bar = Services.io.newURI("http://bar", null, null);
       return Services.io.newURI(uri, null, foo).prePath != foo.prePath ||
              Services.io.newURI(uri, null, bar).prePath != bar.prePath;
     }
 
@@ -187,21 +180,39 @@ this.AppsUtils = {
         return false;
       }
     }
 
     return true;
   },
 
   /**
- * Determine the type of app (app, privileged, certified)
- * that is installed by the manifest
- * @param object aManifest
- * @returns integer
- **/
+   * Determines whether the manifest allows installs for the given origin.
+   * @param object aManifest
+   * @param string aInstallOrigin
+   * @return boolean
+   **/
+  checkInstallAllowed: function checkInstallAllowed(aManifest, aInstallOrigin) {
+    if (!aManifest.installs_allowed_from) {
+      return true;
+    }
+
+    function cbCheckAllowedOrigin(aOrigin) {
+      return aOrigin == "*" || aOrigin == aInstallOrigin;
+    }
+
+    return aManifest.installs_allowed_from.some(cbCheckAllowedOrigin);
+  },
+
+  /**
+   * Determine the type of app (app, privileged, certified)
+   * that is installed by the manifest
+   * @param object aManifest
+   * @returns integer
+   **/
   getAppManifestStatus: function getAppManifestStatus(aManifest) {
     let type = aManifest.type || "web";
 
     switch(type) {
     case "web":
       return Ci.nsIPrincipal.APP_STATUS_INSTALLED;
     case "privileged":
       return Ci.nsIPrincipal.APP_STATUS_PRIVILEGED;
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -109,19 +109,22 @@ WebappsRegistry.prototype = {
         try {
           manifest = JSON.parse(xhr.responseText, installOrigin);
         } catch (e) {
           Services.DOMRequest.fireError(request, "MANIFEST_PARSE_ERROR");
           Cu.reportError("Error installing app from: " + installOrigin + ": " + "MANIFEST_PARSE_ERROR");
           return;
         }
 
-        if (!AppsUtils.checkManifest(manifest, installOrigin)) {
+        if (!AppsUtils.checkManifest(manifest)) {
           Services.DOMRequest.fireError(request, "INVALID_MANIFEST");
           Cu.reportError("Error installing app from: " + installOrigin + ": " + "INVALID_MANIFEST");
+        } else if (!AppsUtils.checkInstallAllowed(manifest, installOrigin)) {
+          Services.DOMRequest.fireError(request, "INSTALL_FROM_DENIED");
+          Cu.reportError("Error installing app from: " + installOrigin + ": " + "INSTALL_FROM_DENIED");
         } else if (!this.checkAppStatus(manifest)) {
           Services.DOMRequest.fireError(request, "INVALID_SECURITY_LEVEL");
           Cu.reportError("Error installing app, '" + manifest.name + "': " + "INVALID_SECURITY_LEVEL");
         } else {
           let receipts = (aParams && aParams.receipts && Array.isArray(aParams.receipts)) ? aParams.receipts : [];
           let categories = (aParams && aParams.categories && Array.isArray(aParams.categories)) ? aParams.categories : [];
           let etag = xhr.getResponseHeader("Etag");
           cpmm.sendAsyncMessage("Webapps:Install", { app: { installOrigin: installOrigin,
@@ -213,19 +216,21 @@ WebappsRegistry.prototype = {
       if (xhr.status == 200) {
         let manifest;
         try {
           manifest = JSON.parse(xhr.responseText, installOrigin);
         } catch(e) {
           Services.DOMRequest.fireError(request, "MANIFEST_PARSE_ERROR");
           return;
         }
-        if (!(AppsUtils.checkManifest(manifest, installOrigin) &&
+        if (!(AppsUtils.checkManifest(manifest) &&
               manifest.package_path)) {
           Services.DOMRequest.fireError(request, "INVALID_MANIFEST");
+        } else if (!AppsUtils.checkInstallAllowed(manifest, installOrigin)) {
+          Services.DOMRequest.fireError(request, "INSTALL_FROM_DENIED");
         } else {
           if (!this.checkAppStatus(manifest)) {
             Services.DOMRequest.fireError(request, "INVALID_SECURITY_LEVEL");
           } else {
             let receipts = (aParams && aParams.receipts && Array.isArray(aParams.receipts)) ? aParams.receipts : [];
             let categories = (aParams && aParams.categories && Array.isArray(aParams.categories)) ? aParams.categories : [];
             let etag = xhr.getResponseHeader("Etag");
             cpmm.sendAsyncMessage("Webapps:InstallPackage", { app: {
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -1000,18 +1000,20 @@ this.DOMApplicationRegistry = {
       if (xhr.status == 200) {
         let manifest;
         try {
           manifest = JSON.parse(xhr.responseText);
         } catch(e) {
           sendError("MANIFEST_PARSE_ERROR");
           return;
         }
-        if (!AppsUtils.checkManifest(manifest, app.installOrigin)) {
+        if (!AppsUtils.checkManifest(manifest)) {
           sendError("INVALID_MANIFEST");
+        } else if (!AppsUtils.checkInstallAllowed(manifest, app.installOrigin)) {
+          sendError("INSTALL_FROM_DENIED");
         } else {
           app.etag = xhr.getResponseHeader("Etag");
           app.lastCheckedUpdate = Date.now();
           if (app.origin.startsWith("app://")) {
             updatePackagedApp(manifest);
           } else {
             updateHostedApp.call(this, manifest);
           }
@@ -1401,20 +1403,24 @@ this.DOMApplicationRegistry = {
             // Obtain a converter to read from a UTF-8 encoded input stream.
             let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                             .createInstance(Ci.nsIScriptableUnicodeConverter);
             converter.charset = "UTF-8";
 
             let manifest = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(istream,
                                                                  istream.available()) || ""));
 
-            if (!AppsUtils.checkManifest(manifest, aApp.installOrigin)) {
+            if (!AppsUtils.checkManifest(manifest)) {
               throw "INVALID_MANIFEST";
             }
 
+            if (!AppsUtils.checkInstallAllowed(manifest, aApp.installOrigin)) {
+              throw "INSTALL_FROM_DENIED";
+            }
+
             if (!checkAppStatus(manifest)) {
               throw "INVALID_SECURITY_LEVEL";
             }
 
             if (aOnSuccess) {
               aOnSuccess(id, manifest);
             }
             delete self.downloads[aApp.manifestURL];
--- a/dom/tests/mochitest/webapps/apps/Makefile.in
+++ b/dom/tests/mochitest/webapps/apps/Makefile.in
@@ -17,16 +17,18 @@ MOCHITEST_CHROME_FILES	= \
     missing_required_field.webapp^headers^ \
     json_syntax_error.webapp \
     json_syntax_error.webapp^headers^ \
     no_delegated_install.webapp \
     no_delegated_install.webapp^headers^ \
     bad_content_type.webapp \
     utf8.webapp \
     utf8.webapp^headers^ \
+    installs_allowed_from_chrome_mochitests.webapp \
+    installs_allowed_from_example.com.webapp \
     invalid_launch_path.webapp \
     invalid_launch_path.webapp^headers^ \
     invalid_launch_path2.webapp \
     invalid_launch_path2.webapp^headers^ \
     invalid_entry_point.webapp \
     invalid_entry_point.webapp^headers^ \
     invalid_locale_entry_point.webapp \
     invalid_locale_entry_point.webapp^headers^ \
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webapps/apps/installs_allowed_from_chrome_mochitests.webapp
@@ -0,0 +1,4 @@
+{
+  "name": "An application which only allows chrome://mochitests to install it",
+  "installs_allowed_from": ["chrome://mochitests"]
+}
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webapps/apps/installs_allowed_from_example.com.webapp
@@ -0,0 +1,4 @@
+{
+  "name": "An application which only allows https://example.com to install it",
+  "installs_allowed_from": ["https://example.com"]
+}
--- a/dom/tests/mochitest/webapps/test_install_errors.xul
+++ b/dom/tests/mochitest/webapps/test_install_errors.xul
@@ -25,16 +25,18 @@ var steps = [
   permissionDenied,
   invalidContent,
   installPackageNotImplemented,
   invalidLaunchPath,
   invalidLaunchPath2,
   invalidEntryPoint,
   invalidLocaleEntryPoint,
   fileURL,
+  originNotAllowed,
+  originAllowed,
 ];
 
 runAll(steps);
 
 function noArgs(next) {
   try {
     navigator.mozApps.install();
   } catch (e) {
@@ -164,10 +166,48 @@ function fileURL(next) {
   } catch(ex) {
     is(ex.message, "INVALID_URL_SCHEME: 'file'; must be 'http' or 'https'",
        "attempt to install existent file: URL throws exception");
   }
 
   next();
 }
 
+function originNotAllowed(next) {
+  var url = "http://test/chrome/dom/tests/mochitest/webapps/apps/installs_allowed_from_example.com.webapp";
+
+  confirmNextInstall();
+  var request = navigator.mozApps.install(url, null);
+
+  request.onerror = function onInstallError() {
+    is(this.error.name, "INSTALL_FROM_DENIED", "origin is not in installs_allowed_from");
+    next();
+  };
+
+  request.onsuccess = function onInstall() {
+    ok(false, "test should fail because of installs_allowed_from");
+    this.result.uninstall().onsuccess = function onUninstall() {
+      next();
+    };
+  };
+}
+
+function originAllowed(next) {
+  var url = "http://test/chrome/dom/tests/mochitest/webapps/apps/installs_allowed_from_chrome_mochitests.webapp";
+
+  confirmNextInstall();
+  var request = navigator.mozApps.install(url, null);
+
+  request.onerror = function onInstallError() {
+    ok(false, "installation error: " + this.error.name);
+    next();
+  };
+
+  request.onsuccess = function onInstall() {
+    ok(true, "test origin is in installs_allowed_from");
+    this.result.uninstall().onsuccess = function onUninstall() {
+      next();
+    };
+  };
+}
+
 </script>
 </window>