Skip to content

Commit

Permalink
Signing: locate the program on $PATH
Browse files Browse the repository at this point in the history
With a git config "gpg.program = gpg" we'd just try to execute "gpg",
which would fail.

Trying to run the executable via FS.runInShell() to let the shell locate
the program on $PATH didn't work for me on Mac OS even though the shell
was invoked as "sh -l". It also has the problem that some arguments
would need to be escaped, which depends on the shell (Unix sh vs.
Windows cmd.exe).

Instead search $PATH explicitly to figure out the full path of the
program to run.

Change-Id: I0cd40147efc06f2dc607ec859debd58067c8ec0a
  • Loading branch information
tomaswolf committed Feb 7, 2025
1 parent 86d9d29 commit 9c7dc7b
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ public class CoreText extends NLS {
/** */
public static String DisconnectProviderOperation_disconnecting;

/** */
public static String ExternalGpg_invalidPath;

/** */
public static String ExternalGpgSigner_bufferError;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ DeleteResourcesOperation_deletingResources=Deleting resources
DeleteResourcesOperation_deleteFailed=Deleting resource {0} failed.
DeleteResourcesOperation_deleteFailedSeeLog=Deleting resources failed. See log for details
DisconnectProviderOperation_disconnecting=Disconnecting Git team provider.
ExternalGpg_invalidPath=Executable path ''{0}'' is invalid
ExternalGpgSigner_bufferError=Error while getting output of external program
ExternalGpgSigner_cannotSearch=Cannot find GPG executable on the bash $PATH
ExternalGpgSigner_environmentError=Cannot prepare GPG environment properly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.internal.CoreText;
Expand All @@ -28,25 +33,42 @@
*/
class ExternalGpg {

private static String gpg;
private static final Map<String, String> EXECUTABLES = new ConcurrentHashMap<>();

private static String gpgsm;
static String getGpg() {
return get("gpg"); //$NON-NLS-1$
}

static synchronized String getGpg() {
if (gpg == null) {
gpg = findProgram("gpg"); //$NON-NLS-1$
}
return gpg.isEmpty() ? null : gpg;
static String getGpgSm() {
return get("gpgsm"); //$NON-NLS-1$
}

static synchronized String getGpgSm() {
if (gpgsm == null) {
gpgsm = findProgram("gpgsm"); //$NON-NLS-1$
static String findExecutable(String program) {
try {
Path exe = Paths.get(program);
if (!exe.isAbsolute() && exe.getNameCount() == 1) {
String resolved = get(program);
if (resolved != null) {
return resolved;
}
}
} catch (InvalidPathException e) {
Activator.logWarning(
MessageFormat.format(CoreText.ExternalGpg_invalidPath,
program),
e);
}
return gpgsm.isEmpty() ? null : gpgsm;
return program;
}

private static String get(String program) {
String resolved = EXECUTABLES.computeIfAbsent(program,
ExternalGpg::findProgram);
return resolved.isEmpty() ? null : resolved;
}

private static String findProgram(String program) {
// Called only for simple names.
SystemReader system = SystemReader.getInstance();
String path = system.getenv("PATH"); //$NON-NLS-1$
String exe = null;
Expand All @@ -64,8 +86,7 @@ private static String findProgram(String program) {
ExternalProcessRunner.run(process, null, b -> {
try (BufferedReader r = new BufferedReader(
new InputStreamReader(b.openInputStream(),
SystemReader.getInstance()
.getDefaultCharset()))) {
system.getDefaultCharset()))) {
result[0] = r.readLine();
}
}, null);
Expand All @@ -78,11 +99,28 @@ private static String findProgram(String program) {
}
if (exe == null) {
exe = searchPath(path,
system.isWindows() ? program + ".exe" : program); //$NON-NLS-1$
system.isWindows() ? completeWindowsPath(program)
: program);
}
return exe == null ? "" : exe; //$NON-NLS-1$
}

private static String completeWindowsPath(String program) {
// On Windows, Java Process follows the CreateProcess function, which
// appends ".exe" if the program name has no extension and does not end
// in a period, unless it includes a path.
//
// See
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
// and
// https://github.com/openjdk/jdk/blob/43b7e9f54/src/java.base/windows/classes/java/lang/ProcessImpl.java#L338
String name = new File(program).getName();
if (name.equals(program) && name.indexOf('.') < 0) {
return program + ".exe"; //$NON-NLS-1$
}
return program;
}

private static String searchPath(String path, String name) {
if (StringUtils.isEmptyOrNull(path)) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ public SignatureVerification verify(Repository repository, GpgConfig config,
if (StringUtils.isEmptyOrNull(program)) {
throw new IOException(CoreText.ExternalGpgSigner_gpgNotFound);
}
} else {
program = ExternalGpg.findExecutable(program);
}
File signatureFile = null;
SignatureVerification[] verification = { null };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ public GpgSignature sign(Repository repository, GpgConfig config,
if (StringUtils.isEmptyOrNull(program)) {
throw new IOException(CoreText.ExternalGpgSigner_gpgNotFound);
}
} else {
program = ExternalGpg.findExecutable(program);
}
String keySpec = signingKey;
if (keySpec == null) {
Expand Down

0 comments on commit 9c7dc7b

Please sign in to comment.