blob: 1824560985c714e85e9d6b13846bf8c65b39dbc3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.catalina.util;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Map;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.buf.B2CConverter;
import org.apache.tomcat.util.res.StringManager;
/**
* General purpose request parsing and encoding utility methods.
*
* @author Craig R. McClanahan
* @author Tim Tye
*/
public final class RequestUtil {
private static final Log log = LogFactory.getLog(RequestUtil.class);
/**
* The string resources for this package.
*/
private static final StringManager sm =
StringManager.getManager("org.apache.catalina.util");
/**
* Filter the specified message string for characters that are sensitive
* in HTML. This avoids potential attacks caused by including JavaScript
* codes in the request URL that is often reported in error messages.
*
* @param message The message string to be filtered
*/
public static String filter(String message) {
if (message == null)
return (null);
char content[] = new char[message.length()];
message.getChars(0, message.length(), content, 0);
StringBuilder result = new StringBuilder(content.length + 50);
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case '<':
result.append("&lt;");
break;
case '>':
result.append("&gt;");
break;
case '&':
result.append("&amp;");
break;
case '"':
result.append("&quot;");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
/**
* Append request parameters from the specified String to the specified
* Map. It is presumed that the specified Map is not accessed from any
* other thread, so no synchronization is performed.
* <p>
* <strong>IMPLEMENTATION NOTE</strong>: URL decoding is performed
* individually on the parsed name and value elements, rather than on
* the entire query string ahead of time, to properly deal with the case
* where the name or value includes an encoded "=" or "&amp;" character
* that would otherwise be interpreted as a delimiter.
*
* @param map Map that accumulates the resulting parameters
* @param data Input string containing request parameters
* @param encoding The encoding to use; encoding must not be null.
* If an unsupported encoding is specified the parameters will not be
* parsed and the map will not be modified
*
* @deprecated Unused. This will be removed in Tomcat 8.5.x.
*/
@Deprecated
public static void parseParameters(Map<String,String[]> map, String data,
String encoding) {
if ((data != null) && (data.length() > 0)) {
// use the specified encoding to extract bytes out of the
// given string so that the encoding is not lost.
byte[] bytes = null;
try {
bytes = data.getBytes(B2CConverter.getCharset(encoding));
parseParameters(map, bytes, encoding);
} catch (UnsupportedEncodingException uee) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("requestUtil.parseParameters.uee",
encoding), uee);
}
}
}
}
/**
* Convert a byte character value to hexadecimal digit value.
*
* @param b the character value byte
*/
private static byte convertHexDigit( byte b ) {
if ((b >= '0') && (b <= '9')) return (byte)(b - '0');
if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10);
if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10);
throw new IllegalArgumentException(
sm.getString("requestUtil.convertHexDigit.notHex",
Character.valueOf((char)b)));
}
/**
* Put name and value pair in map. When name already exist, add value
* to array of values.
*
* @param map The map to populate
* @param name The parameter name
* @param value The parameter value
*/
private static void putMapEntry( Map<String,String[]> map, String name,
String value) {
String[] newValues = null;
String[] oldValues = map.get(name);
if (oldValues == null) {
newValues = new String[1];
newValues[0] = value;
} else {
newValues = new String[oldValues.length + 1];
System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
newValues[oldValues.length] = value;
}
map.put(name, newValues);
}
/**
* Append request parameters from the specified String to the specified
* Map. It is presumed that the specified Map is not accessed from any
* other thread, so no synchronization is performed.
* <p>
* <strong>IMPLEMENTATION NOTE</strong>: URL decoding is performed
* individually on the parsed name and value elements, rather than on
* the entire query string ahead of time, to properly deal with the case
* where the name or value includes an encoded "=" or "&amp;" character
* that would otherwise be interpreted as a delimiter.
*
* NOTE: byte array data is modified by this method. Caller beware.
*
* @param map Map that accumulates the resulting parameters
* @param data Input string containing request parameters
* @param encoding The encoding to use; if null, the default encoding is
* used
*
* @exception UnsupportedEncodingException if the requested encoding is not
* supported.
*
* @deprecated Unused. This will be removed in Tomcat 8.5.x.
*/
@Deprecated
public static void parseParameters(Map<String,String[]> map, byte[] data,
String encoding) throws UnsupportedEncodingException {
Charset charset = B2CConverter.getCharset(encoding);
if (data != null && data.length > 0) {
int ix = 0;
int ox = 0;
String key = null;
String value = null;
while (ix < data.length) {
byte c = data[ix++];
switch ((char) c) {
case '&':
value = new String(data, 0, ox, charset);
if (key != null) {
putMapEntry(map, key, value);
key = null;
}
ox = 0;
break;
case '=':
if (key == null) {
key = new String(data, 0, ox, charset);
ox = 0;
} else {
data[ox++] = c;
}
break;
case '+':
data[ox++] = (byte)' ';
break;
case '%':
data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4)
+ convertHexDigit(data[ix++]));
break;
default:
data[ox++] = c;
}
}
//The last value does not end in '&'. So save it now.
if (key != null) {
value = new String(data, 0, ox, charset);
putMapEntry(map, key, value);
}
}
}
}