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
--- 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;
+ }
}