toolkit/webapps/NativeApp.jsm

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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 this.EXPORTED_SYMBOLS = ["NativeApp"];
michael@0 6
michael@0 7 const Cc = Components.classes;
michael@0 8 const Ci = Components.interfaces;
michael@0 9 const Cu = Components.utils;
michael@0 10 const Cr = Components.results;
michael@0 11
michael@0 12 Cu.import("resource://gre/modules/Services.jsm");
michael@0 13 Cu.import("resource://gre/modules/FileUtils.jsm");
michael@0 14 Cu.import("resource://gre/modules/NetUtil.jsm");
michael@0 15 Cu.import("resource://gre/modules/osfile.jsm");
michael@0 16 Cu.import("resource://gre/modules/WebappOSUtils.jsm");
michael@0 17 Cu.import("resource://gre/modules/AppsUtils.jsm");
michael@0 18 Cu.import("resource://gre/modules/Task.jsm");
michael@0 19 Cu.import("resource://gre/modules/Promise.jsm");
michael@0 20
michael@0 21 const ERR_NOT_INSTALLED = "The application isn't installed";
michael@0 22 const ERR_UPDATES_UNSUPPORTED_OLD_NAMING_SCHEME =
michael@0 23 "Updates for apps installed with the old naming scheme unsupported";
michael@0 24
michael@0 25 // 0755
michael@0 26 const PERMS_DIRECTORY = OS.Constants.libc.S_IRWXU |
michael@0 27 OS.Constants.libc.S_IRGRP | OS.Constants.libc.S_IXGRP |
michael@0 28 OS.Constants.libc.S_IROTH | OS.Constants.libc.S_IXOTH;
michael@0 29
michael@0 30 // 0644
michael@0 31 const PERMS_FILE = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR |
michael@0 32 OS.Constants.libc.S_IRGRP |
michael@0 33 OS.Constants.libc.S_IROTH;
michael@0 34
michael@0 35 const DESKTOP_DIR = OS.Constants.Path.desktopDir;
michael@0 36 const HOME_DIR = OS.Constants.Path.homeDir;
michael@0 37 const TMP_DIR = OS.Constants.Path.tmpDir;
michael@0 38
michael@0 39 /**
michael@0 40 * This function implements the common constructor for
michael@0 41 * the Windows, Mac and Linux native app shells. It sets
michael@0 42 * the app unique name. It's meant to be called as
michael@0 43 * CommonNativeApp.call(this, ...) from the platform-specific
michael@0 44 * constructor.
michael@0 45 *
michael@0 46 * @param aApp {Object} the app object provided to the install function
michael@0 47 * @param aManifest {Object} the manifest data provided by the web app
michael@0 48 * @param aCategories {Array} array of app categories
michael@0 49 * @param aRegistryDir {String} (optional) path to the registry
michael@0 50 *
michael@0 51 */
michael@0 52 function CommonNativeApp(aApp, aManifest, aCategories, aRegistryDir) {
michael@0 53 let manifest = new ManifestHelper(aManifest, aApp.origin);
michael@0 54
michael@0 55 aApp.name = manifest.name;
michael@0 56 this.uniqueName = WebappOSUtils.getUniqueName(aApp);
michael@0 57
michael@0 58 this.appName = sanitize(manifest.name);
michael@0 59 this.appNameAsFilename = stripStringForFilename(this.appName);
michael@0 60
michael@0 61 if (aApp.updateManifest) {
michael@0 62 this.isPackaged = true;
michael@0 63 }
michael@0 64
michael@0 65 this.categories = aCategories.slice(0);
michael@0 66
michael@0 67 this.registryDir = aRegistryDir || OS.Constants.Path.profileDir;
michael@0 68
michael@0 69 this.app = aApp;
michael@0 70
michael@0 71 this._dryRun = false;
michael@0 72 try {
michael@0 73 if (Services.prefs.getBoolPref("browser.mozApps.installer.dry_run")) {
michael@0 74 this._dryRun = true;
michael@0 75 }
michael@0 76 } catch (ex) {}
michael@0 77 }
michael@0 78
michael@0 79 CommonNativeApp.prototype = {
michael@0 80 uniqueName: null,
michael@0 81 appName: null,
michael@0 82 appNameAsFilename: null,
michael@0 83 iconURI: null,
michael@0 84 developerName: null,
michael@0 85 shortDescription: null,
michael@0 86 categories: null,
michael@0 87 webappJson: null,
michael@0 88 runtimeFolder: null,
michael@0 89 manifest: null,
michael@0 90 registryDir: null,
michael@0 91
michael@0 92 /**
michael@0 93 * This function reads and parses the data from the app
michael@0 94 * manifest and stores it in the NativeApp object.
michael@0 95 *
michael@0 96 * @param aManifest {Object} the manifest data provided by the web app
michael@0 97 *
michael@0 98 */
michael@0 99 _setData: function(aManifest) {
michael@0 100 let manifest = new ManifestHelper(aManifest, this.app.origin);
michael@0 101 let origin = Services.io.newURI(this.app.origin, null, null);
michael@0 102
michael@0 103 let biggestIcon = getBiggestIconURL(manifest.icons);
michael@0 104 try {
michael@0 105 let iconURI = Services.io.newURI(biggestIcon, null, null);
michael@0 106 if (iconURI.scheme == "data") {
michael@0 107 this.iconURI = iconURI;
michael@0 108 }
michael@0 109 } catch (ex) {}
michael@0 110
michael@0 111 if (!this.iconURI) {
michael@0 112 try {
michael@0 113 this.iconURI = Services.io.newURI(origin.resolve(biggestIcon), null, null);
michael@0 114 }
michael@0 115 catch (ex) {}
michael@0 116 }
michael@0 117
michael@0 118 if (manifest.developer) {
michael@0 119 if (manifest.developer.name) {
michael@0 120 let devName = sanitize(manifest.developer.name.substr(0, 128));
michael@0 121 if (devName) {
michael@0 122 this.developerName = devName;
michael@0 123 }
michael@0 124 }
michael@0 125
michael@0 126 if (manifest.developer.url) {
michael@0 127 this.developerUrl = manifest.developer.url;
michael@0 128 }
michael@0 129 }
michael@0 130
michael@0 131 if (manifest.description) {
michael@0 132 let firstLine = manifest.description.split("\n")[0];
michael@0 133 let shortDesc = firstLine.length <= 256
michael@0 134 ? firstLine
michael@0 135 : firstLine.substr(0, 253) + "…";
michael@0 136 this.shortDescription = sanitize(shortDesc);
michael@0 137 } else {
michael@0 138 this.shortDescription = this.appName;
michael@0 139 }
michael@0 140
michael@0 141 if (manifest.version) {
michael@0 142 this.version = manifest.version;
michael@0 143 }
michael@0 144
michael@0 145 this.webappJson = {
michael@0 146 // The app registry is the Firefox profile from which the app
michael@0 147 // was installed.
michael@0 148 "registryDir": this.registryDir,
michael@0 149 "app": {
michael@0 150 "manifest": aManifest,
michael@0 151 "origin": this.app.origin,
michael@0 152 "manifestURL": this.app.manifestURL,
michael@0 153 "installOrigin": this.app.installOrigin,
michael@0 154 "categories": this.categories,
michael@0 155 "receipts": this.app.receipts,
michael@0 156 "installTime": this.app.installTime,
michael@0 157 }
michael@0 158 };
michael@0 159
michael@0 160 if (this.app.etag) {
michael@0 161 this.webappJson.app.etag = this.app.etag;
michael@0 162 }
michael@0 163
michael@0 164 if (this.app.packageEtag) {
michael@0 165 this.webappJson.app.packageEtag = this.app.packageEtag;
michael@0 166 }
michael@0 167
michael@0 168 if (this.app.updateManifest) {
michael@0 169 this.webappJson.app.updateManifest = this.app.updateManifest;
michael@0 170 }
michael@0 171
michael@0 172 this.runtimeFolder = OS.Constants.Path.libDir;
michael@0 173 },
michael@0 174
michael@0 175 /**
michael@0 176 * This function retrieves the icon for an app.
michael@0 177 * If the retrieving fails, it uses the default chrome icon.
michael@0 178 */
michael@0 179 _getIcon: function(aTmpDir) {
michael@0 180 try {
michael@0 181 // If the icon is in the zip package, we should modify the url
michael@0 182 // to point to the zip file (we can't use the app protocol yet
michael@0 183 // because the app isn't installed yet).
michael@0 184 if (this.iconURI.scheme == "app") {
michael@0 185 let zipUrl = OS.Path.toFileURI(OS.Path.join(aTmpDir,
michael@0 186 this.zipFile));
michael@0 187
michael@0 188 let filePath = this.iconURI.QueryInterface(Ci.nsIURL).filePath;
michael@0 189
michael@0 190 this.iconURI = Services.io.newURI("jar:" + zipUrl + "!" + filePath,
michael@0 191 null, null);
michael@0 192 }
michael@0 193
michael@0 194
michael@0 195 let [ mimeType, icon ] = yield downloadIcon(this.iconURI);
michael@0 196 yield this._processIcon(mimeType, icon, aTmpDir);
michael@0 197 }
michael@0 198 catch(e) {
michael@0 199 Cu.reportError("Failure retrieving icon: " + e);
michael@0 200
michael@0 201 let iconURI = Services.io.newURI(DEFAULT_ICON_URL, null, null);
michael@0 202
michael@0 203 let [ mimeType, icon ] = yield downloadIcon(iconURI);
michael@0 204 yield this._processIcon(mimeType, icon, aTmpDir);
michael@0 205
michael@0 206 // Set the iconURI property so that the user notification will have the
michael@0 207 // correct icon.
michael@0 208 this.iconURI = iconURI;
michael@0 209 }
michael@0 210 },
michael@0 211
michael@0 212 /**
michael@0 213 * Creates the profile to be used for this app.
michael@0 214 */
michael@0 215 createProfile: function() {
michael@0 216 if (this._dryRun) {
michael@0 217 return null;
michael@0 218 }
michael@0 219
michael@0 220 let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"].
michael@0 221 getService(Ci.nsIToolkitProfileService);
michael@0 222
michael@0 223 try {
michael@0 224 let appProfile = profSvc.createDefaultProfileForApp(this.uniqueName,
michael@0 225 null, null);
michael@0 226 return appProfile.localDir;
michael@0 227 } catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {
michael@0 228 return null;
michael@0 229 }
michael@0 230 },
michael@0 231 };
michael@0 232
michael@0 233 #ifdef XP_WIN
michael@0 234
michael@0 235 #include WinNativeApp.js
michael@0 236
michael@0 237 #elifdef XP_MACOSX
michael@0 238
michael@0 239 #include MacNativeApp.js
michael@0 240
michael@0 241 #elifdef XP_UNIX
michael@0 242
michael@0 243 #include LinuxNativeApp.js
michael@0 244
michael@0 245 #endif
michael@0 246
michael@0 247 /* Helper Functions */
michael@0 248
michael@0 249 /**
michael@0 250 * Async write a data string into a file
michael@0 251 *
michael@0 252 * @param aPath the path to the file to write to
michael@0 253 * @param aData a string with the data to be written
michael@0 254 */
michael@0 255 function writeToFile(aPath, aData) {
michael@0 256 return Task.spawn(function() {
michael@0 257 let data = new TextEncoder().encode(aData);
michael@0 258
michael@0 259 let file;
michael@0 260 try {
michael@0 261 file = yield OS.File.open(aPath, { truncate: true, write: true },
michael@0 262 { unixMode: PERMS_FILE });
michael@0 263 yield file.write(data);
michael@0 264 } finally {
michael@0 265 yield file.close();
michael@0 266 }
michael@0 267 });
michael@0 268 }
michael@0 269
michael@0 270 /**
michael@0 271 * Removes unprintable characters from a string.
michael@0 272 */
michael@0 273 function sanitize(aStr) {
michael@0 274 let unprintableRE = new RegExp("[\\x00-\\x1F\\x7F]" ,"gi");
michael@0 275 return aStr.replace(unprintableRE, "");
michael@0 276 }
michael@0 277
michael@0 278 /**
michael@0 279 * Strips all non-word characters from the beginning and end of a string.
michael@0 280 * Strips invalid characters from the string.
michael@0 281 *
michael@0 282 */
michael@0 283 function stripStringForFilename(aPossiblyBadFilenameString) {
michael@0 284 // Strip everything from the front up to the first [0-9a-zA-Z]
michael@0 285 let stripFrontRE = new RegExp("^\\W*", "gi");
michael@0 286
michael@0 287 // Strip white space characters starting from the last [0-9a-zA-Z]
michael@0 288 let stripBackRE = new RegExp("\\s*$", "gi");
michael@0 289
michael@0 290 // Strip invalid characters from the filename
michael@0 291 let filenameRE = new RegExp("[<>:\"/\\\\|\\?\\*]", "gi");
michael@0 292
michael@0 293 let stripped = aPossiblyBadFilenameString.replace(stripFrontRE, "");
michael@0 294 stripped = stripped.replace(stripBackRE, "");
michael@0 295 stripped = stripped.replace(filenameRE, "");
michael@0 296
michael@0 297 // If the filename ends up empty, let's call it "webapp".
michael@0 298 if (stripped == "") {
michael@0 299 stripped = "webapp";
michael@0 300 }
michael@0 301
michael@0 302 return stripped;
michael@0 303 }
michael@0 304
michael@0 305 /**
michael@0 306 * Finds a unique name available in a folder (i.e., non-existent file)
michael@0 307 *
michael@0 308 * @param aPathSet a set of paths that represents the set of
michael@0 309 * directories where we want to write
michael@0 310 * @param aName string with the filename (minus the extension) desired
michael@0 311 * @param aExtension string with the file extension, including the dot
michael@0 312
michael@0 313 * @return file name or null if folder is unwritable or unique name
michael@0 314 * was not available
michael@0 315 */
michael@0 316 function getAvailableFileName(aPathSet, aName, aExtension) {
michael@0 317 return Task.spawn(function*() {
michael@0 318 let name = aName + aExtension;
michael@0 319
michael@0 320 function checkUnique(aName) {
michael@0 321 return Task.spawn(function*() {
michael@0 322 for (let path of aPathSet) {
michael@0 323 if (yield OS.File.exists(OS.Path.join(path, aName))) {
michael@0 324 return false;
michael@0 325 }
michael@0 326 }
michael@0 327
michael@0 328 return true;
michael@0 329 });
michael@0 330 }
michael@0 331
michael@0 332 if (yield checkUnique(name)) {
michael@0 333 return name;
michael@0 334 }
michael@0 335
michael@0 336 // If we're here, the plain name wasn't enough. Let's try modifying the name
michael@0 337 // by adding "(" + num + ")".
michael@0 338 for (let i = 2; i < 100; i++) {
michael@0 339 name = aName + " (" + i + ")" + aExtension;
michael@0 340
michael@0 341 if (yield checkUnique(name)) {
michael@0 342 return name;
michael@0 343 }
michael@0 344 }
michael@0 345
michael@0 346 throw "No available filename";
michael@0 347 });
michael@0 348 }
michael@0 349
michael@0 350 /**
michael@0 351 * Attempts to remove files or directories.
michael@0 352 *
michael@0 353 * @param aPaths An array with paths to files to remove
michael@0 354 */
michael@0 355 function removeFiles(aPaths) {
michael@0 356 for (let path of aPaths) {
michael@0 357 let file = getFile(path);
michael@0 358
michael@0 359 try {
michael@0 360 if (file.exists()) {
michael@0 361 file.followLinks = false;
michael@0 362 file.remove(true);
michael@0 363 }
michael@0 364 } catch(ex) {}
michael@0 365 }
michael@0 366 }
michael@0 367
michael@0 368 /**
michael@0 369 * Move (overwriting) the contents of one directory into another.
michael@0 370 *
michael@0 371 * @param srcPath A path to the source directory
michael@0 372 * @param destPath A path to the destination directory
michael@0 373 */
michael@0 374 function moveDirectory(srcPath, destPath) {
michael@0 375 let srcDir = getFile(srcPath);
michael@0 376 let destDir = getFile(destPath);
michael@0 377
michael@0 378 let entries = srcDir.directoryEntries;
michael@0 379 let array = [];
michael@0 380 while (entries.hasMoreElements()) {
michael@0 381 let entry = entries.getNext().QueryInterface(Ci.nsIFile);
michael@0 382 if (entry.isDirectory()) {
michael@0 383 yield moveDirectory(entry.path, OS.Path.join(destPath, entry.leafName));
michael@0 384 } else {
michael@0 385 entry.moveTo(destDir, entry.leafName);
michael@0 386 }
michael@0 387 }
michael@0 388
michael@0 389 // The source directory is now empty, remove it.
michael@0 390 yield OS.File.removeEmptyDir(srcPath);
michael@0 391 }
michael@0 392
michael@0 393 function escapeXML(aStr) {
michael@0 394 return aStr.toString()
michael@0 395 .replace(/&/g, "&amp;")
michael@0 396 .replace(/"/g, "&quot;")
michael@0 397 .replace(/'/g, "&apos;")
michael@0 398 .replace(/</g, "&lt;")
michael@0 399 .replace(/>/g, "&gt;");
michael@0 400 }
michael@0 401
michael@0 402 // Helper to create a nsIFile from a set of path components
michael@0 403 function getFile() {
michael@0 404 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
michael@0 405 file.initWithPath(OS.Path.join.apply(OS.Path, arguments));
michael@0 406 return file;
michael@0 407 }
michael@0 408
michael@0 409 /* More helpers for handling the app icon */
michael@0 410 #include WebappsIconHelpers.js

mercurial