/* * eXist Open Source Native XML Database Copyright (C) 2001-06 Wolfgang M. * Meier meier@ifs.tu-darmstadt.de http://exist.sourceforge.net * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id$ */ package org.exist.http.servlets; import org.exist.util.MimeType; import javax.servlet.RequestDispatcher; import javax.servlet.ServletInputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.io.InputStreamReader; import java.net.URLDecoder; import java.security.Principal; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.Vector; /** A wrapper for HttpServletRequest * - differentiates between POST parameters in the URL or Content Body * - keeps content Body of the POST request, making it available many times * through {@link #getContentBodyInputStream()} . * * A method of differentiating between POST parameters in the URL or Content Body of the request was needed. * The standard javax.servlet.http.HTTPServletRequest does not differentiate between URL or content body parameters, * this class does, the type is indicated in RequestParameter.type. * * To differentiate manually we need to read the URL (getQueryString()) and the Content body (getInputStream()), * this is problematic with the standard javax.servlet.http.HTTPServletRequest as parameter functions (getParameterMap(), getParameterNames(), getParameter(String), getParameterValues(String)) * affect the input stream functions (getInputStream(), getReader()) and vice versa. * * This class solves this by reading the Request Parameters initially from both the URL and the Content Body of the Request * and storing them in the private variable params for later use. * * @author Adam Retter <adam.retter@devon.gov.uk> * @serial 2006-02-28 * @version 1.1 */ //TODO: check loops to make sure they only iterate as few times as needed //TODO: do we need to do anything with encoding strings manually? public class HttpServletRequestWrapper implements HttpServletRequest { /** Simple Enumeration implementation for String's, needed for getParameterNames() */ private class StringEnumeration implements Enumeration { private String[] strings = null; //Strings in the Enumeration int aryPos = -1; //Current Position in Enumeration /** * StringEnumeration Constructor * @param strings an array of strings for the Enumeration */ StringEnumeration(String[] strings) { if(strings != null) { if(strings.length > 0) { //Create a new array this.strings = new String[strings.length]; //Copy the data over System.arraycopy(strings, 0, this.strings, 0, strings.length); //Set the position to the start of the array aryPos = 0; } } } /** * @see java.util.Enumeration#hasMoreElements */ public boolean hasMoreElements() { if(aryPos != -1) { if(aryPos < strings.length) { return true; } } return false; } /** * @see java.util.Enumeration#nextElement */ public Object nextElement() { if(aryPos != -1) { if(aryPos < strings.length) { Object s = (Object)strings[aryPos]; aryPos++; return s; } else { throw new NoSuchElementException("No more String's in the Enumeration, End Reached"); } } else { throw new NoSuchElementException("Enumeration is empty"); } } } /** Simple class to hold the value and type of a request parameter */ private class RequestParamater { public final static int PARAM_TYPE_URL = 1; //parameter from the URL of the request public final static int PARAM_TYPE_CONTENT = 2; //parameter from the Content of the request private String value = null; //parameter value private int type = 0; //parameter type, either PARAM_TYPE_URL or PARAM_TYPE_CONTENT /** * RequestParameter Constructor * @param value Value of the Request Parameter * @param type Type of the Request Parameter, URL (1) or Content (2) */ RequestParamater(String value, int type) { this.value = value; this.type = type; } /** * Request parameter value accessor * @return Value of Request parameter */ public String getValue() { return(value); } /** * Request parameter type accessor * @return Type of Request parameter */ public int getType() { return(type); } } //Members private HttpServletRequest request = null; //The Request /** The encoding for the Request */ private String formEncoding = null; /** The Request Parameters * * params LinkedHashMap * ==================== * params keys are String * params values are Vector of RequestParameter's */ private LinkedHashMap params = null; /** the content Body of the POST request; * it must be stored because once the Servlet input stream has been * consumed, the content Body is no more readable. */ private String contentBodyAsString; byte[] contentBody; /** * HttpServletRequestWrapper Constructor * @param request The HttpServletRequest to wrap * @param formEncoding The encoding to use */ public HttpServletRequestWrapper(HttpServletRequest request, String formEncoding) throws UnsupportedEncodingException { this.request = request; this.formEncoding = formEncoding; params = new LinkedHashMap(); initialiseWrapper(); } //Initalises the wrapper, setup encoding and parameter hashtable private void initialiseWrapper() throws UnsupportedEncodingException { //encoding if(request.getCharacterEncoding() == null) { request.setCharacterEncoding(formEncoding); } else { formEncoding = getCharacterEncoding(); } //Parse out parameters from the URL parseURLParameters(this.request.getQueryString()); //If POST request, Parse out parameters from the Content Body if(request.getMethod().toUpperCase().equals("POST")) { //If there is some Content if(request.getContentLength() > 0) { // If a form POST , and not a document POST String contentType = request.getContentType().toLowerCase(); int semicolon = contentType.indexOf(';'); if (semicolon>0) { contentType = contentType.substring(0,semicolon).trim(); } if( contentType.equals("application/x-www-form-urlencoded") && request.getHeader("ContentType") == null ) { //Parse out parameters from the Content Body parseContentBodyParameters(); } else if (contentType.equals(MimeType.XML_TYPE.getName())) { // if an XML-RPC contentBodyAsString = getContentBody(); } } } } //Stores parameters from the QueryString of the request private void parseURLParameters(String querystring) { if(querystring != null) { //Parse any parameters from the URL parseParameters(querystring, RequestParamater.PARAM_TYPE_URL); } } /** Stores parameters from the Content Body of the Request */ private void parseContentBodyParameters() { String content = getContentBody(); //Parse any parameters from the Content Body parseParameters( content, RequestParamater.PARAM_TYPE_CONTENT); } private String getContentBody() { //Create a buffer big enough to hold the Content Body contentBody = new byte[ request.getContentLength() ]; String result = ""; try { String encoding = request.getCharacterEncoding(); if(encoding == null) encoding = "UTF-8"; //Read the Content Body into the buffer InputStream is = request.getInputStream(); int bytes = 0; int offset = 0; int max = 4096; while (( bytes = is.read( contentBody, offset, max )) != -1) { offset += bytes; } result = new String(contentBody, encoding); } catch(IOException ioe) { //TODO: handle this properly System.err.println( "Error Reading the Content Body into the buffer: " + ioe ); ioe.printStackTrace(); } return result; } /** Parses Parameters into param objects and stores them in a vector in params */ private void parseParameters(String parameters, int type) { //Split parameters into an array String[] nameValuePairs = parameters.split("&"); for (int k = 0; k < nameValuePairs.length; k++) { //Split parameter into name and value String[] thePair = nameValuePairs[k].split("="); try { //URL Decode the parameter name and value thePair[0] = URLDecoder.decode(thePair[0], formEncoding); if(thePair.length == 2) { thePair[1] = URLDecoder.decode(thePair[1], formEncoding); } } catch(UnsupportedEncodingException uee) { //TODO: handle this properly uee.printStackTrace(); } //Have we encountered a parameter with this name? if(params.containsKey(thePair[0])) { //key exists in hash map, add value and type to vector Vector vecValues = (Vector)params.get(thePair[0]); vecValues.add(new RequestParamater((thePair.length == 2 ? thePair[1] : new String()), type)); params.put(thePair[0], vecValues); } else { //not in hash map so add a vector with the initial value Vector vecValues = new Vector(); vecValues.add(new RequestParamater((thePair.length == 2 ? thePair[1] : new String()), type)); params.put(thePair[0], vecValues); } } } /** * @see javax.servlet.http.HttpServletRequest#getAuthType */ public String getAuthType() { return request.getAuthType(); } /** * @see javax.servlet.http.HttpServletRequest#getCookies */ public Cookie[] getCookies() { return request.getCookies(); } /** * @see javax.servlet.http.HttpServletRequest#getDateHeader */ public long getDateHeader(String name) { return request.getDateHeader(name); } /** * @see javax.servlet.http.HttpServletRequest#getHeader */ public String getHeader(String name) { return request.getHeader(name); } /** * @see javax.servlet.http.HttpServletRequest#getHeaders */ public Enumeration getHeaders(String name) { return request.getHeaders(name); } /** * @see javax.servlet.http.HttpServletRequest#getHeaderNames */ public Enumeration getHeaderNames() { return request.getHeaderNames(); } /** * @see javax.servlet.http.HttpServletRequest#getIntHeader */ public int getIntHeader(String name) { return request.getIntHeader(name); } /** * @see javax.servlet.http.HttpServletRequest#getMethod */ public String getMethod() { return request.getMethod(); } /** * @see javax.servlet.http.HttpServletRequest#getPathInfo */ public String getPathInfo() { return request.getPathInfo(); } /** * @see javax.servlet.http.HttpServletRequest#getPathTranslated */ public String getPathTranslated() { return request.getPathInfo(); } /** * @see javax.servlet.http.HttpServletRequest#getContextPath */ public String getContextPath() { return request.getContextPath(); } /** * @see javax.servlet.http.HttpServletRequest#getQueryString */ public String getQueryString() { return request.getQueryString(); } /** * @see javax.servlet.http.HttpServletRequest#getRemoteUser */ public String getRemoteUser() { return request.getRemoteUser(); } /** * @see javax.servlet.http.HttpServletRequest#isUserInRole */ public boolean isUserInRole(String name) { return request.isUserInRole(name); } /** * @see javax.servlet.http.HttpServletRequest#getUserPrincipal */ public Principal getUserPrincipal() { return request.getUserPrincipal(); } /** * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId */ public String getRequestedSessionId() { return request.getRequestedSessionId(); } /** * @see javax.servlet.http.HttpServletRequest#getRequestURI */ public String getRequestURI() { return request.getRequestURI(); } /** * @see javax.servlet.http.HttpServletRequest#getRequestURL */ public StringBuffer getRequestURL() { return request.getRequestURL(); } /** * @see javax.servlet.http.HttpServletRequest#getServletPath */ public String getServletPath() { return request.getServletPath(); } /** * @see javax.servlet.http.HttpServletRequest#getSession */ public HttpSession getSession(boolean create) { return request.getSession(create); } /** * @see javax.servlet.http.HttpServletRequest#getSession */ public HttpSession getSession() { return request.getSession(); } /** * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid */ public boolean isRequestedSessionIdValid() { return request.isRequestedSessionIdValid(); } /** * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie */ public boolean isRequestedSessionIdFromCookie() { return request.isRequestedSessionIdFromCookie(); } /** * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL */ public boolean isRequestedSessionIdFromURL() { return request.isRequestedSessionIdFromURL(); } /** * @deprecated use isRequestedSessionIdFromURL() instead. * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl */ public boolean isRequestedSessionIdFromUrl() { return request.isRequestedSessionIdFromUrl(); } /** * @see javax.servlet.http.HttpServletRequest#getAttribute */ public Object getAttribute(String name) { return request.getAttribute(name); } /** * @see javax.servlet.http.HttpServletRequest#getAttributeNames */ public Enumeration getAttributeNames() { return request.getAttributeNames(); } /** * @see javax.servlet.http.HttpServletRequest#getCharacterEncoding */ public String getCharacterEncoding() { return request.getCharacterEncoding(); } /** * @see javax.servlet.http.HttpServletRequest#setCharacterEncoding */ public void setCharacterEncoding(String env) throws UnsupportedEncodingException { request.setCharacterEncoding(env); } /** * @see javax.servlet.http.HttpServletRequest#getContentLength */ public int getContentLength() { return request.getContentLength(); } /** * @see javax.servlet.http.HttpServletRequest#getContentType */ public String getContentType() { return request.getContentType(); } /** * @see javax.servlet.http.HttpServletRequest#getInputStream */ public ServletInputStream getInputStream() throws IOException { if (contentBodyRecorded()) return new CachingServletInputStream(); else return request.getInputStream(); } // public InputStream getStringBufferInputStream() throws IOException { // return new StringBufferInputStream( contentBodyAsString ); // } /** making the content Body of the POST request available many times, for processing by , e.g. Rpc processor . */ public InputStream getContentBodyInputStream() throws IOException { return new ByteArrayInputStream( contentBody ); } /** * get the value of a Request parameter by its name from the local parameter store * @param name The name of the Request parameter to get the value for * @return The value of the Request parameter with the specified name */ public String getParameter(String name) { //Does the parameter exist? if(params.containsKey(name)) { //Get the parameters vector of values Vector vecParameterValues = (Vector)params.get(name); //return the first value in the vector return ((RequestParamater)vecParameterValues.get(0)).getValue(); } else { return null; } } /** * get the names of the Request parameters from the local parameter store * @return An enumeration of string values representing the Request parameters names */ public Enumeration getParameterNames() { //get the key set as an array Object[] keySet = params.keySet().toArray(); //create a new string array, the same size as the ket set array String[] strKeySet = new String[keySet.length]; //copy the data from the key set to the string key set System.arraycopy(keySet, 0, strKeySet, 0, keySet.length); //return an enumeration of strings of the keys return new StringEnumeration(strKeySet); } /** * get the values of the Request parameter indicated by name from the local parameter store * @param name The name of the Request parameter to get the values for * @return The String array of the Request parameter's values */ public String[] getParameterValues(String name) { //Does the parameter exist? if(params.containsKey(name)) { //Get the parameters vector of values Vector vecParameterValues = (Vector)params.get(name); //Create a string array to hold the values String[] values = new String[vecParameterValues.size()]; //Copy each value into the string array for(int i = 0; i < vecParameterValues.size(); i++) { values[i] = ((RequestParamater)vecParameterValues.get(i)).getValue(); } //return the string array of values return(values); } else { return null; } } /** * get a Map of Request parameters (keys and values) from the local parameter store * @return Map of Request Parameters. Key is of type String and Value is of type String[]. */ public Map getParameterMap() { //Map to hold the parameters LinkedHashMap mapParameters = new LinkedHashMap(); Set setParams = params.entrySet(); //iterate through the Request Parameters for(Iterator itParams = setParams.iterator(); itParams.hasNext(); ) { //Get the parameter Map.Entry me = (Map.Entry)itParams.next(); //Get the parameters values Vector vecParamValues = (Vector)me.getValue(); //Create a string array to hold the parameter values String[] values = new String[vecParamValues.size()]; //Copy the parameter values into a string array int i = 0; for(Iterator itParamValues = vecParamValues.iterator(); itParamValues.hasNext(); i++) { values[i] = ((RequestParamater)itParamValues.next()).getValue(); } mapParameters.put(me.getKey(), values); //Store the parameter in a map } return mapParameters; //return the Map of parameters } /** * @see javax.servlet.http.HttpServletRequest#getProtocol */ public String getProtocol() { return request.getProtocol(); } /** * @see javax.servlet.http.HttpServletRequest#getScheme */ public String getScheme() { return request.getScheme(); } /** * @see javax.servlet.http.HttpServletRequest#getServerName */ public String getServerName() { return request.getServerName(); } /** * @see javax.servlet.http.HttpServletRequest#getServerPort */ public int getServerPort() { return request.getServerPort(); } public BufferedReader getReader() throws IOException { if (contentBodyRecorded()) return new BufferedReader(new InputStreamReader(getContentBodyInputStream(), request.getCharacterEncoding())); else return request.getReader(); } /** * @see javax.servlet.http.HttpServletRequest#getRemoteAddr */ public String getRemoteAddr() { return request.getRemoteAddr(); } /** * @see javax.servlet.http.HttpServletRequest#getRemoteHost */ public String getRemoteHost() { return request.getRemoteHost(); } /** * @see javax.servlet.http.HttpServletRequest#setAttribute */ public void setAttribute(String name, Object o) { request.setAttribute(name, o); } /** * @see javax.servlet.http.HttpServletRequest#removeAttribute */ public void removeAttribute(String name) { request.removeAttribute(name); } /** * @see javax.servlet.http.HttpServletRequest#getLocale */ public Locale getLocale() { return request.getLocale(); } /** * @see javax.servlet.http.HttpServletRequest#getLocales */ public Enumeration getLocales() { return request.getLocales(); } /** * @see javax.servlet.http.HttpServletRequest#isSecure */ public boolean isSecure() { return request.isSecure(); } /** * @see javax.servlet.http.HttpServletRequest#getRequestDispatcher */ public RequestDispatcher getRequestDispatcher(String name) { return request.getRequestDispatcher(name); } /** * @deprecated use use ServletContext#getRealPath(java.lang.String) instead. * @see javax.servlet.http.HttpServletRequest#getRealPath */ public String getRealPath(String path) { return request.getRealPath(path); } /** * @see javax.servlet.http.HttpServletRequest#getRemotePort */ public int getRemotePort() { return request.getRemotePort(); } /** * @see javax.servlet.http.HttpServletRequest#getLocalName */ public String getLocalName() { return request.getLocalName(); } /** * @see javax.servlet.http.HttpServletRequest#getLocalAddr */ public String getLocalAddr() { return request.getLocalAddr(); } /** * @see javax.servlet.http.HttpServletRequest#getLocalPort */ public int getLocalPort() { return request.getLocalPort(); } /** * Similar to javax.servlet.http.HttpServletRequest.toString() , * except it includes output of the Request parameters from the Request's Content Body * @return String representation of HttpServletRequestWrapper */ public String toString() { // If POST request AND there is some content AND its not a file upload // AND content Body has not been recorded if ( request.getMethod().toUpperCase().equals("POST") && request.getContentLength() > 0 && !request.getContentType().toUpperCase().startsWith( "MULTIPART/") && ! contentBodyRecorded() ) { // Also return the content parameters, these are not part // of the standard HttpServletRequest.toString() output StringBuffer buf = new StringBuffer( request.toString()); Set setParams = params.entrySet(); for (Iterator itParams = setParams.iterator(); itParams.hasNext();) { Map.Entry me = (Map.Entry) itParams.next(); Vector vecParamValues = (Vector) me.getValue(); for (Iterator itParamValues = vecParamValues.iterator(); itParamValues .hasNext();) { RequestParamater p = (RequestParamater) itParamValues .next(); if (p.type == RequestParamater.PARAM_TYPE_CONTENT) { if (buf.charAt(buf.length() - 1) != '\n') buf.append("&"); buf.append((String) me.getKey()); buf.append("="); buf.append(p.getValue()); } } } buf.append( System.getProperty("line.separator") + System.getProperty("line.separator") ); return buf.toString(); } else if ( contentBodyRecorded() ) { // XML-RPC request or plain XML REST POST StringBuffer buf = new StringBuffer( request.toString() ); buf.append(contentBodyAsString); buf.append( System.getProperty("line.separator") + System.getProperty("line.separator") ); return buf.toString(); } else { //Return standard HttpServletRequest.toString() output return request.toString(); } } private boolean contentBodyRecorded() { return contentBody != null && contentBody.length > 0; } private class CachingServletInputStream extends ServletInputStream { private ByteArrayInputStream istream; public CachingServletInputStream() { if (contentBody == null) istream = new ByteArrayInputStream(new byte[0]); else istream = new ByteArrayInputStream(contentBody); } public int read() throws IOException { return istream.read(); } public int read(byte b[]) throws IOException { return istream.read(b); } public int read(byte b[], int off, int len) throws IOException { return istream.read(b, off, len); } public int available() throws IOException { return istream.available(); } } }