001 package org.apache.fulcrum.parser;
002
003
004 /*
005 * Licensed to the Apache Software Foundation (ASF) under one
006 * or more contributor license agreements. See the NOTICE file
007 * distributed with this work for additional information
008 * regarding copyright ownership. The ASF licenses this file
009 * to you under the Apache License, Version 2.0 (the
010 * "License"); you may not use this file except in compliance
011 * with the License. You may obtain a copy of the License at
012 *
013 * http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing,
016 * software distributed under the License is distributed on an
017 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
018 * KIND, either express or implied. See the License for the
019 * specific language governing permissions and limitations
020 * under the License.
021 */
022
023
024 import java.io.UnsupportedEncodingException;
025 import java.net.URLDecoder;
026 import java.util.Enumeration;
027 import java.util.Iterator;
028 import java.util.List;
029 import java.util.StringTokenizer;
030
031 import javax.servlet.http.HttpServletRequest;
032
033 import org.apache.avalon.framework.service.ServiceException;
034 import org.apache.commons.fileupload.FileItem;
035 import org.apache.commons.lang.ArrayUtils;
036
037 /**
038 * DefaultParameterParser is a utility object to handle parsing and
039 * retrieving the data passed via the GET/POST/PATH_INFO arguments.
040 *
041 * <p>NOTE: The name= portion of a name=value pair may be converted
042 * to lowercase or uppercase when the object is initialized and when
043 * new data is added. This behaviour is determined by the url.case.folding
044 * property in TurbineResources.properties. Adding a name/value pair may
045 * overwrite existing name=value pairs if the names match:
046 *
047 * <pre>
048 * ParameterParser pp = data.getParameters();
049 * pp.add("ERROR",1);
050 * pp.add("eRrOr",2);
051 * int result = pp.getInt("ERROR");
052 * </pre>
053 *
054 * In the above example, result is 2.
055 *
056 * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
057 * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
058 * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
059 * @author <a href="mailto:jh@byteaction.de">Jürgen Hoffmann</a>
060 * @version $Id: DefaultParameterParser.java 812786 2009-09-09 07:01:49Z tv $
061 */
062 public class DefaultParameterParser
063 extends BaseValueParser
064 implements ParameterParser
065 {
066 /**
067 * The servlet request to parse.
068 */
069 private HttpServletRequest request = null;
070
071 /**
072 * The raw data of a file upload.
073 */
074 private byte[] uploadData = null;
075
076 /**
077 * Create a new empty instance of ParameterParser. Uses the
078 * default character encoding (US-ASCII).
079 *
080 * <p>To add name/value pairs to this set of parameters, use the
081 * <code>add()</code> methods.
082 *
083 */
084 public DefaultParameterParser()
085 {
086 super();
087 }
088
089 /**
090 * Create a new empty instance of ParameterParser. Takes a
091 * character encoding name to use when converting strings to
092 * bytes.
093 *
094 * <p>To add name/value pairs to this set of parameters, use the
095 * <code>add()</code> methods.
096 *
097 * @param characterEncoding The character encoding of strings.
098 */
099 public DefaultParameterParser(String characterEncoding)
100 {
101 super (characterEncoding);
102 }
103
104 /**
105 * Disposes the parser.
106 */
107 public void dispose()
108 {
109 this.request = null;
110 this.uploadData = null;
111 super.dispose();
112 }
113
114 /**
115 * Gets the parsed servlet request.
116 *
117 * @return the parsed servlet request or null.
118 */
119 public HttpServletRequest getRequest()
120 {
121 return request;
122 }
123
124 /**
125 * Sets the servlet request to the parser. This requires a
126 * valid HttpServletRequest object. It will attempt to parse out
127 * the GET/POST/PATH_INFO data and store the data into a Map.
128 * There are convenience methods for retrieving the data as a
129 * number of different datatypes. The PATH_INFO data must be a
130 * URLEncoded() string.
131 * <p>
132 * To add name/value pairs to this set of parameters, use the
133 * <code>add()</code> methods.
134 *
135 * @param request An HttpServletRequest.
136 */
137 public void setRequest(HttpServletRequest request)
138 {
139 clear();
140
141 uploadData = null;
142
143 String enc = request.getCharacterEncoding();
144 setCharacterEncoding(enc != null
145 ? enc
146 : parserService.getParameterEncoding());
147
148 String contentType = request.getHeader("Content-type");
149
150 if (parserService.getAutomaticUpload()
151 && contentType != null
152 && contentType.startsWith("multipart/form-data"))
153 {
154 if (getLogger().isDebugEnabled())
155 {
156 getLogger().debug("Running the Fulcrum Upload Service");
157 }
158
159 try
160 {
161 List fileItems = parserService.parseUpload(request);
162
163 if (fileItems != null)
164 {
165 for (Iterator it = fileItems.iterator(); it.hasNext();)
166 {
167 FileItem fi = (FileItem) it.next();
168 if (fi.isFormField())
169 {
170 getLogger().debug("Found an simple form field: " + fi.getFieldName() +", adding value " + fi.getString());
171
172 String value = null;
173 try
174 {
175 value = fi.getString(getCharacterEncoding());
176 }
177 catch (UnsupportedEncodingException e)
178 {
179 getLogger().error(getCharacterEncoding()
180 + " encoding is not supported."
181 + "Used the default when reading form data.");
182 value = fi.getString();
183 }
184 add(fi.getFieldName(), value);
185 }
186 else
187 {
188 getLogger().debug("Found an uploaded file: " + fi.getFieldName());
189 getLogger().debug("It has " + fi.getSize() + " Bytes and is " + (fi.isInMemory() ? "" : "not ") + "in Memory");
190 getLogger().debug("Adding FileItem as " + fi.getFieldName() + " to the params");
191 add(fi.getFieldName(), fi);
192 }
193 }
194 }
195 }
196 catch (ServiceException e)
197 {
198 getLogger().error("File upload failed", e);
199 }
200 }
201
202 for (Enumeration names = request.getParameterNames();
203 names.hasMoreElements();)
204 {
205 String paramName = (String) names.nextElement();
206 add(paramName,
207 request.getParameterValues(paramName));
208 }
209
210 // Also cache any pathinfo variables that are passed around as
211 // if they are query string data.
212 try
213 {
214 boolean isNameTok = true;
215 String paramName = null;
216 String paramValue = null;
217
218 for ( StringTokenizer st =
219 new StringTokenizer(request.getPathInfo(), "/");
220 st.hasMoreTokens();)
221 {
222 if (isNameTok)
223 {
224 paramName = URLDecoder.decode(st.nextToken(), getCharacterEncoding());
225 isNameTok = false;
226 }
227 else
228 {
229 paramValue = URLDecoder.decode(st.nextToken(), getCharacterEncoding());
230 if (paramName != null && paramName.length() > 0)
231 {
232 add(paramName, paramValue);
233 }
234 isNameTok = true;
235 }
236 }
237 }
238 catch (Exception e)
239 {
240 // If anything goes wrong above, don't worry about it.
241 // Chances are that the path info was wrong anyways and
242 // things that depend on it being right will fail later
243 // and should be caught later.
244 }
245
246 this.request = request;
247
248 if (getLogger().isDebugEnabled())
249 {
250 getLogger().debug("Parameters found in the Request:");
251 for (Iterator it = keySet().iterator(); it.hasNext();)
252 {
253 String key = (String) it.next();
254 getLogger().debug("Key: " + key + " -> " + getString(key));
255 }
256 }
257 }
258
259 /**
260 * Sets the uploadData byte[]
261 *
262 * @param uploadData A byte[] with data.
263 */
264 public void setUploadData ( byte[] uploadData )
265 {
266 this.uploadData = uploadData;
267 }
268
269 /**
270 * Gets the uploadData byte[]
271 *
272 * @return uploadData A byte[] with data.
273 */
274 public byte[] getUploadData ()
275 {
276 return this.uploadData;
277 }
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 value)
291 {
292 add(name, value);
293 }
294
295
296 /**
297 * Add a FileItem object as a parameters. If there are any
298 * FileItems already associated with the name, append to the
299 * array. The reason for this is that RFC 1867 allows multiple
300 * files to be associated with single HTML input element.
301 *
302 * @param name A String with the name.
303 * @param value A FileItem with the value.
304 */
305 public void add(String name, FileItem value)
306 {
307 FileItem[] items = this.getFileItems(name);
308 items = (FileItem []) ArrayUtils.add(items, value);
309 parameters.put(convert(name), items);
310 }
311
312
313 /**
314 * Return a FileItem object for the given name. If the name does
315 * not exist or the object stored is not a FileItem, return null.
316 *
317 * @param name A String with the name.
318 * @return A FileItem.
319 */
320 public FileItem getFileItem(String name)
321 {
322 try
323 {
324 FileItem value = null;
325 Object object = parameters.get(convert(name));
326 if (object != null)
327 value = ((FileItem[])object)[0];
328 return value;
329 }
330 catch ( ClassCastException e )
331 {
332 return null;
333 }
334 }
335
336 /**
337 * Return an array of FileItem objects for the given name. If the
338 * name does not exist or the object stored is not a FileItem
339 * array, return null.
340 *
341 * @param name A String with the name.
342 * @return A FileItem[].
343 */
344 public FileItem[] getFileItems(String name)
345 {
346 try
347 {
348 return (FileItem[])parameters.get(convert(name));
349 }
350 catch ( ClassCastException e )
351 {
352 return null;
353 }
354 }
355 }