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

mercurial