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