From 296edc3fdd56dcad178807292d70abc692aa7bc8 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 21 Jan 2025 05:40:27 -0500 Subject: [PATCH] Unset Mozilla environment variables when launching files and URLs (#4983) Fixes #4981 --- chrome/content/zotero/xpcom/fileHandlers.js | 11 ++- .../zotero/xpcom/utilities_internal.js | 79 +++++++++++++++++++ chrome/content/zotero/xpcom/zotero.js | 41 +++++++++- 3 files changed, 128 insertions(+), 3 deletions(-) diff --git a/chrome/content/zotero/xpcom/fileHandlers.js b/chrome/content/zotero/xpcom/fileHandlers.js index 65dcb07918..5717e9c709 100644 --- a/chrome/content/zotero/xpcom/fileHandlers.js +++ b/chrome/content/zotero/xpcom/fileHandlers.js @@ -149,6 +149,7 @@ Zotero.FileHandlers = { handler = handler || systemHandler; if (handler) { if (Zotero.isMac) { + Zotero.Utilities.Internal.Environment.clearMozillaVariables(); try { await Zotero.Utilities.Internal.exec('/usr/bin/open', ['-a', handler, path]); return true; @@ -156,6 +157,9 @@ Zotero.FileHandlers = { catch (e) { Zotero.logError(e); } + finally { + Zotero.Utilities.Internal.Environment.restoreMozillaVariables(); + } } try { @@ -498,8 +502,13 @@ Zotero.FileHandlers = { if (!Zotero.File.pathToFile(command).isExecutable()) { throw new Error(`${command} is not an executable`); } + + Zotero.Utilities.Internal.Environment.clearMozillaVariables(); + // Do not await - Zotero.Utilities.Internal.exec(command, args); + var promise = Zotero.Utilities.Internal.exec(command, args); + + promise.finally(() => Zotero.Utilities.Internal.Environment.restoreMozillaVariables()); }, }; diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index 4eac987273..042bd14dbe 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -2899,6 +2899,85 @@ Zotero.Utilities.Internal.getProcessID = function () { }; +Zotero.Utilities.Internal.Environment = { + /** + * Unset an environment variable + * + * Since nsIEnvironment doesn't have an unset method + */ + unset: function (varName) { + Components.utils.import("resource://gre/modules/ctypes.jsm"); + + let lib; + let result; + let success; + // Windows + // https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew + if (Zotero.isWin) { + lib = ctypes.open("kernel32.dll"); + let SetEnvironmentVariable = lib.declare( + "SetEnvironmentVariableW", + ctypes.winapi_abi, + ctypes.int, // Return type + ctypes.char16_t.ptr, // Argument type: LPCTSTR (variable name) + ctypes.char16_t.ptr // Argument type: LPCTSTR (value, NULL to unset) + ); + result = SetEnvironmentVariable(varName, null); + success = result != 0; // "If the function succeeds, the return value is nonzero." + } + // macOS or Linux + else { + lib = ctypes.open(Zotero.isMac ? "/usr/lib/libSystem.dylib" : "libc.so.6"); + let unsetenv = lib.declare( + "unsetenv", + ctypes.default_abi, + ctypes.int, // Return type + ctypes.char.ptr // Argument type: const char * + ); + result = unsetenv(varName); + success = result == 0; + } + + // Check the result + if (success) { + //Zotero.debug(`Unset environment variable ${varName}`); + } + else { + Zotero.logError(`Failed to unset environment variable ${varName} (${result})`); + } + + lib.close(); + }, + + /** + * Clear the Mozilla environment variables that we changed in the launcher + * + * Call this before launching another process that might be Firefox so that we don't end up + * using the wrong Firefox profile when launching URLs or PDFs. On Windows, it's not necessary + * to call this when launching URLs, only processes. + * + * https://github.com/zotero/zotero/issues/4981 + */ + clearMozillaVariables: function () { + this.unset("MOZ_ALLOW_DOWNGRADE"); + this.unset("MOZ_LEGACY_PROFILES"); + }, + + /** + * Re-set the Mozilla environment variables that we changed in the launcher + * + * Call this in a finally() after using unsetMozillaVariables(). This mostly isn't necessary, + * since most new launches of Zotero would use the modified launcher, but a restart on Linux + * skips our shell script where we set these variables. + */ + restoreMozillaVariables: function () { + var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment); + env.set("MOZ_ALLOW_DOWNGRADE", "1"); + env.set("MOZ_LEGACY_PROFILES", "1"); + }, +}; + + /** * Base64 encode / decode * From http://www.webtoolkit.info/ diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js index 8f96b41ac1..7cfcc8b8df 100644 --- a/chrome/content/zotero/xpcom/zotero.js +++ b/chrome/content/zotero/xpcom/zotero.js @@ -992,6 +992,9 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); */ this.launchFile = function (file) { file = Zotero.File.pathToFile(file); + + Zotero.Utilities.Internal.Environment.clearMozillaVariables(); + try { Zotero.debug("Launching " + file.path); file.launch(); @@ -1034,6 +1037,9 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); ); } } + finally { + Zotero.Utilities.Internal.Environment.restoreMozillaVariables(); + } }; @@ -1058,8 +1064,12 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); args = [filePath]; } + Zotero.Utilities.Internal.Environment.clearMozillaVariables(); + // Async, but we don't want to block - Zotero.Utilities.Internal.exec(applicationPath, args); + var promise = Zotero.Utilities.Internal.exec(applicationPath, args); + + promise.finally(() => Zotero.Utilities.Internal.Environment.restoreMozillaVariables()); }; @@ -1089,12 +1099,29 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); if (!found.value) { throw new Error(`Handler not found for '${scheme}' URLs`); } - svc.loadURI(Services.io.newURI(url, null, null)); + try { + if (!Zotero.isWin) { + Zotero.Utilities.Internal.Environment.clearMozillaVariables(); + } + + svc.loadURI(Services.io.newURI(url, null, null)); + } + finally { + if (!Zotero.isWin) { + Zotero.Utilities.Internal.Environment.restoreMozillaVariables(); + } + } return; } } + var mozCleared = false; try { + if (!Zotero.isWin) { + Zotero.Utilities.Internal.Environment.clearMozillaVariables(); + mozCleared = true; + } + var uri = Services.io.newURI(url, null, null); var handler = Components.classes['@mozilla.org/uriloader/external-protocol-service;1'] .getService(Components.interfaces.nsIExternalProtocolService) @@ -1119,6 +1146,11 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); + "check extensions.zotero." + pref + " in about:config"); } + if (!mozCleared) { + Zotero.Utilities.Internal.Environment.clearMozillaVariables(); + mozCleared = true; + } + var proc = Components.classes["@mozilla.org/process/util;1"] .createInstance(Components.interfaces.nsIProcess); proc.init(exec); @@ -1126,6 +1158,11 @@ Services.scriptloader.loadSubScript("resource://zotero/polyfill.js"); var args = [url]; proc.runw(false, args, args.length); } + finally { + if (mozCleared) { + Zotero.Utilities.Internal.Environment.restoreMozillaVariables(); + } + } }