/* * Copyright (C) 2000 - 2013 TagServlet Ltd * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://openbd.org/ * $Id: RequestUtil.java 2374 2013-06-10 22:14:24Z alan $ */ /* * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. * * [Additional notices, if required by prior licensing conditions] * */ package com.naryx.tagfusion.util; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.servlet.http.Cookie; import com.naryx.tagfusion.cfm.engine.cfEngine; /** * General purpose request parsing and encoding utility methods. */ public final class RequestUtil extends Object { /** * Encode a cookie as per RFC 2109. The resulting string can be used as the value for a <code>Set-Cookie</code> header. * * @param cookie * The cookie to encode. * @return A string following RFC 2109. */ public static String encodeCookie(Cookie cookie) { StringBuilder buf = new StringBuilder(cookie.getName()); buf.append("="); buf.append(cookie.getValue()); if (cookie.getComment() != null) { buf.append("; Comment=\""); buf.append(cookie.getComment()); buf.append("\""); } if (cookie.getDomain() != null) { buf.append("; Domain=\""); buf.append(cookie.getDomain()); buf.append("\""); } long age = cookie.getMaxAge(); if (cookie.getMaxAge() >= 0) { buf.append("; Max-Age=\""); buf.append(age); buf.append("\""); } if (cookie.getPath() != null) { buf.append("; Path=\""); buf.append(cookie.getPath()); buf.append("\""); } if (cookie.getSecure()) { buf.append("; Secure"); } if (cookie.getVersion() > 0) { buf.append("; Version=\""); buf.append(cookie.getVersion()); buf.append("\""); } return (buf.toString()); } /** * 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("<"); break; case '>': result.append(">"); break; case '&': result.append("&"); break; case '"': result.append("""); break; default: result.append(content[i]); } } return (result.toString()); } /** * Normalize a relative URI path that may have relative values ("/./", "/../", and so on ) it it. <strong>WARNING</strong> - This method is useful only for normalizing application-generated paths. It does not try to perform security checks for malicious input. * * @param path * Relative path to be normalized */ public static String normalize(String path) { if (path == null) return null; // Create a place for the normalized path String normalized = path; if (normalized.equals("/.")) return "/"; // Add a leading "/" if necessary if (!normalized.startsWith("/")) normalized = "/" + normalized; // Resolve occurrences of "//" in the normalized path while (true) { int index = normalized.indexOf("//"); if (index < 0) break; normalized = normalized.substring(0, index) + normalized.substring(index + 1); } // Resolve occurrences of "/./" in the normalized path while (true) { int index = normalized.indexOf("/./"); if (index < 0) break; normalized = normalized.substring(0, index) + normalized.substring(index + 2); } // Resolve occurrences of "/../" in the normalized path while (true) { int index = normalized.indexOf("/../"); if (index < 0) break; if (index == 0) return (null); // Trying to go outside our context int index2 = normalized.lastIndexOf('/', index - 1); normalized = normalized.substring(0, index2) + normalized.substring(index + 3); } // Return the normalized path that we have completed return (normalized); } /** * Parse the character encoding from the specified content type header. If the content type is null, or there is no explicit character encoding, <code>null</code> is returned. * * @param contentType * a content type header */ public static String parseCharacterEncoding(String contentType) { if (contentType == null) return (null); int start = contentType.indexOf("charset="); if (start < 0) return (null); String encoding = contentType.substring(start + 8); int end = encoding.indexOf(';'); if (end >= 0) encoding = encoding.substring(0, end); encoding = encoding.trim(); if ((encoding.length() > 2) && (encoding.startsWith("\"")) && (encoding.endsWith("\""))) encoding = encoding.substring(1, encoding.length() - 1); return (encoding.trim()); } /** * Parse a cookie header into an array of cookies according to RFC 2109. * * @param header * Value of an HTTP "Cookie" header */ public static Cookie[] parseCookieHeader(String header) { if ((header == null) || (header.length() < 1)) return (new Cookie[0]); List<Cookie> cookies = new ArrayList<Cookie>(); while (header.length() > 0) { int semicolon = header.indexOf(';'); if (semicolon < 0) semicolon = header.length(); if (semicolon == 0) break; String token = header.substring(0, semicolon); if (semicolon < header.length()) header = header.substring(semicolon + 1); else header = ""; try { int equals = token.indexOf('='); if (equals > 0) { String name = token.substring(0, equals).trim(); String value = token.substring(equals + 1).trim(); cookies.add(new Cookie(name, value)); } } catch (Throwable e) { ; } } return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()])); } /** * 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 "&" 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 urlParameters * true if we're parsing parameters on the URL * * @exception IllegalArgumentException * if the data is malformed */ public static void parseParameters(Map map, String data, String encoding) throws UnsupportedEncodingException { 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. If an // encoding is not specified, let it use platform default byte[] bytes = null; try { if (encoding == null) { bytes = data.getBytes(); } else { bytes = data.getBytes(encoding); } } catch (UnsupportedEncodingException uee) { } parseParameters(map, bytes, encoding); } } /** * Decode and return the specified URL-encoded String. When the byte array is converted to a string, the system default character encoding is used... This may be different than some other servers. * * @param str * The url-encoded string * * @exception IllegalArgumentException * if a '%' character is not followed by a valid 2-digit hexadecimal number */ public static String URLDecode(String str) { return URLDecode(str, null); } /** * Decode and return the specified URL-encoded String. * * @param str * The url-encoded string * @param enc * The encoding to use; if null, the default encoding is used * @exception IllegalArgumentException * if a '%' character is not followed by a valid 2-digit hexadecimal number */ public static String URLDecode(String str, String enc) { if (str == null) return (null); // use the specified encoding to extract bytes out of the // given string so that the encoding is not lost. If an // encoding is not specified, let it use platform default byte[] bytes = null; try { if (enc == null) { bytes = str.getBytes(); } else { bytes = str.getBytes(enc); } } catch (UnsupportedEncodingException uee) { } return URLDecode(bytes, enc); } /** * Decode and return the specified URL-encoded byte array. * * @param bytes * The url-encoded byte array * @exception IllegalArgumentException * if a '%' character is not followed by a valid 2-digit hexadecimal number */ public static String URLDecode(byte[] bytes) { return URLDecode(bytes, null); } /** * Decode and return the specified URL-encoded byte array. * * @param bytes * The url-encoded byte array * @param enc * The encoding to use; if null, the default encoding is used * @exception IllegalArgumentException * if a '%' character is not followed by a valid 2-digit hexadecimal number */ public static String URLDecode(byte[] bytes, String enc) { if (bytes == null) return (null); int len = bytes.length; int ix = 0; int ox = 0; while (ix < len) { byte b = bytes[ix++]; // Get byte to test if (b == '+') { b = (byte) ' '; } else if (b == '%') { b = (byte) ((convertHexDigit(bytes[ix++]) << 4) + convertHexDigit(bytes[ix++])); } bytes[ox++] = b; } if (enc != null) { try { return new String(bytes, 0, ox, enc); } catch (Exception e) { e.printStackTrace(); } } return new String(bytes, 0, ox); } /** * Convert a byte character value to hexidecimal 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); return 0; } /** * Put name value pair in map. * * @param b * the character value byte * * Put name and value pair in map. When name already exist, add value to array of values. */ private static void putMapEntry(Map map, String name, String value) { String[] newValues = null; String[] oldValues = (String[]) 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 "&" 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 * Encoding to use for converting hex * * @exception UnsupportedEncodingException * if the data is malformed * * BLUEDRAGON: this has been modified to convert keys (names) to lowercase. */ public static void parseParameters(Map map, byte[] data, String encoding) throws UnsupportedEncodingException { 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, encoding); if (key != null) { putMapEntry(map, key, value); key = null; } else { // added condition for BlueDragon. Allows for keys with no values putMapEntry(map, value, ""); } ox = 0; break; case '=': if (key == null) { key = new String(data, 0, ox, encoding); // modified for BlueDragon if ( !cfEngine.isFormUrlCaseMaintained() ) key = key.toLowerCase(); ox = 0; } else { data[ox++] = c; } break; case '+': data[ox++] = (byte) ' '; break; case '%': if (ix + 1 < data.length) { data[ox++] = (byte) ((convertHexDigit(data[ix++]) << 4) + convertHexDigit(data[ix++])); } else { // added for BlueDragon - if the data ends with an incomplete % escape sequence then this param is ignored return; } 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, encoding); putMapEntry(map, key, value); } else if (ox > 0) { // added for BlueDragon. Allows for keys with no values key = new String(data, 0, ox, encoding); putMapEntry(map, key, ""); } } } }