001: /*
002: * Copyright 2005 Joe Walker
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.directwebremoting.dwrp;
017:
018: import java.io.BufferedReader;
019: import java.io.IOException;
020: import java.io.InputStreamReader;
021: import java.util.Enumeration;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.Map;
025: import java.util.StringTokenizer;
026:
027: import javax.servlet.http.HttpServletRequest;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031: import org.directwebremoting.extend.FormField;
032: import org.directwebremoting.extend.ServerException;
033: import org.directwebremoting.util.LocalUtil;
034: import org.directwebremoting.util.Messages;
035:
036: /**
037: * Utilities to parse GET and POST requests from the DWR javascript section.
038: * @author Joe Walker [joe at getahead dot ltd dot uk]
039: */
040: public class ParseUtil {
041: /**
042: * The javascript outbound marshaller prefixes the toString value with a
043: * colon and the original type information. This undoes that.
044: * @param data The string to be split up
045: * @return A string array containing the split data
046: */
047: public static String[] splitInbound(String data) {
048: String[] reply = new String[2];
049:
050: int colon = data
051: .indexOf(ProtocolConstants.INBOUND_TYPE_SEPARATOR);
052: if (colon == -1) {
053: log.error("Missing : in conversion data (" + data + ')');
054: reply[LocalUtil.INBOUND_INDEX_TYPE] = ProtocolConstants.TYPE_STRING;
055: reply[LocalUtil.INBOUND_INDEX_VALUE] = data;
056: } else {
057: reply[LocalUtil.INBOUND_INDEX_TYPE] = data.substring(0,
058: colon);
059: reply[LocalUtil.INBOUND_INDEX_VALUE] = data
060: .substring(colon + 1);
061: }
062:
063: return reply;
064: }
065:
066: /**
067: * Parse an inbound request into a set of fields
068: * @param request The original browser's request
069: * @return The set of fields parsed from the request
070: * @throws ServerException
071: */
072: public Map<String, FormField> parseRequest(
073: HttpServletRequest request) throws ServerException {
074: boolean get = "GET".equals(request.getMethod());
075: if (get) {
076: return parseGet(request);
077: } else {
078: return parsePost(request);
079: }
080: }
081:
082: /**
083: * Parse an HTTP POST request to fill out the scriptName, methodName and
084: * paramList properties. This method should not fail unless it will not
085: * be possible to return any sort of error to the user. Failure cases should
086: * be handled by the <code>checkParams()</code> method.
087: * @param req The original browser's request
088: * @return The equivalent of HttpServletRequest.getParameterMap() for now
089: * @throws ServerException If reading from the request body stream fails
090: */
091: private Map<String, FormField> parsePost(HttpServletRequest req)
092: throws ServerException {
093: Map<String, FormField> paramMap;
094:
095: if (isMultipartContent(req)) {
096: paramMap = UPLOADER.parseRequest(req);
097: } else {
098: paramMap = parseBasicPost(req);
099: }
100:
101: // If there is only 1 param then this must be a broken Safari.
102: if (paramMap.size() == 1) {
103: parseBrokenMacPost(paramMap);
104: }
105:
106: return paramMap;
107: }
108:
109: /**
110: * Utility method that determines whether the request contains multipart
111: * content.
112: * @param request The servlet request to be evaluated. Must be non-null.
113: * @return true if the request is multipart, false otherwise.
114: */
115: public static final boolean isMultipartContent(
116: HttpServletRequest request) {
117: if (!"post".equals(request.getMethod().toLowerCase())) {
118: return false;
119: }
120:
121: String contentType = request.getContentType();
122: if (contentType == null) {
123: return false;
124: }
125:
126: if (contentType.toLowerCase().startsWith("multipart/")) {
127: return true;
128: }
129:
130: return false;
131: }
132:
133: /**
134: * The default parse case for a normal form submit
135: * @param req The http request
136: * @return a map of parsed parameters
137: * @throws ServerException
138: */
139: @SuppressWarnings("unchecked")
140: private Map<String, FormField> parseBasicPost(HttpServletRequest req)
141: throws ServerException {
142: Map<String, FormField> paramMap;
143: paramMap = new HashMap<String, FormField>();
144:
145: BufferedReader in = null;
146: try {
147: // I've had reports of data loss in Tomcat 5.0 that relate to this bug
148: // http://issues.apache.org/bugzilla/show_bug.cgi?id=27447
149: // See mails to users@dwr.dev.java.net:
150: // Subject: "Tomcat 5.x read-ahead problem"
151: // From: CAKALIC, JAMES P [AG-Contractor/1000]
152: // It would be more normal to do the following:
153: // BufferedReader in = req.getReader();
154: in = new BufferedReader(new InputStreamReader(req
155: .getInputStream()));
156:
157: while (true) {
158: String line = in.readLine();
159:
160: if (line == null) {
161: if (paramMap.isEmpty()) {
162: // Normally speaking we should just bail out, but if
163: // we are using DWR with Acegi without ActiveX on IE,
164: // then Acegi 'fixes' the parameters for us.
165: Enumeration<String> en = req
166: .getParameterNames();
167: while (en.hasMoreElements()) {
168: String name = en.nextElement();
169: paramMap.put(name, new FormField(req
170: .getParameter(name)));
171: }
172: }
173:
174: break;
175: }
176:
177: if (line.indexOf('&') != -1) {
178: // If there are any &'s then this must be iframe post and all the
179: // parameters have got dumped on one line, split with &
180: log.debug("Using iframe POST mode");
181: StringTokenizer st = new StringTokenizer(line, "&");
182: while (st.hasMoreTokens()) {
183: String part = st.nextToken();
184: part = LocalUtil.decode(part);
185:
186: parsePostLine(part, paramMap);
187: }
188: } else {
189: // Hooray, this is a normal one!
190: parsePostLine(line, paramMap);
191: }
192: }
193: } catch (Exception ex) {
194: throw new ServerException(Messages
195: .getString("ParseUtil.InputReadFailed"), ex);
196: } finally {
197: if (in != null) {
198: try {
199: in.close();
200: } catch (IOException ex) {
201: // Ignore
202: }
203: }
204: }
205: return paramMap;
206: }
207:
208: /**
209: * All the parameters have got dumped on one line split with \n
210: * See: http://bugzilla.opendarwin.org/show_bug.cgi?id=3565
211: * https://dwr.dev.java.net/issues/show_bug.cgi?id=93
212: * http://jira.atlassian.com/browse/JRA-8354
213: * http://developer.apple.com/internet/safari/uamatrix.html
214: * @param paramMap The broken parsed parameter
215: */
216: private static void parseBrokenMacPost(
217: Map<String, FormField> paramMap) {
218: // This looks like a broken Mac where the line endings are confused
219: log.debug("Using Broken Safari POST mode");
220:
221: // Iterators insist that we call hasNext() before we start
222: Iterator<String> it = paramMap.keySet().iterator();
223: if (!it.hasNext()) {
224: throw new IllegalStateException(
225: "No entries in non empty map!");
226: }
227:
228: // So get the first
229: String key = it.next();
230: String value = paramMap.get(key).getString();
231: String line = key + ProtocolConstants.INBOUND_DECL_SEPARATOR
232: + value;
233:
234: StringTokenizer st = new StringTokenizer(line, "\n");
235: while (st.hasMoreTokens()) {
236: String part = st.nextToken();
237: part = LocalUtil.decode(part);
238:
239: parsePostLine(part, paramMap);
240: }
241: }
242:
243: /**
244: * Sort out a single line in a POST request
245: * @param line The line to parse
246: * @param paramMap The map to add parsed parameters to
247: */
248: private static void parsePostLine(String line,
249: Map<String, FormField> paramMap) {
250: if (line.length() == 0) {
251: return;
252: }
253:
254: int sep = line
255: .indexOf(ProtocolConstants.INBOUND_DECL_SEPARATOR);
256: if (sep == -1) {
257: paramMap.put(line, null);
258: } else {
259: String key = line.substring(0, sep);
260: String value = line
261: .substring(sep
262: + ProtocolConstants.INBOUND_DECL_SEPARATOR
263: .length());
264:
265: paramMap.put(key, new FormField(value));
266: }
267: }
268:
269: /**
270: * Parse an HTTP GET request to fill out the scriptName, methodName and
271: * paramList properties. This method should not fail unless it will not
272: * be possible to return any sort of error to the user. Failure cases should
273: * be handled by the <code>checkParams()</code> method.
274: * @param req The original browser's request
275: * @return Simply HttpRequest.getParameterMap() for now
276: * @throws ServerException If the parsing fails
277: */
278: @SuppressWarnings("unchecked")
279: private Map<String, FormField> parseGet(HttpServletRequest req)
280: throws ServerException {
281: Map<String, FormField> convertedMap = new HashMap<String, FormField>();
282: Map<String, String[]> paramMap = req.getParameterMap();
283:
284: for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
285: String key = entry.getKey();
286: String[] array = entry.getValue();
287:
288: if (array.length == 1) {
289: convertedMap.put(key, new FormField(array[0]));
290: } else {
291: throw new ServerException(Messages.getString(
292: "ParseUtil.MultiValues", key));
293: }
294: }
295:
296: return convertedMap;
297: }
298:
299: /**
300: * What implementation of FileUpload are we using?
301: */
302: private static final FileUpload UPLOADER;
303:
304: /**
305: * The log stream
306: */
307: private static final Log log = LogFactory.getLog(ParseUtil.class);
308:
309: /**
310: * Work out which is the correct implementation of FileUpload
311: */
312: static {
313: FileUpload test;
314: try {
315: test = new CommonsFileUpload();
316: log.debug("Using commons-file-upload.");
317: } catch (NoClassDefFoundError ex) {
318: test = new UnsupportedFileUpload();
319: log
320: .debug("Failed to find commons-file-upload. File upload is not supported.");
321: } catch (Exception ex) {
322: test = new UnsupportedFileUpload();
323: log
324: .debug("Failed to start commons-file-upload. File upload is not supported.");
325: }
326:
327: UPLOADER = test;
328: }
329: }
|