bf087c
From 6e823f2fcb904efccd8ae16d6aab8c1b34a09d5c Mon Sep 17 00:00:00 2001
bf087c
From: Kristian Rosenvold <krosenvold@apache.org>
bf087c
Date: Tue, 8 Oct 2013 18:21:04 +0200
bf087c
Subject: [PATCH] [PLXUTILS-161] Commandline shell injection problems
bf087c
bf087c
Patch by Charles Duffy, applied unmodified
bf087c
---
bf087c
 .../org/codehaus/plexus/util/cli/Commandline.java  | 38 +++++++++++---
bf087c
 .../plexus/util/cli/shell/BourneShell.java         | 60 +++++++---------------
bf087c
 .../org/codehaus/plexus/util/cli/shell/Shell.java  | 35 ++++++++++---
bf087c
 .../codehaus/plexus/util/cli/CommandlineTest.java  | 37 +++++++------
bf087c
 .../plexus/util/cli/shell/BourneShellTest.java     | 19 ++++---
bf087c
 5 files changed, 107 insertions(+), 82 deletions(-)
bf087c
bf087c
diff --git a/src/main/java/org/codehaus/plexus/util/cli/Commandline.java b/src/main/java/org/codehaus/plexus/util/cli/Commandline.java
bf087c
index 5e0d5af..7346c7e 100644
bf087c
--- a/src/main/java/org/codehaus/plexus/util/cli/Commandline.java
bf087c
+++ b/src/main/java/org/codehaus/plexus/util/cli/Commandline.java
bf087c
@@ -139,6 +139,8 @@ public class Commandline
bf087c
      * Create a new command line object.
bf087c
      * Shell is autodetected from operating system
bf087c
      *
bf087c
+     * Shell usage is only desirable when generating code for remote execution.
bf087c
+     *
bf087c
      * @param toProcess
bf087c
      */
bf087c
     public Commandline( String toProcess, Shell shell )
bf087c
@@ -167,6 +169,8 @@ public class Commandline
bf087c
     /**
bf087c
      * Create a new command line object.
bf087c
      * Shell is autodetected from operating system
bf087c
+     *
bf087c
+     * Shell usage is only desirable when generating code for remote execution.
bf087c
      */
bf087c
     public Commandline( Shell shell )
bf087c
     {
bf087c
@@ -174,8 +178,7 @@ public class Commandline
bf087c
     }
bf087c
 
bf087c
     /**
bf087c
-     * Create a new command line object.
bf087c
-     * Shell is autodetected from operating system
bf087c
+     * Create a new command line object, given a command following POSIX sh quoting rules
bf087c
      *
bf087c
      * @param toProcess
bf087c
      */
bf087c
@@ -203,7 +206,6 @@ public class Commandline
bf087c
 
bf087c
     /**
bf087c
      * Create a new command line object.
bf087c
-     * Shell is autodetected from operating system
bf087c
      */
bf087c
     public Commandline()
bf087c
     {
bf087c
@@ -253,7 +255,7 @@ public class Commandline
bf087c
         {
bf087c
             if ( realPos == -1 )
bf087c
             {
bf087c
-                realPos = ( getExecutable() == null ? 0 : 1 );
bf087c
+                realPos = ( getLiteralExecutable() == null ? 0 : 1 );
bf087c
                 for ( int i = 0; i < position; i++ )
bf087c
                 {
bf087c
                     Arg arg = (Arg) arguments.elementAt( i );
bf087c
@@ -404,6 +406,21 @@ public class Commandline
bf087c
         this.executable = executable;
bf087c
     }
bf087c
 
bf087c
+    /**
bf087c
+     * @return Executable to be run, as a literal string (no shell quoting/munging)
bf087c
+     */
bf087c
+    public String getLiteralExecutable()
bf087c
+    {
bf087c
+        return executable;
bf087c
+    }
bf087c
+
bf087c
+    /**
bf087c
+     * Return an executable name, quoted for shell use.
bf087c
+     *
bf087c
+     * Shell usage is only desirable when generating code for remote execution.
bf087c
+     *
bf087c
+     * @return Executable to be run, quoted for shell interpretation
bf087c
+     */
bf087c
     public String getExecutable()
bf087c
     {
bf087c
         String exec = shell.getExecutable();
bf087c
@@ -483,7 +500,7 @@ public class Commandline
bf087c
     public String[] getCommandline()
bf087c
     {
bf087c
         final String[] args = getArguments();
bf087c
-        String executable = getExecutable();
bf087c
+        String executable = getLiteralExecutable();
bf087c
 
bf087c
         if ( executable == null )
bf087c
         {
bf087c
@@ -497,6 +514,8 @@ public class Commandline
bf087c
 
bf087c
     /**
bf087c
      * Returns the shell, executable and all defined arguments.
bf087c
+     *
bf087c
+     * Shell usage is only desirable when generating code for remote execution.
bf087c
      */
bf087c
     public String[] getShellCommandline()
bf087c
     {
bf087c
@@ -633,7 +652,7 @@ public class Commandline
bf087c
         {
bf087c
             if ( workingDir == null )
bf087c
             {
bf087c
-                process = Runtime.getRuntime().exec( getShellCommandline(), environment );
bf087c
+                process = Runtime.getRuntime().exec( getCommandline(), environment, workingDir );
bf087c
             }
bf087c
             else
bf087c
             {
bf087c
@@ -648,7 +667,7 @@ public class Commandline
bf087c
                         + "\" does not specify a directory." );
bf087c
                 }
bf087c
 
bf087c
-                process = Runtime.getRuntime().exec( getShellCommandline(), environment, workingDir );
bf087c
+                process = Runtime.getRuntime().exec( getCommandline(), environment, workingDir );
bf087c
             }
bf087c
         }
bf087c
         catch ( IOException ex )
bf087c
@@ -669,7 +688,7 @@ public class Commandline
bf087c
             shell.setWorkingDirectory( workingDir );
bf087c
         }
bf087c
 
bf087c
-        if ( shell.getExecutable() == null )
bf087c
+        if ( shell.getOriginalExecutable() == null )
bf087c
         {
bf087c
             shell.setExecutable( executable );
bf087c
         }
bf087c
@@ -684,6 +703,8 @@ public class Commandline
bf087c
     /**
bf087c
      * Allows to set the shell to be used in this command line.
bf087c
      *
bf087c
+     * Shell usage is only desirable when generating code for remote execution.
bf087c
+     *
bf087c
      * @param shell
bf087c
      * @since 1.2
bf087c
      */
bf087c
@@ -695,6 +716,7 @@ public class Commandline
bf087c
     /**
bf087c
      * Get the shell to be used in this command line.
bf087c
      *
bf087c
+     * Shell usage is only desirable when generating code for remote execution.
bf087c
      * @since 1.2
bf087c
      */
bf087c
     public Shell getShell()
bf087c
diff --git a/src/main/java/org/codehaus/plexus/util/cli/shell/BourneShell.java b/src/main/java/org/codehaus/plexus/util/cli/shell/BourneShell.java
bf087c
index e4b4cde..3c07fb6 100644
bf087c
--- a/src/main/java/org/codehaus/plexus/util/cli/shell/BourneShell.java
bf087c
+++ b/src/main/java/org/codehaus/plexus/util/cli/shell/BourneShell.java
bf087c
@@ -17,7 +17,6 @@ package org.codehaus.plexus.util.cli.shell;
bf087c
  */
bf087c
 
bf087c
 import org.codehaus.plexus.util.Os;
bf087c
-import org.codehaus.plexus.util.StringUtils;
bf087c
 
bf087c
 import java.util.ArrayList;
bf087c
 import java.util.List;
bf087c
@@ -29,34 +28,18 @@ import java.util.List;
bf087c
 public class BourneShell
bf087c
     extends Shell
bf087c
 {
bf087c
-    private static final char[] BASH_QUOTING_TRIGGER_CHARS = {
bf087c
-        ' ',
bf087c
-        '$',
bf087c
-        ';',
bf087c
-        '&',
bf087c
-        '|',
bf087c
-        '<',
bf087c
-        '>',
bf087c
-        '*',
bf087c
-        '?',
bf087c
-        '(',
bf087c
-        ')',
bf087c
-        '[',
bf087c
-        ']',
bf087c
-        '{',
bf087c
-        '}',
bf087c
-        '`' };
bf087c
 
bf087c
     public BourneShell()
bf087c
     {
bf087c
-        this( false );
bf087c
+        this(false);
bf087c
     }
bf087c
 
bf087c
     public BourneShell( boolean isLoginShell )
bf087c
     {
bf087c
+        setUnconditionalQuoting( true );
bf087c
         setShellCommand( "/bin/sh" );
bf087c
         setArgumentQuoteDelimiter( '\'' );
bf087c
-        setExecutableQuoteDelimiter( '\"' );
bf087c
+        setExecutableQuoteDelimiter( '\'' );
bf087c
         setSingleQuotedArgumentEscaped( true );
bf087c
         setSingleQuotedExecutableEscaped( false );
bf087c
         setQuotedExecutableEnabled( true );
bf087c
@@ -76,7 +59,7 @@ public class BourneShell
bf087c
             return super.getExecutable();
bf087c
         }
bf087c
 
bf087c
-        return unifyQuotes( super.getExecutable());
bf087c
+        return quoteOneItem( super.getOriginalExecutable(), true );
bf087c
     }
bf087c
 
bf087c
     public List<String> getShellArgsList()
bf087c
@@ -126,46 +109,41 @@ public class BourneShell
bf087c
         StringBuffer sb = new StringBuffer();
bf087c
         sb.append( "cd " );
bf087c
 
bf087c
-        sb.append( unifyQuotes( dir ) );
bf087c
+        sb.append( quoteOneItem( dir, false ) );
bf087c
         sb.append( " && " );
bf087c
 
bf087c
         return sb.toString();
bf087c
     }
bf087c
 
bf087c
-    protected char[] getQuotingTriggerChars()
bf087c
-    {
bf087c
-        return BASH_QUOTING_TRIGGER_CHARS;
bf087c
-    }
bf087c
-
bf087c
     /**
bf087c
      * 

Unify quotes in a path for the Bourne Shell.

bf087c
      *
bf087c
      * 
bf087c
-     * BourneShell.unifyQuotes(null)                       = null
bf087c
-     * BourneShell.unifyQuotes("")                         = (empty)
bf087c
-     * BourneShell.unifyQuotes("/test/quotedpath'abc")     = /test/quotedpath\'abc
bf087c
-     * BourneShell.unifyQuotes("/test/quoted path'abc")    = "/test/quoted path'abc"
bf087c
-     * BourneShell.unifyQuotes("/test/quotedpath\"abc")    = "/test/quotedpath\"abc"
bf087c
-     * BourneShell.unifyQuotes("/test/quoted path\"abc")   = "/test/quoted path\"abc"
bf087c
-     * BourneShell.unifyQuotes("/test/quotedpath\"'abc")   = "/test/quotedpath\"'abc"
bf087c
-     * BourneShell.unifyQuotes("/test/quoted path\"'abc")  = "/test/quoted path\"'abc"
bf087c
+     * BourneShell.quoteOneItem(null)                       = null
bf087c
+     * BourneShell.quoteOneItem("")                         = ''
bf087c
+     * BourneShell.quoteOneItem("/test/quotedpath'abc")     = '/test/quotedpath'"'"'abc'
bf087c
+     * BourneShell.quoteOneItem("/test/quoted path'abc")    = '/test/quoted pat'"'"'habc'
bf087c
+     * BourneShell.quoteOneItem("/test/quotedpath\"abc")    = '/test/quotedpath"abc'
bf087c
+     * BourneShell.quoteOneItem("/test/quoted path\"abc")   = '/test/quoted path"abc'
bf087c
+     * BourneShell.quoteOneItem("/test/quotedpath\"'abc")   = '/test/quotedpath"'"'"'abc'
bf087c
+     * BourneShell.quoteOneItem("/test/quoted path\"'abc")  = '/test/quoted path"'"'"'abc'
bf087c
      * 
bf087c
      *
bf087c
      * @param path not null path.
bf087c
      * @return the path unified correctly for the Bourne shell.
bf087c
      */
bf087c
-    protected static String unifyQuotes( String path )
bf087c
+    protected String quoteOneItem( String path, boolean isExecutable )
bf087c
     {
bf087c
         if ( path == null )
bf087c
         {
bf087c
             return null;
bf087c
         }
bf087c
 
bf087c
-        if ( path.indexOf( " " ) == -1 && path.indexOf( "'" ) != -1 && path.indexOf( "\"" ) == -1 )
bf087c
-        {
bf087c
-            return StringUtils.escape( path );
bf087c
-        }
bf087c
+        StringBuilder sb = new StringBuilder();
bf087c
+        sb.append( "'" );
bf087c
+        sb.append( path.replace( "'", "'\"'\"'" ) );
bf087c
+        sb.append( "'" );
bf087c
 
bf087c
-        return StringUtils.quoteAndEscape( path, '\"', BASH_QUOTING_TRIGGER_CHARS );
bf087c
+        return sb.toString();
bf087c
     }
bf087c
 }
bf087c
diff --git a/src/main/java/org/codehaus/plexus/util/cli/shell/Shell.java b/src/main/java/org/codehaus/plexus/util/cli/shell/Shell.java
bf087c
index 571b249..a42eae8 100644
bf087c
--- a/src/main/java/org/codehaus/plexus/util/cli/shell/Shell.java
bf087c
+++ b/src/main/java/org/codehaus/plexus/util/cli/shell/Shell.java
bf087c
@@ -48,6 +48,8 @@ public class Shell
bf087c
 
bf087c
     private boolean quotedArgumentsEnabled = true;
bf087c
 
bf087c
+    private boolean unconditionallyQuote = false;
bf087c
+
bf087c
     private String executable;
bf087c
 
bf087c
     private String workingDir;
bf087c
@@ -69,6 +71,16 @@ public class Shell
bf087c
     private String argumentEscapePattern = "\\%s";
bf087c
 
bf087c
     /**
bf087c
+     * Toggle unconditional quoting
bf087c
+     *
bf087c
+     * @param unconditionallyQuote
bf087c
+     */
bf087c
+    public void setUnconditionalQuoting(boolean unconditionallyQuote)
bf087c
+    {
bf087c
+        this.unconditionallyQuote = unconditionallyQuote;
bf087c
+    }
bf087c
+
bf087c
+    /**
bf087c
      * Set the command to execute the shell (eg. COMMAND.COM, /bin/bash,...)
bf087c
      *
bf087c
      * @param shellCommand
bf087c
@@ -129,6 +141,19 @@ public class Shell
bf087c
         return getRawCommandLine( executable, arguments );
bf087c
     }
bf087c
 
bf087c
+    protected String quoteOneItem(String inputString, boolean isExecutable)
bf087c
+    {
bf087c
+        char[] escapeChars = getEscapeChars( isSingleQuotedExecutableEscaped(), isDoubleQuotedExecutableEscaped() );
bf087c
+        return StringUtils.quoteAndEscape(
bf087c
+            inputString,
bf087c
+            isExecutable ? getExecutableQuoteDelimiter() : getArgumentQuoteDelimiter(),
bf087c
+            escapeChars,
bf087c
+            getQuotingTriggerChars(),
bf087c
+            '\\',
bf087c
+            unconditionallyQuote
bf087c
+        );
bf087c
+    }
bf087c
+
bf087c
     protected List<String> getRawCommandLine( String executable, String[] arguments )
bf087c
     {
bf087c
         List<String> commandLine = new ArrayList<String>();
bf087c
@@ -144,9 +169,7 @@ public class Shell
bf087c
 
bf087c
             if ( isQuotedExecutableEnabled() )
bf087c
             {
bf087c
-                char[] escapeChars = getEscapeChars( isSingleQuotedExecutableEscaped(), isDoubleQuotedExecutableEscaped() );
bf087c
-
bf087c
-                sb.append( StringUtils.quoteAndEscape( getExecutable(), getExecutableQuoteDelimiter(), escapeChars, getQuotingTriggerChars(), '\\', false ) );
bf087c
+                sb.append( quoteOneItem( getOriginalExecutable(), true ) );
bf087c
             }
bf087c
             else
bf087c
             {
bf087c
@@ -162,9 +185,7 @@ public class Shell
bf087c
 
bf087c
             if ( isQuotedArgumentsEnabled() )
bf087c
             {
bf087c
-                char[] escapeChars = getEscapeChars( isSingleQuotedArgumentEscaped(), isDoubleQuotedArgumentEscaped() );
bf087c
-
bf087c
-                sb.append( StringUtils.quoteAndEscape( arguments[i], getArgumentQuoteDelimiter(), escapeChars, getQuotingTriggerChars(), getArgumentEscapePattern(), false ) );
bf087c
+                sb.append( quoteOneItem( arguments[i], false ) );
bf087c
             }
bf087c
             else
bf087c
             {
bf087c
@@ -278,7 +299,7 @@ public class Shell
bf087c
             commandLine.addAll( getShellArgsList() );
bf087c
         }
bf087c
 
bf087c
-        commandLine.addAll( getCommandLine( getExecutable(), arguments ) );
bf087c
+        commandLine.addAll( getCommandLine( getOriginalExecutable(), arguments ) );
bf087c
 
bf087c
         return commandLine;
bf087c
 
bf087c
diff --git a/src/test/java/org/codehaus/plexus/util/cli/CommandlineTest.java b/src/test/java/org/codehaus/plexus/util/cli/CommandlineTest.java
bf087c
index b22814b..42bbb7f 100644
bf087c
--- a/src/test/java/org/codehaus/plexus/util/cli/CommandlineTest.java
bf087c
+++ b/src/test/java/org/codehaus/plexus/util/cli/CommandlineTest.java
bf087c
@@ -16,6 +16,7 @@ package org.codehaus.plexus.util.cli;
bf087c
  * limitations under the License.
bf087c
  */
bf087c
 
bf087c
+import junit.framework.TestCase;
bf087c
 import org.codehaus.plexus.util.IOUtil;
bf087c
 import org.codehaus.plexus.util.Os;
bf087c
 import org.codehaus.plexus.util.StringUtils;
bf087c
@@ -23,15 +24,7 @@ import org.codehaus.plexus.util.cli.shell.BourneShell;
bf087c
 import org.codehaus.plexus.util.cli.shell.CmdShell;
bf087c
 import org.codehaus.plexus.util.cli.shell.Shell;
bf087c
 
bf087c
-import java.io.File;
bf087c
-import java.io.FileWriter;
bf087c
-import java.io.IOException;
bf087c
-import java.io.InputStreamReader;
bf087c
-import java.io.Reader;
bf087c
-import java.io.StringWriter;
bf087c
-import java.io.Writer;
bf087c
-
bf087c
-import junit.framework.TestCase;
bf087c
+import java.io.*;
bf087c
 
bf087c
 public class CommandlineTest
bf087c
     extends TestCase
bf087c
@@ -252,7 +245,7 @@ public class CommandlineTest
bf087c
 
bf087c
         assertEquals( "/bin/sh", shellCommandline[0] );
bf087c
         assertEquals( "-c", shellCommandline[1] );
bf087c
-        String expectedShellCmd = "/bin/echo \'hello world\'";
bf087c
+        String expectedShellCmd = "'/bin/echo' 'hello world'";
bf087c
         if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
bf087c
         {
bf087c
             expectedShellCmd = "\\bin\\echo \'hello world\'";
bf087c
@@ -282,12 +275,12 @@ public class CommandlineTest
bf087c
 
bf087c
         assertEquals( "/bin/sh", shellCommandline[0] );
bf087c
         assertEquals( "-c", shellCommandline[1] );
bf087c
-        String expectedShellCmd = "cd \"" + root.getAbsolutePath()
bf087c
-                                  + "path with spaces\" && /bin/echo \'hello world\'";
bf087c
+        String expectedShellCmd = "cd '" + root.getAbsolutePath()
bf087c
+                                  + "path with spaces' && '/bin/echo' 'hello world'";
bf087c
         if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
bf087c
         {
bf087c
-            expectedShellCmd = "cd \"" + root.getAbsolutePath()
bf087c
-                               + "path with spaces\" && \\bin\\echo \'hello world\'";
bf087c
+            expectedShellCmd = "cd '" + root.getAbsolutePath()
bf087c
+                               + "path with spaces' && '\\bin\\echo' 'hello world'";
bf087c
         }
bf087c
         assertEquals( expectedShellCmd, shellCommandline[2] );
bf087c
     }
bf087c
@@ -311,7 +304,7 @@ public class CommandlineTest
bf087c
 
bf087c
         assertEquals( "/bin/sh", shellCommandline[0] );
bf087c
         assertEquals( "-c", shellCommandline[1] );
bf087c
-        String expectedShellCmd = "/bin/echo \'hello world\'";
bf087c
+        String expectedShellCmd = "'/bin/echo' ''\"'\"'hello world'\"'\"''";
bf087c
         if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
bf087c
         {
bf087c
             expectedShellCmd = "\\bin\\echo \'hello world\'";
bf087c
@@ -341,7 +334,7 @@ public class CommandlineTest
bf087c
         }
bf087c
         else
bf087c
         {
bf087c
-            assertEquals( "/usr/bin a b", shellCommandline[2] );
bf087c
+            assertEquals( "'/usr/bin' 'a' 'b'", shellCommandline[2] );
bf087c
         }
bf087c
     }
bf087c
 
bf087c
@@ -388,6 +381,18 @@ public class CommandlineTest
bf087c
     }
bf087c
 
bf087c
     /**
bf087c
+     * Test an executable with shell-expandable content in its path.
bf087c
+     *
bf087c
+     * @throws Exception
bf087c
+     */
bf087c
+    public void testPathWithShellExpansionStrings()
bf087c
+        throws Exception
bf087c
+    {
bf087c
+        File dir = new File( System.getProperty( "basedir" ), "target/test/dollar$test" );
bf087c
+        createAndCallScript( dir, "echo Quoted" );
bf087c
+    }
bf087c
+
bf087c
+    /**
bf087c
      * Test an executable with a single quotation mark \" in its path only for non Windows box.
bf087c
      *
bf087c
      * @throws Exception
bf087c
diff --git a/src/test/java/org/codehaus/plexus/util/cli/shell/BourneShellTest.java b/src/test/java/org/codehaus/plexus/util/cli/shell/BourneShellTest.java
bf087c
index 2a987ed..0e06c63 100644
bf087c
--- a/src/test/java/org/codehaus/plexus/util/cli/shell/BourneShellTest.java
bf087c
+++ b/src/test/java/org/codehaus/plexus/util/cli/shell/BourneShellTest.java
bf087c
@@ -16,14 +16,13 @@ package org.codehaus.plexus.util.cli.shell;
bf087c
  * limitations under the License.
bf087c
  */
bf087c
 
bf087c
+import junit.framework.TestCase;
bf087c
 import org.codehaus.plexus.util.StringUtils;
bf087c
 import org.codehaus.plexus.util.cli.Commandline;
bf087c
 
bf087c
 import java.util.Arrays;
bf087c
 import java.util.List;
bf087c
 
bf087c
-import junit.framework.TestCase;
bf087c
-
bf087c
 public class BourneShellTest
bf087c
     extends TestCase
bf087c
 {
bf087c
@@ -42,7 +41,7 @@ public class BourneShellTest
bf087c
 
bf087c
         String executable = StringUtils.join( sh.getShellCommandLine( new String[]{} ).iterator(), " " );
bf087c
 
bf087c
-        assertEquals( "/bin/sh -c cd /usr/local/bin && chmod", executable );
bf087c
+        assertEquals( "/bin/sh -c cd '/usr/local/bin' && 'chmod'", executable );
bf087c
     }
bf087c
 
bf087c
     public void testQuoteWorkingDirectoryAndExecutable_WDPathWithSingleQuotes()
bf087c
@@ -54,7 +53,7 @@ public class BourneShellTest
bf087c
 
bf087c
         String executable = StringUtils.join( sh.getShellCommandLine( new String[]{} ).iterator(), " " );
bf087c
 
bf087c
-        assertEquals( "/bin/sh -c cd \"/usr/local/\'something else\'\" && chmod", executable );
bf087c
+        assertEquals( "/bin/sh -c cd '/usr/local/'\"'\"'something else'\"'\"'' && 'chmod'", executable );
bf087c
     }
bf087c
 
bf087c
     public void testQuoteWorkingDirectoryAndExecutable_WDPathWithSingleQuotes_BackslashFileSep()
bf087c
@@ -66,7 +65,7 @@ public class BourneShellTest
bf087c
 
bf087c
         String executable = StringUtils.join( sh.getShellCommandLine( new String[]{} ).iterator(), " " );
bf087c
 
bf087c
-        assertEquals( "/bin/sh -c cd \"\\usr\\local\\\'something else\'\" && chmod", executable );
bf087c
+        assertEquals( "/bin/sh -c cd '\\usr\\local\\\'\"'\"'something else'\"'\"'' && 'chmod'", executable );
bf087c
     }
bf087c
 
bf087c
     public void testPreserveSingleQuotesOnArgument()
bf087c
@@ -82,7 +81,7 @@ public class BourneShellTest
bf087c
 
bf087c
         String cli = StringUtils.join( shellCommandLine.iterator(), " " );
bf087c
         System.out.println( cli );
bf087c
-        assertTrue( cli.endsWith( args[0] ) );
bf087c
+        assertTrue( cli.endsWith("''\"'\"'some arg with spaces'\"'\"''"));
bf087c
     }
bf087c
 
bf087c
     public void testAddSingleQuotesOnArgumentWithSpaces()
bf087c
@@ -114,7 +113,7 @@ public class BourneShellTest
bf087c
 
bf087c
         String cli = StringUtils.join( shellCommandLine.iterator(), " " );
bf087c
         System.out.println( cli );
bf087c
-        assertEquals("cd /usr/bin && chmod 'arg'\\''withquote'", shellCommandLine.get(shellCommandLine.size() - 1));
bf087c
+        assertEquals("cd '/usr/bin' && 'chmod' 'arg'\"'\"'withquote'", shellCommandLine.get(shellCommandLine.size() - 1));
bf087c
     }
bf087c
 
bf087c
     public void testArgumentsWithsemicolon()
bf087c
@@ -146,7 +145,7 @@ public class BourneShellTest
bf087c
 
bf087c
         assertEquals( "/bin/sh", lines[0] );
bf087c
         assertEquals( "-c", lines[1] );
bf087c
-        assertEquals( "chmod --password ';password'", lines[2] );
bf087c
+        assertEquals( "'chmod' '--password' ';password'", lines[2] );
bf087c
 
bf087c
         commandline = new Commandline( newShell() );
bf087c
         commandline.setExecutable( "chmod" );
bf087c
@@ -158,7 +157,7 @@ public class BourneShellTest
bf087c
 
bf087c
         assertEquals( "/bin/sh", lines[0] );
bf087c
         assertEquals( "-c", lines[1] );
bf087c
-        assertEquals( "chmod --password ';password'", lines[2] );
bf087c
+        assertEquals( "'chmod' '--password' ';password'", lines[2] );
bf087c
 
bf087c
         commandline = new Commandline( new CmdShell() );
bf087c
         commandline.getShell().setQuotedArgumentsEnabled( true );
bf087c
@@ -206,7 +205,7 @@ public class BourneShellTest
bf087c
 
bf087c
         assertEquals( "/bin/sh", lines[0] );
bf087c
         assertEquals( "-c", lines[1] );
bf087c
-        assertEquals( "chmod ' ' '|' '&&' '||' ';' ';;' '&' '()' '<' '<<' '>' '>>' '*' '?' '[' ']' '{' '}' '`'",
bf087c
+        assertEquals( "'chmod' ' ' '|' '&&' '||' ';' ';;' '&' '()' '<' '<<' '>' '>>' '*' '?' '[' ']' '{' '}' '`'",
bf087c
                       lines[2] );
bf087c
 
bf087c
     }
bf087c
-- 
bf087c
1.8.4.2
bf087c