/* * Copyright 2005 Joe Walker * * Licensed 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.directwebremoting.dwrp; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.StringTokenizer; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.directwebremoting.extend.FormField; import org.directwebremoting.extend.ProtocolConstants; import org.directwebremoting.extend.ServerException; import org.directwebremoting.util.LocalUtil; /** * A Batch is a request from the client. * This can be either a from a call ({@link CallBatch}) or an active reverse * ajax poll ({@link PollBatch}) * @author Joe Walker [joe at getahead dot ltd dot uk] */ public class Batch { /** * Initialize the batch from an {@link HttpServletRequest} */ public Batch(HttpServletRequest request) throws ServerException { get = "GET".equals(request.getMethod()); if (get) { extraParameters = parseGet(request); } else { extraParameters = parsePost(request); } scriptSessionId = extractParameter(ProtocolConstants.INBOUND_KEY_SCRIPT_SESSIONID); httpSessionId = extractParameter(ProtocolConstants.INBOUND_KEY_HTTP_SESSIONID); page = LocalUtil.urlDecode(extractParameter(ProtocolConstants.INBOUND_KEY_PAGE)); windowName = extractParameter(ProtocolConstants.INBOUND_KEY_WINDOWNAME); } /** * Initialize the batch from a set of pre-parsed parameters */ public Batch(Map<String, FormField> allParameters, boolean get) { this.extraParameters = allParameters; this.get = get; scriptSessionId = extractParameter(ProtocolConstants.INBOUND_KEY_SCRIPT_SESSIONID); httpSessionId = extractParameter(ProtocolConstants.INBOUND_KEY_HTTP_SESSIONID); page = extractParameter(ProtocolConstants.INBOUND_KEY_PAGE); windowName = extractParameter(ProtocolConstants.INBOUND_KEY_WINDOWNAME); } /** * Extract a parameter and ensure it is in the request. * This is needed to cope with Jetty continuations that are not real * continuations. * @param paramName The name of the parameter sent * @return The found value */ protected String extractParameter(String paramName) { FormField formField = extraParameters.remove(paramName); if (formField == null) { throw new IllegalArgumentException("Failed to find parameter: " + paramName); } return formField.getString(); } /** * Parse an HTTP POST request to fill out the scriptName, methodName and * paramList properties. This method should not fail unless it will not * be possible to return any sort of error to the user. Failure cases should * be handled by the <code>checkParams()</code> method. * @param req The original browser's request * @return The equivalent of HttpServletRequest.getParameterMap() for now * @throws ServerException If reading from the request body stream fails */ private Map<String, FormField> parsePost(HttpServletRequest req) throws ServerException { Map<String, FormField> paramMap; if (isMultipartContent(req)) { paramMap = UPLOADER.parseRequest(req); } else { paramMap = parseBasicPost(req); } // If there is only 1 param then this must be a broken Safari. if (paramMap.size() == 1) { parseBrokenMacPost(paramMap); } return paramMap; } /** * Utility method that determines whether the request contains multipart * content. * @param request The servlet request to be evaluated. Must be non-null. * @return true if the request is multipart, false otherwise. */ public static boolean isMultipartContent(HttpServletRequest request) { if (!"post".equals(request.getMethod().toLowerCase())) { return false; } String contentType = request.getContentType(); if (contentType == null) { return false; } if (contentType.toLowerCase().startsWith("multipart/")) { return true; } return false; } /** * The default parse case for a normal form submit * @param req The http request * @return a map of parsed parameters * @throws ServerException */ @SuppressWarnings("unchecked") private Map<String, FormField> parseBasicPost(HttpServletRequest req) throws ServerException { Map<String, FormField> paramMap; paramMap = new HashMap<String, FormField>(); BufferedReader in = null; try { // I've had reports of data loss in Tomcat 5.0 that relate to this bug // http://issues.apache.org/bugzilla/show_bug.cgi?id=27447 // See mails to users@dwr.dev.java.net: // Subject: "Tomcat 5.x read-ahead problem" // From: CAKALIC, JAMES P [AG-Contractor/1000] // It would be more normal to do the following: // BufferedReader in = req.getReader(); in = new BufferedReader(new InputStreamReader(req.getInputStream())); while (true) { String line = in.readLine(); if (line == null) { if (paramMap.isEmpty()) { // Normally speaking we should just bail out, but if // we are using DWR with Acegi without ActiveX on IE, // then Acegi 'fixes' the parameters for us. Enumeration<String> en = req.getParameterNames(); while (en.hasMoreElements()) { String name = en.nextElement(); paramMap.put(name, new FormField(req.getParameter(name))); } } break; } if (line.indexOf('&') != -1) { // If there are any &'s then this must be iframe post and all the // parameters have got dumped on one line, split with & log.debug("Using iframe POST mode"); StringTokenizer st = new StringTokenizer(line, "&"); while (st.hasMoreTokens()) { String part = st.nextToken(); part = LocalUtil.urlDecode(part); parsePostLine(part, paramMap); } } else { // Hooray, this is a normal one! parsePostLine(line, paramMap); } } } catch (Exception ex) { throw new ServerException("Failed to read input", ex); } finally { if (in != null) { try { in.close(); } catch (IOException ex) { // Ignore } } } return paramMap; } /** * All the parameters have got dumped on one line split with \n * See: http://bugzilla.opendarwin.org/show_bug.cgi?id=3565 * https://dwr.dev.java.net/issues/show_bug.cgi?id=93 * http://jira.atlassian.com/browse/JRA-8354 * http://developer.apple.com/internet/safari/uamatrix.html * @param paramMap The broken parsed parameter */ private static void parseBrokenMacPost(Map<String, FormField> paramMap) { // This looks like a broken Mac where the line endings are confused log.debug("Using Broken Safari POST mode"); // Iterators insist that we call hasNext() before we start Iterator<String> it = paramMap.keySet().iterator(); if (!it.hasNext()) { throw new IllegalStateException("No entries in non empty map!"); } // So get the first String key = it.next(); String value = paramMap.get(key).getString(); String line = key + ProtocolConstants.INBOUND_DECL_SEPARATOR + value; StringTokenizer st = new StringTokenizer(line, "\n"); while (st.hasMoreTokens()) { String part = st.nextToken(); part = LocalUtil.urlDecode(part); parsePostLine(part, paramMap); } } /** * Sort out a single line in a POST request * @param line The line to parse * @param paramMap The map to add parsed parameters to */ private static void parsePostLine(String line, Map<String, FormField> paramMap) { if (line.length() == 0) { return; } int sep = line.indexOf(ProtocolConstants.INBOUND_DECL_SEPARATOR); if (sep == -1) { paramMap.put(line, null); } else { String key = line.substring(0, sep); String value = line.substring(sep + ProtocolConstants.INBOUND_DECL_SEPARATOR.length()); paramMap.put(key, new FormField(value)); } } /** * Parse an HTTP GET request to fill out the scriptName, methodName and * paramList properties. This method should not fail unless it will not * be possible to return any sort of error to the user. Failure cases should * be handled by the <code>checkParams()</code> method. * @param req The original browser's request * @return Simply HttpRequest.getParameterMap() for now * @throws ServerException If the parsing fails */ @SuppressWarnings("unchecked") private Map<String, FormField> parseGet(HttpServletRequest req) throws ServerException { Map<String, FormField> convertedMap = new HashMap<String, FormField>(); Map<String, String[]> paramMap = req.getParameterMap(); for (Map.Entry<String, String[]> entry : paramMap.entrySet()) { String key = entry.getKey(); String[] array = entry.getValue(); if (array.length == 1) { convertedMap.put(key, new FormField(array[0])); } else { log.error("Multiple values for key: " + key); throw new ServerException("Multiple values for key. See console for more information"); } } return convertedMap; } /** * Is this request from a GET? * @return true if the request is a GET request */ public boolean isGet() { return get; } /** * Is it a GET request? */ private final boolean get; /** * @return the scriptSessionId */ public String getScriptSessionId() { return scriptSessionId; } /** * The unique ID sent to the current page */ private final String scriptSessionId; /** * @return the httpSessionId */ public String getHttpSessionId() { return httpSessionId; } /** * The unique ID sent to the browser in the session cookie */ private final String httpSessionId; /** * @return the page */ public String getPage() { return page; } /** * The page that the request was sent from */ private final String page; /** * @return the window name */ public String getWindowName() { return windowName; } /** * Window name is used by reverse ajax to get around the 2 connection limit */ private final String windowName; /** * @return the spareParameters */ public Map<String, FormField> getExtraParameters() { return extraParameters; } /** * All the parameters sent by the browser */ private final Map<String, FormField> extraParameters; /** * What implementation of FileUpload are we using? */ private static final FileUpload UPLOADER; /** * The log stream */ private static final Log log = LogFactory.getLog(Batch.class); /** * Work out which is the correct implementation of FileUpload */ static { FileUpload test; try { test = new CommonsFileUpload(); log.debug("Using commons-file-upload."); } catch (NoClassDefFoundError ex) { test = new UnsupportedFileUpload(); log.debug("Failed to find commons-file-upload. File upload is not supported."); } catch (Exception ex) { test = new UnsupportedFileUpload(); log.debug("Failed to start commons-file-upload. File upload is not supported."); } UPLOADER = test; } }