From 714b143bdaebab0a7346fd5298b18a4268435105 Mon Sep 17 00:00:00 2001 From: Eric Bottard Date: Fri, 18 Apr 2014 15:12:00 +0200 Subject: [PATCH] SHL-139: Handle escape sequences in option values --- .../springframework/shell/core/Tokenizer.java | 64 ++++++++++++++++++- .../shell/core/TokenizerTests.java | 16 +++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/shell/core/Tokenizer.java b/src/main/java/org/springframework/shell/core/Tokenizer.java index 1b401a2e..41387ffa 100644 --- a/src/main/java/org/springframework/shell/core/Tokenizer.java +++ b/src/main/java/org/springframework/shell/core/Tokenizer.java @@ -152,6 +152,10 @@ public class Tokenizer { lastValueDelimiter = endDelimiter; lastValueStartOffset = pos; while (pos < buffer.length && buffer[pos] != endDelimiter) { + if (buffer[pos] == ESCAPE_CHAR) { + sb.append(processCharacterEscapeCodes(endDelimiter)); + continue; + } if (lookAhead(ESCAPE_CHAR, endDelimiter)) { sb.append(endDelimiter); pos += 2; @@ -193,6 +197,59 @@ public class Tokenizer { return sb.toString(); } + /** + * When the escape character is encountered, consume and return the escaped sequence. Note that depending on which + * end delimiter is currently in use, not all combinations need to be escaped + * @param endDelimiter the current endDelimiter + */ + private char processCharacterEscapeCodes(char endDelimiter) { + pos++; + if (pos >= buffer.length) { + throw new IllegalArgumentException("Ran out of input in escape sequence"); + } + switch (buffer[pos]) { + case ESCAPE_CHAR: + pos++; // consume the second escape char + return ESCAPE_CHAR; + case 't': + pos++; + return '\t'; + case 'r': + pos++; + return '\r'; + case 'n': + pos++; + return '\n'; + case 'f': + pos++; + return '\f'; + case 'u': + if (pos + 5 > buffer.length) { + throw new IllegalArgumentException("Ran out input in unicode escape sequence"); + } + String hex = new String(buffer, pos + 1, 4); + try { + char code = (char) Integer.parseInt(hex, 16); + pos += 5; + return code; + } + catch (NumberFormatException e) { + throw new IllegalArgumentException("Illegal unicode escape sequence: " + ESCAPE_CHAR + "u" + hex); + } + + default: + if (buffer[pos] == endDelimiter) { + pos++; + return endDelimiter; + } + else { + // Not an actual escape. Do not increment pos, + // and return the \ we consumed at the very beginning + return ESCAPE_CHAR; + } + } + } + /** * Return the offset at which the last value seen started (NOT including any delimiter). */ @@ -229,7 +286,12 @@ public class Tokenizer { * Apply delimiter escaping to the given string, using the actual delimiter that was used for the last value. */ public String escape(String value) { - return value.replace("" + lastValueDelimiter, "" + ESCAPE_CHAR + lastValueDelimiter); + String result = value.replace("" + lastValueDelimiter, "" + ESCAPE_CHAR + lastValueDelimiter); + result = result.replace("\r", "\\r"); + result = result.replace("\n", "\\n"); + result = result.replace("\t", "\\t"); + result = result.replace("\f", "\\f"); + return result; } /** diff --git a/src/test/java/org/springframework/shell/core/TokenizerTests.java b/src/test/java/org/springframework/shell/core/TokenizerTests.java index 1a4c227c..c41dbcbd 100644 --- a/src/test/java/org/springframework/shell/core/TokenizerTests.java +++ b/src/test/java/org/springframework/shell/core/TokenizerTests.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -198,6 +199,21 @@ public class TokenizerTests { } + @Test + public void testEscapeSequences() { + Map result = tokenize("--foo \\t\\n\\r\\f"); + assertEquals(Collections.singletonMap("foo", "\t\n\r\f"), result); + + // Unicode and space-escape + result = tokenize("--foo \\u65e5\\ \\u672c"); + assertEquals(Collections.singletonMap("foo", "\u65e5 \u672c"), result); + + // backslash escape + result = tokenize("--foo \\\\u\\g"); + assertEquals(Collections.singletonMap("foo", "\\u\\g"), result); + + } + private Map tokenize(String what) { return new Tokenizer(what).getTokens(); }