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    *      http://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.text;
18  
19  import static org.apache.commons.text.StringEscapeUtils.escapeXSI;
20  import static org.apache.commons.text.StringEscapeUtils.unescapeXSI;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotNull;
24  import static org.junit.jupiter.api.Assertions.assertNull;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  import static org.junit.jupiter.api.Assertions.fail;
28  
29  import java.io.IOException;
30  import java.io.StringWriter;
31  import java.lang.reflect.Constructor;
32  import java.lang.reflect.Modifier;
33  import java.nio.charset.StandardCharsets;
34  import java.nio.file.Files;
35  import java.nio.file.Paths;
36  
37  import org.junit.jupiter.api.Test;
38  
39  /**
40   * Tests {@link StringEscapeUtils}.
41   *
42   * <p>
43   * This code has been adapted from Apache Commons Lang 3.5.
44   * </p>
45   */
46  public class StringEscapeUtilsTest {
47      private static final String FOO = "foo";
48  
49      private static final String[][] HTML_ESCAPES = {
50              {"no escaping", "plain text", "plain text"},
51              {"no escaping", "plain text", "plain text"},
52              {"empty string", "", ""},
53              {"null", null, null},
54              {"ampersand", "bread &amp; butter", "bread & butter"},
55              {"quotes", "&quot;bread&quot; &amp; butter", "\"bread\" & butter"},
56              {"final character only", "greater than &gt;", "greater than >"},
57              {"first character only", "&lt; less than", "< less than"},
58              {"apostrophe", "Huntington's chorea", "Huntington's chorea"},
59              {"languages", "English,Fran&ccedil;ais,\u65E5\u672C\u8A9E (nihongo)",
60                  "English,Fran\u00E7ais,\u65E5\u672C\u8A9E (nihongo)"},
61              {"8-bit ascii shouldn't number-escape", "\u0080\u009F", "\u0080\u009F"},
62      };
63  
64      private void assertEscapeJava(final String escaped, final String original) throws IOException {
65          assertEscapeJava(escaped, original, null);
66      }
67  
68      private void assertEscapeJava(final String expected, final String original, String message) throws IOException {
69          final String converted = StringEscapeUtils.escapeJava(original);
70          message = "escapeJava(String) failed" + (message == null ? "" : ": " + message);
71          assertEquals(expected, converted, message);
72  
73          final StringWriter writer = new StringWriter();
74          StringEscapeUtils.ESCAPE_JAVA.translate(original, writer);
75          assertEquals(expected, writer.toString());
76      }
77  
78      private void assertUnescapeJava(final String unescaped, final String original) throws IOException {
79          assertUnescapeJava(unescaped, original, null);
80      }
81  
82      private void assertUnescapeJava(final String unescaped, final String original, final String message)
83              throws IOException {
84          final String actual = StringEscapeUtils.unescapeJava(original);
85  
86          assertEquals(unescaped, actual, "unescape(String) failed"
87                          + (message == null ? "" : ": " + message)
88                          + ": expected '" + StringEscapeUtils.escapeJava(unescaped)
89                          // we escape this so we can see it in the error message
90                          + "' actual '" + StringEscapeUtils.escapeJava(actual) + "'");
91  
92          final StringWriter writer = new StringWriter();
93          StringEscapeUtils.UNESCAPE_JAVA.translate(original, writer);
94          assertEquals(unescaped, writer.toString());
95      }
96  
97      private void checkCsvEscapeWriter(final String expected, final String value) throws IOException {
98          final StringWriter writer = new StringWriter();
99          StringEscapeUtils.ESCAPE_CSV.translate(value, writer);
100         assertEquals(expected, writer.toString());
101     }
102 
103     private void checkCsvUnescapeWriter(final String expected, final String value) throws IOException {
104         final StringWriter writer = new StringWriter();
105         StringEscapeUtils.UNESCAPE_CSV.translate(value, writer);
106         assertEquals(expected, writer.toString());
107     }
108 
109     @Test
110     public void testBuilder() {
111         final String result =
112                 StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_XML10).escape("<").append(">").toString();
113         assertEquals("&lt;>", result);
114     }
115 
116     @Test
117     public void testConstructor() {
118         assertNotNull(new StringEscapeUtils());
119         final Constructor<?>[] cons = StringEscapeUtils.class.getDeclaredConstructors();
120         assertEquals(1, cons.length);
121         assertTrue(Modifier.isPublic(cons[0].getModifiers()));
122         assertTrue(Modifier.isPublic(StringEscapeUtils.class.getModifiers()));
123         assertFalse(Modifier.isFinal(StringEscapeUtils.class.getModifiers()));
124     }
125 
126 
127     // HTML and XML
128     @Test
129     public void testDeleteCharacter() {
130       final String deleteString = "Delete: \u007F";
131       assertEquals("Delete: \\u007F", StringEscapeUtils.escapeJson(deleteString));
132     }
133 
134     @Test
135     public void testEscapeCsvString() {
136         assertEquals("foo.bar", StringEscapeUtils.escapeCsv("foo.bar"));
137         assertEquals("\"foo,bar\"", StringEscapeUtils.escapeCsv("foo,bar"));
138         assertEquals("\"foo\nbar\"", StringEscapeUtils.escapeCsv("foo\nbar"));
139         assertEquals("\"foo\rbar\"", StringEscapeUtils.escapeCsv("foo\rbar"));
140         assertEquals("\"foo\"\"bar\"", StringEscapeUtils.escapeCsv("foo\"bar"));
141         assertEquals("foo\uD84C\uDFB4bar", StringEscapeUtils.escapeCsv("foo\uD84C\uDFB4bar"));
142         assertEquals("", StringEscapeUtils.escapeCsv(""));
143         assertNull(StringEscapeUtils.escapeCsv(null));
144     }
145 
146     @Test
147     public void testEscapeCsvWriter() throws IOException {
148         checkCsvEscapeWriter("foo.bar", "foo.bar");
149         checkCsvEscapeWriter("\"foo,bar\"", "foo,bar");
150         checkCsvEscapeWriter("\"foo\nbar\"", "foo\nbar");
151         checkCsvEscapeWriter("\"foo\rbar\"", "foo\rbar");
152         checkCsvEscapeWriter("\"foo\"\"bar\"", "foo\"bar");
153         checkCsvEscapeWriter("foo\uD84C\uDFB4bar", "foo\uD84C\uDFB4bar");
154         checkCsvEscapeWriter("", null);
155         checkCsvEscapeWriter("", "");
156     }
157 
158 @Test
159 public void testEscapeEcmaScript() {
160     assertNull(StringEscapeUtils.escapeEcmaScript(null));
161     try {
162         StringEscapeUtils.ESCAPE_ECMASCRIPT.translate(null, null);
163         fail("Exception expected!");
164     } catch (final IOException ex) {
165         fail("Exception expected!");
166     } catch (final IllegalArgumentException ex) {
167         // expected
168     }
169     try {
170         StringEscapeUtils.ESCAPE_ECMASCRIPT.translate("", null);
171         fail("Exception expected!");
172     } catch (final IOException ex) {
173         fail("Exception expected!");
174     } catch (final IllegalArgumentException ex) {
175         // expected
176     }
177 
178     assertEquals("He didn\\'t say, \\\"stop!\\\"", StringEscapeUtils.escapeEcmaScript("He didn't say, \"stop!\""));
179     assertEquals("document.getElementById(\\\"test\\\").value = \\'<script>alert(\\'aaa\\');<\\/script>\\';",
180             StringEscapeUtils.escapeEcmaScript(
181                     "document.getElementById(\"test\").value = '<script>alert('aaa');</script>';"));
182 }
183 
184     /**
185      * Tests https://issues.apache.org/jira/browse/LANG-339
186      */
187     @Test
188     public void testEscapeHiragana() {
189         // Some random Japanese Unicode characters
190         final String original = "\u304B\u304C\u3068";
191         final String escaped = StringEscapeUtils.escapeHtml4(original);
192         assertEquals(original, escaped,
193                 "Hiragana character Unicode behavior should not be being escaped by escapeHtml4");
194 
195         final String unescaped = StringEscapeUtils.unescapeHtml4(escaped);
196 
197         assertEquals(escaped, unescaped, "Hiragana character Unicode behavior has changed - expected no unescaping");
198     }
199 
200     @Test
201     public void testEscapeHtml3() {
202         for (final String[] element : HTML_ESCAPES) {
203             final String message = element[0];
204             final String expected = element[1];
205             final String original = element[2];
206             assertEquals(expected, StringEscapeUtils.escapeHtml4(original), message);
207             final StringWriter sw = new StringWriter();
208             try {
209                 StringEscapeUtils.ESCAPE_HTML3.translate(original, sw);
210             } catch (final IOException e) {
211                 // expected
212             }
213             final String actual = original == null ? null : sw.toString();
214             assertEquals(expected, actual, message);
215         }
216     }
217 
218     @Test
219         public void testEscapeHtml4() {
220             for (final String[] element : HTML_ESCAPES) {
221                 final String message = element[0];
222                 final String expected = element[1];
223                 final String original = element[2];
224                 assertEquals(expected, StringEscapeUtils.escapeHtml4(original), message);
225                 final StringWriter sw = new StringWriter();
226                 try {
227                     StringEscapeUtils.ESCAPE_HTML4.translate(original, sw);
228                 } catch (final IOException e) {
229                     // expected
230                 }
231                 final String actual = original == null ? null : sw.toString();
232                 assertEquals(expected, actual, message);
233             }
234         }
235 
236     /**
237      * Tests // https://issues.apache.org/jira/browse/LANG-480
238      */
239     @Test
240     public void testEscapeHtmlHighUnicode() {
241         // this is the utf8 representation of the character:
242         // COUNTING ROD UNIT DIGIT THREE
243         // in Unicode
244         // code point: U+1D362
245         final byte[] data = {(byte) 0xF0, (byte) 0x9D, (byte) 0x8D, (byte) 0xA2};
246 
247         final String original = new String(data, StandardCharsets.UTF_8);
248 
249         final String escaped = StringEscapeUtils.escapeHtml4(original);
250         assertEquals(original, escaped, "High Unicode should not have been escaped");
251 
252         final String unescaped = StringEscapeUtils.unescapeHtml4(escaped);
253         assertEquals(original, unescaped, "High Unicode should have been unchanged");
254 
255         // TODO: I think this should hold, needs further investigation
256         //        String unescapedFromEntity = StringEscapeUtils.unescapeHtml4("&#119650;");
257         //        assertEquals("High Unicode should have been unescaped", original, unescapedFromEntity);
258     }
259 
260 
261 
262     @Test
263     public void testEscapeHtmlThree() {
264         assertNull(StringEscapeUtils.escapeHtml3(null));
265         assertEquals("a", StringEscapeUtils.escapeHtml3("a"));
266         assertEquals("&lt;b&gt;a", StringEscapeUtils.escapeHtml3("<b>a"));
267     }
268 
269     @Test
270     public void testEscapeHtmlVersions() {
271         assertEquals("&Beta;", StringEscapeUtils.escapeHtml4("\u0392"));
272         assertEquals("\u0392", StringEscapeUtils.unescapeHtml4("&Beta;"));
273 
274         // TODO: refine API for escaping/unescaping specific HTML versions
275     }
276 
277     @Test
278     public void testEscapeJava() throws IOException {
279         assertNull(StringEscapeUtils.escapeJava(null));
280         try {
281             StringEscapeUtils.ESCAPE_JAVA.translate(null, null);
282             fail("Exception expected!");
283         } catch (final IOException ex) {
284             fail("Exception expected!");
285         } catch (final IllegalArgumentException ex) {
286             // expected
287         }
288         try {
289             StringEscapeUtils.ESCAPE_JAVA.translate("", null);
290             fail("Exception expected!");
291         } catch (final IOException ex) {
292             fail("Exception expected!");
293         } catch (final IllegalArgumentException ex) {
294             // expected
295         }
296 
297         assertEscapeJava("", "", "empty string");
298         assertEscapeJava(FOO, FOO);
299         assertEscapeJava("\\t", "\t", "tab");
300         assertEscapeJava("\\\\", "\\", "backslash");
301         assertEscapeJava("'", "'", "single quote should not be escaped");
302         assertEscapeJava("\\\\\\b\\t\\r", "\\\b\t\r");
303         assertEscapeJava("\\u1234", "\u1234");
304         assertEscapeJava("\\u0234", "\u0234");
305         assertEscapeJava("\\u00EF", "\u00ef");
306         assertEscapeJava("\\u0001", "\u0001");
307         assertEscapeJava("\\uABCD", "\uabcd", "Should use capitalized Unicode hex");
308 
309         assertEscapeJava("He didn't say, \\\"stop!\\\"",
310                 "He didn't say, \"stop!\"");
311         assertEscapeJava("This space is non-breaking:" + "\\u00A0", "This space is non-breaking:\u00a0",
312                 "non-breaking space");
313         assertEscapeJava("\\uABCD\\u1234\\u012C",
314                 "\uABCD\u1234\u012C");
315     }
316 
317     /**
318      * Tests https://issues.apache.org/jira/browse/LANG-421
319      */
320     @Test
321     public void testEscapeJavaWithSlash() {
322         final String input = "String with a slash (/) in it";
323 
324         final String actual = StringEscapeUtils.escapeJava(input);
325 
326         /*
327          * In 2.4 StringEscapeUtils.escapeJava(String) escapes '/' characters, which are not a valid character
328          * to escape in a Java string.
329          */
330         assertEquals(input, actual);
331     }
332 
333     @Test
334     public void testEscapeJson() {
335         assertNull(StringEscapeUtils.escapeJson(null));
336         try {
337             StringEscapeUtils.ESCAPE_JSON.translate(null, null);
338             fail("Exception expected!");
339         } catch (final IOException ex) {
340             fail("Exception expected!");
341         } catch (final IllegalArgumentException ex) {
342             // expected
343         }
344         try {
345             StringEscapeUtils.ESCAPE_JSON.translate("", null);
346             fail("Exception expected!");
347         } catch (final IOException ex) {
348             fail("Exception expected!");
349         } catch (final IllegalArgumentException ex) {
350             // expected
351         }
352 
353         assertEquals("He didn't say, \\\"stop!\\\"", StringEscapeUtils.escapeJson("He didn't say, \"stop!\""));
354 
355         final String expected = "\\\"foo\\\" isn't \\\"bar\\\". specials: \\b\\r\\n\\f\\t\\\\\\/";
356         final String input = "\"foo\" isn't \"bar\". specials: \b\r\n\f\t\\/";
357 
358         assertEquals(expected, StringEscapeUtils.escapeJson(input));
359     }
360 
361     @Test
362     public void testEscapeXml10() {
363         assertEquals("a&lt;b&gt;c&quot;d&apos;e&amp;f", StringEscapeUtils.escapeXml10("a<b>c\"d'e&f"));
364         assertEquals("a\tb\rc\nd", StringEscapeUtils.escapeXml10("a\tb\rc\nd"),
365                 "XML 1.0 should not escape \t \n \r");
366         assertEquals("ab", StringEscapeUtils.escapeXml10("a\u0000\u0001\u0008\u000b\u000c\u000e\u001fb"),
367                 "XML 1.0 should omit most #x0-x8 | #xb | #xc | #xe-#x19");
368         assertEquals("a\ud7ff  \ue000b", StringEscapeUtils.escapeXml10("a\ud7ff\ud800 \udfff \ue000b"),
369                 "XML 1.0 should omit #xd800-#xdfff");
370         assertEquals("a\ufffdb", StringEscapeUtils.escapeXml10("a\ufffd\ufffe\uffffb"),
371                 "XML 1.0 should omit #xfffe | #xffff");
372         assertEquals("a\u007e&#127;&#132;\u0085&#134;&#159;\u00a0b",
373                 StringEscapeUtils.escapeXml10("a\u007e\u007f\u0084\u0085\u0086\u009f\u00a0b"),
374                 "XML 1.0 should escape #x7f-#x84 | #x86 - #x9f, for XML 1.1 compatibility");
375     }
376 
377     @Test
378     public void testEscapeXml11() {
379         assertEquals("a&lt;b&gt;c&quot;d&apos;e&amp;f", StringEscapeUtils.escapeXml11("a<b>c\"d'e&f"));
380         assertEquals("a\tb\rc\nd", StringEscapeUtils.escapeXml11("a\tb\rc\nd"),
381                 "XML 1.1 should not escape \t \n \r");
382         assertEquals("ab", StringEscapeUtils.escapeXml11("a\u0000b"),
383                 "XML 1.1 should omit #x0");
384         assertEquals("a&#1;&#8;&#11;&#12;&#14;&#31;b",
385                 StringEscapeUtils.escapeXml11("a\u0001\u0008\u000b\u000c\u000e\u001fb"),
386                 "XML 1.1 should escape #x1-x8 | #xb | #xc | #xe-#x19");
387         assertEquals("a\u007e&#127;&#132;\u0085&#134;&#159;\u00a0b",
388                 StringEscapeUtils.escapeXml11("a\u007e\u007f\u0084\u0085\u0086\u009f\u00a0b"),
389                 "XML 1.1 should escape #x7F-#x84 | #x86-#x9F");
390         assertEquals("a\ud7ff  \ue000b", StringEscapeUtils.escapeXml11("a\ud7ff\ud800 \udfff \ue000b"),
391                 "XML 1.1 should omit #xd800-#xdfff");
392         assertEquals("a\ufffdb", StringEscapeUtils.escapeXml11("a\ufffd\ufffe\uffffb"),
393                 "XML 1.1 should omit #xfffe | #xffff");
394     }
395 
396     @Test
397     public void testEscapeXSI() {
398         assertNull(null, escapeXSI(null));
399         assertEquals("He\\ didn\\'t\\ say,\\ \\\"Stop!\\\"", escapeXSI("He didn't say, \"Stop!\""));
400         assertEquals("\\\\", escapeXSI("\\"));
401         assertEquals("", escapeXSI("\n"));
402     }
403 
404     @Test
405     public void testLang313() {
406         assertEquals("& &", StringEscapeUtils.unescapeHtml4("& &amp;"));
407     }
408 
409     /**
410      * Tests https://issues.apache.org/jira/browse/LANG-708
411      *
412      * @throws IOException
413      *             if an I/O error occurs
414      */
415     @Test
416     public void testLang708() throws IOException {
417         final byte[] inputBytes = Files.readAllBytes(Paths.get("src/test/resources/org/apache/commons/text/stringEscapeUtilsTestData.txt"));
418         final String input = new String(inputBytes, StandardCharsets.UTF_8);
419         final String escaped = StringEscapeUtils.escapeEcmaScript(input);
420         // just the end:
421         assertTrue(escaped.endsWith("}]"), escaped);
422         // a little more:
423         assertTrue(escaped.endsWith("\"valueCode\\\":\\\"\\\"}]"), escaped);
424     }
425 
426     /**
427      * Tests https://issues.apache.org/jira/browse/LANG-911
428      */
429     @Test
430     public void testLang911() {
431         final String bellsTest = "\ud83d\udc80\ud83d\udd14";
432         final String value = StringEscapeUtils.escapeJava(bellsTest);
433         final String valueTest = StringEscapeUtils.unescapeJava(value);
434         assertEquals(bellsTest, valueTest);
435     }
436 
437     // Tests issue #38569
438     // https://issues.apache.org/bugzilla/show_bug.cgi?id=38569
439     @Test
440     public void testStandaloneAmphersand() {
441         assertEquals("<P&O>", StringEscapeUtils.unescapeHtml4("&lt;P&O&gt;"));
442         assertEquals("test & <", StringEscapeUtils.unescapeHtml4("test & &lt;"));
443         assertEquals("<P&O>", StringEscapeUtils.unescapeXml("&lt;P&O&gt;"));
444         assertEquals("test & <", StringEscapeUtils.unescapeXml("test & &lt;"));
445     }
446 
447     @Test
448     public void testUnescapeCsvString() {
449         assertEquals("foo.bar", StringEscapeUtils.unescapeCsv("foo.bar"));
450         assertEquals("foo,bar", StringEscapeUtils.unescapeCsv("\"foo,bar\""));
451         assertEquals("foo\nbar", StringEscapeUtils.unescapeCsv("\"foo\nbar\""));
452         assertEquals("foo\rbar", StringEscapeUtils.unescapeCsv("\"foo\rbar\""));
453         assertEquals("foo\"bar", StringEscapeUtils.unescapeCsv("\"foo\"\"bar\""));
454         assertEquals("foo\uD84C\uDFB4bar", StringEscapeUtils.unescapeCsv("foo\uD84C\uDFB4bar"));
455         assertEquals("", StringEscapeUtils.unescapeCsv(""));
456         assertNull(StringEscapeUtils.unescapeCsv(null));
457 
458         assertEquals("foo.bar", StringEscapeUtils.unescapeCsv("\"foo.bar\""));
459     }
460 
461     @Test
462     public void testUnescapeCsvWriter() throws IOException {
463         checkCsvUnescapeWriter("foo.bar", "foo.bar");
464         checkCsvUnescapeWriter("foo,bar", "\"foo,bar\"");
465         checkCsvUnescapeWriter("foo\nbar", "\"foo\nbar\"");
466         checkCsvUnescapeWriter("foo\rbar", "\"foo\rbar\"");
467         checkCsvUnescapeWriter("foo\"bar", "\"foo\"\"bar\"");
468         checkCsvUnescapeWriter("foo\uD84C\uDFB4bar", "foo\uD84C\uDFB4bar");
469         checkCsvUnescapeWriter("", null);
470         checkCsvUnescapeWriter("", "");
471 
472         checkCsvUnescapeWriter("foo.bar", "\"foo.bar\"");
473     }
474 
475     @Test
476     public void testUnescapeEcmaScript() {
477         assertNull(StringEscapeUtils.unescapeEcmaScript(null));
478         assertEquals("8lvc1u+6B#-I", StringEscapeUtils.unescapeEcmaScript("8lvc1u+6B#-I"));
479         assertEquals("<script src=\"build/main.bundle.js\"></script>",
480                 StringEscapeUtils.unescapeEcmaScript("<script src=\"build/main.bundle.js\"></script>"));
481         assertEquals("<script src=\"build/main.bundle.js\"></script>>",
482                 StringEscapeUtils.unescapeEcmaScript("<script src=\"build/main.bundle.js\"></script>>"));
483     }
484 
485     @Test
486     public void testUnescapeHexCharsHtml() {
487         // Simple easy to grok test
488         assertEquals("\u0080\u009F", StringEscapeUtils.unescapeHtml4("&#x80;&#x9F;"), "hex number unescape");
489         assertEquals("\u0080\u009F", StringEscapeUtils.unescapeHtml4("&#X80;&#X9F;"), "hex number unescape");
490         // Test all Character values:
491         for (char i = Character.MIN_VALUE; i < Character.MAX_VALUE; i++) {
492             final char c2 = (char) (i + 1);
493             final String expected = Character.toString(i) + Character.toString(c2);
494             final String escapedC1 = "&#x" + Integer.toHexString(i) + ";";
495             final String escapedC2 = "&#x" + Integer.toHexString(c2) + ";";
496             assertEquals(expected, StringEscapeUtils.unescapeHtml4(escapedC1 + escapedC2),
497                     "hex number unescape index " + i);
498         }
499     }
500 
501     @Test
502     public void testUnescapeHtml3() {
503         for (final String[] element : HTML_ESCAPES) {
504             final String message = element[0];
505             final String expected = element[2];
506             final String original = element[1];
507             assertEquals(expected, StringEscapeUtils.unescapeHtml3(original), message);
508 
509             final StringWriter sw = new StringWriter();
510             try {
511                 StringEscapeUtils.UNESCAPE_HTML3.translate(original, sw);
512             } catch (final IOException e) {
513                 // expected
514             }
515             final String actual = original == null ? null : sw.toString();
516             assertEquals(expected, actual, message);
517         }
518         // \u00E7 is a cedilla (c with wiggle under)
519         // note that the test string must be 7-bit-clean (Unicode escaped) or else it will compile incorrectly
520         // on some locales
521         assertEquals("Fran\u00E7ais", StringEscapeUtils.unescapeHtml3("Fran\u00E7ais"), "funny chars pass through OK");
522 
523         assertEquals("Hello&;World", StringEscapeUtils.unescapeHtml3("Hello&;World"));
524         assertEquals("Hello&#;World", StringEscapeUtils.unescapeHtml3("Hello&#;World"));
525         assertEquals("Hello&# ;World", StringEscapeUtils.unescapeHtml3("Hello&# ;World"));
526         assertEquals("Hello&##;World", StringEscapeUtils.unescapeHtml3("Hello&##;World"));
527     }
528 
529     @Test
530     public void testUnescapeHtml4() {
531         for (final String[] element : HTML_ESCAPES) {
532             final String message = element[0];
533             final String expected = element[2];
534             final String original = element[1];
535             assertEquals(expected, StringEscapeUtils.unescapeHtml4(original), message);
536 
537             final StringWriter sw = new StringWriter();
538             try {
539                 StringEscapeUtils.UNESCAPE_HTML4.translate(original, sw);
540             } catch (final IOException e) {
541                 // expected
542             }
543             final String actual = original == null ? null : sw.toString();
544             assertEquals(expected, actual, message);
545         }
546         // \u00E7 is a cedilla (c with wiggle under)
547         // note that the test string must be 7-bit-clean (Unicode escaped) or else it will compile incorrectly
548         // on some locales
549         assertEquals("Fran\u00E7ais", StringEscapeUtils.unescapeHtml4("Fran\u00E7ais"), "funny chars pass through OK");
550 
551         assertEquals("Hello&;World", StringEscapeUtils.unescapeHtml4("Hello&;World"));
552         assertEquals("Hello&#;World", StringEscapeUtils.unescapeHtml4("Hello&#;World"));
553         assertEquals("Hello&# ;World", StringEscapeUtils.unescapeHtml4("Hello&# ;World"));
554         assertEquals("Hello&##;World", StringEscapeUtils.unescapeHtml4("Hello&##;World"));
555     }
556 
557     @Test
558     public void testUnescapeJava() throws IOException {
559         assertNull(StringEscapeUtils.unescapeJava(null));
560         try {
561             StringEscapeUtils.UNESCAPE_JAVA.translate(null, null);
562             fail("Exception expected!");
563         } catch (final IOException ex) {
564             fail("Exception expected!");
565         } catch (final IllegalArgumentException ex) {
566             // expected
567         }
568         try {
569             StringEscapeUtils.UNESCAPE_JAVA.translate("", null);
570             fail("Exception expected!");
571         } catch (final IOException ex) {
572             fail("Exception expected!");
573         } catch (final IllegalArgumentException ex) {
574             // expected
575         }
576         assertThrows(RuntimeException.class, () -> StringEscapeUtils.unescapeJava("\\u02-3"));
577 
578         assertUnescapeJava("", "");
579         assertUnescapeJava("test", "test");
580         assertUnescapeJava("\ntest\b", "\\ntest\\b");
581         assertUnescapeJava("\u123425foo\ntest\b", "\\u123425foo\\ntest\\b");
582         assertUnescapeJava("'\foo\teste\r", "\\'\\foo\\teste\\r");
583         assertUnescapeJava("", "\\");
584         //foo
585         assertUnescapeJava("\uABCDx", "\\uabcdx", "lowercase Unicode");
586         assertUnescapeJava("\uABCDx", "\\uABCDx", "uppercase Unicode");
587         assertUnescapeJava("\uABCD", "\\uabcd", "Unicode as final character");
588     }
589 
590     @Test
591     public void testUnescapeJson() {
592         final String jsonString =
593                 "{\"age\":100,\"name\":\"kyong.com\n\",\"messages\":[\"msg 1\",\"msg 2\",\"msg 3\"]}";
594 
595         assertEquals("", StringEscapeUtils.unescapeJson(""));
596         assertEquals(" ", StringEscapeUtils.unescapeJson(" "));
597         assertEquals("a:b", StringEscapeUtils.unescapeJson("a:b"));
598         assertEquals(jsonString, StringEscapeUtils.unescapeJson(jsonString));
599     }
600 
601     @Test // TEXT-120
602     public void testUnescapeJsonDoubleQuoteAndForwardSlash() {
603       final String escapedJsonString = "double quote: \\\" and a forward slash: \\/";
604       final String jsonString = "double quote: \" and a forward slash: /";
605 
606       assertEquals(jsonString, StringEscapeUtils.unescapeJson(escapedJsonString));
607     }
608 
609     @Test
610     public void testUnescapeUnknownEntity() {
611         assertEquals("&zzzz;", StringEscapeUtils.unescapeHtml4("&zzzz;"));
612     }
613 
614     /**
615      * Reverse of the above.
616      *
617      * @see <a href="https://issues.apache.org/jira/browse/LANG-729">LANG-729</a>
618      */
619     @Test
620     public void testUnescapeXmlSupplementaryCharacters() {
621         assertEquals("\uD84C\uDFB4", StringEscapeUtils.unescapeXml("&#144308;"),
622                 "Supplementary character must be represented using a single escape");
623 
624         assertEquals("a b c \uD84C\uDFB4", StringEscapeUtils.unescapeXml("a b c &#144308;"),
625                 "Supplementary characters mixed with basic characters should be decoded correctly");
626     }
627 
628     @Test
629     public void testUnscapeXSI() {
630         assertNull(null, unescapeXSI(null));
631         assertEquals("\"", unescapeXSI("\\\""));
632         assertEquals("He didn't say, \"Stop!\"", unescapeXSI("He\\ didn\\'t\\ say,\\ \\\"Stop!\\\""));
633         assertEquals("\\", unescapeXSI("\\\\"));
634         assertEquals("", unescapeXSI("\\"));
635     }
636 }