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 if (ch == '<') {
068                buf.append("&lt;");
069            } else if (ch == '>') {
070                buf.append("&gt;");
071            } else if (ch == '&') {
072                buf.append("&amp;");
073            } else if (ch == '"') {
074                buf.append("&quot;");
075            } else {
076                buf.append(ch);
077            }
078        }
079        return buf.toString();
080    }
081
082    /**
083     * Ensures that embedded CDEnd strings (]]&gt;) are handled properly
084     * within message, NDC and throwable tag text.
085     *
086     * @param buf StringBuilder holding the XML data to this point.  The
087     *            initial CDStart (&lt;![CDATA[) and final CDEnd (]]&gt;) of the CDATA
088     *            section are the responsibility of the calling method.
089     * @param str The String that is inserted into an existing CDATA Section within buf.
090     */
091    public static void appendEscapingCData(final StringBuilder buf, final String str) {
092        if (str != null) {
093            int end = str.indexOf(CDATA_END);
094            if (end < 0) {
095                buf.append(str);
096            } else {
097                int start = 0;
098                while (end > -1) {
099                    buf.append(str.substring(start, end));
100                    buf.append(CDATA_EMBEDED_END);
101                    start = end + CDATA_END_LEN;
102                    if (start < str.length()) {
103                        end = str.indexOf(CDATA_END, start);
104                    } else {
105                        return;
106                    }
107                }
108                buf.append(str.substring(start));
109            }
110        }
111    }
112
113    /**
114     * This method takes a string which may contain JSON reserved chars and
115     * escapes them.
116     *
117     * @param input The text to be converted.
118     * @return The input string with the special characters replaced.
119     */
120    public static String escapeJsonControlCharacters(final String input) {
121        // Check if the string is null, zero length or devoid of special characters
122        // if so, return what was sent in.
123
124        // TODO: escaped Unicode chars.
125
126        if (Strings.isEmpty(input)
127            || (input.indexOf('"') == -1 &&
128            input.indexOf('\\') == -1 &&
129            input.indexOf('/') == -1 &&
130            input.indexOf('\b') == -1 &&
131            input.indexOf('\f') == -1 &&
132            input.indexOf('\n') == -1 &&
133            input.indexOf('\r') == -1 &&
134            input.indexOf('\t') == -1)) {
135            return input;
136        }
137
138        final StringBuilder buf = new StringBuilder(input.length() + 6);
139
140        final int len = input.length();
141        for (int i = 0; i < len; i++) {
142            final char ch = input.charAt(i);
143            final String escBs = "\\";
144            switch (ch) {
145            case '"':
146                buf.append(escBs);
147                buf.append(ch);
148                break;
149            case '\\':
150                buf.append(escBs);
151                buf.append(ch);
152                break;
153            case '/':
154                buf.append(escBs);
155                buf.append(ch);
156                break;
157            case '\b':
158                buf.append(escBs);
159                buf.append('b');
160                break;
161            case '\f':
162                buf.append(escBs);
163                buf.append('f');
164                break;
165            case '\n':
166                buf.append(escBs);
167                buf.append('n');
168                break;
169            case '\r':
170                buf.append(escBs);
171                buf.append('r');
172                break;
173            case '\t':
174                buf.append(escBs);
175                buf.append('t');
176                break;
177            default:
178                buf.append(ch);
179            }
180        }
181        return buf.toString();
182    }
183}