blob: 88656d496adb00c39b73ea03bf4893657c43e563 [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.cocoon.servletservice.util;
import org.apache.commons.collections.iterators.IteratorEnumeration;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
/**
* This class is used by the <code>RequestWrapper</code>. It parses
* a query string, decodes request parameters, and creates a parameter map
* ready for use by the <code>Request</code> object.
*
* @version $Id$
* @since 1.0.0
*/
public final class RequestParameters implements Serializable {
/**
* The parameter names are the keys and the value is a String array object
*/
private final Map values;
/**
* Construct a new object from a queryString
*
* @param queryString request query string
*/
public RequestParameters(String queryString) {
this.values = new HashMap(5);
if (queryString != null) {
StringTokenizer st = new StringTokenizer(queryString, "&");
while (st.hasMoreTokens()) {
String pair = st.nextToken();
int pos = pair.indexOf('=');
if (pos != -1) {
setParameter(decode(pair.substring(0, pos)),
decode(pair.substring(pos + 1, pair.length())));
} else {
setParameter(decode(pair), "");
}
}
}
}
/**
* Add a parameter.
* The parameter is added with the given value.
*
* @param name The name of the parameter.
* @param value The value of the parameter.
*/
private void setParameter(String name, String value) {
String[] values = (String[]) this.values.get(name);
if (values == null) {
values = new String[] { value };
} else {
String[] v2 = new String[values.length + 1];
System.arraycopy(values, 0, v2, 0, values.length);
v2[values.length] = value;
values = v2;
}
this.values.put(name, values);
}
/**
* Get the value of a parameter.
*
* @param name The name of the parameter.
* @return The value of the first parameter with the name
* or <CODE>null</CODE>
*/
public String getParameter(String name) {
String[] values = (String[]) this.values.get(name);
if (values != null) {
return values[0];
}
return null;
}
/**
* Get the value of a parameter.
*
* @param name The name of the parameter.
* @param defaultValue The default value if the parameter does not exist.
* @return The value of the first parameter with the name
* or <CODE>defaultValue</CODE>
*/
public String getParameter(String name, String defaultValue) {
String[] values = (String[]) this.values.get(name);
if (values != null) {
return values[0];
}
return defaultValue;
}
/**
* Get all values of a parameter.
*
* @param name The name of the parameter.
* @return Array of the (String) values or null if the parameter
* is not defined.
*/
public String[] getParameterValues(String name) {
return (String[]) values.get(name);
}
/**
* Get all parameter names.
*
* @return Enumeration for the (String) parameter names.
*/
public Enumeration getParameterNames() {
return new IteratorEnumeration(values.keySet().iterator());
}
/**
* Get map of parameter names to String array values
*
* @return map of parameter values
*/
public Map getParameterMap() {
return Collections.unmodifiableMap(values);
}
/**
* Decodes URL encoded string. Supports decoding of '+', '%XX' encoding,
* and '%uXXXX' encoding.
*
* @param s URL encoded string
* @return decoded string
*/
private static String decode(String s) {
byte[] decoded = new byte[s.length() / 3 + 1];
int decodedLength = 0;
final int length = s.length();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; ) {
char c = s.charAt(i);
switch (c) {
case '+':
sb.append(' ');
i++;
break;
case '%':
// Check if this is a non-standard %u9999 encoding
// (see COCOON-1950)
try {
if (s.charAt(i + 1) == 'u') {
sb.append((char) Integer.parseInt(s.substring(i + 2, i + 6), 16));
i += 6;
break;
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid query string. " +
"Illegal hex characters in pattern %" + s.substring(i + 1, i + 6));
} catch (StringIndexOutOfBoundsException e) {
throw new IllegalArgumentException("Invalid query string. " +
"% character should be followed by 2 hexadecimal characters.");
}
// Process sequence of %XX encoded bytes in one go since several bytes can represent
// single character.
while (i < length && s.charAt(i) == '%') {
if (i + 2 >= length) {
throw new IllegalArgumentException("Invalid query string. " +
"% character should be followed by 2 hexadecimal characters.");
}
// If found %u9999, rollback '%' and finish this loop
if (s.charAt(i + 1) == 'u') {
i--;
break;
}
try {
decoded[decodedLength++] = (byte) Integer.parseInt(s.substring(i + 1, i + 3), 16);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid query string. " +
"Illegal hex characters in pattern %" + s.substring(i + 1, i + 3));
}
i += 3;
}
if (decodedLength > 0) {
try {
sb.append(new String(decoded, 0, decodedLength, "UTF-8"));
} catch (UnsupportedEncodingException e) {
// The situation that UTF-8 is not supported is quite theoretical, so throw a runtime exception
throw new RuntimeException("Problem in decode: UTF-8 encoding not supported.");
}
decodedLength = 0;
}
break;
default:
sb.append(c);
i++;
break;
}
}
return sb.toString();
}
}