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