dom/apps/src/AppsUtils.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/apps/src/AppsUtils.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,738 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +const Cu = Components.utils;
    1.11 +const Cc = Components.classes;
    1.12 +const Ci = Components.interfaces;
    1.13 +const Cr = Components.results;
    1.14 +
    1.15 +Cu.import("resource://gre/modules/osfile.jsm");
    1.16 +Cu.import("resource://gre/modules/Services.jsm");
    1.17 +Cu.import("resource://gre/modules/Task.jsm");
    1.18 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.19 +Cu.import("resource://gre/modules/Promise.jsm");
    1.20 +
    1.21 +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
    1.22 +  "resource://gre/modules/FileUtils.jsm");
    1.23 +
    1.24 +XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
    1.25 +  "resource://gre/modules/WebappOSUtils.jsm");
    1.26 +
    1.27 +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
    1.28 +  "resource://gre/modules/NetUtil.jsm");
    1.29 +
    1.30 +// Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
    1.31 +
    1.32 +this.EXPORTED_SYMBOLS = ["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"];
    1.33 +
    1.34 +function debug(s) {
    1.35 +  //dump("-*- AppsUtils.jsm: " + s + "\n");
    1.36 +}
    1.37 +
    1.38 +this.isAbsoluteURI = function(aURI) {
    1.39 +  let foo = Services.io.newURI("http://foo", null, null);
    1.40 +  let bar = Services.io.newURI("http://bar", null, null);
    1.41 +  return Services.io.newURI(aURI, null, foo).prePath != foo.prePath ||
    1.42 +         Services.io.newURI(aURI, null, bar).prePath != bar.prePath;
    1.43 +}
    1.44 +
    1.45 +this.mozIApplication = function(aApp) {
    1.46 +  _setAppProperties(this, aApp);
    1.47 +}
    1.48 +
    1.49 +mozIApplication.prototype = {
    1.50 +  hasPermission: function(aPermission) {
    1.51 +    let uri = Services.io.newURI(this.origin, null, null);
    1.52 +    let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
    1.53 +                   .getService(Ci.nsIScriptSecurityManager);
    1.54 +    // This helper checks an URI inside |aApp|'s origin and part of |aApp| has a
    1.55 +    // specific permission. It is not checking if browsers inside |aApp| have such
    1.56 +    // permission.
    1.57 +    let principal = secMan.getAppCodebasePrincipal(uri, this.localId,
    1.58 +                                                   /*mozbrowser*/false);
    1.59 +    let perm = Services.perms.testExactPermissionFromPrincipal(principal,
    1.60 +                                                               aPermission);
    1.61 +    return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
    1.62 +  },
    1.63 +
    1.64 +  QueryInterface: function(aIID) {
    1.65 +    if (aIID.equals(Ci.mozIApplication) ||
    1.66 +        aIID.equals(Ci.nsISupports))
    1.67 +      return this;
    1.68 +    throw Cr.NS_ERROR_NO_INTERFACE;
    1.69 +  }
    1.70 +}
    1.71 +
    1.72 +function _setAppProperties(aObj, aApp) {
    1.73 +  aObj.name = aApp.name;
    1.74 +  aObj.csp = aApp.csp;
    1.75 +  aObj.installOrigin = aApp.installOrigin;
    1.76 +  aObj.origin = aApp.origin;
    1.77 +#ifdef MOZ_ANDROID_SYNTHAPKS
    1.78 +  aObj.apkPackageName = aApp.apkPackageName;
    1.79 +#endif
    1.80 +  aObj.receipts = aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null;
    1.81 +  aObj.installTime = aApp.installTime;
    1.82 +  aObj.manifestURL = aApp.manifestURL;
    1.83 +  aObj.appStatus = aApp.appStatus;
    1.84 +  aObj.removable = aApp.removable;
    1.85 +  aObj.id = aApp.id;
    1.86 +  aObj.localId = aApp.localId;
    1.87 +  aObj.basePath = aApp.basePath;
    1.88 +  aObj.progress = aApp.progress || 0.0;
    1.89 +  aObj.installState = aApp.installState || "installed";
    1.90 +  aObj.downloadAvailable = aApp.downloadAvailable;
    1.91 +  aObj.downloading = aApp.downloading;
    1.92 +  aObj.readyToApplyDownload = aApp.readyToApplyDownload;
    1.93 +  aObj.downloadSize = aApp.downloadSize || 0;
    1.94 +  aObj.lastUpdateCheck = aApp.lastUpdateCheck;
    1.95 +  aObj.updateTime = aApp.updateTime;
    1.96 +  aObj.etag = aApp.etag;
    1.97 +  aObj.packageEtag = aApp.packageEtag;
    1.98 +  aObj.manifestHash = aApp.manifestHash;
    1.99 +  aObj.packageHash = aApp.packageHash;
   1.100 +  aObj.staged = aApp.staged;
   1.101 +  aObj.installerAppId = aApp.installerAppId || Ci.nsIScriptSecurityManager.NO_APP_ID;
   1.102 +  aObj.installerIsBrowser = !!aApp.installerIsBrowser;
   1.103 +  aObj.storeId = aApp.storeId || "";
   1.104 +  aObj.storeVersion = aApp.storeVersion || 0;
   1.105 +  aObj.role = aApp.role || "";
   1.106 +  aObj.redirects = aApp.redirects;
   1.107 +}
   1.108 +
   1.109 +this.AppsUtils = {
   1.110 +  // Clones a app, without the manifest.
   1.111 +  cloneAppObject: function(aApp) {
   1.112 +    let obj = {};
   1.113 +    _setAppProperties(obj, aApp);
   1.114 +    return obj;
   1.115 +  },
   1.116 +
   1.117 +  getAppByManifestURL: function getAppByManifestURL(aApps, aManifestURL) {
   1.118 +    debug("getAppByManifestURL " + aManifestURL);
   1.119 +    // This could be O(1) if |webapps| was a dictionary indexed on manifestURL
   1.120 +    // which should be the unique app identifier.
   1.121 +    // It's currently O(n).
   1.122 +    for (let id in aApps) {
   1.123 +      let app = aApps[id];
   1.124 +      if (app.manifestURL == aManifestURL) {
   1.125 +        return new mozIApplication(app);
   1.126 +      }
   1.127 +    }
   1.128 +
   1.129 +    return null;
   1.130 +  },
   1.131 +
   1.132 +  getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aApps, aManifestURL) {
   1.133 +    debug("getAppLocalIdByManifestURL " + aManifestURL);
   1.134 +    for (let id in aApps) {
   1.135 +      if (aApps[id].manifestURL == aManifestURL) {
   1.136 +        return aApps[id].localId;
   1.137 +      }
   1.138 +    }
   1.139 +
   1.140 +    return Ci.nsIScriptSecurityManager.NO_APP_ID;
   1.141 +  },
   1.142 +
   1.143 +  getAppLocalIdByStoreId: function(aApps, aStoreId) {
   1.144 +    debug("getAppLocalIdByStoreId:" + aStoreId);
   1.145 +    for (let id in aApps) {
   1.146 +      if (aApps[id].storeId == aStoreId) {
   1.147 +        return aApps[id].localId;
   1.148 +      }
   1.149 +    }
   1.150 +
   1.151 +    return Ci.nsIScriptSecurityManager.NO_APP_ID;
   1.152 +  },
   1.153 +
   1.154 +  getCSPByLocalId: function getCSPByLocalId(aApps, aLocalId) {
   1.155 +    debug("getCSPByLocalId " + aLocalId);
   1.156 +    for (let id in aApps) {
   1.157 +      let app = aApps[id];
   1.158 +      if (app.localId == aLocalId) {
   1.159 +	  return ( app.csp || "" );
   1.160 +      }
   1.161 +    }
   1.162 +
   1.163 +    return "";
   1.164 +  },
   1.165 +
   1.166 +  getAppByLocalId: function getAppByLocalId(aApps, aLocalId) {
   1.167 +    debug("getAppByLocalId " + aLocalId);
   1.168 +    for (let id in aApps) {
   1.169 +      let app = aApps[id];
   1.170 +      if (app.localId == aLocalId) {
   1.171 +        return new mozIApplication(app);
   1.172 +      }
   1.173 +    }
   1.174 +
   1.175 +    return null;
   1.176 +  },
   1.177 +
   1.178 +  getManifestURLByLocalId: function getManifestURLByLocalId(aApps, aLocalId) {
   1.179 +    debug("getManifestURLByLocalId " + aLocalId);
   1.180 +    for (let id in aApps) {
   1.181 +      let app = aApps[id];
   1.182 +      if (app.localId == aLocalId) {
   1.183 +        return app.manifestURL;
   1.184 +      }
   1.185 +    }
   1.186 +
   1.187 +    return "";
   1.188 +  },
   1.189 +
   1.190 +  getCoreAppsBasePath: function getCoreAppsBasePath() {
   1.191 +    debug("getCoreAppsBasePath()");
   1.192 +    try {
   1.193 +      return FileUtils.getDir("coreAppsDir", ["webapps"], false).path;
   1.194 +    } catch(e) {
   1.195 +      return null;
   1.196 +    }
   1.197 +  },
   1.198 +
   1.199 +  getAppInfo: function getAppInfo(aApps, aAppId) {
   1.200 +    let app = aApps[aAppId];
   1.201 +
   1.202 +    if (!app) {
   1.203 +      debug("No webapp for " + aAppId);
   1.204 +      return null;
   1.205 +    }
   1.206 +
   1.207 +    // We can have 3rd party apps that are non-removable,
   1.208 +    // so we can't use the 'removable' property for isCoreApp
   1.209 +    // Instead, we check if the app is installed under /system/b2g
   1.210 +    let isCoreApp = false;
   1.211 +
   1.212 +#ifdef MOZ_WIDGET_GONK
   1.213 +    isCoreApp = app.basePath == this.getCoreAppsBasePath();
   1.214 +#endif
   1.215 +    debug(app.basePath + " isCoreApp: " + isCoreApp);
   1.216 +    return { "path": WebappOSUtils.getPackagePath(app),
   1.217 +             "isCoreApp": isCoreApp };
   1.218 +  },
   1.219 +
   1.220 +  /**
   1.221 +    * Remove potential HTML tags from displayable fields in the manifest.
   1.222 +    * We check name, description, developer name, and permission description
   1.223 +    */
   1.224 +  sanitizeManifest: function(aManifest) {
   1.225 +    let sanitizer = Cc["@mozilla.org/parserutils;1"]
   1.226 +                      .getService(Ci.nsIParserUtils);
   1.227 +    if (!sanitizer) {
   1.228 +      return;
   1.229 +    }
   1.230 +
   1.231 +    function sanitize(aStr) {
   1.232 +      return sanitizer.convertToPlainText(aStr,
   1.233 +               Ci.nsIDocumentEncoder.OutputRaw, 0);
   1.234 +    }
   1.235 +
   1.236 +    function sanitizeEntryPoint(aRoot) {
   1.237 +      aRoot.name = sanitize(aRoot.name);
   1.238 +
   1.239 +      if (aRoot.description) {
   1.240 +        aRoot.description = sanitize(aRoot.description);
   1.241 +      }
   1.242 +
   1.243 +      if (aRoot.developer && aRoot.developer.name) {
   1.244 +        aRoot.developer.name = sanitize(aRoot.developer.name);
   1.245 +      }
   1.246 +
   1.247 +      if (aRoot.permissions) {
   1.248 +        for (let permission in aRoot.permissions) {
   1.249 +          if (aRoot.permissions[permission].description) {
   1.250 +            aRoot.permissions[permission].description =
   1.251 +             sanitize(aRoot.permissions[permission].description);
   1.252 +          }
   1.253 +        }
   1.254 +      }
   1.255 +    }
   1.256 +
   1.257 +    // First process the main section, then the entry points.
   1.258 +    sanitizeEntryPoint(aManifest);
   1.259 +
   1.260 +    if (aManifest.entry_points) {
   1.261 +      for (let entry in aManifest.entry_points) {
   1.262 +        sanitizeEntryPoint(aManifest.entry_points[entry]);
   1.263 +      }
   1.264 +    }
   1.265 +  },
   1.266 +
   1.267 +  /**
   1.268 +   * From https://developer.mozilla.org/en/OpenWebApps/The_Manifest
   1.269 +   * Only the name property is mandatory.
   1.270 +   */
   1.271 +  checkManifest: function(aManifest, app) {
   1.272 +    if (aManifest.name == undefined)
   1.273 +      return false;
   1.274 +
   1.275 +    this.sanitizeManifest(aManifest);
   1.276 +
   1.277 +    // launch_path, entry_points launch paths, message hrefs, and activity hrefs can't be absolute
   1.278 +    if (aManifest.launch_path && isAbsoluteURI(aManifest.launch_path))
   1.279 +      return false;
   1.280 +
   1.281 +    function checkAbsoluteEntryPoints(entryPoints) {
   1.282 +      for (let name in entryPoints) {
   1.283 +        if (entryPoints[name].launch_path && isAbsoluteURI(entryPoints[name].launch_path)) {
   1.284 +          return true;
   1.285 +        }
   1.286 +      }
   1.287 +      return false;
   1.288 +    }
   1.289 +
   1.290 +    if (checkAbsoluteEntryPoints(aManifest.entry_points))
   1.291 +      return false;
   1.292 +
   1.293 +    for (let localeName in aManifest.locales) {
   1.294 +      if (checkAbsoluteEntryPoints(aManifest.locales[localeName].entry_points)) {
   1.295 +        return false;
   1.296 +      }
   1.297 +    }
   1.298 +
   1.299 +    if (aManifest.activities) {
   1.300 +      for (let activityName in aManifest.activities) {
   1.301 +        let activity = aManifest.activities[activityName];
   1.302 +        if (activity.href && isAbsoluteURI(activity.href)) {
   1.303 +          return false;
   1.304 +        }
   1.305 +      }
   1.306 +    }
   1.307 +
   1.308 +    // |messages| is an array of items, where each item is either a string or
   1.309 +    // a {name: href} object.
   1.310 +    let messages = aManifest.messages;
   1.311 +    if (messages) {
   1.312 +      if (!Array.isArray(messages)) {
   1.313 +        return false;
   1.314 +      }
   1.315 +      for (let item of aManifest.messages) {
   1.316 +        if (typeof item == "object") {
   1.317 +          let keys = Object.keys(item);
   1.318 +          if (keys.length != 1) {
   1.319 +            return false;
   1.320 +          }
   1.321 +          if (isAbsoluteURI(item[keys[0]])) {
   1.322 +            return false;
   1.323 +          }
   1.324 +        }
   1.325 +      }
   1.326 +    }
   1.327 +
   1.328 +    // The 'size' field must be a positive integer.
   1.329 +    if (aManifest.size) {
   1.330 +      aManifest.size = parseInt(aManifest.size);
   1.331 +      if (Number.isNaN(aManifest.size) || aManifest.size < 0) {
   1.332 +        return false;
   1.333 +      }
   1.334 +    }
   1.335 +
   1.336 +    // The 'role' field must be a string.
   1.337 +    if (aManifest.role && (typeof aManifest.role !== "string")) {
   1.338 +      return false;
   1.339 +    }
   1.340 +    return true;
   1.341 +  },
   1.342 +
   1.343 +  checkManifestContentType: function
   1.344 +     checkManifestContentType(aInstallOrigin, aWebappOrigin, aContentType) {
   1.345 +    let hadCharset = { };
   1.346 +    let charset = { };
   1.347 +    let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
   1.348 +    let contentType = netutil.parseContentType(aContentType, charset, hadCharset);
   1.349 +    if (aInstallOrigin != aWebappOrigin &&
   1.350 +        contentType != "application/x-web-app-manifest+json") {
   1.351 +      return false;
   1.352 +    }
   1.353 +    return true;
   1.354 +  },
   1.355 +
   1.356 +  /**
   1.357 +   * Method to apply modifications to webapp manifests file saved internally.
   1.358 +   * For now, only ensure app can't rename itself.
   1.359 +   */
   1.360 +  ensureSameAppName: function ensureSameAppName(aOldManifest, aNewManifest, aApp) {
   1.361 +    // Ensure that app name can't be updated
   1.362 +    aNewManifest.name = aApp.name;
   1.363 +
   1.364 +    // Nor through localized names
   1.365 +    if ('locales' in aNewManifest) {
   1.366 +      let defaultName = new ManifestHelper(aOldManifest, aApp.origin).name;
   1.367 +      for (let locale in aNewManifest.locales) {
   1.368 +        let entry = aNewManifest.locales[locale];
   1.369 +        if (!entry.name) {
   1.370 +          continue;
   1.371 +        }
   1.372 +        // In case previous manifest didn't had a name,
   1.373 +        // we use the default app name
   1.374 +        let localizedName = defaultName;
   1.375 +        if (aOldManifest && 'locales' in aOldManifest &&
   1.376 +            locale in aOldManifest.locales) {
   1.377 +          localizedName = aOldManifest.locales[locale].name;
   1.378 +        }
   1.379 +        entry.name = localizedName;
   1.380 +      }
   1.381 +    }
   1.382 +  },
   1.383 +
   1.384 +  /**
   1.385 +   * Determines whether the manifest allows installs for the given origin.
   1.386 +   * @param object aManifest
   1.387 +   * @param string aInstallOrigin
   1.388 +   * @return boolean
   1.389 +   **/
   1.390 +  checkInstallAllowed: function checkInstallAllowed(aManifest, aInstallOrigin) {
   1.391 +    if (!aManifest.installs_allowed_from) {
   1.392 +      return true;
   1.393 +    }
   1.394 +
   1.395 +    function cbCheckAllowedOrigin(aOrigin) {
   1.396 +      return aOrigin == "*" || aOrigin == aInstallOrigin;
   1.397 +    }
   1.398 +
   1.399 +    return aManifest.installs_allowed_from.some(cbCheckAllowedOrigin);
   1.400 +  },
   1.401 +
   1.402 +  /**
   1.403 +   * Determine the type of app (app, privileged, certified)
   1.404 +   * that is installed by the manifest
   1.405 +   * @param object aManifest
   1.406 +   * @returns integer
   1.407 +   **/
   1.408 +  getAppManifestStatus: function getAppManifestStatus(aManifest) {
   1.409 +    let type = aManifest.type || "web";
   1.410 +
   1.411 +    switch(type) {
   1.412 +    case "web":
   1.413 +      return Ci.nsIPrincipal.APP_STATUS_INSTALLED;
   1.414 +    case "privileged":
   1.415 +      return Ci.nsIPrincipal.APP_STATUS_PRIVILEGED;
   1.416 +    case "certified":
   1.417 +      return Ci.nsIPrincipal.APP_STATUS_CERTIFIED;
   1.418 +    default:
   1.419 +      throw new Error("Webapps.jsm: Undetermined app manifest type");
   1.420 +    }
   1.421 +  },
   1.422 +
   1.423 +  /**
   1.424 +   * Determines if an update or a factory reset occured.
   1.425 +   */
   1.426 +  isFirstRun: function isFirstRun(aPrefBranch) {
   1.427 +    let savedmstone = null;
   1.428 +    try {
   1.429 +      savedmstone = aPrefBranch.getCharPref("gecko.mstone");
   1.430 +    } catch (e) {}
   1.431 +
   1.432 +    let mstone = Services.appinfo.platformVersion;
   1.433 +
   1.434 +    let savedBuildID = null;
   1.435 +    try {
   1.436 +      savedBuildID = aPrefBranch.getCharPref("gecko.buildID");
   1.437 +    } catch (e) {}
   1.438 +
   1.439 +    let buildID = Services.appinfo.platformBuildID;
   1.440 +
   1.441 +    aPrefBranch.setCharPref("gecko.mstone", mstone);
   1.442 +    aPrefBranch.setCharPref("gecko.buildID", buildID);
   1.443 +
   1.444 +    return ((mstone != savedmstone) || (buildID != savedBuildID));
   1.445 +  },
   1.446 +
   1.447 +  /**
   1.448 +   * Check if two manifests have the same set of properties and that the
   1.449 +   * values of these properties are the same, in each locale.
   1.450 +   * Manifests here are raw json ones.
   1.451 +   */
   1.452 +  compareManifests: function compareManifests(aManifest1, aManifest2) {
   1.453 +    // 1. check if we have the same locales in both manifests.
   1.454 +    let locales1 = [];
   1.455 +    let locales2 = [];
   1.456 +    if (aManifest1.locales) {
   1.457 +      for (let locale in aManifest1.locales) {
   1.458 +        locales1.push(locale);
   1.459 +      }
   1.460 +    }
   1.461 +    if (aManifest2.locales) {
   1.462 +      for (let locale in aManifest2.locales) {
   1.463 +        locales2.push(locale);
   1.464 +      }
   1.465 +    }
   1.466 +    if (locales1.sort().join() !== locales2.sort().join()) {
   1.467 +      return false;
   1.468 +    }
   1.469 +
   1.470 +    // Helper function to check the app name and developer information for
   1.471 +    // two given roots.
   1.472 +    let checkNameAndDev = function(aRoot1, aRoot2) {
   1.473 +      let name1 = aRoot1.name;
   1.474 +      let name2 = aRoot2.name;
   1.475 +      if (name1 !== name2) {
   1.476 +        return false;
   1.477 +      }
   1.478 +
   1.479 +      let dev1 = aRoot1.developer;
   1.480 +      let dev2 = aRoot2.developer;
   1.481 +      if ((dev1 && !dev2) || (dev2 && !dev1)) {
   1.482 +        return false;
   1.483 +      }
   1.484 +
   1.485 +      return (!dev1 && !dev2) ||
   1.486 +             (dev1.name === dev2.name && dev1.url === dev2.url);
   1.487 +    }
   1.488 +
   1.489 +    // 2. For each locale, check if the name and dev info are the same.
   1.490 +    if (!checkNameAndDev(aManifest1, aManifest2)) {
   1.491 +      return false;
   1.492 +    }
   1.493 +
   1.494 +    for (let locale in aManifest1.locales) {
   1.495 +      if (!checkNameAndDev(aManifest1.locales[locale],
   1.496 +                           aManifest2.locales[locale])) {
   1.497 +        return false;
   1.498 +      }
   1.499 +    }
   1.500 +
   1.501 +    // Nothing failed.
   1.502 +    return true;
   1.503 +  },
   1.504 +
   1.505 +  // Asynchronously loads a JSON file. aPath is a string representing the path
   1.506 +  // of the file to be read.
   1.507 +  loadJSONAsync: function(aPath) {
   1.508 +    let deferred = Promise.defer();
   1.509 +
   1.510 +    try {
   1.511 +      let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   1.512 +      file.initWithPath(aPath);
   1.513 +
   1.514 +      let channel = NetUtil.newChannel(file);
   1.515 +      channel.contentType = "application/json";
   1.516 +
   1.517 +      NetUtil.asyncFetch(channel, function(aStream, aResult) {
   1.518 +        if (!Components.isSuccessCode(aResult)) {
   1.519 +          deferred.resolve(null);
   1.520 +
   1.521 +          if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) {
   1.522 +            // We expect this under certain circumstances, like for webapps.json
   1.523 +            // on firstrun, so we return early without reporting an error.
   1.524 +            return;
   1.525 +          }
   1.526 +
   1.527 +          Cu.reportError("AppsUtils: Could not read from json file " + aPath);
   1.528 +          return;
   1.529 +        }
   1.530 +
   1.531 +        try {
   1.532 +          // Obtain a converter to read from a UTF-8 encoded input stream.
   1.533 +          let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
   1.534 +                            .createInstance(Ci.nsIScriptableUnicodeConverter);
   1.535 +          converter.charset = "UTF-8";
   1.536 +
   1.537 +          // Read json file into a string
   1.538 +          let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
   1.539 +                                                            aStream.available()) || ""));
   1.540 +          aStream.close();
   1.541 +
   1.542 +          deferred.resolve(data);
   1.543 +        } catch (ex) {
   1.544 +          Cu.reportError("AppsUtils: Could not parse JSON: " +
   1.545 +                         aPath + " " + ex + "\n" + ex.stack);
   1.546 +          deferred.resolve(null);
   1.547 +        }
   1.548 +      });
   1.549 +    } catch (ex) {
   1.550 +      Cu.reportError("AppsUtils: Could not read from " +
   1.551 +                     aPath + " : " + ex + "\n" + ex.stack);
   1.552 +      deferred.resolve(null);
   1.553 +    }
   1.554 +
   1.555 +    return deferred.promise;
   1.556 +  },
   1.557 +
   1.558 +  // Returns the MD5 hash of a string.
   1.559 +  computeHash: function(aString) {
   1.560 +    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
   1.561 +                      .createInstance(Ci.nsIScriptableUnicodeConverter);
   1.562 +    converter.charset = "UTF-8";
   1.563 +    let result = {};
   1.564 +    // Data is an array of bytes.
   1.565 +    let data = converter.convertToByteArray(aString, result);
   1.566 +
   1.567 +    let hasher = Cc["@mozilla.org/security/hash;1"]
   1.568 +                   .createInstance(Ci.nsICryptoHash);
   1.569 +    hasher.init(hasher.MD5);
   1.570 +    hasher.update(data, data.length);
   1.571 +    // We're passing false to get the binary hash and not base64.
   1.572 +    let hash = hasher.finish(false);
   1.573 +
   1.574 +    function toHexString(charCode) {
   1.575 +      return ("0" + charCode.toString(16)).slice(-2);
   1.576 +    }
   1.577 +
   1.578 +    // Convert the binary hash data to a hex string.
   1.579 +    return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
   1.580 +  }
   1.581 +}
   1.582 +
   1.583 +/**
   1.584 + * Helper object to access manifest information with locale support
   1.585 + */
   1.586 +this.ManifestHelper = function(aManifest, aOrigin) {
   1.587 +  this._origin = Services.io.newURI(aOrigin, null, null);
   1.588 +  this._manifest = aManifest;
   1.589 +  let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry)
   1.590 +                                                          .QueryInterface(Ci.nsIToolkitChromeRegistry);
   1.591 +  let locale = chrome.getSelectedLocale("global").toLowerCase();
   1.592 +  this._localeRoot = this._manifest;
   1.593 +
   1.594 +  if (this._manifest.locales && this._manifest.locales[locale]) {
   1.595 +    this._localeRoot = this._manifest.locales[locale];
   1.596 +  }
   1.597 +  else if (this._manifest.locales) {
   1.598 +    // try with the language part of the locale ("en" for en-GB) only
   1.599 +    let lang = locale.split('-')[0];
   1.600 +    if (lang != locale && this._manifest.locales[lang])
   1.601 +      this._localeRoot = this._manifest.locales[lang];
   1.602 +  }
   1.603 +};
   1.604 +
   1.605 +ManifestHelper.prototype = {
   1.606 +  _localeProp: function(aProp) {
   1.607 +    if (this._localeRoot[aProp] != undefined)
   1.608 +      return this._localeRoot[aProp];
   1.609 +    return this._manifest[aProp];
   1.610 +  },
   1.611 +
   1.612 +  get name() {
   1.613 +    return this._localeProp("name");
   1.614 +  },
   1.615 +
   1.616 +  get description() {
   1.617 +    return this._localeProp("description");
   1.618 +  },
   1.619 +
   1.620 +  get type() {
   1.621 +    return this._localeProp("type");
   1.622 +  },
   1.623 +
   1.624 +  get version() {
   1.625 +    return this._localeProp("version");
   1.626 +  },
   1.627 +
   1.628 +  get launch_path() {
   1.629 +    return this._localeProp("launch_path");
   1.630 +  },
   1.631 +
   1.632 +  get developer() {
   1.633 +    // Default to {} in order to avoid exception in code
   1.634 +    // that doesn't check for null `developer`
   1.635 +    return this._localeProp("developer") || {};
   1.636 +  },
   1.637 +
   1.638 +  get icons() {
   1.639 +    return this._localeProp("icons");
   1.640 +  },
   1.641 +
   1.642 +  get appcache_path() {
   1.643 +    return this._localeProp("appcache_path");
   1.644 +  },
   1.645 +
   1.646 +  get orientation() {
   1.647 +    return this._localeProp("orientation");
   1.648 +  },
   1.649 +
   1.650 +  get package_path() {
   1.651 +    return this._localeProp("package_path");
   1.652 +  },
   1.653 +
   1.654 +  get size() {
   1.655 +    return this._manifest["size"] || 0;
   1.656 +  },
   1.657 +
   1.658 +  get permissions() {
   1.659 +    if (this._manifest.permissions) {
   1.660 +      return this._manifest.permissions;
   1.661 +    }
   1.662 +    return {};
   1.663 +  },
   1.664 +
   1.665 +  get biggestIconURL() {
   1.666 +    let icons = this._localeProp("icons");
   1.667 +    if (!icons) {
   1.668 +      return null;
   1.669 +    }
   1.670 +
   1.671 +    let iconSizes = Object.keys(icons);
   1.672 +    if (iconSizes.length == 0) {
   1.673 +      return null;
   1.674 +    }
   1.675 +
   1.676 +    iconSizes.sort((a, b) => a - b);
   1.677 +    let biggestIconSize = iconSizes.pop();
   1.678 +    let biggestIcon = icons[biggestIconSize];
   1.679 +    let biggestIconURL = this._origin.resolve(biggestIcon);
   1.680 +
   1.681 +    return biggestIconURL;
   1.682 +  },
   1.683 +
   1.684 +  iconURLForSize: function(aSize) {
   1.685 +    let icons = this._localeProp("icons");
   1.686 +    if (!icons)
   1.687 +      return null;
   1.688 +    let dist = 100000;
   1.689 +    let icon = null;
   1.690 +    for (let size in icons) {
   1.691 +      let iSize = parseInt(size);
   1.692 +      if (Math.abs(iSize - aSize) < dist) {
   1.693 +        icon = this._origin.resolve(icons[size]);
   1.694 +        dist = Math.abs(iSize - aSize);
   1.695 +      }
   1.696 +    }
   1.697 +    return icon;
   1.698 +  },
   1.699 +
   1.700 +  fullLaunchPath: function(aStartPoint) {
   1.701 +    // If no start point is specified, we use the root launch path.
   1.702 +    // In all error cases, we just return null.
   1.703 +    if ((aStartPoint || "") === "") {
   1.704 +      return this._origin.resolve(this._localeProp("launch_path") || "");
   1.705 +    }
   1.706 +
   1.707 +    // Search for the l10n entry_points property.
   1.708 +    let entryPoints = this._localeProp("entry_points");
   1.709 +    if (!entryPoints) {
   1.710 +      return null;
   1.711 +    }
   1.712 +
   1.713 +    if (entryPoints[aStartPoint]) {
   1.714 +      return this._origin.resolve(entryPoints[aStartPoint].launch_path || "");
   1.715 +    }
   1.716 +
   1.717 +    return null;
   1.718 +  },
   1.719 +
   1.720 +  resolveFromOrigin: function(aURI) {
   1.721 +    // This should be enforced higher up, but check it here just in case.
   1.722 +    if (isAbsoluteURI(aURI)) {
   1.723 +      throw new Error("Webapps.jsm: non-relative URI passed to resolveFromOrigin");
   1.724 +    }
   1.725 +    return this._origin.resolve(aURI);
   1.726 +  },
   1.727 +
   1.728 +  fullAppcachePath: function() {
   1.729 +    let appcachePath = this._localeProp("appcache_path");
   1.730 +    return this._origin.resolve(appcachePath ? appcachePath : "");
   1.731 +  },
   1.732 +
   1.733 +  fullPackagePath: function() {
   1.734 +    let packagePath = this._localeProp("package_path");
   1.735 +    return this._origin.resolve(packagePath ? packagePath : "");
   1.736 +  },
   1.737 +
   1.738 +  get role() {
   1.739 +    return this._manifest.role || "";
   1.740 +  }
   1.741 +}

mercurial