dom/apps/src/AppsUtils.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 const Cu = Components.utils;
michael@0 8 const Cc = Components.classes;
michael@0 9 const Ci = Components.interfaces;
michael@0 10 const Cr = Components.results;
michael@0 11
michael@0 12 Cu.import("resource://gre/modules/osfile.jsm");
michael@0 13 Cu.import("resource://gre/modules/Services.jsm");
michael@0 14 Cu.import("resource://gre/modules/Task.jsm");
michael@0 15 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 16 Cu.import("resource://gre/modules/Promise.jsm");
michael@0 17
michael@0 18 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
michael@0 19 "resource://gre/modules/FileUtils.jsm");
michael@0 20
michael@0 21 XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
michael@0 22 "resource://gre/modules/WebappOSUtils.jsm");
michael@0 23
michael@0 24 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
michael@0 25 "resource://gre/modules/NetUtil.jsm");
michael@0 26
michael@0 27 // Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
michael@0 28
michael@0 29 this.EXPORTED_SYMBOLS = ["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"];
michael@0 30
michael@0 31 function debug(s) {
michael@0 32 //dump("-*- AppsUtils.jsm: " + s + "\n");
michael@0 33 }
michael@0 34
michael@0 35 this.isAbsoluteURI = function(aURI) {
michael@0 36 let foo = Services.io.newURI("http://foo", null, null);
michael@0 37 let bar = Services.io.newURI("http://bar", null, null);
michael@0 38 return Services.io.newURI(aURI, null, foo).prePath != foo.prePath ||
michael@0 39 Services.io.newURI(aURI, null, bar).prePath != bar.prePath;
michael@0 40 }
michael@0 41
michael@0 42 this.mozIApplication = function(aApp) {
michael@0 43 _setAppProperties(this, aApp);
michael@0 44 }
michael@0 45
michael@0 46 mozIApplication.prototype = {
michael@0 47 hasPermission: function(aPermission) {
michael@0 48 let uri = Services.io.newURI(this.origin, null, null);
michael@0 49 let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
michael@0 50 .getService(Ci.nsIScriptSecurityManager);
michael@0 51 // This helper checks an URI inside |aApp|'s origin and part of |aApp| has a
michael@0 52 // specific permission. It is not checking if browsers inside |aApp| have such
michael@0 53 // permission.
michael@0 54 let principal = secMan.getAppCodebasePrincipal(uri, this.localId,
michael@0 55 /*mozbrowser*/false);
michael@0 56 let perm = Services.perms.testExactPermissionFromPrincipal(principal,
michael@0 57 aPermission);
michael@0 58 return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
michael@0 59 },
michael@0 60
michael@0 61 QueryInterface: function(aIID) {
michael@0 62 if (aIID.equals(Ci.mozIApplication) ||
michael@0 63 aIID.equals(Ci.nsISupports))
michael@0 64 return this;
michael@0 65 throw Cr.NS_ERROR_NO_INTERFACE;
michael@0 66 }
michael@0 67 }
michael@0 68
michael@0 69 function _setAppProperties(aObj, aApp) {
michael@0 70 aObj.name = aApp.name;
michael@0 71 aObj.csp = aApp.csp;
michael@0 72 aObj.installOrigin = aApp.installOrigin;
michael@0 73 aObj.origin = aApp.origin;
michael@0 74 #ifdef MOZ_ANDROID_SYNTHAPKS
michael@0 75 aObj.apkPackageName = aApp.apkPackageName;
michael@0 76 #endif
michael@0 77 aObj.receipts = aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null;
michael@0 78 aObj.installTime = aApp.installTime;
michael@0 79 aObj.manifestURL = aApp.manifestURL;
michael@0 80 aObj.appStatus = aApp.appStatus;
michael@0 81 aObj.removable = aApp.removable;
michael@0 82 aObj.id = aApp.id;
michael@0 83 aObj.localId = aApp.localId;
michael@0 84 aObj.basePath = aApp.basePath;
michael@0 85 aObj.progress = aApp.progress || 0.0;
michael@0 86 aObj.installState = aApp.installState || "installed";
michael@0 87 aObj.downloadAvailable = aApp.downloadAvailable;
michael@0 88 aObj.downloading = aApp.downloading;
michael@0 89 aObj.readyToApplyDownload = aApp.readyToApplyDownload;
michael@0 90 aObj.downloadSize = aApp.downloadSize || 0;
michael@0 91 aObj.lastUpdateCheck = aApp.lastUpdateCheck;
michael@0 92 aObj.updateTime = aApp.updateTime;
michael@0 93 aObj.etag = aApp.etag;
michael@0 94 aObj.packageEtag = aApp.packageEtag;
michael@0 95 aObj.manifestHash = aApp.manifestHash;
michael@0 96 aObj.packageHash = aApp.packageHash;
michael@0 97 aObj.staged = aApp.staged;
michael@0 98 aObj.installerAppId = aApp.installerAppId || Ci.nsIScriptSecurityManager.NO_APP_ID;
michael@0 99 aObj.installerIsBrowser = !!aApp.installerIsBrowser;
michael@0 100 aObj.storeId = aApp.storeId || "";
michael@0 101 aObj.storeVersion = aApp.storeVersion || 0;
michael@0 102 aObj.role = aApp.role || "";
michael@0 103 aObj.redirects = aApp.redirects;
michael@0 104 }
michael@0 105
michael@0 106 this.AppsUtils = {
michael@0 107 // Clones a app, without the manifest.
michael@0 108 cloneAppObject: function(aApp) {
michael@0 109 let obj = {};
michael@0 110 _setAppProperties(obj, aApp);
michael@0 111 return obj;
michael@0 112 },
michael@0 113
michael@0 114 getAppByManifestURL: function getAppByManifestURL(aApps, aManifestURL) {
michael@0 115 debug("getAppByManifestURL " + aManifestURL);
michael@0 116 // This could be O(1) if |webapps| was a dictionary indexed on manifestURL
michael@0 117 // which should be the unique app identifier.
michael@0 118 // It's currently O(n).
michael@0 119 for (let id in aApps) {
michael@0 120 let app = aApps[id];
michael@0 121 if (app.manifestURL == aManifestURL) {
michael@0 122 return new mozIApplication(app);
michael@0 123 }
michael@0 124 }
michael@0 125
michael@0 126 return null;
michael@0 127 },
michael@0 128
michael@0 129 getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aApps, aManifestURL) {
michael@0 130 debug("getAppLocalIdByManifestURL " + aManifestURL);
michael@0 131 for (let id in aApps) {
michael@0 132 if (aApps[id].manifestURL == aManifestURL) {
michael@0 133 return aApps[id].localId;
michael@0 134 }
michael@0 135 }
michael@0 136
michael@0 137 return Ci.nsIScriptSecurityManager.NO_APP_ID;
michael@0 138 },
michael@0 139
michael@0 140 getAppLocalIdByStoreId: function(aApps, aStoreId) {
michael@0 141 debug("getAppLocalIdByStoreId:" + aStoreId);
michael@0 142 for (let id in aApps) {
michael@0 143 if (aApps[id].storeId == aStoreId) {
michael@0 144 return aApps[id].localId;
michael@0 145 }
michael@0 146 }
michael@0 147
michael@0 148 return Ci.nsIScriptSecurityManager.NO_APP_ID;
michael@0 149 },
michael@0 150
michael@0 151 getCSPByLocalId: function getCSPByLocalId(aApps, aLocalId) {
michael@0 152 debug("getCSPByLocalId " + aLocalId);
michael@0 153 for (let id in aApps) {
michael@0 154 let app = aApps[id];
michael@0 155 if (app.localId == aLocalId) {
michael@0 156 return ( app.csp || "" );
michael@0 157 }
michael@0 158 }
michael@0 159
michael@0 160 return "";
michael@0 161 },
michael@0 162
michael@0 163 getAppByLocalId: function getAppByLocalId(aApps, aLocalId) {
michael@0 164 debug("getAppByLocalId " + aLocalId);
michael@0 165 for (let id in aApps) {
michael@0 166 let app = aApps[id];
michael@0 167 if (app.localId == aLocalId) {
michael@0 168 return new mozIApplication(app);
michael@0 169 }
michael@0 170 }
michael@0 171
michael@0 172 return null;
michael@0 173 },
michael@0 174
michael@0 175 getManifestURLByLocalId: function getManifestURLByLocalId(aApps, aLocalId) {
michael@0 176 debug("getManifestURLByLocalId " + aLocalId);
michael@0 177 for (let id in aApps) {
michael@0 178 let app = aApps[id];
michael@0 179 if (app.localId == aLocalId) {
michael@0 180 return app.manifestURL;
michael@0 181 }
michael@0 182 }
michael@0 183
michael@0 184 return "";
michael@0 185 },
michael@0 186
michael@0 187 getCoreAppsBasePath: function getCoreAppsBasePath() {
michael@0 188 debug("getCoreAppsBasePath()");
michael@0 189 try {
michael@0 190 return FileUtils.getDir("coreAppsDir", ["webapps"], false).path;
michael@0 191 } catch(e) {
michael@0 192 return null;
michael@0 193 }
michael@0 194 },
michael@0 195
michael@0 196 getAppInfo: function getAppInfo(aApps, aAppId) {
michael@0 197 let app = aApps[aAppId];
michael@0 198
michael@0 199 if (!app) {
michael@0 200 debug("No webapp for " + aAppId);
michael@0 201 return null;
michael@0 202 }
michael@0 203
michael@0 204 // We can have 3rd party apps that are non-removable,
michael@0 205 // so we can't use the 'removable' property for isCoreApp
michael@0 206 // Instead, we check if the app is installed under /system/b2g
michael@0 207 let isCoreApp = false;
michael@0 208
michael@0 209 #ifdef MOZ_WIDGET_GONK
michael@0 210 isCoreApp = app.basePath == this.getCoreAppsBasePath();
michael@0 211 #endif
michael@0 212 debug(app.basePath + " isCoreApp: " + isCoreApp);
michael@0 213 return { "path": WebappOSUtils.getPackagePath(app),
michael@0 214 "isCoreApp": isCoreApp };
michael@0 215 },
michael@0 216
michael@0 217 /**
michael@0 218 * Remove potential HTML tags from displayable fields in the manifest.
michael@0 219 * We check name, description, developer name, and permission description
michael@0 220 */
michael@0 221 sanitizeManifest: function(aManifest) {
michael@0 222 let sanitizer = Cc["@mozilla.org/parserutils;1"]
michael@0 223 .getService(Ci.nsIParserUtils);
michael@0 224 if (!sanitizer) {
michael@0 225 return;
michael@0 226 }
michael@0 227
michael@0 228 function sanitize(aStr) {
michael@0 229 return sanitizer.convertToPlainText(aStr,
michael@0 230 Ci.nsIDocumentEncoder.OutputRaw, 0);
michael@0 231 }
michael@0 232
michael@0 233 function sanitizeEntryPoint(aRoot) {
michael@0 234 aRoot.name = sanitize(aRoot.name);
michael@0 235
michael@0 236 if (aRoot.description) {
michael@0 237 aRoot.description = sanitize(aRoot.description);
michael@0 238 }
michael@0 239
michael@0 240 if (aRoot.developer && aRoot.developer.name) {
michael@0 241 aRoot.developer.name = sanitize(aRoot.developer.name);
michael@0 242 }
michael@0 243
michael@0 244 if (aRoot.permissions) {
michael@0 245 for (let permission in aRoot.permissions) {
michael@0 246 if (aRoot.permissions[permission].description) {
michael@0 247 aRoot.permissions[permission].description =
michael@0 248 sanitize(aRoot.permissions[permission].description);
michael@0 249 }
michael@0 250 }
michael@0 251 }
michael@0 252 }
michael@0 253
michael@0 254 // First process the main section, then the entry points.
michael@0 255 sanitizeEntryPoint(aManifest);
michael@0 256
michael@0 257 if (aManifest.entry_points) {
michael@0 258 for (let entry in aManifest.entry_points) {
michael@0 259 sanitizeEntryPoint(aManifest.entry_points[entry]);
michael@0 260 }
michael@0 261 }
michael@0 262 },
michael@0 263
michael@0 264 /**
michael@0 265 * From https://developer.mozilla.org/en/OpenWebApps/The_Manifest
michael@0 266 * Only the name property is mandatory.
michael@0 267 */
michael@0 268 checkManifest: function(aManifest, app) {
michael@0 269 if (aManifest.name == undefined)
michael@0 270 return false;
michael@0 271
michael@0 272 this.sanitizeManifest(aManifest);
michael@0 273
michael@0 274 // launch_path, entry_points launch paths, message hrefs, and activity hrefs can't be absolute
michael@0 275 if (aManifest.launch_path && isAbsoluteURI(aManifest.launch_path))
michael@0 276 return false;
michael@0 277
michael@0 278 function checkAbsoluteEntryPoints(entryPoints) {
michael@0 279 for (let name in entryPoints) {
michael@0 280 if (entryPoints[name].launch_path && isAbsoluteURI(entryPoints[name].launch_path)) {
michael@0 281 return true;
michael@0 282 }
michael@0 283 }
michael@0 284 return false;
michael@0 285 }
michael@0 286
michael@0 287 if (checkAbsoluteEntryPoints(aManifest.entry_points))
michael@0 288 return false;
michael@0 289
michael@0 290 for (let localeName in aManifest.locales) {
michael@0 291 if (checkAbsoluteEntryPoints(aManifest.locales[localeName].entry_points)) {
michael@0 292 return false;
michael@0 293 }
michael@0 294 }
michael@0 295
michael@0 296 if (aManifest.activities) {
michael@0 297 for (let activityName in aManifest.activities) {
michael@0 298 let activity = aManifest.activities[activityName];
michael@0 299 if (activity.href && isAbsoluteURI(activity.href)) {
michael@0 300 return false;
michael@0 301 }
michael@0 302 }
michael@0 303 }
michael@0 304
michael@0 305 // |messages| is an array of items, where each item is either a string or
michael@0 306 // a {name: href} object.
michael@0 307 let messages = aManifest.messages;
michael@0 308 if (messages) {
michael@0 309 if (!Array.isArray(messages)) {
michael@0 310 return false;
michael@0 311 }
michael@0 312 for (let item of aManifest.messages) {
michael@0 313 if (typeof item == "object") {
michael@0 314 let keys = Object.keys(item);
michael@0 315 if (keys.length != 1) {
michael@0 316 return false;
michael@0 317 }
michael@0 318 if (isAbsoluteURI(item[keys[0]])) {
michael@0 319 return false;
michael@0 320 }
michael@0 321 }
michael@0 322 }
michael@0 323 }
michael@0 324
michael@0 325 // The 'size' field must be a positive integer.
michael@0 326 if (aManifest.size) {
michael@0 327 aManifest.size = parseInt(aManifest.size);
michael@0 328 if (Number.isNaN(aManifest.size) || aManifest.size < 0) {
michael@0 329 return false;
michael@0 330 }
michael@0 331 }
michael@0 332
michael@0 333 // The 'role' field must be a string.
michael@0 334 if (aManifest.role && (typeof aManifest.role !== "string")) {
michael@0 335 return false;
michael@0 336 }
michael@0 337 return true;
michael@0 338 },
michael@0 339
michael@0 340 checkManifestContentType: function
michael@0 341 checkManifestContentType(aInstallOrigin, aWebappOrigin, aContentType) {
michael@0 342 let hadCharset = { };
michael@0 343 let charset = { };
michael@0 344 let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
michael@0 345 let contentType = netutil.parseContentType(aContentType, charset, hadCharset);
michael@0 346 if (aInstallOrigin != aWebappOrigin &&
michael@0 347 contentType != "application/x-web-app-manifest+json") {
michael@0 348 return false;
michael@0 349 }
michael@0 350 return true;
michael@0 351 },
michael@0 352
michael@0 353 /**
michael@0 354 * Method to apply modifications to webapp manifests file saved internally.
michael@0 355 * For now, only ensure app can't rename itself.
michael@0 356 */
michael@0 357 ensureSameAppName: function ensureSameAppName(aOldManifest, aNewManifest, aApp) {
michael@0 358 // Ensure that app name can't be updated
michael@0 359 aNewManifest.name = aApp.name;
michael@0 360
michael@0 361 // Nor through localized names
michael@0 362 if ('locales' in aNewManifest) {
michael@0 363 let defaultName = new ManifestHelper(aOldManifest, aApp.origin).name;
michael@0 364 for (let locale in aNewManifest.locales) {
michael@0 365 let entry = aNewManifest.locales[locale];
michael@0 366 if (!entry.name) {
michael@0 367 continue;
michael@0 368 }
michael@0 369 // In case previous manifest didn't had a name,
michael@0 370 // we use the default app name
michael@0 371 let localizedName = defaultName;
michael@0 372 if (aOldManifest && 'locales' in aOldManifest &&
michael@0 373 locale in aOldManifest.locales) {
michael@0 374 localizedName = aOldManifest.locales[locale].name;
michael@0 375 }
michael@0 376 entry.name = localizedName;
michael@0 377 }
michael@0 378 }
michael@0 379 },
michael@0 380
michael@0 381 /**
michael@0 382 * Determines whether the manifest allows installs for the given origin.
michael@0 383 * @param object aManifest
michael@0 384 * @param string aInstallOrigin
michael@0 385 * @return boolean
michael@0 386 **/
michael@0 387 checkInstallAllowed: function checkInstallAllowed(aManifest, aInstallOrigin) {
michael@0 388 if (!aManifest.installs_allowed_from) {
michael@0 389 return true;
michael@0 390 }
michael@0 391
michael@0 392 function cbCheckAllowedOrigin(aOrigin) {
michael@0 393 return aOrigin == "*" || aOrigin == aInstallOrigin;
michael@0 394 }
michael@0 395
michael@0 396 return aManifest.installs_allowed_from.some(cbCheckAllowedOrigin);
michael@0 397 },
michael@0 398
michael@0 399 /**
michael@0 400 * Determine the type of app (app, privileged, certified)
michael@0 401 * that is installed by the manifest
michael@0 402 * @param object aManifest
michael@0 403 * @returns integer
michael@0 404 **/
michael@0 405 getAppManifestStatus: function getAppManifestStatus(aManifest) {
michael@0 406 let type = aManifest.type || "web";
michael@0 407
michael@0 408 switch(type) {
michael@0 409 case "web":
michael@0 410 return Ci.nsIPrincipal.APP_STATUS_INSTALLED;
michael@0 411 case "privileged":
michael@0 412 return Ci.nsIPrincipal.APP_STATUS_PRIVILEGED;
michael@0 413 case "certified":
michael@0 414 return Ci.nsIPrincipal.APP_STATUS_CERTIFIED;
michael@0 415 default:
michael@0 416 throw new Error("Webapps.jsm: Undetermined app manifest type");
michael@0 417 }
michael@0 418 },
michael@0 419
michael@0 420 /**
michael@0 421 * Determines if an update or a factory reset occured.
michael@0 422 */
michael@0 423 isFirstRun: function isFirstRun(aPrefBranch) {
michael@0 424 let savedmstone = null;
michael@0 425 try {
michael@0 426 savedmstone = aPrefBranch.getCharPref("gecko.mstone");
michael@0 427 } catch (e) {}
michael@0 428
michael@0 429 let mstone = Services.appinfo.platformVersion;
michael@0 430
michael@0 431 let savedBuildID = null;
michael@0 432 try {
michael@0 433 savedBuildID = aPrefBranch.getCharPref("gecko.buildID");
michael@0 434 } catch (e) {}
michael@0 435
michael@0 436 let buildID = Services.appinfo.platformBuildID;
michael@0 437
michael@0 438 aPrefBranch.setCharPref("gecko.mstone", mstone);
michael@0 439 aPrefBranch.setCharPref("gecko.buildID", buildID);
michael@0 440
michael@0 441 return ((mstone != savedmstone) || (buildID != savedBuildID));
michael@0 442 },
michael@0 443
michael@0 444 /**
michael@0 445 * Check if two manifests have the same set of properties and that the
michael@0 446 * values of these properties are the same, in each locale.
michael@0 447 * Manifests here are raw json ones.
michael@0 448 */
michael@0 449 compareManifests: function compareManifests(aManifest1, aManifest2) {
michael@0 450 // 1. check if we have the same locales in both manifests.
michael@0 451 let locales1 = [];
michael@0 452 let locales2 = [];
michael@0 453 if (aManifest1.locales) {
michael@0 454 for (let locale in aManifest1.locales) {
michael@0 455 locales1.push(locale);
michael@0 456 }
michael@0 457 }
michael@0 458 if (aManifest2.locales) {
michael@0 459 for (let locale in aManifest2.locales) {
michael@0 460 locales2.push(locale);
michael@0 461 }
michael@0 462 }
michael@0 463 if (locales1.sort().join() !== locales2.sort().join()) {
michael@0 464 return false;
michael@0 465 }
michael@0 466
michael@0 467 // Helper function to check the app name and developer information for
michael@0 468 // two given roots.
michael@0 469 let checkNameAndDev = function(aRoot1, aRoot2) {
michael@0 470 let name1 = aRoot1.name;
michael@0 471 let name2 = aRoot2.name;
michael@0 472 if (name1 !== name2) {
michael@0 473 return false;
michael@0 474 }
michael@0 475
michael@0 476 let dev1 = aRoot1.developer;
michael@0 477 let dev2 = aRoot2.developer;
michael@0 478 if ((dev1 && !dev2) || (dev2 && !dev1)) {
michael@0 479 return false;
michael@0 480 }
michael@0 481
michael@0 482 return (!dev1 && !dev2) ||
michael@0 483 (dev1.name === dev2.name && dev1.url === dev2.url);
michael@0 484 }
michael@0 485
michael@0 486 // 2. For each locale, check if the name and dev info are the same.
michael@0 487 if (!checkNameAndDev(aManifest1, aManifest2)) {
michael@0 488 return false;
michael@0 489 }
michael@0 490
michael@0 491 for (let locale in aManifest1.locales) {
michael@0 492 if (!checkNameAndDev(aManifest1.locales[locale],
michael@0 493 aManifest2.locales[locale])) {
michael@0 494 return false;
michael@0 495 }
michael@0 496 }
michael@0 497
michael@0 498 // Nothing failed.
michael@0 499 return true;
michael@0 500 },
michael@0 501
michael@0 502 // Asynchronously loads a JSON file. aPath is a string representing the path
michael@0 503 // of the file to be read.
michael@0 504 loadJSONAsync: function(aPath) {
michael@0 505 let deferred = Promise.defer();
michael@0 506
michael@0 507 try {
michael@0 508 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
michael@0 509 file.initWithPath(aPath);
michael@0 510
michael@0 511 let channel = NetUtil.newChannel(file);
michael@0 512 channel.contentType = "application/json";
michael@0 513
michael@0 514 NetUtil.asyncFetch(channel, function(aStream, aResult) {
michael@0 515 if (!Components.isSuccessCode(aResult)) {
michael@0 516 deferred.resolve(null);
michael@0 517
michael@0 518 if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) {
michael@0 519 // We expect this under certain circumstances, like for webapps.json
michael@0 520 // on firstrun, so we return early without reporting an error.
michael@0 521 return;
michael@0 522 }
michael@0 523
michael@0 524 Cu.reportError("AppsUtils: Could not read from json file " + aPath);
michael@0 525 return;
michael@0 526 }
michael@0 527
michael@0 528 try {
michael@0 529 // Obtain a converter to read from a UTF-8 encoded input stream.
michael@0 530 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 531 .createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 532 converter.charset = "UTF-8";
michael@0 533
michael@0 534 // Read json file into a string
michael@0 535 let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
michael@0 536 aStream.available()) || ""));
michael@0 537 aStream.close();
michael@0 538
michael@0 539 deferred.resolve(data);
michael@0 540 } catch (ex) {
michael@0 541 Cu.reportError("AppsUtils: Could not parse JSON: " +
michael@0 542 aPath + " " + ex + "\n" + ex.stack);
michael@0 543 deferred.resolve(null);
michael@0 544 }
michael@0 545 });
michael@0 546 } catch (ex) {
michael@0 547 Cu.reportError("AppsUtils: Could not read from " +
michael@0 548 aPath + " : " + ex + "\n" + ex.stack);
michael@0 549 deferred.resolve(null);
michael@0 550 }
michael@0 551
michael@0 552 return deferred.promise;
michael@0 553 },
michael@0 554
michael@0 555 // Returns the MD5 hash of a string.
michael@0 556 computeHash: function(aString) {
michael@0 557 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 558 .createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 559 converter.charset = "UTF-8";
michael@0 560 let result = {};
michael@0 561 // Data is an array of bytes.
michael@0 562 let data = converter.convertToByteArray(aString, result);
michael@0 563
michael@0 564 let hasher = Cc["@mozilla.org/security/hash;1"]
michael@0 565 .createInstance(Ci.nsICryptoHash);
michael@0 566 hasher.init(hasher.MD5);
michael@0 567 hasher.update(data, data.length);
michael@0 568 // We're passing false to get the binary hash and not base64.
michael@0 569 let hash = hasher.finish(false);
michael@0 570
michael@0 571 function toHexString(charCode) {
michael@0 572 return ("0" + charCode.toString(16)).slice(-2);
michael@0 573 }
michael@0 574
michael@0 575 // Convert the binary hash data to a hex string.
michael@0 576 return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
michael@0 577 }
michael@0 578 }
michael@0 579
michael@0 580 /**
michael@0 581 * Helper object to access manifest information with locale support
michael@0 582 */
michael@0 583 this.ManifestHelper = function(aManifest, aOrigin) {
michael@0 584 this._origin = Services.io.newURI(aOrigin, null, null);
michael@0 585 this._manifest = aManifest;
michael@0 586 let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry)
michael@0 587 .QueryInterface(Ci.nsIToolkitChromeRegistry);
michael@0 588 let locale = chrome.getSelectedLocale("global").toLowerCase();
michael@0 589 this._localeRoot = this._manifest;
michael@0 590
michael@0 591 if (this._manifest.locales && this._manifest.locales[locale]) {
michael@0 592 this._localeRoot = this._manifest.locales[locale];
michael@0 593 }
michael@0 594 else if (this._manifest.locales) {
michael@0 595 // try with the language part of the locale ("en" for en-GB) only
michael@0 596 let lang = locale.split('-')[0];
michael@0 597 if (lang != locale && this._manifest.locales[lang])
michael@0 598 this._localeRoot = this._manifest.locales[lang];
michael@0 599 }
michael@0 600 };
michael@0 601
michael@0 602 ManifestHelper.prototype = {
michael@0 603 _localeProp: function(aProp) {
michael@0 604 if (this._localeRoot[aProp] != undefined)
michael@0 605 return this._localeRoot[aProp];
michael@0 606 return this._manifest[aProp];
michael@0 607 },
michael@0 608
michael@0 609 get name() {
michael@0 610 return this._localeProp("name");
michael@0 611 },
michael@0 612
michael@0 613 get description() {
michael@0 614 return this._localeProp("description");
michael@0 615 },
michael@0 616
michael@0 617 get type() {
michael@0 618 return this._localeProp("type");
michael@0 619 },
michael@0 620
michael@0 621 get version() {
michael@0 622 return this._localeProp("version");
michael@0 623 },
michael@0 624
michael@0 625 get launch_path() {
michael@0 626 return this._localeProp("launch_path");
michael@0 627 },
michael@0 628
michael@0 629 get developer() {
michael@0 630 // Default to {} in order to avoid exception in code
michael@0 631 // that doesn't check for null `developer`
michael@0 632 return this._localeProp("developer") || {};
michael@0 633 },
michael@0 634
michael@0 635 get icons() {
michael@0 636 return this._localeProp("icons");
michael@0 637 },
michael@0 638
michael@0 639 get appcache_path() {
michael@0 640 return this._localeProp("appcache_path");
michael@0 641 },
michael@0 642
michael@0 643 get orientation() {
michael@0 644 return this._localeProp("orientation");
michael@0 645 },
michael@0 646
michael@0 647 get package_path() {
michael@0 648 return this._localeProp("package_path");
michael@0 649 },
michael@0 650
michael@0 651 get size() {
michael@0 652 return this._manifest["size"] || 0;
michael@0 653 },
michael@0 654
michael@0 655 get permissions() {
michael@0 656 if (this._manifest.permissions) {
michael@0 657 return this._manifest.permissions;
michael@0 658 }
michael@0 659 return {};
michael@0 660 },
michael@0 661
michael@0 662 get biggestIconURL() {
michael@0 663 let icons = this._localeProp("icons");
michael@0 664 if (!icons) {
michael@0 665 return null;
michael@0 666 }
michael@0 667
michael@0 668 let iconSizes = Object.keys(icons);
michael@0 669 if (iconSizes.length == 0) {
michael@0 670 return null;
michael@0 671 }
michael@0 672
michael@0 673 iconSizes.sort((a, b) => a - b);
michael@0 674 let biggestIconSize = iconSizes.pop();
michael@0 675 let biggestIcon = icons[biggestIconSize];
michael@0 676 let biggestIconURL = this._origin.resolve(biggestIcon);
michael@0 677
michael@0 678 return biggestIconURL;
michael@0 679 },
michael@0 680
michael@0 681 iconURLForSize: function(aSize) {
michael@0 682 let icons = this._localeProp("icons");
michael@0 683 if (!icons)
michael@0 684 return null;
michael@0 685 let dist = 100000;
michael@0 686 let icon = null;
michael@0 687 for (let size in icons) {
michael@0 688 let iSize = parseInt(size);
michael@0 689 if (Math.abs(iSize - aSize) < dist) {
michael@0 690 icon = this._origin.resolve(icons[size]);
michael@0 691 dist = Math.abs(iSize - aSize);
michael@0 692 }
michael@0 693 }
michael@0 694 return icon;
michael@0 695 },
michael@0 696
michael@0 697 fullLaunchPath: function(aStartPoint) {
michael@0 698 // If no start point is specified, we use the root launch path.
michael@0 699 // In all error cases, we just return null.
michael@0 700 if ((aStartPoint || "") === "") {
michael@0 701 return this._origin.resolve(this._localeProp("launch_path") || "");
michael@0 702 }
michael@0 703
michael@0 704 // Search for the l10n entry_points property.
michael@0 705 let entryPoints = this._localeProp("entry_points");
michael@0 706 if (!entryPoints) {
michael@0 707 return null;
michael@0 708 }
michael@0 709
michael@0 710 if (entryPoints[aStartPoint]) {
michael@0 711 return this._origin.resolve(entryPoints[aStartPoint].launch_path || "");
michael@0 712 }
michael@0 713
michael@0 714 return null;
michael@0 715 },
michael@0 716
michael@0 717 resolveFromOrigin: function(aURI) {
michael@0 718 // This should be enforced higher up, but check it here just in case.
michael@0 719 if (isAbsoluteURI(aURI)) {
michael@0 720 throw new Error("Webapps.jsm: non-relative URI passed to resolveFromOrigin");
michael@0 721 }
michael@0 722 return this._origin.resolve(aURI);
michael@0 723 },
michael@0 724
michael@0 725 fullAppcachePath: function() {
michael@0 726 let appcachePath = this._localeProp("appcache_path");
michael@0 727 return this._origin.resolve(appcachePath ? appcachePath : "");
michael@0 728 },
michael@0 729
michael@0 730 fullPackagePath: function() {
michael@0 731 let packagePath = this._localeProp("package_path");
michael@0 732 return this._origin.resolve(packagePath ? packagePath : "");
michael@0 733 },
michael@0 734
michael@0 735 get role() {
michael@0 736 return this._manifest.role || "";
michael@0 737 }
michael@0 738 }

mercurial