Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quoting for start arguments #10099

Merged
merged 13 commits into from
Jul 14, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class CommandLineBuilder
{
Expand Down Expand Up @@ -54,86 +55,201 @@ public static String findJavaBin()
}

/**
* Perform an optional quoting of the argument, being intelligent with spaces and quotes as needed. If a subString is set in quotes it won't the subString
* won't be escaped.
*
* @param arg the argument to quote
* @return the quoted and escaped argument
* @deprecated no replacement, quoting is done by {@link #toQuotedString()} now.
* @param arg string
* @return Quoted string
* @deprecated use {@link #shellQuoteIfNeeded(String)}
*/
@Deprecated
public static String quote(String arg)
{
return "'" + arg + "'";
joakime marked this conversation as resolved.
Show resolved Hide resolved
}

private List<String> args;
private final StringBuilder commandLine = new StringBuilder();
private final List<String> args = new ArrayList<>();
private final String separator;

public CommandLineBuilder()
{
args = new ArrayList<String>();
this(false);
}

@Deprecated
public CommandLineBuilder(String bin)
{
this();
args.add(bin);
}

public CommandLineBuilder(boolean multiline)
{
separator = multiline ? (" \\" + System.lineSeparator() + " ") : " ";
}

/**
* Add a simple argument to the command line.
* <p>
* Will quote arguments that have a space in them.
* This method applies single quotes suitable for a POSIX compliant shell if
* necessary.
*
* @param input The string to quote if needed
* @return The quoted string or the original string if quotes are not necessary
*/
public static String shellQuoteIfNeeded(String input)
{
// Single quotes are used because double quotes
// are evaluated differently by some shells.

if (input == null)
return null;
if (input.length() == 0)
return "''";

int i = 0;
boolean needsQuoting = false;
while (!needsQuoting && i < input.length())
{
char c = input.charAt(i++);

// needs quoting unless a limited set of known good characters
needsQuoting = !(
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
c == '/' ||
c == ':' ||
c == '.' ||
c == ',' ||
c == '+' ||
c == '-' ||
c == '_'
gregw marked this conversation as resolved.
Show resolved Hide resolved
);
sbordet marked this conversation as resolved.
Show resolved Hide resolved
}

if (!needsQuoting)
return input;

StringBuilder builder = new StringBuilder(input.length() * 2);
builder.append("'");
builder.append(input, 0, --i);

while (i < input.length())
{
char c = input.charAt(i++);
if (c == '\'')
{
// There is no escape for a literal single quote, so we must leave the quotes
// and then escape the single quote. We test for the start/end of the string, so
// we can be less ugly in those cases.
if (i == 1)
builder.insert(0, "\\").append("'");
else if (i == input.length())
builder.append("'\\");
else
builder.append("'\\''");
}
else
builder.append(c);
}
builder.append("'");

return builder.toString();
}

/**
* Add a simple argument to the command line, quoted if necessary.
*
* @param arg the simple argument to add
*/
public void addArg(String arg)
{
if (arg != null)
{
if (commandLine.length() > 0)
commandLine.append(separator);
args.add(arg);
commandLine.append(shellQuoteIfNeeded(arg));
}
}

/**
* Similar to {@link #addArg(String)} but concats both name + value with an "=" sign, quoting were needed, and excluding the "=" portion if the value is
* undefined or empty.
*
* <pre>
* addEqualsArg("-Dname", "value") = "-Dname=value"
* addEqualsArg("-Djetty.home", "/opt/company inc/jetty (7)/") = "-Djetty.home=/opt/company\ inc/jetty\ (7)/"
* addEqualsArg("-Djenkins.workspace", "/opt/workspaces/jetty jdk7/") = "-Djenkins.workspace=/opt/workspaces/jetty\ jdk7/"
* addEqualsArg("-Dstress", null) = "-Dstress"
* addEqualsArg("-Dstress", "") = "-Dstress"
* </pre>
*
* @deprecated use {@link #addArg(String, String)}
*/
@Deprecated
public void addEqualsArg(String name, String value)
{
addArg(name, value);
}

/**
* Add a "name=value" style argument to the command line with
* name and value quoted if necessary.
* @param name the name
* @param value the value
*/
public void addEqualsArg(String name, String value)
public void addArg(String name, String value)
gregw marked this conversation as resolved.
Show resolved Hide resolved
{
Objects.requireNonNull(name);

if (commandLine.length() > 0)
commandLine.append(separator);

if ((value != null) && (value.length() > 0))
{
args.add(name + "=" + value);
commandLine.append(shellQuoteIfNeeded(name)).append('=').append(shellQuoteIfNeeded(value));
}
else
{
args.add(name);
commandLine.append(shellQuoteIfNeeded(name));
}
}

/**
* Add a simple argument to the command line.
* <p>
* Will <b>NOT</b> quote/escape arguments that have a space in them.
*
* @param arg the simple argument to add
* @deprecated use {@link #addArg(String)}
*/
@Deprecated
public void addRawArg(String arg)
{
if (arg != null)
addArg(arg);
}

/**
* Adds a "-OPTION" style option to the command line with no quoting, for example `--help`.
* @param option the option
*/
public void addOption(String option)
{
addOption(option, null, null);
}

/**
* Adds a "-OPTIONname=value" style option to the command line with
* name and value quoted if necessary, for example "-Dprop=value".
* @param option the option
* @param name the name
* @param value the value
*/
public void addOption(String option, String name, String value)
{
Objects.requireNonNull(option);

if (commandLine.length() > 0)
commandLine.append(separator);

if (name == null || name.length() == 0)
sbordet marked this conversation as resolved.
Show resolved Hide resolved
{
args.add(arg);
commandLine.append(option);
args.add(option);
}
else if ((value != null) && (value.length() > 0))
{
args.add(option + name + "=" + value);
commandLine.append(option).append(shellQuoteIfNeeded(name)).append('=').append(shellQuoteIfNeeded(value));
}
else
{
args.add(option + name);
commandLine.append(option).append(shellQuoteIfNeeded(name));
}
}

Expand All @@ -144,48 +260,35 @@ public List<String> getArgs()

@Override
public String toString()
{
return toString(" ");
}

public String toString(String delim)
gregw marked this conversation as resolved.
Show resolved Hide resolved
{
StringBuilder buf = new StringBuilder();

for (String arg : args)
{
if (buf.length() > 0)
{
buf.append(delim);
}
buf.append(' ');
buf.append(arg); // we assume escaping has occurred during addArg
}

return buf.toString();
}

/**
* @deprecated use {@link #toCommandLine()}
*/
@Deprecated
public String toQuotedString()
{
return toCommandLine();
}

/**
* A version of {@link #toString()} where every arg is evaluated for potential {@code '} (single-quote tick) wrapping.
*
* @return the toString but each arg that has spaces is surrounded by {@code '} (single-quote tick)
*/
public String toQuotedString()
public String toCommandLine()
gregw marked this conversation as resolved.
Show resolved Hide resolved
{
StringBuilder buf = new StringBuilder();

for (String arg : args)
{
if (buf.length() > 0)
buf.append(' ');
boolean needsQuotes = (arg.contains(" "));
if (needsQuotes)
buf.append("'");
buf.append(arg);
if (needsQuotes)
buf.append("'");
}

return buf.toString();
return commandLine.toString();
}

public void debug()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ public void start(StartArgs args) throws IOException, InterruptedException
{
CommandLineBuilder cmd = args.getMainArgs(args.getDryRunParts());
cmd.debug();
System.out.println(cmd.toQuotedString());
System.out.println(cmd.toCommandLine());
}

if (args.isStopCommand())
Expand Down
Loading