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&#252;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    }