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.

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

mercurial