1 package org.apache.turbine.util.parser;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.net.URLDecoder;
23
24 import java.util.Enumeration;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.StringTokenizer;
30
31 import javax.servlet.http.HttpServletRequest;
32
33 import org.apache.commons.collections.set.CompositeSet;
34 import org.apache.commons.fileupload.FileItem;
35 import org.apache.commons.lang.ArrayUtils;
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38
39 import org.apache.turbine.TurbineConstants;
40 import org.apache.turbine.services.upload.TurbineUpload;
41 import org.apache.turbine.services.upload.UploadService;
42 import org.apache.turbine.util.TurbineException;
43 import org.apache.turbine.util.pool.Recyclable;
44
45 /***
46 * DefaultParameterParser is a utility object to handle parsing and
47 * retrieving the data passed via the GET/POST/PATH_INFO arguments.
48 *
49 * <p>NOTE: The name= portion of a name=value pair may be converted
50 * to lowercase or uppercase when the object is initialized and when
51 * new data is added. This behaviour is determined by the url.case.folding
52 * property in TurbineResources.properties. Adding a name/value pair may
53 * overwrite existing name=value pairs if the names match:
54 *
55 * <pre>
56 * ParameterParser pp = data.getParameters();
57 * pp.add("ERROR",1);
58 * pp.add("eRrOr",2);
59 * int result = pp.getInt("ERROR");
60 * </pre>
61 *
62 * In the above example, result is 2.
63 *
64 * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
65 * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
66 * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
67 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
68 * @version $Id: DefaultParameterParser.java 534527 2007-05-02 16:10:59Z tv $
69 */
70 public class DefaultParameterParser
71 extends BaseValueParser
72 implements ParameterParser, Recyclable
73 {
74 /*** Logging */
75 private static Log log = LogFactory.getLog(DefaultParameterParser.class);
76
77 /*** The servlet request to parse. */
78 private HttpServletRequest request = null;
79
80 /*** The raw data of a file upload. */
81 private byte[] uploadData = null;
82
83 /*** Map of request parameters to FileItem[]'s */
84 private Map fileParameters = new HashMap();
85
86 /*** Turbine Upload Service reference */
87 private static UploadService uploadService = null;
88
89 /*** Do we have an upload Service? */
90 private static boolean uploadServiceIsAvailable = false;
91
92 /***
93 * Create a new empty instance of ParameterParser. Uses the
94 * default character encoding (US-ASCII).
95 *
96 * <p>To add name/value pairs to this set of parameters, use the
97 * <code>add()</code> methods.
98 */
99 public DefaultParameterParser()
100 {
101 super();
102 configureUploadService();
103 }
104
105 /***
106 * Create a new empty instance of ParameterParser. Takes a
107 * character encoding name to use when converting strings to
108 * bytes.
109 *
110 * <p>To add name/value pairs to this set of parameters, use the
111 * <code>add()</code> methods.
112 *
113 * @param characterEncoding The character encoding of strings.
114 */
115 public DefaultParameterParser(String characterEncoding)
116 {
117 super(characterEncoding);
118 configureUploadService();
119 }
120
121 /***
122 * Checks for availability of the Upload Service. We do this
123 * check only once at Startup, because the getService() call
124 * is really expensive and we don't have to run it every time
125 * we process a request.
126 */
127 private void configureUploadService()
128 {
129 uploadServiceIsAvailable = TurbineUpload.isAvailable();
130 if (uploadServiceIsAvailable)
131 {
132 uploadService = TurbineUpload.getService();
133 }
134 }
135
136 /***
137 * Disposes the parser.
138 */
139 public void dispose()
140 {
141 this.request = null;
142 this.uploadData = null;
143 this.fileParameters.clear();
144 super.dispose();
145 }
146
147 /***
148 * Gets the parsed servlet request.
149 *
150 * @return the parsed servlet request or null.
151 */
152 public HttpServletRequest getRequest()
153 {
154 return request;
155 }
156
157 /***
158 * Sets the servlet request to the parser. This requires a
159 * valid HttpServletRequest object. It will attempt to parse out
160 * the GET/POST/PATH_INFO data and store the data into a Map.
161 * There are convenience methods for retrieving the data as a
162 * number of different datatypes. The PATH_INFO data must be a
163 * URLEncoded() string.
164 * <p>
165 * To add name/value pairs to this set of parameters, use the
166 * <code>add()</code> methods.
167 *
168 * @param request An HttpServletRequest.
169 */
170 public void setRequest(HttpServletRequest request)
171 {
172 clear();
173
174 uploadData = null;
175
176 String enc = request.getCharacterEncoding();
177 setCharacterEncoding(enc != null
178 ? enc
179 : TurbineConstants.PARAMETER_ENCODING_DEFAULT);
180
181 String contentType = request.getHeader("Content-type");
182
183 if (uploadServiceIsAvailable
184 && uploadService.getAutomatic()
185 && contentType != null
186 && contentType.startsWith("multipart/form-data"))
187 {
188 if (log.isDebugEnabled())
189 {
190 log.debug("Running the Turbine Upload Service");
191 }
192
193 try
194 {
195 TurbineUpload.parseRequest(request, this);
196 }
197 catch (TurbineException e)
198 {
199 log.error("File upload failed", e);
200 }
201 }
202
203 for (Enumeration names = request.getParameterNames();
204 names.hasMoreElements();)
205 {
206 String paramName = (String) names.nextElement();
207 add(paramName,
208 request.getParameterValues(paramName));
209 }
210
211
212
213 try
214 {
215 boolean isNameTok = true;
216 String paramName = null;
217 String paramValue = null;
218
219 for ( StringTokenizer st =
220 new StringTokenizer(request.getPathInfo(), "/");
221 st.hasMoreTokens();)
222 {
223 if (isNameTok)
224 {
225 paramName = URLDecoder.decode(st.nextToken(), getCharacterEncoding());
226 isNameTok = false;
227 }
228 else
229 {
230 paramValue = URLDecoder.decode(st.nextToken(), getCharacterEncoding());
231 if (paramName.length() > 0)
232 {
233 add(paramName, paramValue);
234 }
235 isNameTok = true;
236 }
237 }
238 }
239 catch (Exception e)
240 {
241
242
243
244
245 }
246
247 this.request = request;
248
249 if (log.isDebugEnabled())
250 {
251 log.debug("Parameters found in the Request:");
252 for (Iterator it = keySet().iterator(); it.hasNext();)
253 {
254 String key = (String) it.next();
255 log.debug("Key: " + key + " -> " + getString(key));
256 }
257 }
258 }
259
260 /***
261 * Sets the uploadData byte[]
262 *
263 * @param uploadData A byte[] with data.
264 */
265 public void setUploadData(byte[] uploadData)
266 {
267 this.uploadData = uploadData;
268 }
269
270 /***
271 * Gets the uploadData byte[]
272 *
273 * @return uploadData A byte[] with data.
274 */
275 public byte[] getUploadData()
276 {
277 return uploadData;
278 }
279
280 /***
281 * Add a FileItem object as a parameters. If there are any
282 * FileItems already associated with the name, append to the
283 * array. The reason for this is that RFC 1867 allows multiple
284 * files to be associated with single HTML input element.
285 *
286 * @param name A String with the name.
287 * @param value A FileItem with the value.
288 * @deprecated Use add(String name, FileItem item)
289 */
290 public void append(String name, FileItem item)
291 {
292 add(name, item);
293 }
294
295 /***
296 * Add a FileItem object as a parameters. If there are any
297 * FileItems already associated with the name, append to the
298 * array. The reason for this is that RFC 1867 allows multiple
299 * files to be associated with single HTML input element.
300 *
301 * @param name A String with the name.
302 * @param value A FileItem with the value.
303 */
304 public void add(String name, FileItem item)
305 {
306 FileItem[] items = getFileItemParam(name);
307 items = (FileItem []) ArrayUtils.add(items, item);
308 putFileItemParam(name, items);
309 }
310
311 /***
312 * Gets the set of keys (FileItems and regular parameters)
313 *
314 * @return A <code>Set</code> of the keys.
315 */
316 public Set keySet()
317 {
318 return new CompositeSet(new Set[] { super.keySet(), fileParameters.keySet() } );
319 }
320
321 /***
322 * Determine whether a given key has been inserted. All keys are
323 * stored in lowercase strings, so override method to account for
324 * this.
325 *
326 * @param key An Object with the key to search for.
327 * @return True if the object is found.
328 */
329 public boolean containsKey(Object key)
330 {
331 if (super.containsKey(key))
332 {
333 return true;
334 }
335
336 return fileParameters.containsKey(convert(String.valueOf(key)));
337 }
338
339
340 /***
341 * Return a FileItem object for the given name. If the name does
342 * not exist or the object stored is not a FileItem, return null.
343 *
344 * @param name A String with the name.
345 * @return A FileItem.
346 */
347 public FileItem getFileItem(String name)
348 {
349 FileItem [] value = getFileItemParam(name);
350
351 return (value == null
352 || value.length == 0)
353 ? null : value[0];
354 }
355
356 /***
357 * Return an array of FileItem objects for the given name. If the
358 * name does not exist, return null.
359 *
360 * @param name A String with the name.
361 * @return An Array of FileItems or null.
362 */
363 public FileItem[] getFileItems(String name)
364 {
365 return getFileItemParam(name);
366 }
367
368 /***
369 * Puts a key into the parameters map. Makes sure that the name is always
370 * mapped correctly. This method also enforces the usage of arrays for the
371 * parameters.
372 *
373 * @param name A String with the name.
374 * @param value An array of Objects with the values.
375 *
376 */
377 protected void putFileItemParam(final String name, final FileItem [] value)
378 {
379 String key = convert(name);
380 if (key != null)
381 {
382 fileParameters.put(key, value);
383 }
384 }
385
386 /***
387 * fetches a key from the parameters map. Makes sure that the name is
388 * always mapped correctly.
389 *
390 * @param name A string with the name
391 *
392 * @return the value object array or null if not set
393 */
394 protected FileItem [] getFileItemParam(final String name)
395 {
396 String key = convert(name);
397
398 return (key != null) ? (FileItem []) fileParameters.get(key) : null;
399 }
400
401 /***
402 * This method is only used in toString() and can be used by
403 * derived classes to add their local parameters to the toString()
404
405 * @param name A string with the name
406 *
407 * @return the value object array or null if not set
408 */
409 protected Object [] getToStringParam(final String name)
410 {
411 if (super.containsKey(name))
412 {
413 return getParam(name);
414 }
415 else
416 {
417 return getFileItemParam(name);
418 }
419 }
420 }