8049300: jjs scripting: need way to quote $EXEC command arguments to protect spaces
authormhaupt
Fri, 15 May 2015 16:36:25 +0200
changeset 30700 b60ef3a8b185
parent 30699 a2981bee13d2
child 30701 1a25e71187ff
8049300: jjs scripting: need way to quote $EXEC command arguments to protect spaces Summary: honor quoting with "" and '' as well as escaped spaces Reviewed-by: hannesw, sundar
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptingFunctions.java
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptingFunctions.java	Fri May 15 15:40:57 2015 +0200
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptingFunctions.java	Fri May 15 16:36:25 2015 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -34,10 +34,13 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
-import java.util.StringTokenizer;
 
 /**
  * Global functions supported only in scripting mode.
@@ -133,15 +136,8 @@
         // Current global is need to fetch additional inputs and for additional results.
         final ScriptObject global = Context.getGlobal();
 
-        // Break exec string into tokens.
-        final StringTokenizer tokenizer = new StringTokenizer(JSType.toString(string));
-        final String[] cmdArray = new String[tokenizer.countTokens()];
-        for (int i = 0; tokenizer.hasMoreTokens(); i++) {
-            cmdArray[i] = tokenizer.nextToken();
-        }
-
         // Set up initial process.
-        final ProcessBuilder processBuilder = new ProcessBuilder(cmdArray);
+        final ProcessBuilder processBuilder = new ProcessBuilder(tokenizeCommandLine(JSType.toString(string)));
 
         // Current ENV property state.
         final Object env = global.get(ENV_NAME);
@@ -239,4 +235,43 @@
     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
         return MH.findStatic(MethodHandles.lookup(), ScriptingFunctions.class, name, MH.type(rtype, types));
     }
+
+    /**
+     * Break an exec string into tokens, honoring quoted arguments and escaped
+     * spaces.
+     *
+     * @param execString a {@link String} with the command line to execute.
+     * @return a {@link List} of {@link String}s representing the tokens that
+     * constitute the command line.
+     * @throws IOException in case {@link StreamTokenizer#nextToken()} raises it.
+     */
+    private static List<String> tokenizeCommandLine(final String execString) throws IOException {
+        final StreamTokenizer tokenizer = new StreamTokenizer(new StringReader(execString));
+        tokenizer.resetSyntax();
+        tokenizer.wordChars(0, 255);
+        tokenizer.whitespaceChars(0, ' ');
+        tokenizer.commentChar('#');
+        tokenizer.quoteChar('"');
+        tokenizer.quoteChar('\'');
+        final List<String> cmdList = new ArrayList<>();
+        final StringBuilder toAppend = new StringBuilder();
+        while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) {
+            final String s = tokenizer.sval;
+            // The tokenizer understands about honoring quoted strings and recognizes
+            // them as one token that possibly contains multiple space-separated words.
+            // It does not recognize quoted spaces, though, and will split after the
+            // escaping \ character. This is handled here.
+            if (s.endsWith("\\")) {
+                // omit trailing \, append space instead
+                toAppend.append(s.substring(0, s.length() - 1)).append(' ');
+            } else {
+                cmdList.add(toAppend.append(s).toString());
+                toAppend.setLength(0);
+            }
+        }
+        if (toAppend.length() != 0) {
+            cmdList.add(toAppend.toString());
+        }
+        return cmdList;
+    }
 }