View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3;
18  
19  import static org.apache.commons.lang3.LangAssertions.assertNullPointerException;
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  
27  import java.io.IOException;
28  import java.io.StringWriter;
29  import java.lang.reflect.Constructor;
30  import java.lang.reflect.Modifier;
31  import java.nio.charset.StandardCharsets;
32  import java.nio.file.Files;
33  import java.nio.file.Paths;
34  
35  import org.apache.commons.lang3.text.translate.CharSequenceTranslator;
36  import org.apache.commons.lang3.text.translate.NumericEntityEscaper;
37  import org.junit.jupiter.api.Test;
38  
39  /**
40   * Tests for {@link StringEscapeUtils}.
41   */
42  @Deprecated
43  class StringEscapeUtilsTest extends AbstractLangTest {
44      private static final String FOO = "foo";
45  
46      /** HTML and XML */
47      private static final String[][] HTML_ESCAPES = {
48          {"no escaping", "plain text", "plain text"},
49          {"no escaping", "plain text", "plain text"},
50          {"empty string", "", ""},
51          {"null", null, null},
52          {"ampersand", "bread & butter", "bread & butter"},
53          {"quotes", ""bread" & butter", "\"bread\" & butter"},
54          {"final character only", "greater than >", "greater than >"},
55          {"first character only", "&lt; less than", "< less than"},
56          {"apostrophe", "Huntington's chorea", "Huntington's chorea"},
57          {"languages", "English,Fran&ccedil;ais,\u65E5\u672C\u8A9E (nihongo)", "English,Fran\u00E7ais,\u65E5\u672C\u8A9E (nihongo)"},
58          {"8-bit ascii shouldn't number-escape", "\u0080\u009F", "\u0080\u009F"},
59      };
60  
61      private void assertEscapeJava(final String escaped, final String original) throws IOException {
62          assertEscapeJava(null, escaped, original);
63      }
64  
65      private void assertEscapeJava(String message, final String expected, final String original) throws IOException {
66          final String converted = StringEscapeUtils.escapeJava(original);
67          message = "escapeJava(String) failed" + (message == null ? "" : ": " + message);
68          assertEquals(expected, converted, message);
69  
70          final StringWriter writer = new StringWriter();
71          StringEscapeUtils.ESCAPE_JAVA.translate(original, writer);
72          assertEquals(expected, writer.toString());
73      }
74  
75      private void assertUnescapeJava(final String unescaped, final String original) throws IOException {
76          assertUnescapeJava(null, unescaped, original);
77      }
78  
79      private void assertUnescapeJava(final String message, final String unescaped, final String original) throws IOException {
80          final String expected = unescaped;
81          final String actual = StringEscapeUtils.unescapeJava(original);
82  
83          assertEquals(expected, actual,
84                  "unescape(String) failed" + (message == null ? "" : ": " + message) + ": expected '" + StringEscapeUtils.escapeJava(expected) +
85                  // we escape this so we can see it in the error message
86                          "' actual '" + StringEscapeUtils.escapeJava(actual) + "'");
87  
88          final StringWriter writer = new StringWriter();
89          StringEscapeUtils.UNESCAPE_JAVA.translate(original, writer);
90          assertEquals(unescaped, writer.toString());
91  
92      }
93  
94      private void checkCsvEscapeWriter(final String expected, final String value) throws IOException {
95          final StringWriter writer = new StringWriter();
96          StringEscapeUtils.ESCAPE_CSV.translate(value, writer);
97          assertEquals(expected, writer.toString());
98      }
99  
100     private void checkCsvUnescapeWriter(final String expected, final String value) throws IOException {
101         final StringWriter writer = new StringWriter();
102         StringEscapeUtils.UNESCAPE_CSV.translate(value, writer);
103         assertEquals(expected, writer.toString());
104     }
105 
106     @Test
107     void testConstructor() {
108         assertNotNull(new StringEscapeUtils());
109         final Constructor<?>[] cons = StringEscapeUtils.class.getDeclaredConstructors();
110         assertEquals(1, cons.length);
111         assertTrue(Modifier.isPublic(cons[0].getModifiers()));
112         assertTrue(Modifier.isPublic(StringEscapeUtils.class.getModifiers()));
113         assertFalse(Modifier.isFinal(StringEscapeUtils.class.getModifiers()));
114     }
115 
116     @Test
117     void testEscapeCsvIllegalStateException() {
118         final StringWriter writer = new StringWriter();
119         assertThrows(IllegalStateException.class, () -> StringEscapeUtils.ESCAPE_CSV.translate("foo", -1, writer));
120     }
121 
122     @Test
123     void testEscapeCsvString() {
124         assertEquals("foo.bar", StringEscapeUtils.escapeCsv("foo.bar"));
125         assertEquals("\"foo,bar\"", StringEscapeUtils.escapeCsv("foo,bar"));
126         assertEquals("\"foo\nbar\"", StringEscapeUtils.escapeCsv("foo\nbar"));
127         assertEquals("\"foo\rbar\"", StringEscapeUtils.escapeCsv("foo\rbar"));
128         assertEquals("\"foo\"\"bar\"", StringEscapeUtils.escapeCsv("foo\"bar"));
129         assertEquals("foo\uD84C\uDFB4bar", StringEscapeUtils.escapeCsv("foo\uD84C\uDFB4bar"));
130         assertEquals("", StringEscapeUtils.escapeCsv(""));
131         assertNull(StringEscapeUtils.escapeCsv(null));
132     }
133 
134     @Test
135     void testEscapeCsvWriter() throws Exception {
136         checkCsvEscapeWriter("foo.bar", "foo.bar");
137         checkCsvEscapeWriter("\"foo,bar\"", "foo,bar");
138         checkCsvEscapeWriter("\"foo\nbar\"", "foo\nbar");
139         checkCsvEscapeWriter("\"foo\rbar\"", "foo\rbar");
140         checkCsvEscapeWriter("\"foo\"\"bar\"", "foo\"bar");
141         checkCsvEscapeWriter("foo\uD84C\uDFB4bar", "foo\uD84C\uDFB4bar");
142         checkCsvEscapeWriter("", null);
143         checkCsvEscapeWriter("", "");
144     }
145 
146     @Test
147     void testEscapeEcmaScript() {
148         assertNull(StringEscapeUtils.escapeEcmaScript(null));
149         assertNullPointerException(() -> StringEscapeUtils.ESCAPE_ECMASCRIPT.translate(null, null));
150         assertNullPointerException(() -> StringEscapeUtils.ESCAPE_ECMASCRIPT.translate("", null));
151 
152         assertEquals("He didn\\'t say, \\\"stop!\\\"", StringEscapeUtils.escapeEcmaScript("He didn't say, \"stop!\""));
153         assertEquals("document.getElementById(\\\"test\\\").value = \\'<script>alert(\\'aaa\\');<\\/script>\\';",
154                 StringEscapeUtils.escapeEcmaScript("document.getElementById(\"test\").value = '<script>alert('aaa');</script>';"));
155     }
156 
157     /**
158      * Tests https://issues.apache.org/jira/browse/LANG-339
159      */
160     @Test
161     void testEscapeHiragana() {
162         // Some random Japanese Unicode characters
163         final String original = "\u304B\u304C\u3068";
164         final String escaped = StringEscapeUtils.escapeHtml4(original);
165         assertEquals(original, escaped, "Hiragana character Unicode behavior should not be being escaped by escapeHtml4");
166 
167         final String unescaped = StringEscapeUtils.unescapeHtml4(escaped);
168 
169         assertEquals(escaped, unescaped, "Hiragana character Unicode behavior has changed - expected no unescaping");
170     }
171 
172     @Test
173     void testEscapeHtml() throws IOException {
174         for (final String[] element : HTML_ESCAPES) {
175             final String message = element[0];
176             final String expected = element[1];
177             final String original = element[2];
178             assertEquals(expected, StringEscapeUtils.escapeHtml4(original), message);
179             final StringWriter sw = new StringWriter();
180             StringEscapeUtils.ESCAPE_HTML4.translate(original, sw);
181             final String actual = original == null ? null : sw.toString();
182             assertEquals(expected, actual, message);
183         }
184     }
185 
186     /**
187      * Tests // https://issues.apache.org/jira/browse/LANG-480
188      */
189     @Test
190     void testEscapeHtmlHighUnicode() {
191         // this is the utf8 representation of the character:
192         // COUNTING ROD UNIT DIGIT THREE
193         // in Unicode
194         // code point: U+1D362
195         final byte[] data = { (byte) 0xF0, (byte) 0x9D, (byte) 0x8D, (byte) 0xA2 };
196 
197         final String original = new String(data, StandardCharsets.UTF_8);
198 
199         final String escaped = StringEscapeUtils.escapeHtml4(original);
200         assertEquals(original, escaped, "High Unicode should not have been escaped");
201 
202         final String unescaped = StringEscapeUtils.unescapeHtml4(escaped);
203         assertEquals(original, unescaped, "High Unicode should have been unchanged");
204 
205 // TODO: I think this should hold, needs further investigation
206 //        String unescapedFromEntity = StringEscapeUtils.unescapeHtml4("&#119650;");
207 //        assertEquals("High Unicode should have been unescaped", original, unescapedFromEntity);
208     }
209 
210     @Test
211     void testEscapeHtmlVersions() {
212         assertEquals("&Beta;", StringEscapeUtils.escapeHtml4("\u0392"));
213         assertEquals("\u0392", StringEscapeUtils.unescapeHtml4("&Beta;"));
214 
215         // TODO: refine API for escaping/unescaping specific HTML versions
216     }
217 
218     @Test
219     void testEscapeJava() throws IOException {
220         assertNull(StringEscapeUtils.escapeJava(null));
221         assertNullPointerException(() -> StringEscapeUtils.ESCAPE_JAVA.translate(null, null));
222         assertNullPointerException(() -> StringEscapeUtils.ESCAPE_JAVA.translate("", null));
223 
224         assertEscapeJava("empty string", "", "");
225         assertEscapeJava(FOO, FOO);
226         assertEscapeJava("tab", "\\t", "\t");
227         assertEscapeJava("backslash", "\\\\", "\\");
228         assertEscapeJava("single quote should not be escaped", "'", "'");
229         assertEscapeJava("\\\\\\b\\t\\r", "\\\b\t\r");
230         assertEscapeJava("\\u1234", "\u1234");
231         assertEscapeJava("\\u0234", "\u0234");
232         assertEscapeJava("\\u00EF", "\u00ef");
233         assertEscapeJava("\\u0001", "\u0001");
234         assertEscapeJava("Should use capitalized Unicode hex", "\\uABCD", "\uabcd");
235 
236         assertEscapeJava("He didn't say, \\\"stop!\\\"", "He didn't say, \"stop!\"");
237         assertEscapeJava("non-breaking space", "This space is non-breaking:" + "\\u00A0", "This space is non-breaking:\u00a0");
238         assertEscapeJava("\\uABCD\\u1234\\u012C", "\uABCD\u1234\u012C");
239     }
240 
241     /**
242      * Tests https://issues.apache.org/jira/browse/LANG-421
243      */
244     @Test
245     void testEscapeJavaWithSlash() {
246         final String input = "String with a slash (/) in it";
247 
248         final String expected = input;
249         final String actual = StringEscapeUtils.escapeJava(input);
250 
251         /*
252          * In 2.4 StringEscapeUtils.escapeJava(String) escapes '/' characters, which are not a valid character to escape in a Java string.
253          */
254         assertEquals(expected, actual);
255     }
256 
257     @Test
258     void testEscapeJson() {
259         assertNull(StringEscapeUtils.escapeJson(null));
260         assertNullPointerException(() -> StringEscapeUtils.ESCAPE_JSON.translate(null, null));
261         assertNullPointerException(() -> StringEscapeUtils.ESCAPE_JSON.translate("", null));
262 
263         assertEquals("He didn't say, \\\"stop!\\\"", StringEscapeUtils.escapeJson("He didn't say, \"stop!\""));
264 
265         final String expected = "\\\"foo\\\" isn't \\\"bar\\\". specials: \\b\\r\\n\\f\\t\\\\\\/";
266         final String input = "\"foo\" isn't \"bar\". specials: \b\r\n\f\t\\/";
267 
268         assertEquals(expected, StringEscapeUtils.escapeJson(input));
269     }
270 
271     @Test
272     void testEscapeXml() throws Exception {
273         assertEquals("&lt;abc&gt;", StringEscapeUtils.escapeXml("<abc>"));
274         assertEquals("<abc>", StringEscapeUtils.unescapeXml("&lt;abc&gt;"));
275 
276         assertEquals("\u00A1", StringEscapeUtils.escapeXml("\u00A1"), "XML should not escape >0x7f values");
277         assertEquals("\u00A0", StringEscapeUtils.unescapeXml("&#160;"), "XML should be able to unescape >0x7f values");
278         assertEquals("\u00A0", StringEscapeUtils.unescapeXml("&#0160;"), "XML should be able to unescape >0x7f values with one leading 0");
279         assertEquals("\u00A0", StringEscapeUtils.unescapeXml("&#00160;"), "XML should be able to unescape >0x7f values with two leading 0s");
280         assertEquals("\u00A0", StringEscapeUtils.unescapeXml("&#000160;"), "XML should be able to unescape >0x7f values with three leading 0s");
281 
282         assertEquals("ain't", StringEscapeUtils.unescapeXml("ain&apos;t"));
283         assertEquals("ain&apos;t", StringEscapeUtils.escapeXml("ain't"));
284         assertEquals("", StringEscapeUtils.escapeXml(""));
285         assertNull(StringEscapeUtils.escapeXml(null));
286         assertNull(StringEscapeUtils.unescapeXml(null));
287 
288         StringWriter sw = new StringWriter();
289         StringEscapeUtils.ESCAPE_XML.translate("<abc>", sw);
290         assertEquals("&lt;abc&gt;", sw.toString(), "XML was escaped incorrectly");
291 
292         sw = new StringWriter();
293         StringEscapeUtils.UNESCAPE_XML.translate("&lt;abc&gt;", sw);
294         assertEquals("<abc>", sw.toString(), "XML was unescaped incorrectly");
295     }
296 
297     @Test
298     void testEscapeXml10() {
299         assertEquals("a&lt;b&gt;c&quot;d&apos;e&amp;f", StringEscapeUtils.escapeXml10("a<b>c\"d'e&f"));
300         assertEquals("a\tb\rc\nd", StringEscapeUtils.escapeXml10("a\tb\rc\nd"), "XML 1.0 should not escape \t \n \r");
301         assertEquals("ab", StringEscapeUtils.escapeXml10("a\u0000\u0001\u0008\u000b\u000c\u000e\u001fb"),
302                 "XML 1.0 should omit most #x0-x8 | #xb | #xc | #xe-#x19");
303         assertEquals("a\ud7ff  \ue000b", StringEscapeUtils.escapeXml10("a\ud7ff\ud800 \udfff \ue000b"), "XML 1.0 should omit #xd800-#xdfff");
304         assertEquals("a\ufffdb", StringEscapeUtils.escapeXml10("a\ufffd\ufffe\uffffb"), "XML 1.0 should omit #xfffe | #xffff");
305         assertEquals("a\u007e&#127;&#132;\u0085&#134;&#159;\u00a0b", StringEscapeUtils.escapeXml10("a\u007e\u007f\u0084\u0085\u0086\u009f\u00a0b"),
306                 "XML 1.0 should escape #x7f-#x84 | #x86 - #x9f, for XML 1.1 compatibility");
307     }
308 
309     @Test
310     void testEscapeXml11() {
311         assertEquals("a&lt;b&gt;c&quot;d&apos;e&amp;f", StringEscapeUtils.escapeXml11("a<b>c\"d'e&f"));
312         assertEquals("a\tb\rc\nd", StringEscapeUtils.escapeXml11("a\tb\rc\nd"), "XML 1.1 should not escape \t \n \r");
313         assertEquals("ab", StringEscapeUtils.escapeXml11("a\u0000b"), "XML 1.1 should omit #x0");
314         assertEquals("a&#1;&#8;&#11;&#12;&#14;&#31;b", StringEscapeUtils.escapeXml11("a\u0001\u0008\u000b\u000c\u000e\u001fb"),
315                 "XML 1.1 should escape #x1-x8 | #xb | #xc | #xe-#x19");
316         assertEquals("a\u007e&#127;&#132;\u0085&#134;&#159;\u00a0b", StringEscapeUtils.escapeXml11("a\u007e\u007f\u0084\u0085\u0086\u009f\u00a0b"),
317                 "XML 1.1 should escape #x7F-#x84 | #x86-#x9F");
318         assertEquals("a\ud7ff  \ue000b", StringEscapeUtils.escapeXml11("a\ud7ff\ud800 \udfff \ue000b"), "XML 1.1 should omit #xd800-#xdfff");
319         assertEquals("a\ufffdb", StringEscapeUtils.escapeXml11("a\ufffd\ufffe\uffffb"), "XML 1.1 should omit #xfffe | #xffff");
320     }
321 
322     @Test
323     void testEscapeXmlAllCharacters() {
324         // https://www.w3.org/TR/xml/#charsets says:
325         // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character,
326         // excluding the surrogate blocks, FFFE, and FFFF. */
327         final CharSequenceTranslator escapeXml = StringEscapeUtils.ESCAPE_XML.with(NumericEntityEscaper.below(9), NumericEntityEscaper.between(0xB, 0xC),
328                 NumericEntityEscaper.between(0xE, 0x19), NumericEntityEscaper.between(0xD800, 0xDFFF), NumericEntityEscaper.between(0xFFFE, 0xFFFF),
329                 NumericEntityEscaper.above(0x110000));
330 
331         assertEquals("&#0;&#1;&#2;&#3;&#4;&#5;&#6;&#7;&#8;", escapeXml.translate("\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008"));
332         assertEquals("\t", escapeXml.translate("\t")); // 0x9
333         assertEquals("\n", escapeXml.translate("\n")); // 0xA
334         assertEquals("&#11;&#12;", escapeXml.translate("\u000B\u000C"));
335         assertEquals("\r", escapeXml.translate("\r")); // 0xD
336         assertEquals("Hello World! Ain&apos;t this great?", escapeXml.translate("Hello World! Ain't this great?"));
337         assertEquals("&#14;&#15;&#24;&#25;", escapeXml.translate("\u000E\u000F\u0018\u0019"));
338     }
339 
340     /**
341      * Tests Supplementary characters.
342      * <p>
343      * From https://www.w3.org/International/questions/qa-escapes
344      * </p>
345      * <blockquote> Supplementary characters are those Unicode characters that have code points higher than the characters in the Basic Multilingual Plane
346      * (BMP). In UTF-16 a supplementary character is encoded using two 16-bit surrogate code points from the BMP. Because of this, some people think that
347      * supplementary characters need to be represented using two escapes, but this is incorrect - you must use the single, code point value for that character.
348      * For example, use &amp;&#35;x233B4&#59; rather than &amp;&#35;xD84C&#59;&amp;&#35;xDFB4&#59;. </blockquote>
349      *
350      * @see <a href="https://www.w3.org/International/questions/qa-escapes">Using character escapes in markup and CSS</a>
351      * @see <a href="https://issues.apache.org/jira/browse/LANG-728">LANG-728</a>
352      */
353     @Test
354     void testEscapeXmlSupplementaryCharacters() {
355         final CharSequenceTranslator escapeXml = StringEscapeUtils.ESCAPE_XML.with(NumericEntityEscaper.between(0x7f, Integer.MAX_VALUE));
356 
357         assertEquals("&#144308;", escapeXml.translate("\uD84C\uDFB4"), "Supplementary character must be represented using a single escape");
358 
359         assertEquals("a b c &#144308;", escapeXml.translate("a b c \uD84C\uDFB4"),
360                 "Supplementary characters mixed with basic characters should be encoded correctly");
361     }
362 
363     @Test
364     void testLang313() {
365         assertEquals("& &", StringEscapeUtils.unescapeHtml4("& &amp;"));
366     }
367 
368     /**
369      * Tests https://issues.apache.org/jira/browse/LANG-708
370      *
371      * @throws IOException if an I/O error occurs
372      */
373     @Test
374     void testLang708() throws IOException {
375         final byte[] inputBytes = Files.readAllBytes(Paths.get("src/test/resources/lang-708-input.txt"));
376         final String input = new String(inputBytes, StandardCharsets.UTF_8);
377         final String escaped = StringEscapeUtils.escapeEcmaScript(input);
378         // just the end:
379         assertTrue(escaped.endsWith("}]"), escaped);
380         // a little more:
381         assertTrue(escaped.endsWith("\"valueCode\\\":\\\"\\\"}]"), escaped);
382     }
383 
384     /**
385      * Tests https://issues.apache.org/jira/browse/LANG-720
386      */
387     @Test
388     void testLang720() {
389         final String input = "\ud842\udfb7" + "A";
390         final String escaped = StringEscapeUtils.escapeXml(input);
391         assertEquals(input, escaped);
392     }
393 
394     /**
395      * Tests https://issues.apache.org/jira/browse/LANG-911
396      */
397     @Test
398     void testLang911() {
399         final String bellsTest = "\ud83d\udc80\ud83d\udd14";
400         final String value = StringEscapeUtils.escapeJava(bellsTest);
401         final String valueTest = StringEscapeUtils.unescapeJava(value);
402         assertEquals(bellsTest, valueTest);
403     }
404 
405     // Tests issue LANG-150
406     // https://issues.apache.org/jira/browse/LANG-150
407     @Test
408     void testStandaloneAmphersand() {
409         assertEquals("<P&O>", StringEscapeUtils.unescapeHtml4("&lt;P&O&gt;"));
410         assertEquals("test & <", StringEscapeUtils.unescapeHtml4("test & &lt;"));
411         assertEquals("<P&O>", StringEscapeUtils.unescapeXml("&lt;P&O&gt;"));
412         assertEquals("test & <", StringEscapeUtils.unescapeXml("test & &lt;"));
413     }
414 
415     @Test
416     void testUnescapeCsvIllegalStateException() {
417         final StringWriter writer = new StringWriter();
418         assertThrows(IllegalStateException.class, () -> StringEscapeUtils.UNESCAPE_CSV.translate("foo", -1, writer));
419     }
420 
421     @Test
422     void testUnescapeCsvString() {
423         assertEquals("foo.bar", StringEscapeUtils.unescapeCsv("foo.bar"));
424         assertEquals("foo,bar", StringEscapeUtils.unescapeCsv("\"foo,bar\""));
425         assertEquals("foo\nbar", StringEscapeUtils.unescapeCsv("\"foo\nbar\""));
426         assertEquals("foo\rbar", StringEscapeUtils.unescapeCsv("\"foo\rbar\""));
427         assertEquals("foo\"bar", StringEscapeUtils.unescapeCsv("\"foo\"\"bar\""));
428         assertEquals("foo\uD84C\uDFB4bar", StringEscapeUtils.unescapeCsv("foo\uD84C\uDFB4bar"));
429         assertEquals("", StringEscapeUtils.unescapeCsv(""));
430         assertNull(StringEscapeUtils.unescapeCsv(null));
431 
432         assertEquals("\"foo.bar\"", StringEscapeUtils.unescapeCsv("\"foo.bar\""));
433     }
434 
435     @Test
436     void testUnescapeCsvWriter() throws Exception {
437         checkCsvUnescapeWriter("foo.bar", "foo.bar");
438         checkCsvUnescapeWriter("foo,bar", "\"foo,bar\"");
439         checkCsvUnescapeWriter("foo\nbar", "\"foo\nbar\"");
440         checkCsvUnescapeWriter("foo\rbar", "\"foo\rbar\"");
441         checkCsvUnescapeWriter("foo\"bar", "\"foo\"\"bar\"");
442         checkCsvUnescapeWriter("foo\uD84C\uDFB4bar", "foo\uD84C\uDFB4bar");
443         checkCsvUnescapeWriter("", null);
444         checkCsvUnescapeWriter("", "");
445 
446         checkCsvUnescapeWriter("\"foo.bar\"", "\"foo.bar\"");
447     }
448 
449     @Test
450     void testUnescapeEcmaScript() {
451         assertNull(StringEscapeUtils.escapeEcmaScript(null));
452         assertNullPointerException(() -> StringEscapeUtils.UNESCAPE_ECMASCRIPT.translate(null, null));
453         assertNullPointerException(() -> StringEscapeUtils.UNESCAPE_ECMASCRIPT.translate("", null));
454 
455         assertEquals("He didn't say, \"stop!\"", StringEscapeUtils.unescapeEcmaScript("He didn\\'t say, \\\"stop!\\\""));
456         assertEquals("document.getElementById(\"test\").value = '<script>alert('aaa');</script>';",
457                 StringEscapeUtils.unescapeEcmaScript("document.getElementById(\\\"test\\\").value = \\'<script>alert(\\'aaa\\');<\\/script>\\';"));
458     }
459 
460     @Test
461     void testUnescapeHexCharsHtml() {
462         // Simple easy to grok test
463         assertEquals("\u0080\u009F", StringEscapeUtils.unescapeHtml4("&#x80;&#x9F;"), "hex number unescape");
464         assertEquals("\u0080\u009F", StringEscapeUtils.unescapeHtml4("&#X80;&#X9F;"), "hex number unescape");
465         // Test all Character values:
466         for (char i = Character.MIN_VALUE; i < Character.MAX_VALUE; i++) {
467             final Character c1 = Character.valueOf(i);
468             final Character c2 = Character.valueOf((char) (i + 1));
469             final String expected = c1.toString() + c2;
470             final String escapedC1 = "&#x" + Integer.toHexString(c1.charValue()) + ";";
471             final String escapedC2 = "&#x" + Integer.toHexString(c2.charValue()) + ";";
472             assertEquals(expected, StringEscapeUtils.unescapeHtml4(escapedC1 + escapedC2), "hex number unescape index " + (int) i);
473         }
474     }
475 
476     @Test
477     void testUnescapeHtml4() throws IOException {
478         for (final String[] element : HTML_ESCAPES) {
479             final String message = element[0];
480             final String expected = element[2];
481             final String original = element[1];
482             assertEquals(expected, StringEscapeUtils.unescapeHtml4(original), message);
483 
484             final StringWriter sw = new StringWriter();
485             StringEscapeUtils.UNESCAPE_HTML4.translate(original, sw);
486             final String actual = original == null ? null : sw.toString();
487             assertEquals(expected, actual, message);
488         }
489         // \u00E7 is a cedilla (c with wiggle under)
490         // note that the test string must be 7-bit-clean (Unicode escaped) or else it will compile incorrectly
491         // on some locales
492         assertEquals("Fran\u00E7ais", StringEscapeUtils.unescapeHtml4("Fran\u00E7ais"), "funny chars pass through OK");
493 
494         assertEquals("Hello&;World", StringEscapeUtils.unescapeHtml4("Hello&;World"));
495         assertEquals("Hello&#;World", StringEscapeUtils.unescapeHtml4("Hello&#;World"));
496         assertEquals("Hello&# ;World", StringEscapeUtils.unescapeHtml4("Hello&# ;World"));
497         assertEquals("Hello&##;World", StringEscapeUtils.unescapeHtml4("Hello&##;World"));
498     }
499 
500     @Test
501     void testUnescapeJava() throws IOException {
502         assertNull(StringEscapeUtils.unescapeJava(null));
503         assertNullPointerException(() -> StringEscapeUtils.UNESCAPE_JAVA.translate(null, null));
504         assertNullPointerException(() -> StringEscapeUtils.UNESCAPE_JAVA.translate("", null));
505         assertThrows(RuntimeException.class, () -> StringEscapeUtils.unescapeJava("\\u02-3"));
506 
507         assertUnescapeJava("", "");
508         assertUnescapeJava("test", "test");
509         assertUnescapeJava("\ntest\b", "\\ntest\\b");
510         assertUnescapeJava("\u123425foo\ntest\b", "\\u123425foo\\ntest\\b");
511         assertUnescapeJava("'\foo\teste\r", "\\'\\foo\\teste\\r");
512         assertUnescapeJava("", "\\");
513         // foo
514         assertUnescapeJava("lowercase Unicode", "\uABCDx", "\\uabcdx");
515         assertUnescapeJava("uppercase Unicode", "\uABCDx", "\\uABCDx");
516         assertUnescapeJava("Unicode as final character", "\uABCD", "\\uabcd");
517     }
518 
519     @Test
520     void testUnescapeJson() {
521         assertNull(StringEscapeUtils.unescapeJson(null));
522         assertNullPointerException(() -> StringEscapeUtils.UNESCAPE_JSON.translate(null, null));
523         assertNullPointerException(() -> StringEscapeUtils.UNESCAPE_JSON.translate("", null));
524 
525         assertEquals("He didn't say, \"stop!\"", StringEscapeUtils.unescapeJson("He didn't say, \\\"stop!\\\""));
526 
527         final String expected = "\"foo\" isn't \"bar\". specials: \b\r\n\f\t\\/";
528         final String input = "\\\"foo\\\" isn't \\\"bar\\\". specials: \\b\\r\\n\\f\\t\\\\\\/";
529 
530         assertEquals(expected, StringEscapeUtils.unescapeJson(input));
531     }
532 
533     @Test
534     void testUnescapeUnknownEntity() {
535         assertEquals("&zzzz;", StringEscapeUtils.unescapeHtml4("&zzzz;"));
536     }
537 
538     /**
539      * Reverse of the above.
540      *
541      * @see <a href="https://issues.apache.org/jira/browse/LANG-729">LANG-729</a>
542      */
543     @Test
544     void testUnescapeXmlSupplementaryCharacters() {
545         assertEquals("\uD84C\uDFB4", StringEscapeUtils.unescapeXml("&#144308;"), "Supplementary character must be represented using a single escape");
546 
547         assertEquals("a b c \uD84C\uDFB4", StringEscapeUtils.unescapeXml("a b c &#144308;"),
548                 "Supplementary characters mixed with basic characters should be decoded correctly");
549     }
550 }