Skip to content

Commit

Permalink
Fixes #9999 and #10055 - proper quoting for dry-run
Browse files Browse the repository at this point in the history
Using "strong quoting" with the single quote character to quote argument of --dry-run output.

Signed-off-by: gregw <[email protected]>
Signed-off-by: Simone Bordet <[email protected]>
Co-authored-by: Simone Bordet <[email protected]>
  • Loading branch information
gregw and sbordet authored Jul 14, 2023
1 parent 9a05c75 commit 11d2a32
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 106 deletions.
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 + "'";
}

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 == '_'
);
}

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)
{
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)
{
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)
{
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()
{
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

0 comments on commit 11d2a32

Please sign in to comment.