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.pattern;
018
019import java.util.List;
020
021import org.apache.logging.log4j.core.LogEvent;
022import org.apache.logging.log4j.core.config.Configuration;
023import org.apache.logging.log4j.core.config.plugins.Plugin;
024import org.apache.logging.log4j.core.layout.PatternLayout;
025import org.apache.logging.log4j.util.Chars;
026import org.apache.logging.log4j.util.EnglishEnums;
027import org.apache.logging.log4j.util.PerformanceSensitive;
028import org.apache.logging.log4j.util.StringBuilders;
029
030/**
031 * Converter that encodes the output from a pattern using a specified format. Supported formats include HTML
032 * (default) and JSON.
033 */
034@Plugin(name = "encode", category = PatternConverter.CATEGORY)
035@ConverterKeys({"enc", "encode"})
036@PerformanceSensitive("allocation")
037public final class EncodingPatternConverter extends LogEventPatternConverter {
038
039    private final List<PatternFormatter> formatters;
040    private final EscapeFormat escapeFormat;
041
042    /**
043     * Private constructor.
044     *
045     * @param formatters   the PatternFormatters to generate the text to manipulate.
046     * @param escapeFormat the escape format strategy to use for encoding output of formatters
047     */
048    private EncodingPatternConverter(final List<PatternFormatter> formatters,
049                                     final EscapeFormat escapeFormat) {
050        super("encode", "encode");
051        this.formatters = formatters;
052        this.escapeFormat = escapeFormat;
053    }
054
055    /**
056     * Creates an EncodingPatternConverter using a pattern string and an optional escape format.
057     *
058     * @param config  the current Configuration
059     * @param options first option is the nested pattern format; second option is the escape format (optional)
060     * @return instance of pattern converter.
061     */
062    public static EncodingPatternConverter newInstance(final Configuration config, final String[] options) {
063        if (options.length > 2 || options.length == 0) {
064            LOGGER.error("Incorrect number of options on escape. Expected 1 or 2, but received {}",
065                options.length);
066            return null;
067        }
068        if (options[0] == null) {
069            LOGGER.error("No pattern supplied on escape");
070            return null;
071        }
072        final EscapeFormat escapeFormat = options.length < 2 ? EscapeFormat.HTML
073            : EnglishEnums.valueOf(EscapeFormat.class, options[1], EscapeFormat.HTML);
074        final PatternParser parser = PatternLayout.createPatternParser(config);
075        final List<PatternFormatter> formatters = parser.parse(options[0]);
076        return new EncodingPatternConverter(formatters, escapeFormat);
077    }
078
079    /**
080     * {@inheritDoc}
081     */
082    @Override
083    public void format(final LogEvent event, final StringBuilder toAppendTo) {
084        final int start = toAppendTo.length();
085        for (int i = 0; i < formatters.size(); i++) {
086            formatters.get(i).format(event, toAppendTo);
087        }
088        escapeFormat.escape(toAppendTo, start);
089    }
090
091    private enum EscapeFormat {
092        HTML {
093            @Override
094            void escape(final StringBuilder toAppendTo, final int start) {
095                for (int i = toAppendTo.length() - 1; i >= start; i--) { // backwards: length may change
096                    final char c = toAppendTo.charAt(i);
097                    switch (c) {
098                        case '\r':
099                            toAppendTo.setCharAt(i, '\\');
100                            toAppendTo.insert(i + 1, 'r');
101                            break;
102                        case '\n':
103                            toAppendTo.setCharAt(i, '\\');
104                            toAppendTo.insert(i + 1, 'n');
105                            break;
106                        case '&':
107                            toAppendTo.setCharAt(i, '&');
108                            toAppendTo.insert(i + 1, "amp;");
109                            break;
110                        case '<':
111                            toAppendTo.setCharAt(i, '&');
112                            toAppendTo.insert(i + 1, "lt;");
113                            break;
114                        case '>':
115                            toAppendTo.setCharAt(i, '&');
116                            toAppendTo.insert(i + 1, "gt;");
117                            break;
118                        case '"':
119                            toAppendTo.setCharAt(i, '&');
120                            toAppendTo.insert(i + 1, "quot;");
121                            break;
122                        case '\'':
123                            toAppendTo.setCharAt(i, '&');
124                            toAppendTo.insert(i + 1, "apos;");
125                            break;
126                        case '/':
127                            toAppendTo.setCharAt(i, '&');
128                            toAppendTo.insert(i + 1, "#x2F;");
129                            break;
130                    }
131                }
132            }
133        },
134
135        /**
136         * JSON string escaping as defined in RFC 4627.
137         *
138         * @see <a href="https://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>
139         */
140        JSON {
141            @Override
142            void escape(final StringBuilder toAppendTo, final int start) {
143                StringBuilders.escapeJson(toAppendTo, start);
144            }
145        },
146
147        CRLF {
148            @Override
149            void escape(final StringBuilder toAppendTo, final int start) {
150                for (int i = toAppendTo.length() - 1; i >= start; i--) { // backwards: length may change
151                    final char c = toAppendTo.charAt(i);
152                    switch (c) {
153                        case '\r':
154                            toAppendTo.setCharAt(i, '\\');
155                            toAppendTo.insert(i + 1, 'r');
156                            break;
157                        case '\n':
158                            toAppendTo.setCharAt(i, '\\');
159                            toAppendTo.insert(i + 1, 'n');
160                            break;
161                    }
162                }
163            }
164        },
165
166        /**
167         * XML string escaping as defined in XML specification.
168         *
169         * @see <a href="https://www.w3.org/TR/xml/">XML specification</a>
170         */
171        XML {
172            @Override
173            void escape(final StringBuilder toAppendTo, final int start) {
174                StringBuilders.escapeXml(toAppendTo, start);
175            }
176        };
177
178        /**
179         * Escapes text using a standardized format from a given starting point to the end of the string.
180         *
181         * @param toAppendTo string buffer to escape
182         * @param start      where to start escaping from
183         */
184        abstract void escape(final StringBuilder toAppendTo, final int start);
185    }
186}