001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.util;
018
019import org.apache.logging.log4j.util.Strings;
020
021
022/**
023 * Utility class for transforming strings.
024 */
025public final class Transform {
026
027    private static final String CDATA_START = "<![CDATA[";
028    private static final String CDATA_END = "]]>";
029    private static final String CDATA_PSEUDO_END = "]]&gt;";
030    private static final String CDATA_EMBEDED_END = CDATA_END + CDATA_PSEUDO_END + CDATA_START;
031    private static final int CDATA_END_LEN = CDATA_END.length();
032
033    private Transform() {
034    }
035
036    /**
037     * This method takes a string which may contain HTML tags (ie,
038     * &lt;b&gt;, &lt;table&gt;, etc) and replaces any
039     * '&lt;',  '&gt;' , '&amp;' or '&quot;'
040     * characters with respective predefined entity references.
041     *
042     * @param input The text to be converted.
043     * @return The input string with the special characters replaced.
044     */
045    public static String escapeHtmlTags(final String input) {
046        // Check if the string is null, zero length or devoid of special characters
047        // if so, return what was sent in.
048
049        if (Strings.isEmpty(input)
050            || (input.indexOf('"') == -1 &&
051            input.indexOf('&') == -1 &&
052            input.indexOf('<') == -1 &&
053            input.indexOf('>') == -1)) {
054            return input;
055        }
056
057        //Use a StringBuilder in lieu of String concatenation -- it is
058        //much more efficient this way.
059
060        final StringBuilder buf = new StringBuilder(input.length() + 6);
061
062        final int len = input.length();
063        for (int i = 0; i < len; i++) {
064            final char ch = input.charAt(i);
065            if (ch > '>') {
066                buf.append(ch);
067            } else
068                switch (ch) {
069                case '<':
070                    buf.append("&lt;");
071                    break;
072                case '>':
073                    buf.append("&gt;");
074                    break;
075                case '&':
076                    buf.append("&amp;");
077                    break;
078                case '"':
079                    buf.append("&quot;");
080                    break;
081                default:
082                    buf.append(ch);
083                    break;
084                }
085        }
086        return buf.toString();
087    }
088
089    /**
090     * Ensures that embedded CDEnd strings (]]&gt;) are handled properly
091     * within message, NDC and throwable tag text.
092     *
093     * @param buf StringBuilder holding the XML data to this point.  The
094     *            initial CDStart (&lt;![CDATA[) and final CDEnd (]]&gt;) of the CDATA
095     *            section are the responsibility of the calling method.
096     * @param str The String that is inserted into an existing CDATA Section within buf.
097     */
098    public static void appendEscapingCData(final StringBuilder buf, final String str) {
099        if (str != null) {
100            int end = str.indexOf(CDATA_END);
101            if (end < 0) {
102                buf.append(str);
103            } else {
104                int start = 0;
105                while (end > -1) {
106                    buf.append(str.substring(start, end));
107                    buf.append(CDATA_EMBEDED_END);
108                    start = end + CDATA_END_LEN;
109                    if (start < str.length()) {
110                        end = str.indexOf(CDATA_END, start);
111                    } else {
112                        return;
113                    }
114                }
115                buf.append(str.substring(start));
116            }
117        }
118    }
119
120    /**
121     * This method takes a string which may contain JSON reserved chars and
122     * escapes them.
123     *
124     * @param input The text to be converted.
125     * @return The input string with the special characters replaced.
126     */
127    public static String escapeJsonControlCharacters(final String input) {
128        // Check if the string is null, zero length or devoid of special characters
129        // if so, return what was sent in.
130
131        // TODO: escaped Unicode chars.
132
133        if (Strings.isEmpty(input)
134            || (input.indexOf('"') == -1 &&
135            input.indexOf('\\') == -1 &&
136            input.indexOf('/') == -1 &&
137            input.indexOf('\b') == -1 &&
138            input.indexOf('\f') == -1 &&
139            input.indexOf('\n') == -1 &&
140            input.indexOf('\r') == -1 &&
141            input.indexOf('\t') == -1)) {
142            return input;
143        }
144
145        final StringBuilder buf = new StringBuilder(input.length() + 6);
146
147        final int len = input.length();
148        for (int i = 0; i < len; i++) {
149            final char ch = input.charAt(i);
150            final String escBs = "\\";
151            switch (ch) {
152            case '"':
153                buf.append(escBs);
154                buf.append(ch);
155                break;
156            case '\\':
157                buf.append(escBs);
158                buf.append(ch);
159                break;
160            case '/':
161                buf.append(escBs);
162                buf.append(ch);
163                break;
164            case '\b':
165                buf.append(escBs);
166                buf.append('b');
167                break;
168            case '\f':
169                buf.append(escBs);
170                buf.append('f');
171                break;
172            case '\n':
173                buf.append(escBs);
174                buf.append('n');
175                break;
176            case '\r':
177                buf.append(escBs);
178                buf.append('r');
179                break;
180            case '\t':
181                buf.append(escBs);
182                buf.append('t');
183                break;
184            default:
185                buf.append(ch);
186            }
187        }
188        return buf.toString();
189    }
190}