View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.helpers;
15  
16  import java.util.regex.Pattern;
17  
18  /**
19   * Utility class for transforming strings.
20   * 
21   * @author Ceki Gülcü
22   * @author Michael A. McAngus
23   */
24  public class Transform {
25      private static final String CDATA_START = "<![CDATA[";
26      private static final String CDATA_END = "]]>";
27      private static final String CDATA_PSEUDO_END = "]]&gt;";
28      private static final String CDATA_EMBEDED_END = CDATA_END + CDATA_PSEUDO_END + CDATA_START;
29      private static final int CDATA_END_LEN = CDATA_END.length();
30      private static final Pattern UNSAFE_XML_CHARS = Pattern.compile("[\u0000-\u0008\u000b\u000c\u000e-\u001f<>&'\"]");
31  
32      /**
33       * This method takes a string which may contain HTML tags (ie, &lt;b&gt;,
34       * &lt;table&gt;, etc) and replaces any '&lt;','&gt;' ... characters with
35       * respective predefined entity references.
36       * 
37       * @param input The text to be converted.
38       */
39      public static String escapeTags(final String input) {
40          if (input == null || input.length() == 0 || !UNSAFE_XML_CHARS.matcher(input).find()) {
41              return input;
42          }
43          StringBuffer buf = new StringBuffer(input);
44          return escapeTags(buf);
45      }
46  
47      /**
48       * This method takes a StringBuilder which may contain HTML tags (ie, &lt;b&gt;,
49       * &lt;table&gt;, etc) and replaces any '&lt;' and '&gt;' characters with
50       * respective predefined entity references.
51       * 
52       * @param buf StringBuffer to transform
53       * @return
54       */
55      public static String escapeTags(final StringBuffer buf) {
56          for (int i = 0; i < buf.length(); i++) {
57              char ch = buf.charAt(i);
58              switch (ch) {
59              case '\t':
60              case '\n':
61              case '\r':
62                  // These characters are below '\u0020' but are allowed:
63                  break;
64              case '&':
65                  buf.replace(i, i + 1, "&amp;");
66                  break;
67              case '<':
68                  buf.replace(i, i + 1, "&lt;");
69                  break;
70              case '>':
71                  buf.replace(i, i + 1, "&gt;");
72                  break;
73              case '"':
74                  buf.replace(i, i + 1, "&quot;");
75                  break;
76              case '\'':
77                  buf.replace(i, i + 1, "&#39;");
78                  break;
79              default:
80                  if (ch < '\u0020') {
81                      // These characters are not allowed,
82                      // replace them with "Object replacement character":
83                      buf.replace(i, i + 1, "\uFFFD");
84                  }
85                  break;
86              }
87          }
88          return buf.toString();
89      }
90  
91      /**
92       * Ensures that embedded CDEnd strings (]]&gt;) are handled properly within
93       * message, NDC and throwable tag text.
94       * 
95       * @param output Writer. The initial CDStart (&lt;![CDATA[) and final CDEnd
96       *               (]]&gt;) of the CDATA section are the responsibility of the
97       *               calling method.
98       * 
99       * @param str    The String that is inserted into an existing CDATA Section.
100      */
101     public static void appendEscapingCDATA(StringBuilder output, String str) {
102         if (str == null) {
103             return;
104         }
105 
106         int end = str.indexOf(CDATA_END);
107 
108         if (end < 0) {
109             output.append(str);
110 
111             return;
112         }
113 
114         int start = 0;
115 
116         while (end > -1) {
117             output.append(str.substring(start, end));
118             output.append(CDATA_EMBEDED_END);
119             start = end + CDATA_END_LEN;
120 
121             if (start < str.length()) {
122                 end = str.indexOf(CDATA_END, start);
123             } else {
124                 return;
125             }
126         }
127 
128         output.append(str.substring(start));
129     }
130 }