9805a3
From ad1c8a07230c9e0ba26da54fa383ecefde949cb5 Mon Sep 17 00:00:00 2001
9805a3
From: Marian Koncek <mkoncek@redhat.com>
9805a3
Date: Fri, 8 Apr 2022 11:55:26 +0200
9805a3
Subject: [PATCH] Unconditionally single quote executable and arguments
9805a3
9805a3
Upstream: https://github.com/apache/maven-shared-utils/pull/40/commits
9805a3
9805a3
---
9805a3
 .../shared/utils/cli/shell/BourneShell.java   | 45 ++++++++-----------
9805a3
 .../maven/shared/utils/cli/shell/Shell.java   | 43 ++++++++++++------
9805a3
 .../utils/cli/shell/BourneShellTest.java      | 35 ++++++++++-----
9805a3
 3 files changed, 73 insertions(+), 50 deletions(-)
9805a3
9805a3
diff --git a/src/main/java/org/apache/maven/shared/utils/cli/shell/BourneShell.java b/src/main/java/org/apache/maven/shared/utils/cli/shell/BourneShell.java
9805a3
index 845e4a4..fc4984a 100644
9805a3
--- a/src/main/java/org/apache/maven/shared/utils/cli/shell/BourneShell.java
9805a3
+++ b/src/main/java/org/apache/maven/shared/utils/cli/shell/BourneShell.java
9805a3
@@ -23,7 +23,6 @@ package org.apache.maven.shared.utils.cli.shell;
9805a3
 import java.util.ArrayList;
9805a3
 import java.util.List;
9805a3
 import org.apache.maven.shared.utils.Os;
9805a3
-import org.apache.maven.shared.utils.StringUtils;
9805a3
 
9805a3
 /**
9805a3
  * @author Jason van Zyl
9805a3
@@ -31,14 +30,12 @@ import org.apache.maven.shared.utils.StringUtils;
9805a3
 public class BourneShell
9805a3
     extends Shell
9805a3
 {
9805a3
-    private static final char[] BASH_QUOTING_TRIGGER_CHARS =
9805a3
-        { ' ', '$', ';', '&', '|', '<', '>', '*', '?', '(', ')', '[', ']', '{', '}', '`' };
9805a3
-
9805a3
     public BourneShell()
9805a3
     {
9805a3
+        setUnconditionalQuoting( true );
9805a3
         setShellCommand( "/bin/sh" );
9805a3
         setArgumentQuoteDelimiter( '\'' );
9805a3
-        setExecutableQuoteDelimiter( '\"' );
9805a3
+        setExecutableQuoteDelimiter( '\'' );
9805a3
         setSingleQuotedArgumentEscaped( true );
9805a3
         setSingleQuotedExecutableEscaped( false );
9805a3
         setQuotedExecutableEnabled( true );
9805a3
@@ -54,7 +51,7 @@ public class BourneShell
9805a3
             return super.getExecutable();
9805a3
         }
9805a3
 
9805a3
-        return unifyQuotes( super.getExecutable() );
9805a3
+        return quoteOneItem( super.getExecutable(), true );
9805a3
     }
9805a3
 
9805a3
     public List<String> getShellArgsList()
9805a3
@@ -104,46 +101,40 @@ public class BourneShell
9805a3
         StringBuilder sb = new StringBuilder();
9805a3
         sb.append( "cd " );
9805a3
 
9805a3
-        sb.append( unifyQuotes( dir ) );
9805a3
+        sb.append( quoteOneItem( dir, false ) );
9805a3
         sb.append( " && " );
9805a3
 
9805a3
         return sb.toString();
9805a3
     }
9805a3
 
9805a3
-    protected char[] getQuotingTriggerChars()
9805a3
-    {
9805a3
-        return BASH_QUOTING_TRIGGER_CHARS;
9805a3
-    }
9805a3
-
9805a3
     /**
9805a3
      * 

Unify quotes in a path for the Bourne Shell.

9805a3
      * 

9805a3
      * 
9805a3
-     * BourneShell.unifyQuotes(null)                       = null
9805a3
-     * BourneShell.unifyQuotes("")                         = (empty)
9805a3
-     * BourneShell.unifyQuotes("/test/quotedpath'abc")     = /test/quotedpath\'abc
9805a3
-     * BourneShell.unifyQuotes("/test/quoted path'abc")    = "/test/quoted path'abc"
9805a3
-     * BourneShell.unifyQuotes("/test/quotedpath\"abc")    = "/test/quotedpath\"abc"
9805a3
-     * BourneShell.unifyQuotes("/test/quoted path\"abc")   = "/test/quoted path\"abc"
9805a3
-     * BourneShell.unifyQuotes("/test/quotedpath\"'abc")   = "/test/quotedpath\"'abc"
9805a3
-     * BourneShell.unifyQuotes("/test/quoted path\"'abc")  = "/test/quoted path\"'abc"
9805a3
+     * BourneShell.quoteOneItem(null)                       = null
9805a3
+     * BourneShell.quoteOneItem("")                         = ''
9805a3
+     * BourneShell.quoteOneItem("/test/quotedpath'abc")     = '/test/quotedpath'"'"'abc'
9805a3
+     * BourneShell.quoteOneItem("/test/quoted path'abc")    = '/test/quoted pat'"'"'habc'
9805a3
+     * BourneShell.quoteOneItem("/test/quotedpath\"abc")    = '/test/quotedpath"abc'
9805a3
+     * BourneShell.quoteOneItem("/test/quoted path\"abc")   = '/test/quoted path"abc'
9805a3
+     * BourneShell.quoteOneItem("/test/quotedpath\"'abc")   = '/test/quotedpath"'"'"'abc'
9805a3
+     * BourneShell.quoteOneItem("/test/quoted path\"'abc")  = '/test/quoted path"'"'"'abc'
9805a3
      * 
9805a3
      *
9805a3
      * @param path not null path.
9805a3
      * @return the path unified correctly for the Bourne shell.
9805a3
      */
9805a3
-    private static String unifyQuotes( String path )
9805a3
+    protected String quoteOneItem( String path, boolean isExecutable )
9805a3
     {
9805a3
         if ( path == null )
9805a3
         {
9805a3
             return null;
9805a3
         }
9805a3
 
9805a3
-        if ( path.indexOf( ' ' ) == -1 && path.indexOf( '\'' ) != -1 && path.indexOf( '"' ) == -1 )
9805a3
-        {
9805a3
-            return StringUtils.escape( path );
9805a3
-        }
9805a3
-
9805a3
-        return StringUtils.quoteAndEscape( path, '\"', BASH_QUOTING_TRIGGER_CHARS );
9805a3
+        StringBuilder sb = new StringBuilder();
9805a3
+        sb.append( "'" );
9805a3
+        sb.append( path.replace( "'", "'\"'\"'" ) );
9805a3
+        sb.append( "'" );
9805a3
+        return sb.toString();
9805a3
     }
9805a3
 }
9805a3
diff --git a/src/main/java/org/apache/maven/shared/utils/cli/shell/Shell.java b/src/main/java/org/apache/maven/shared/utils/cli/shell/Shell.java
9805a3
index 70dddd0..2b889ff 100644
9805a3
--- a/src/main/java/org/apache/maven/shared/utils/cli/shell/Shell.java
9805a3
+++ b/src/main/java/org/apache/maven/shared/utils/cli/shell/Shell.java
9805a3
@@ -50,6 +50,8 @@ public class Shell
9805a3
 
9805a3
     private boolean quotedArgumentsEnabled = true;
9805a3
 
9805a3
+    private boolean unconditionalQuoting = false;
9805a3
+
9805a3
     private String executable;
9805a3
 
9805a3
     private String workingDir;
9805a3
@@ -113,6 +115,19 @@ public class Shell
9805a3
         }
9805a3
     }
9805a3
 
9805a3
+    protected String quoteOneItem( String inputString, boolean isExecutable )
9805a3
+    {
9805a3
+        char[] escapeChars = getEscapeChars( isSingleQuotedExecutableEscaped(), isDoubleQuotedExecutableEscaped() );
9805a3
+        return StringUtils.quoteAndEscape(
9805a3
+                inputString,
9805a3
+                isExecutable ? getExecutableQuoteDelimiter() : getArgumentQuoteDelimiter(),
9805a3
+                escapeChars,
9805a3
+                getQuotingTriggerChars(),
9805a3
+                '\\',
9805a3
+                unconditionalQuoting
9805a3
+        );
9805a3
+    }
9805a3
+
9805a3
     /**
9805a3
      * Get the command line for the provided executable and arguments in this shell
9805a3
      *
9805a3
@@ -125,12 +140,12 @@ public class Shell
9805a3
         return getRawCommandLine( executable, arguments );
9805a3
     }
9805a3
 
9805a3
-    List<String> getRawCommandLine( String executable, String... arguments )
9805a3
+    List<String> getRawCommandLine( String executableParameter, String... arguments )
9805a3
     {
9805a3
         List<String> commandLine = new ArrayList<String>();
9805a3
         StringBuilder sb = new StringBuilder();
9805a3
 
9805a3
-        if ( executable != null )
9805a3
+        if ( executableParameter != null )
9805a3
         {
9805a3
             String preamble = getExecutionPreamble();
9805a3
             if ( preamble != null )
9805a3
@@ -140,15 +155,11 @@ public class Shell
9805a3
 
9805a3
             if ( isQuotedExecutableEnabled() )
9805a3
             {
9805a3
-                char[] escapeChars =
9805a3
-                    getEscapeChars( isSingleQuotedExecutableEscaped(), isDoubleQuotedExecutableEscaped() );
9805a3
-
9805a3
-                sb.append( StringUtils.quoteAndEscape( getExecutable(), getExecutableQuoteDelimiter(), escapeChars,
9805a3
-                                                       getQuotingTriggerChars(), '\\', false ) );
9805a3
+                sb.append( quoteOneItem( executableParameter, true ) );
9805a3
             }
9805a3
             else
9805a3
             {
9805a3
-                sb.append( getExecutable() );
9805a3
+                sb.append( executableParameter );
9805a3
             }
9805a3
         }
9805a3
         for ( String argument : arguments )
9805a3
@@ -160,10 +171,7 @@ public class Shell
9805a3
 
9805a3
             if ( isQuotedArgumentsEnabled() )
9805a3
             {
9805a3
-                char[] escapeChars = getEscapeChars( isSingleQuotedArgumentEscaped(), isDoubleQuotedArgumentEscaped() );
9805a3
-
9805a3
-                sb.append( StringUtils.quoteAndEscape( argument, getArgumentQuoteDelimiter(), escapeChars,
9805a3
-                                                       getQuotingTriggerChars(), '\\', false ) );
9805a3
+                sb.append( quoteOneItem( argument, false ) );
9805a3
             }
9805a3
             else
9805a3
             {
9805a3
@@ -268,7 +276,7 @@ public class Shell
9805a3
             commandLine.addAll( getShellArgsList() );
9805a3
         }
9805a3
 
9805a3
-        commandLine.addAll( getCommandLine( getExecutable(), arguments ) );
9805a3
+        commandLine.addAll( getCommandLine( executable, arguments ) );
9805a3
 
9805a3
         return commandLine;
9805a3
 
9805a3
@@ -368,4 +376,13 @@ public class Shell
9805a3
         this.singleQuotedExecutableEscaped = singleQuotedExecutableEscaped;
9805a3
     }
9805a3
 
9805a3
+    public boolean isUnconditionalQuoting()
9805a3
+    {
9805a3
+        return unconditionalQuoting;
9805a3
+    }
9805a3
+
9805a3
+    public void setUnconditionalQuoting( boolean unconditionalQuoting )
9805a3
+    {
9805a3
+        this.unconditionalQuoting = unconditionalQuoting;
9805a3
+    }
9805a3
 }
9805a3
diff --git a/src/test/java/org/apache/maven/shared/utils/cli/shell/BourneShellTest.java b/src/test/java/org/apache/maven/shared/utils/cli/shell/BourneShellTest.java
9805a3
index 741f81c..5509cea 100644
9805a3
--- a/src/test/java/org/apache/maven/shared/utils/cli/shell/BourneShellTest.java
9805a3
+++ b/src/test/java/org/apache/maven/shared/utils/cli/shell/BourneShellTest.java
9805a3
@@ -45,7 +45,7 @@ public class BourneShellTest
9805a3
 
9805a3
         String executable = StringUtils.join( sh.getShellCommandLine( new String[]{} ).iterator(), " " );
9805a3
 
9805a3
-        assertEquals( "/bin/sh -c cd /usr/local/bin && chmod", executable );
9805a3
+        assertEquals( "/bin/sh -c cd '/usr/local/bin' && 'chmod'", executable );
9805a3
     }
9805a3
 
9805a3
     public void testQuoteWorkingDirectoryAndExecutable_WDPathWithSingleQuotes()
9805a3
@@ -57,7 +57,7 @@ public class BourneShellTest
9805a3
 
9805a3
         String executable = StringUtils.join( sh.getShellCommandLine( new String[]{} ).iterator(), " " );
9805a3
 
9805a3
-        assertEquals( "/bin/sh -c cd \"/usr/local/\'something else\'\" && chmod", executable );
9805a3
+        assertEquals( "/bin/sh -c cd '/usr/local/'\"'\"'something else'\"'\"'' && 'chmod'", executable );
9805a3
     }
9805a3
 
9805a3
     public void testQuoteWorkingDirectoryAndExecutable_WDPathWithSingleQuotes_BackslashFileSep()
9805a3
@@ -69,7 +69,7 @@ public class BourneShellTest
9805a3
 
9805a3
         String executable = StringUtils.join( sh.getShellCommandLine( new String[]{} ).iterator(), " " );
9805a3
 
9805a3
-        assertEquals( "/bin/sh -c cd \"\\usr\\local\\\'something else\'\" && chmod", executable );
9805a3
+        assertEquals( "/bin/sh -c cd '\\usr\\local\\'\"'\"'something else'\"'\"'' && 'chmod'", executable );
9805a3
     }
9805a3
 
9805a3
     public void testPreserveSingleQuotesOnArgument()
9805a3
@@ -79,13 +79,13 @@ public class BourneShellTest
9805a3
         sh.setWorkingDirectory( "/usr/bin" );
9805a3
         sh.setExecutable( "chmod" );
9805a3
 
9805a3
-        String[] args = { "\'some arg with spaces\'" };
9805a3
+        String[] args = { "\"some arg with spaces\"" };
9805a3
 
9805a3
         List<String> shellCommandLine = sh.getShellCommandLine( args );
9805a3
 
9805a3
         String cli = StringUtils.join( shellCommandLine.iterator(), " " );
9805a3
         System.out.println( cli );
9805a3
-        assertTrue( cli.endsWith( args[0] ) );
9805a3
+        assertTrue( cli.endsWith( "'\"some arg with spaces\"'" ) );
9805a3
     }
9805a3
 
9805a3
     public void testAddSingleQuotesOnArgumentWithSpaces()
9805a3
@@ -101,7 +101,21 @@ public class BourneShellTest
9805a3
 
9805a3
         String cli = StringUtils.join( shellCommandLine.iterator(), " " );
9805a3
         System.out.println( cli );
9805a3
-        assertTrue( cli.endsWith( "\'" + args[0] + "\'" ) );
9805a3
+        assertTrue( cli.endsWith("'some arg with spaces'"));
9805a3
+    }
9805a3
+
9805a3
+    public void testAddArgumentWithSingleQuote()
9805a3
+    {
9805a3
+        Shell sh = newShell();
9805a3
+
9805a3
+        sh.setWorkingDirectory( "/usr/bin" );
9805a3
+        sh.setExecutable( "chmod" );
9805a3
+
9805a3
+        String[] args = { "arg'withquote" };
9805a3
+
9805a3
+        List<String> shellCommandLine = sh.getShellCommandLine( args );
9805a3
+
9805a3
+        assertEquals("cd '/usr/bin' && 'chmod' 'arg'\"'\"'withquote'", shellCommandLine.get(shellCommandLine.size() - 1));
9805a3
     }
9805a3
 
9805a3
     public void testArgumentsWithsemicolon()
9805a3
@@ -120,7 +134,7 @@ public class BourneShellTest
9805a3
 
9805a3
         String cli = StringUtils.join( shellCommandLine.iterator(), " " );
9805a3
         System.out.println( cli );
9805a3
-        assertTrue( cli.endsWith( "\'" + args[0] + "\'" ) );
9805a3
+        assertTrue( cli.endsWith( "';some&argwithunix$chars'" ) );
9805a3
 
9805a3
         Commandline commandline = new Commandline( newShell() );
9805a3
         commandline.setExecutable( "chmod" );
9805a3
@@ -133,7 +147,7 @@ public class BourneShellTest
9805a3
 
9805a3
         assertEquals( "/bin/sh", lines.get( 0 ) );
9805a3
         assertEquals( "-c", lines.get( 1 ) );
9805a3
-        assertEquals( "chmod --password ';password'", lines.get( 2 ) );
9805a3
+        assertEquals( "'chmod' '--password' ';password'", lines.get( 2 ) );
9805a3
 
9805a3
         commandline = new Commandline( newShell() );
9805a3
         commandline.setExecutable( "chmod" );
9805a3
@@ -145,7 +159,7 @@ public class BourneShellTest
9805a3
 
9805a3
         assertEquals( "/bin/sh", lines.get( 0) );
9805a3
         assertEquals( "-c", lines.get( 1 ) );
9805a3
-        assertEquals( "chmod --password ';password'", lines.get( 2 ) );
9805a3
+        assertEquals( "'chmod' '--password' ';password'", lines.get( 2 ) );
9805a3
 
9805a3
         commandline = new Commandline( new CmdShell() );
9805a3
         commandline.getShell().setQuotedArgumentsEnabled( true );
9805a3
@@ -187,13 +201,14 @@ public class BourneShellTest
9805a3
         commandline.createArg().setValue( "{" );
9805a3
         commandline.createArg().setValue( "}" );
9805a3
         commandline.createArg().setValue( "`" );
9805a3
+        commandline.createArg().setValue( "#" );
9805a3
 
9805a3
         List<String> lines = commandline.getShell().getShellCommandLine( commandline.getArguments() );
9805a3
         System.out.println( lines  );
9805a3
 
9805a3
         assertEquals( "/bin/sh", lines.get( 0 ) );
9805a3
         assertEquals( "-c", lines.get( 1 ) );
9805a3
-        assertEquals( "chmod ' ' '|' '&&' '||' ';' ';;' '&' '()' '<' '<<' '>' '>>' '*' '?' '[' ']' '{' '}' '`'",
9805a3
+        assertEquals( "'chmod' ' ' '|' '&&' '||' ';' ';;' '&' '()' '<' '<<' '>' '>>' '*' '?' '[' ']' '{' '}' '`' '#'",
9805a3
                       lines.get( 2 ) );
9805a3
     }
9805a3
 
9805a3
-- 
9805a3
2.35.1
9805a3