AppsUtils.jsm 27 KB

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