You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2022/02/08 16:37:40 UTC

[mina-sshd] 05/05: Added support for quoted arguments in SftpCommandMain

This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit 82c95e18d3f9a0b960cd0a65b9cd2c37dc0d9d2e
Author: Lyor Goldstein <lg...@apache.org>
AuthorDate: Sat Feb 5 07:46:47 2022 +0200

    Added support for quoted arguments in SftpCommandMain
---
 .../main/java/org/apache/sshd/cli/CliSupport.java  | 72 +++++++++++++++++
 .../apache/sshd/cli/client/SftpCommandMain.java    | 43 +++-------
 .../CliSupportSplitCommandLineArgumentsTest.java   | 94 ++++++++++++++++++++++
 3 files changed, 179 insertions(+), 30 deletions(-)

diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java b/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
index f7306e7..e7cda7d 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/CliSupport.java
@@ -20,10 +20,14 @@ package org.apache.sshd.cli;
 
 import java.io.IOException;
 import java.io.PrintStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 import java.net.SocketAddress;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.logging.Level;
@@ -341,4 +345,72 @@ public abstract class CliSupport {
 
         return new ArrayList<>(available);
     }
+
+    public static String[] splitCommandLineArguments(String line) {
+        line = GenericUtils.trimToEmpty(line);
+        if (GenericUtils.isBlank(line)) {
+            return GenericUtils.EMPTY_STRING_ARRAY;
+        }
+
+        Collection<String> args = Collections.emptyList();
+        for (int index = 0; index < GenericUtils.QUOTES.length(); index++) {
+            char delim = GenericUtils.QUOTES.charAt(index);
+            int startPos = line.indexOf(delim);
+            int endPos = -1;
+            if (startPos >= 0) {
+                endPos = line.indexOf(delim, startPos + 1);
+            }
+
+            if ((startPos >= 0) && (endPos > startPos)) {
+                if (GenericUtils.isEmpty(args)) {
+                    args = new LinkedList<>();
+                }
+
+                String prefix = (startPos > 0) ? line.substring(0, startPos).trim() : "";
+                String[] extra = GenericUtils.split(prefix, ' ');
+                if (!GenericUtils.isEmpty(extra)) {
+                    args.addAll(Arrays.asList(extra));
+                }
+
+                String value = line.substring(startPos + 1, endPos);
+                args.add(value);
+
+                line = (endPos < (line.length() - 1)) ? line.substring(endPos + 1).trim() : "";
+                if (GenericUtils.isBlank(line)) {
+                    break;
+                }
+
+                index = -1; // start delimiters again
+            }
+        }
+
+        // see if any leftovers
+        String[] extra = GenericUtils.split(line, ' ');
+        if (GenericUtils.isEmpty(args)) {
+            return extra;
+        }
+
+        if (!GenericUtils.isEmpty(extra)) {
+            if (GenericUtils.isEmpty(args)) {
+                args = new LinkedList<>();
+            }
+            args.addAll(Arrays.asList(extra));
+        }
+
+        return args.toArray(GenericUtils.EMPTY_STRING_ARRAY);
+    }
+
+    public static void printFieldsValues(Object info, PrintStream stdout) throws Exception {
+        Field[] fields = info.getClass().getFields();
+        for (Field f : fields) {
+            String name = f.getName();
+            int mod = f.getModifiers();
+            if (Modifier.isStatic(mod)) {
+                continue;
+            }
+
+            Object value = f.get(info);
+            stdout.append("    ").append(name).append(": ").println(value);
+        }
+    }
 }
diff --git a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
index 2e98012..e9e6a9c 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/client/SftpCommandMain.java
@@ -26,8 +26,6 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.PrintStream;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
 import java.nio.channels.Channel;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.DirectoryStream;
@@ -44,6 +42,7 @@ import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 
 import org.apache.sshd.cli.CliLogger;
+import org.apache.sshd.cli.CliSupport;
 import org.apache.sshd.cli.client.helper.SftpFileTransferProgressOutputStream;
 import org.apache.sshd.client.ClientFactoryManager;
 import org.apache.sshd.client.session.ClientSession;
@@ -719,7 +718,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
         public boolean executeCommand(
                 String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
                 throws Exception {
-            String[] comps = GenericUtils.split(args, ' ');
+            String[] comps = CliSupport.splitCommandLineArguments(args);
             int numComps = GenericUtils.length(comps);
             String pathArg = (numComps <= 0) ? null : GenericUtils.trimToEmpty(comps[numComps - 1]);
             String flags = (numComps >= 2) ? GenericUtils.trimToEmpty(comps[0]) : null;
@@ -763,7 +762,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
         public boolean executeCommand(
                 String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
                 throws Exception {
-            String[] comps = GenericUtils.split(args, ' ');
+            String[] comps = CliSupport.splitCommandLineArguments(args);
             int numComps = GenericUtils.length(comps);
             String pathArg = (numComps <= 0) ? null : GenericUtils.trimToEmpty(comps[numComps - 1]);
             String flags = (numComps >= 2) ? GenericUtils.trimToEmpty(comps[0]) : null;
@@ -818,7 +817,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
         public boolean executeCommand(
                 String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
                 throws Exception {
-            String[] comps = GenericUtils.split(args, ' ');
+            String[] comps = CliSupport.splitCommandLineArguments(args);
             int numArgs = GenericUtils.length(comps);
             ValidateUtils.checkTrue(numArgs >= 1, "No arguments");
             ValidateUtils.checkTrue(numArgs <= 2, "Too many arguments: %s", args);
@@ -932,7 +931,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
         public boolean executeCommand(
                 String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
                 throws Exception {
-            String[] comps = GenericUtils.split(args, ' ');
+            String[] comps = CliSupport.splitCommandLineArguments(args);
             ValidateUtils.checkTrue(GenericUtils.length(comps) == 2, "Invalid number of arguments: %s", args);
 
             String oldPath = resolveRemotePath(GenericUtils.trimToEmpty(comps[0]));
@@ -959,7 +958,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
         public boolean executeCommand(
                 String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
                 throws Exception {
-            String[] comps = GenericUtils.split(args, ' ');
+            String[] comps = CliSupport.splitCommandLineArguments(args);
             int numArgs = GenericUtils.length(comps);
             ValidateUtils.checkTrue(numArgs <= 1, "Invalid number of arguments: %s", args);
 
@@ -967,7 +966,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
             OpenSSHLimitsExtension ext = sftp.getExtension(OpenSSHLimitsExtension.class);
             ValidateUtils.checkTrue(ext.isSupported(), "Extension not supported by server: %s", ext.getName());
             OpenSSHLimitsExtensionInfo info = ext.limits();
-            printFieldsValues(info, stdout);
+            CliSupport.printFieldsValues(info, stdout);
             return false;
         }
     }
@@ -988,7 +987,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
         public boolean executeCommand(
                 String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
                 throws Exception {
-            String[] comps = GenericUtils.split(args, ' ');
+            String[] comps = CliSupport.splitCommandLineArguments(args);
             int numArgs = GenericUtils.length(comps);
             ValidateUtils.checkTrue(numArgs <= 1, "Invalid number of arguments: %s", args);
 
@@ -999,7 +998,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
             String remPath = resolveRemotePath(
                     (numArgs >= 1) ? GenericUtils.trimToEmpty(comps[0]) : GenericUtils.trimToEmpty(args));
             OpenSSHStatExtensionInfo info = ext.stat(remPath);
-            printFieldsValues(info, stdout);
+            CliSupport.printFieldsValues(info, stdout);
             return false;
         }
     }
@@ -1020,7 +1019,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
         public boolean executeCommand(
                 String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
                 throws Exception {
-            String[] comps = GenericUtils.split(args, ' ');
+            String[] comps = CliSupport.splitCommandLineArguments(args);
             ValidateUtils.checkTrue(GenericUtils.length(comps) <= 1, "Invalid number of arguments: %s", args);
 
             String path = GenericUtils.trimToEmpty(resolveRemotePath(args));
@@ -1046,7 +1045,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
         @Override
         public boolean executeCommand(String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
                 throws Exception {
-            String[] comps = GenericUtils.split(args, ' ');
+            String[] comps = CliSupport.splitCommandLineArguments(args);
             ValidateUtils.checkTrue(GenericUtils.length(comps) <= 1, "Invalid number of arguments: %s", args);
 
             String path = GenericUtils.trimToEmpty(resolveRemotePath(args));
@@ -1202,7 +1201,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
         }
 
         protected void executeCommand(String args, boolean upload, PrintStream stdout) throws IOException {
-            String[] comps = GenericUtils.split(args, ' ');
+            String[] comps = CliSupport.splitCommandLineArguments(args);
             int numArgs = GenericUtils.length(comps);
             ValidateUtils.checkTrue((numArgs >= 1) && (numArgs <= 3), "Invalid number of arguments: %s", args);
 
@@ -1325,7 +1324,7 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
         public boolean executeCommand(
                 String args, BufferedReader stdin, PrintStream stdout, PrintStream stderr)
                 throws Exception {
-            String[] comps = GenericUtils.split(args, ' ');
+            String[] comps = CliSupport.splitCommandLineArguments(args);
             int numArgs = GenericUtils.length(comps);
             if (numArgs <= 0) {
                 stdout.append("    ").append(getName()).append(' ').println(isShowProgress() ? "on" : "off");
@@ -1345,20 +1344,4 @@ public class SftpCommandMain extends SshClientCliSupport implements SftpClientHo
             return false;
         }
     }
-
-    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-    public static void printFieldsValues(Object info, PrintStream stdout) throws Exception {
-        Field[] fields = info.getClass().getFields();
-        for (Field f : fields) {
-            String name = f.getName();
-            int mod = f.getModifiers();
-            if (Modifier.isStatic(mod)) {
-                continue;
-            }
-
-            Object value = f.get(info);
-            stdout.append("    ").append(name).append(": ").println(value);
-        }
-    }
 }
diff --git a/sshd-cli/src/test/java/org/apache/sshd/cli/CliSupportSplitCommandLineArgumentsTest.java b/sshd-cli/src/test/java/org/apache/sshd/cli/CliSupportSplitCommandLineArgumentsTest.java
new file mode 100644
index 0000000..dcdd29b
--- /dev/null
+++ b/sshd-cli/src/test/java/org/apache/sshd/cli/CliSupportSplitCommandLineArgumentsTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.sshd.cli;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory;
+import org.apache.sshd.util.test.NoIoTestCase;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+/**
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Category({ NoIoTestCase.class })
+@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+public class CliSupportSplitCommandLineArgumentsTest extends BaseTestSupport {
+    private final String line;
+    private final String[] expected;
+
+    public CliSupportSplitCommandLineArgumentsTest(String line, String[] expected) {
+        this.line = line;
+        this.expected = expected;
+    }
+
+    @Parameters(name = "{0}")
+    public static List<Object[]> parameters() {
+        return new ArrayList<Object[]>() {
+            // not serializing it
+            private static final long serialVersionUID = 1L;
+
+            {
+                addTestCase(null, GenericUtils.EMPTY_STRING_ARRAY);
+                addTestCase("", GenericUtils.EMPTY_STRING_ARRAY);
+                addTestCase("   ", GenericUtils.EMPTY_STRING_ARRAY);
+                addPaddedTestCase("hello", "hello");
+                addPaddedTestCase("hello world", "hello", "world");
+
+                for (int index = 0; index < GenericUtils.QUOTES.length(); index++) {
+                    char delim = GenericUtils.QUOTES.charAt(index);
+                    addPaddedTestCase(delim + "hello world" + delim, "hello world");
+                    addPaddedTestCase(delim + "hello" + delim + " world", "hello", "world");
+                    addPaddedTestCase("hello " + delim + "world" + delim, "hello", "world");
+                    addPaddedTestCase(delim + "hello" + delim + " " + delim + "world" + delim, "hello", "world");
+                }
+            }
+
+            private void addPaddedTestCase(String line, String... expected) {
+                addTestCase(line, expected);
+                addTestCase("    " + line, expected);
+                addTestCase(line + "    ", expected);
+                addTestCase("    " + line + "    ", expected);
+            }
+
+            private void addTestCase(String line, String... expected) {
+                add(new Object[] { line, expected });
+            }
+        };
+    }
+
+    @Test
+    public void testSplitCommandLineArguments() {
+        String[] actual = CliSupport.splitCommandLineArguments(line);
+        assertArrayEquals(expected, actual);
+    }
+}