From 1404def3f16710d36a874d819613f7f0c4e12ee7 Mon Sep 17 00:00:00 2001
From: Fabio Pistolesi <fabio.pistolesi@forgerock.com>
Date: Thu, 07 Jul 2016 08:33:54 +0000
Subject: [PATCH] OPENDJ-2748 OPENDJ-1667 dsconfig --displayCommand should print a usable command and parse it correctly afterwards

---
 opendj-config/src/test/java/org/forgerock/opendj/config/dsconfig/DSConfigParseTest.java |   48 ++++++++++++++++
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java          |   79 ++++++++++++++-----------
 opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java               |   12 ++++
 3 files changed, 104 insertions(+), 35 deletions(-)

diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
index 2e5881f..388db59 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ConsoleApplication.java
@@ -281,6 +281,18 @@
     }
 
     /**
+     * Displays a message to the output stream without wrapping.
+     *
+     * @param msg
+     *            The message.
+     */
+    public final void printlnNoWrap(final LocalizableMessage msg) {
+        if (!isQuiet()) {
+            out.println(msg);
+        }
+    }
+
+    /**
      * Prints a progress bar on the same output stream line if not in quiet mode.
      *
      * <pre>
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java
index 98b92a5..34128a8 100644
--- a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java
@@ -1341,7 +1341,7 @@
         if (displayEquivalentArgument.isPresent()) {
             println();
             // We assume that the app we are running is this one.
-            println(INFO_DSCFG_NON_INTERACTIVE.get(commandBuilder));
+            printlnNoWrap(INFO_DSCFG_NON_INTERACTIVE.get(commandBuilder));
         }
         if (equivalentCommandFileArgument.isPresent()) {
             String file = equivalentCommandFileArgument.getValue();
@@ -1422,13 +1422,7 @@
 
                 command += line;
                 command = command.trim();
-                // string between quotes support
-                command = replaceSpacesInQuotes(command);
-                // "\ " support
-                command = command.replace("\\ ", "##");
-
-                String displayCommand = command.replace("\\ ", " ");
-                println(LocalizableMessage.raw(displayCommand));
+                printlnNoWrap(LocalizableMessage.raw(command));
 
                 // Append initial arguments to the file line
                 final String[] allArgsArray = buildCommandArgs(initialArgs, command);
@@ -1448,20 +1442,54 @@
     }
 
     private String[] buildCommandArgs(List<String> initialArgs, String batchCommand) {
-        final String[] commandArgs = toCommandArgs(batchCommand);
-        final int length = commandArgs.length + initialArgs.size();
+        final Collection<String> commandArgs = toCommandArgs(batchCommand);
+        final int length = commandArgs.size() + initialArgs.size();
         final List<String> allArguments = new ArrayList<>(length);
-        Collections.addAll(allArguments, commandArgs);
+        allArguments.addAll(commandArgs);
         allArguments.addAll(initialArgs);
         return allArguments.toArray(new String[length]);
     }
 
-    private String[] toCommandArgs(String command) {
-        String[] fileArguments = command.split("\\s+");
-        for (int ii = 0; ii < fileArguments.length; ii++) {
-            fileArguments[ii] = fileArguments[ii].replace("##", " ");
+    static Collection<String> toCommandArgs(String command) {
+        Collection<String> commandArgs = new ArrayList<>();
+        StringBuilder builder = new StringBuilder();
+        boolean inQuotes = false;
+        for (int i = 0; i < command.length(); i++) {
+            final char c = command.charAt(i);
+            switch (c) {
+            default:
+                builder.append(c);
+                break;
+            case '\\':
+                builder.append(command.charAt(++i));
+                break;
+            case '"':
+                if (inQuotes) {
+                    builder = newArgumentString(commandArgs, builder);
+                    inQuotes = false;
+                } else {
+                    inQuotes = true;
+                }
+                break;
+            case ' ':
+                if (inQuotes) {
+                    builder.append(c);
+                } else {
+                    builder = newArgumentString(commandArgs, builder);
+                }
+                break;
+            }
         }
-        return fileArguments;
+        newArgumentString(commandArgs, builder);
+        return commandArgs;
+    }
+
+    private static StringBuilder newArgumentString(Collection<String> commandArgs, StringBuilder stringBuilder) {
+        if (stringBuilder.length() > 0) {
+            commandArgs.add(stringBuilder.toString());
+            stringBuilder = new StringBuilder();
+        }
+        return stringBuilder;
     }
 
     private List<String> removeBatchArgs(String[] args) {
@@ -1487,23 +1515,4 @@
         }
         return initialArgs;
     }
-
-    /** Replace spaces in quotes by "\ ". */
-    private String replaceSpacesInQuotes(final String line) {
-        StringBuilder newLine = new StringBuilder();
-        boolean inQuotes = false;
-        for (int ii = 0; ii < line.length(); ii++) {
-            char ch = line.charAt(ii);
-            if (ch == '\"' || ch == '\'') {
-                inQuotes = !inQuotes;
-                continue;
-            }
-            if (inQuotes && ch == ' ') {
-                newLine.append("\\ ");
-            } else {
-                newLine.append(ch);
-            }
-        }
-        return newLine.toString();
-    }
 }
diff --git a/opendj-config/src/test/java/org/forgerock/opendj/config/dsconfig/DSConfigParseTest.java b/opendj-config/src/test/java/org/forgerock/opendj/config/dsconfig/DSConfigParseTest.java
new file mode 100644
index 0000000..6cd877f
--- /dev/null
+++ b/opendj-config/src/test/java/org/forgerock/opendj/config/dsconfig/DSConfigParseTest.java
@@ -0,0 +1,48 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyright [year] [name of copyright owner]".
+ *
+ * Portions copyright 2016 ForgeRock AS.
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import org.forgerock.testng.ForgeRockTestCase;
+import org.testng.Assert;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.util.Collection;
+
+@Test(groups = { "precommit", "config" })
+public class DSConfigParseTest extends ForgeRockTestCase {
+    @DataProvider
+    public Object[][] escapeSequences() {
+        return new Object[][] {
+            {"global-aci:\\(targetattr=\\\"userPassword\\|\\|authPassword\\\"\\)"
+                    + "\\(version\\ 3.0\\;\\ acl\\ \\\"Self\\ entry\\ read\\'\\\"\\;"
+                    + "\\ allow\\ \\(read,search,compare\\)\\ userdn=\\\"ldap:///self\\\"\\;\\)",
+                "global-aci:(targetattr=\"userPassword||authPassword\")"
+                    + "(version 3.0; acl \"Self entry read'\";"
+                    + " allow (read,search,compare) userdn=\"ldap:///self\";)"
+            },
+            {"cn=\"admin data\"", "cn=admin data"},
+            {"\"cn=admin data\"", "cn=admin data"},
+            {"cn=\\\"admin", "cn=\"admin"}
+        };
+    }
+
+    @Test(dataProvider = "escapeSequences")
+    public void testEscapeSequenceInCommandArgument(String arg, String value) throws Exception {
+        Collection<String> cmdLine = DSConfig.toCommandArgs(arg);
+        Assert.assertEquals(cmdLine.iterator().next(), value);
+    }
+}

--
Gitblit v1.10.0