Repository: spring-cloud/spring-cloud-dataflow
Issue: Stream parser permits unbalanced quotes

You can create tasks such as: 

```
`timestamp --format="asddd`
```

Changed files:
spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/dsl/TaskParserTests.java
spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/dsl/StreamParserTests.java
spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/dsl/Tokenizer.java

diff --git a/spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/dsl/Tokenizer.java b/spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/dsl/Tokenizer.java
index 524a33da2..b3c9b1581 100644
--- a/spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/dsl/Tokenizer.java
+++ b/spring-cloud-dataflow-core/src/main/java/org/springframework/cloud/dataflow/core/dsl/Tokenizer.java
@@ -25,6 +25,7 @@ import org.springframework.util.Assert;
  * Lex some input data into a stream of tokens that can then then be parsed.
  *
  * @author Andy Clement
+ * @author Eric Bottard
  */
 class Tokenizer {
 
@@ -272,7 +273,13 @@ class Tokenizer {
 		}
 		while (!isArgValueIdentifierTerminator(toProcess[pos], quoteOpen));
 		char[] subarray = null;
-		if (quoteClosedCount < 2 && sameQuotes(start, pos - 1)) {
+		if (quoteInUse != null && quoteInUse == '"' && quoteClosedCount == 0 ) {
+			throw new ParseException(expressionString, start,
+				DSLMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING);
+		} else if (quoteInUse != null && quoteInUse == '\\'' && quoteClosedCount == 0) {
+			throw new ParseException(expressionString, start,
+				DSLMessage.NON_TERMINATING_QUOTED_STRING);
+		} else if (quoteClosedCount == 1 && sameQuotes(start, pos - 1)) {
 			tokens.add(new Token(TokenKind.LITERAL_STRING,
 					subArray(start, pos), start, pos));
 		}
diff --git a/spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/dsl/StreamParserTests.java b/spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/dsl/StreamParserTests.java
index d56e43f63..e85ba0279 100644
--- a/spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/dsl/StreamParserTests.java
+++ b/spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/dsl/StreamParserTests.java
@@ -25,9 +25,7 @@ import static org.junit.Assert.fail;
 import java.util.List;
 import java.util.Properties;
 
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 
 /**
  * Parse streams and verify either the correct abstract syntax tree is produced or the current exception comes out.
@@ -36,12 +34,10 @@ import org.junit.rules.ExpectedException;
  * @author David Turanski
  * @author Ilayaperumal Gopinathan
  * @author Mark Fisher
+ * @author Eric Bottard
  */
 public class StreamParserTests {
 
-	@Rule
-	public ExpectedException thrown = ExpectedException.none();
-
 	private StreamNode sn;
 
 
@@ -243,6 +239,16 @@ public class StreamParserTests {
 		assertEquals("new StringBuilder(payload).reverse()", props.get("expression"));
 	}
 
+	@Test
+	public void testUnbalancedSingleQuotes() {
+		checkForParseError("foo | bar --expression='select foo", DSLMessage.NON_TERMINATING_QUOTED_STRING, 23);
+	}
+
+	@Test
+	public void testUnbalancedDoubleQuotes() {
+		checkForParseError("foo | bar --expression=\\"select foo", DSLMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING, 23);
+	}
+
 	@Test
 	public void appArguments_xd1613() {
 		StreamNode ast = null;
@@ -534,20 +540,4 @@ public class StreamParserTests {
 			}
 		}
 	}
-
-	private void checkForParseError(String name, String stream, DSLMessage msg, int pos, String... inserts) {
-		try {
-			StreamNode sn = parse(name, stream);
-			fail("expected to fail but parsed " + sn.stringify());
-		}
-		catch (ParseException e) {
-			assertEquals(msg, e.getMessageCode());
-			assertEquals(pos, e.getPosition());
-			if (inserts != null) {
-				for (int i = 0; i < inserts.length; i++) {
-					assertEquals(inserts[i], e.getInserts()[i]);
-				}
-			}
-		}
-	}
 }
diff --git a/spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/dsl/TaskParserTests.java b/spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/dsl/TaskParserTests.java
index bf21be4de..77492e872 100644
--- a/spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/dsl/TaskParserTests.java
+++ b/spring-cloud-dataflow-core/src/test/java/org/springframework/cloud/dataflow/core/dsl/TaskParserTests.java
@@ -32,6 +32,7 @@ import org.junit.rules.ExpectedException;
  * @author Andy Clement
  * @author David Turanski
  * @author Michael Minella
+ * @author Eric Bottard
  */
 public class TaskParserTests {
 
@@ -246,6 +247,18 @@ public class TaskParserTests {
 		assertEquals(appNode.getArgumentsAsProperties().get("expression"), "payload.replace(\\"abc\\", '')");
 	}
 
+	@Test
+	public void testUnbalancedSingleQuotes() {
+		checkForParseError("timestamp --format='YYYY", DSLMessage.NON_TERMINATING_QUOTED_STRING, 19);
+	}
+
+	@Test
+	public void testUnbalancedDoubleQuotes() {
+		checkForParseError("timestamp --format=\\"YYYY", DSLMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING, 19);
+	}
+
+
+
 	AppNode parse(String taskDefinition) {
 		return new TaskParser(taskDefinition).parse();
 	}
@@ -281,4 +294,4 @@ public class TaskParserTests {
 			}
 		}
 	}
-}
\\ No newline at end of file
+}