From a1e4319d9f7a13a1d162c4e077dbd0f25ae24910 Mon Sep 17 00:00:00 2001 From: pascalschumacher Date: Fri, 4 Nov 2016 19:38:53 +0100 Subject: [PATCH] LANG-1066: Add shell/XSI escape/unescape support patch supplied by Mark --- .../commons/lang3/StringEscapeUtils.java | 132 ++++++++++++++++++ .../commons/lang3/StringEscapeUtilsTest.java | 19 +++ 2 files changed, 151 insertions(+) diff --git a/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java b/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java index a244847f44e..8a8057ecc45 100644 --- a/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringEscapeUtils.java @@ -272,6 +272,40 @@ public int translate(final CharSequence input, final int index, final Writer out } } + /** + * Translator object for escaping Shell command language. + * + * @see Shell Command Language + */ + public static final CharSequenceTranslator ESCAPE_XSI = + new LookupTranslator( + new String[][] { + {"|", "\\|"}, + {"&", "\\&"}, + {";", "\\;"}, + {"<", "\\<"}, + {">", "\\>"}, + {"(", "\\("}, + {")", "\\)"}, + {"$", "\\$"}, + {"`", "\\`"}, + {"\\", "\\\\"}, + {"\"", "\\\""}, + {"'", "\\'"}, + {" ", "\\ "}, + {"\t", "\\\t"}, + {"\r\n", ""}, + {"\n", ""}, + {"*", "\\*"}, + {"?", "\\?"}, + {"[", "\\["}, + {"#", "\\#"}, + {"~", "\\~"}, + {"=", "\\="}, + {"%", "\\%"}, + }); + + /* UNESCAPE TRANSLATORS */ /** @@ -413,6 +447,47 @@ public int translate(final CharSequence input, final int index, final Writer out } } + public static final CharSequenceTranslator UNESCAPE_XSI = new BackslashUnescaper(); + + /** + * Translator object for unescaping backslash escaped entries. + * + * @since 3.6 + */ + static class BackslashUnescaper extends CharSequenceTranslator { + + private static final char BACKSLASH = '\\'; + + @Override + public int translate(final CharSequence input, final int index, final Writer out) throws IOException { + + if(index != 0) { + throw new IllegalStateException("BackslashUnescaper should never reach the [1] index"); + } + + String s = input.toString(); + + int segmentStart = 0; + int searchOffset = 0; + while (true) { + int pos = s.indexOf(BACKSLASH, searchOffset); + if (pos == -1) { + if (segmentStart < s.length()) { + out.write(s.substring(segmentStart)); + } + break; + } + if (pos > segmentStart) { + out.write(s.substring(segmentStart, pos)); + } + segmentStart = pos + 1; + searchOffset = pos + 2; + } + + return Character.codePointCount(input, 0, input.length()); + } + } + /* Helper functions */ /** @@ -801,4 +876,61 @@ public static final String unescapeCsv(final String input) { return UNESCAPE_CSV.translate(input); } + // Shell + /** + *

Escapes the characters in a {@code String} using XSI rules.

+ * + *

Beware! In most cases you don't want to escape shell commands but use multi-argument + * methods provided by {@link java.lang.ProcessBuilder} or {@link java.lang.Runtime#exec(String[])} + * instead.

+ * + *

Example:

+ *
+     * input string: He didn't say, "Stop!"
+     * output string: He\ didn\'t\ say,\ \"Stop!\"
+     * 
+ * + * @see Shell Command Language + * @param input String to escape values in, may be null + * @return String with escaped values, {@code null} if null string input + * @since 3.6 + */ + public static final String escapeXSI(final String input) { + return ESCAPE_XSI.translate(input); + } + + /** + *

Alias for {@link #escapeXSI(String)}.

+ * + * @param input String to escape values in, may be null + * @return String with escaped values, {@code null} if null string input + * @since 3.6 + */ + public static final String escapeShell(final String input) { + return ESCAPE_XSI.translate(input); + } + + /** + *

Unescapes the characters in a {@code String} using XSI rules.

+ * + * @see StringEscapeUtils#escapeXSI(String) + * @param input the {@code String} to unescape, may be null + * @return a new unescaped {@code String}, {@code null} if null string input + * @since 3.6 + */ + public static final String unescapeXSI(final String input) { + return UNESCAPE_XSI.translate(input); + } + + /** + *

Alias for {@link #unescapeXSI(String)}.

+ * + * @param input the {@code String} to unescape, may be null + * @return a new unescaped {@code String}, {@code null} if null string input + * @since 3.6 + */ + public static final String unescapeShell(final String input) { + return UNESCAPE_XSI.translate(input); + } + } diff --git a/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java b/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java index 28be4377121..7e28d539116 100644 --- a/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/StringEscapeUtilsTest.java @@ -16,9 +16,12 @@ */ package org.apache.commons.lang3; +import static org.apache.commons.lang3.StringEscapeUtils.escapeXSI; +import static org.apache.commons.lang3.StringEscapeUtils.unescapeXSI; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -613,4 +616,20 @@ public void testEscapeJson() { assertEquals(expected, StringEscapeUtils.escapeJson(input)); } + @Test + public void testEscapeXSI() { + assertNull(null, escapeXSI(null)); + assertEquals("He\\ didn\\'t\\ say,\\ \\\"Stop!\\\"", escapeXSI("He didn't say, \"Stop!\"")); + assertEquals("\\\\", escapeXSI("\\")); + assertEquals("", escapeXSI("\n")); + } + + @Test + public void testUnscapeXSI() { + assertNull(null, unescapeXSI(null)); + assertEquals("\"", unescapeXSI("\\\"")); + assertEquals("He didn't say, \"Stop!\"", unescapeXSI("He\\ didn\\'t\\ say,\\ \\\"Stop!\\\"")); + assertEquals("\\", unescapeXSI("\\\\")); + assertEquals("", unescapeXSI("\\")); + } }