toolkit/webapps/WebappOSUtils.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.

     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
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu, Constructor: CC } = Components;
     7 Cu.import("resource://gre/modules/Services.jsm");
     8 Cu.import("resource://gre/modules/FileUtils.jsm");
     9 Cu.import("resource://gre/modules/osfile.jsm");
    10 Cu.import("resource://gre/modules/Promise.jsm");
    12 this.EXPORTED_SYMBOLS = ["WebappOSUtils"];
    14 // Returns the MD5 hash of a string.
    15 function computeHash(aString) {
    16   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
    17                   createInstance(Ci.nsIScriptableUnicodeConverter);
    18   converter.charset = "UTF-8";
    19   let result = {};
    20   // Data is an array of bytes.
    21   let data = converter.convertToByteArray(aString, result);
    23   let hasher = Cc["@mozilla.org/security/hash;1"].
    24                createInstance(Ci.nsICryptoHash);
    25   hasher.init(hasher.MD5);
    26   hasher.update(data, data.length);
    27   // We're passing false to get the binary hash and not base64.
    28   let hash = hasher.finish(false);
    30   function toHexString(charCode) {
    31     return ("0" + charCode.toString(16)).slice(-2);
    32   }
    34   // Convert the binary hash data to a hex string.
    35   return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
    36 }
    38 this.WebappOSUtils = {
    39   getUniqueName: function(aApp) {
    40     return this.sanitizeStringForFilename(aApp.name).toLowerCase() + "-" +
    41            computeHash(aApp.manifestURL);
    42   },
    44 #ifdef XP_WIN
    45   /**
    46    * Returns the registry key associated to the given app and a boolean that
    47    * specifies whether we're using the old naming scheme or the new one.
    48    */
    49   getAppRegKey: function(aApp) {
    50     let regKey = Cc["@mozilla.org/windows-registry-key;1"].
    51                  createInstance(Ci.nsIWindowsRegKey);
    53     try {
    54       regKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
    55                   "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +
    56                   this.getUniqueName(aApp), Ci.nsIWindowsRegKey.ACCESS_READ);
    58       return { value: regKey,
    59                namingSchemeVersion: 2};
    60     } catch (ex) {}
    62     // Fall back to the old installation naming scheme
    63     try {
    64       regKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
    65                   "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +
    66                   aApp.origin, Ci.nsIWindowsRegKey.ACCESS_READ);
    68       return { value: regKey,
    69                namingSchemeVersion: 1 };
    70     } catch (ex) {}
    72     return null;
    73   },
    74 #endif
    76   /**
    77    * Returns the executable of the given app, identifying it by its unique name,
    78    * which is in either the new format or the old format.
    79    * On Mac OS X, it returns the identifier of the app.
    80    *
    81    * The new format ensures a readable and unique name for an app by combining
    82    * its name with a hash of its manifest URL.  The old format uses its origin,
    83    * which is only unique until we support multiple apps per origin.
    84    */
    85   getLaunchTarget: function(aApp) {
    86 #ifdef XP_WIN
    87     let appRegKey = this.getAppRegKey(aApp);
    89     if (!appRegKey) {
    90       return null;
    91     }
    93     let appFilename, installLocation;
    94     try {
    95       appFilename = appRegKey.value.readStringValue("AppFilename");
    96       installLocation = appRegKey.value.readStringValue("InstallLocation");
    97     } catch (ex) {
    98       return null;
    99     } finally {
   100       appRegKey.value.close();
   101     }
   103     installLocation = installLocation.substring(1, installLocation.length - 1);
   105     if (appRegKey.namingSchemeVersion == 1 &&
   106         !this.isOldInstallPathValid(aApp, installLocation)) {
   107       return null;
   108     }
   110     let initWithPath = CC("@mozilla.org/file/local;1",
   111                           "nsILocalFile", "initWithPath");
   112     let launchTarget = initWithPath(installLocation);
   113     launchTarget.append(appFilename + ".exe");
   115     return launchTarget;
   116 #elifdef XP_MACOSX
   117     let uniqueName = this.getUniqueName(aApp);
   119     let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].
   120                    createInstance(Ci.nsIMacWebAppUtils);
   122     try {
   123       let path;
   124       if (path = mwaUtils.pathForAppWithIdentifier(uniqueName)) {
   125         return [ uniqueName, path ];
   126       }
   127     } catch(ex) {}
   129     // Fall back to the old installation naming scheme
   130     try {
   131       let path;
   132       if ((path = mwaUtils.pathForAppWithIdentifier(aApp.origin)) &&
   133            this.isOldInstallPathValid(aApp, path)) {
   134         return [ aApp.origin, path ];
   135       }
   136     } catch(ex) {}
   138     return [ null, null ];
   139 #elifdef XP_UNIX
   140     let uniqueName = this.getUniqueName(aApp);
   142     let exeFile = Services.dirsvc.get("Home", Ci.nsIFile);
   143     exeFile.append("." + uniqueName);
   144     exeFile.append("webapprt-stub");
   146     // Fall back to the old installation naming scheme
   147     if (!exeFile.exists()) {
   148       exeFile = Services.dirsvc.get("Home", Ci.nsIFile);
   150       let origin = Services.io.newURI(aApp.origin, null, null);
   151       let installDir = "." + origin.scheme + ";" +
   152                        origin.host +
   153                        (origin.port != -1 ? ";" + origin.port : "");
   155       exeFile.append(installDir);
   156       exeFile.append("webapprt-stub");
   158       if (!exeFile.exists() ||
   159           !this.isOldInstallPathValid(aApp, exeFile.parent.path)) {
   160         return null;
   161       }
   162     }
   164     return exeFile;
   165 #endif
   166   },
   168   getInstallPath: function(aApp) {
   169 #ifdef MOZ_B2G
   170     // All b2g builds
   171     return aApp.basePath + "/" + aApp.id;
   173 #elifdef MOZ_FENNEC
   174    // All fennec
   175     return aApp.basePath + "/" + aApp.id;
   177 #elifdef MOZ_PHOENIX
   178    // Firefox
   180 #ifdef XP_WIN
   181     let execFile = this.getLaunchTarget(aApp);
   182     if (!execFile) {
   183       return null;
   184     }
   186     return execFile.parent.path;
   187 #elifdef XP_MACOSX
   188     let [ bundleID, path ] = this.getLaunchTarget(aApp);
   189     return path;
   190 #elifdef XP_UNIX
   191     let execFile = this.getLaunchTarget(aApp);
   192     if (!execFile) {
   193       return null;
   194     }
   196     return execFile.parent.path;
   197 #endif
   199 #elifdef MOZ_WEBAPP_RUNTIME
   200     // Webapp runtime
   202 #ifdef XP_WIN
   203     let execFile = this.getLaunchTarget(aApp);
   204     if (!execFile) {
   205       return null;
   206     }
   208     return execFile.parent.path;
   209 #elifdef XP_MACOSX
   210     let [ bundleID, path ] = this.getLaunchTarget(aApp);
   211     return path;
   212 #elifdef XP_UNIX
   213     let execFile = this.getLaunchTarget(aApp);
   214     if (!execFile) {
   215       return null;
   216     }
   218     return execFile.parent.path;
   219 #endif
   221 #endif
   222     // Anything unsupported, like Metro
   223     throw new Error("Unsupported apps platform");
   224   },
   226   getPackagePath: function(aApp) {
   227     let packagePath = this.getInstallPath(aApp);
   229     // Only for Firefox on Mac OS X
   230 #ifndef MOZ_B2G
   231 #ifdef XP_MACOSX
   232     packagePath = OS.Path.join(packagePath, "Contents", "Resources");
   233 #endif
   234 #endif
   236     return packagePath;
   237   },
   239   launch: function(aApp) {
   240     let uniqueName = this.getUniqueName(aApp);
   242 #ifdef XP_WIN
   243     let launchTarget = this.getLaunchTarget(aApp);
   244     if (!launchTarget) {
   245       return false;
   246     }
   248     try {
   249       let process = Cc["@mozilla.org/process/util;1"].
   250                     createInstance(Ci.nsIProcess);
   252       process.init(launchTarget);
   253       process.runwAsync([], 0);
   254     } catch (e) {
   255       return false;
   256     }
   258     return true;
   259 #elifdef XP_MACOSX
   260     let [ launchIdentifier, path ] = this.getLaunchTarget(aApp);
   261     if (!launchIdentifier) {
   262       return false;
   263     }
   265     let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].
   266                    createInstance(Ci.nsIMacWebAppUtils);
   268     try {
   269       mwaUtils.launchAppWithIdentifier(launchIdentifier);
   270     } catch(e) {
   271       return false;
   272     }
   274     return true;
   275 #elifdef XP_UNIX
   276     let exeFile = this.getLaunchTarget(aApp);
   277     if (!exeFile) {
   278       return false;
   279     }
   281     try {
   282       let process = Cc["@mozilla.org/process/util;1"]
   283                       .createInstance(Ci.nsIProcess);
   285       process.init(exeFile);
   286       process.runAsync([], 0);
   287     } catch (e) {
   288       return false;
   289     }
   291     return true;
   292 #endif
   293   },
   295   uninstall: function(aApp) {
   296 #ifdef XP_WIN
   297     let appRegKey = this.getAppRegKey(aApp);
   299     if (!appRegKey) {
   300       return Promise.reject("App registry key not found");
   301     }
   303     let deferred = Promise.defer();
   305     try {
   306       let uninstallerPath = appRegKey.value.readStringValue("UninstallString");
   307       uninstallerPath = uninstallerPath.substring(1, uninstallerPath.length - 1);
   309       let uninstaller = Cc["@mozilla.org/file/local;1"].
   310                         createInstance(Ci.nsIFile);
   311       uninstaller.initWithPath(uninstallerPath);
   313       let process = Cc["@mozilla.org/process/util;1"].
   314                     createInstance(Ci.nsIProcess);
   315       process.init(uninstaller);
   316       process.runwAsync(["/S"], 1, (aSubject, aTopic) => {
   317         if (aTopic == "process-finished") {
   318           deferred.resolve(true);
   319         } else {
   320           deferred.reject("Uninstaller failed with exit code: " + aSubject.exitValue);
   321         }
   322       });
   323     } catch (e) {
   324       deferred.reject(e);
   325     } finally {
   326       appRegKey.value.close();
   327     }
   329     return deferred.promise;
   330 #elifdef XP_MACOSX
   331     let [ , path ] = this.getLaunchTarget(aApp);
   332     if (!path) {
   333       return Promise.reject("App not found");
   334     }
   336     let deferred = Promise.defer();
   338     let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].
   339                    createInstance(Ci.nsIMacWebAppUtils);
   341     mwaUtils.trashApp(path, (aResult) => {
   342       if (aResult == Cr.NS_OK) {
   343         deferred.resolve(true);
   344       } else {
   345         deferred.resolve("Error moving the app to the Trash: " + aResult);
   346       }
   347     });
   349     return deferred.promise;
   350 #elifdef XP_UNIX
   351     let exeFile = this.getLaunchTarget(aApp);
   352     if (!exeFile) {
   353       return Promise.reject("App executable file not found");
   354     }
   356     let deferred = Promise.defer();
   358     try {
   359       let process = Cc["@mozilla.org/process/util;1"]
   360                       .createInstance(Ci.nsIProcess);
   362       process.init(exeFile);
   363       process.runAsync(["-remove"], 1, (aSubject, aTopic) => {
   364         if (aTopic == "process-finished") {
   365           deferred.resolve(true);
   366         } else {
   367           deferred.reject("Uninstaller failed with exit code: " + aSubject.exitValue);
   368         }
   369       });
   370     } catch (e) {
   371       deferred.reject(e);
   372     }
   374     return deferred.promise;
   375 #endif
   376   },
   378   /**
   379    * Returns true if the given install path (in the old naming scheme) actually
   380    * belongs to the given application.
   381    */
   382   isOldInstallPathValid: function(aApp, aInstallPath) {
   383     // Applications with an origin that starts with "app" are packaged apps and
   384     // packaged apps have never been installed using the old naming scheme.
   385     // After bug 910465, we'll have a better way to check if an app is
   386     // packaged.
   387     if (aApp.origin.startsWith("app")) {
   388       return false;
   389     }
   391     // Bug 915480: We could check the app name from the manifest to
   392     // better verify the installation path.
   393     return true;
   394   },
   396   /**
   397    * Checks if the given app is locally installed.
   398    */
   399   isLaunchable: function(aApp) {
   400     let uniqueName = this.getUniqueName(aApp);
   402 #ifdef XP_WIN
   403     if (!this.getLaunchTarget(aApp)) {
   404       return false;
   405     }
   407     return true;
   408 #elifdef XP_MACOSX
   409     if (!this.getInstallPath(aApp)) {
   410       return false;
   411     }
   413     return true;
   414 #elifdef XP_UNIX
   415     let env = Cc["@mozilla.org/process/environment;1"]
   416                 .getService(Ci.nsIEnvironment);
   418     let xdg_data_home_env;
   419     try {
   420       xdg_data_home_env = env.get("XDG_DATA_HOME");
   421     } catch(ex) {}
   423     let desktopINI;
   424     if (xdg_data_home_env) {
   425       desktopINI = new FileUtils.File(xdg_data_home_env);
   426     } else {
   427       desktopINI = FileUtils.getFile("Home", [".local", "share"]);
   428     }
   429     desktopINI.append("applications");
   430     desktopINI.append("owa-" + uniqueName + ".desktop");
   432     // Fall back to the old installation naming scheme
   433     if (!desktopINI.exists()) {
   434       if (xdg_data_home_env) {
   435         desktopINI = new FileUtils.File(xdg_data_home_env);
   436       } else {
   437         desktopINI = FileUtils.getFile("Home", [".local", "share"]);
   438       }
   440       let origin = Services.io.newURI(aApp.origin, null, null);
   441       let oldUniqueName = origin.scheme + ";" +
   442                           origin.host +
   443                           (origin.port != -1 ? ";" + origin.port : "");
   445       desktopINI.append("owa-" + oldUniqueName + ".desktop");
   447       if (!desktopINI.exists()) {
   448         return false;
   449       }
   451       let installDir = Services.dirsvc.get("Home", Ci.nsIFile);
   452       installDir.append("." + origin.scheme + ";" + origin.host +
   453                         (origin.port != -1 ? ";" + origin.port : ""));
   455       return isOldInstallPathValid(aApp, installDir.path);
   456     }
   458     return true;
   459 #endif
   460   },
   462   /**
   463    * Sanitize the filename (accepts only a-z, 0-9, - and _)
   464    */
   465   sanitizeStringForFilename: function(aPossiblyBadFilenameString) {
   466     return aPossiblyBadFilenameString.replace(/[^a-z0-9_\-]/gi, "");
   467   }
   468 }

mercurial