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}