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