View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.pattern;
18  
19  import java.util.List;
20  
21  import org.apache.logging.log4j.core.LogEvent;
22  import org.apache.logging.log4j.core.config.Configuration;
23  import org.apache.logging.log4j.core.config.plugins.Plugin;
24  import org.apache.logging.log4j.core.layout.PatternLayout;
25  import org.apache.logging.log4j.util.Chars;
26  import org.apache.logging.log4j.util.EnglishEnums;
27  import org.apache.logging.log4j.util.PerformanceSensitive;
28  import org.apache.logging.log4j.util.StringBuilders;
29  
30  /**
31   * Converter that encodes the output from a pattern using a specified format. Supported formats include HTML
32   * (default) and JSON.
33   */
34  @Plugin(name = "encode", category = PatternConverter.CATEGORY)
35  @ConverterKeys({"enc", "encode"})
36  @PerformanceSensitive("allocation")
37  public final class EncodingPatternConverter extends LogEventPatternConverter {
38  
39      private final List<PatternFormatter> formatters;
40      private final EscapeFormat escapeFormat;
41  
42      /**
43       * Private constructor.
44       *
45       * @param formatters   the PatternFormatters to generate the text to manipulate.
46       * @param escapeFormat the escape format strategy to use for encoding output of formatters
47       */
48      private EncodingPatternConverter(final List<PatternFormatter> formatters,
49                                       final EscapeFormat escapeFormat) {
50          super("encode", "encode");
51          this.formatters = formatters;
52          this.escapeFormat = escapeFormat;
53      }
54  
55      /**
56       * Creates an EncodingPatternConverter using a pattern string and an optional escape format.
57       *
58       * @param config  the current Configuration
59       * @param options first option is the nested pattern format; second option is the escape format (optional)
60       * @return instance of pattern converter.
61       */
62      public static EncodingPatternConverter newInstance(final Configuration config, final String[] options) {
63          if (options.length > 2 || options.length == 0) {
64              LOGGER.error("Incorrect number of options on escape. Expected 1 or 2, but received {}",
65                  options.length);
66              return null;
67          }
68          if (options[0] == null) {
69              LOGGER.error("No pattern supplied on escape");
70              return null;
71          }
72          final EscapeFormat escapeFormat = options.length < 2 ? EscapeFormat.HTML
73              : EnglishEnums.valueOf(EscapeFormat.class, options[1], EscapeFormat.HTML);
74          final PatternParser parser = PatternLayout.createPatternParser(config);
75          final List<PatternFormatter> formatters = parser.parse(options[0]);
76          return new EncodingPatternConverter(formatters, escapeFormat);
77      }
78  
79      /**
80       * {@inheritDoc}
81       */
82      @Override
83      public void format(final LogEvent event, final StringBuilder toAppendTo) {
84          final int start = toAppendTo.length();
85          for (int i = 0; i < formatters.size(); i++) {
86              formatters.get(i).format(event, toAppendTo);
87          }
88          escapeFormat.escape(toAppendTo, start);
89      }
90  
91      private enum EscapeFormat {
92          HTML {
93              @Override
94              void escape(final StringBuilder toAppendTo, final int start) {
95                  for (int i = toAppendTo.length() - 1; i >= start; i--) { // backwards: length may change
96                      final char c = toAppendTo.charAt(i);
97                      switch (c) {
98                          case '\r':
99                              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 }