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
38       *          The text to be converted.
39       */
40      public static String escapeTags(final String input) {
41          if (input == null || input.length() == 0 || !UNSAFE_XML_CHARS.matcher(input).find()) {
42              return input;
43          }
44          StringBuffer buf = new StringBuffer(input);
45          return escapeTags(buf);
46      }
47  
48      /**
49       * This method takes a StringBuilder which may contain HTML tags (ie, &lt;b&gt;,
50       * &lt;table&gt;, etc) and replaces any '&lt;' and '&gt;' characters with
51       * respective predefined entity references.
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
96       *          Writer. The initial CDStart (&lt;![CDATA[) and final CDEnd (]]&gt;) of
97       *          the CDATA section are the responsibility of the calling method.
98       * 
99       * @param str
100      *          The String that is inserted into an existing CDATA Section.
101      */
102     public static void appendEscapingCDATA(StringBuilder output, String str) {
103         if (str == null) {
104             return;
105         }
106 
107         int end = str.indexOf(CDATA_END);
108 
109         if (end < 0) {
110             output.append(str);
111 
112             return;
113         }
114 
115         int start = 0;
116 
117         while (end > -1) {
118             output.append(str.substring(start, end));
119             output.append(CDATA_EMBEDED_END);
120             start = end + CDATA_END_LEN;
121 
122             if (start < str.length()) {
123                 end = str.indexOf(CDATA_END, start);
124             } else {
125                 return;
126             }
127         }
128 
129         output.append(str.substring(start));
130     }
131 }