Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
DefaultParameterParser |
|
| 0.0;0 |
1 | package org.apache.fulcrum.parser; | |
2 | ||
3 | /* | |
4 | * Licensed to the Apache Software Foundation (ASF) under one | |
5 | * or more contributor license agreements. See the NOTICE file | |
6 | * distributed with this work for additional information | |
7 | * regarding copyright ownership. The ASF licenses this file | |
8 | * to you under the Apache License, Version 2.0 (the | |
9 | * "License"); you may not use this file except in compliance | |
10 | * with the License. You may obtain a copy of the License at | |
11 | * | |
12 | * http://www.apache.org/licenses/LICENSE-2.0 | |
13 | * | |
14 | * Unless required by applicable law or agreed to in writing, | |
15 | * software distributed under the License is distributed on an | |
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
17 | * KIND, either express or implied. See the License for the | |
18 | * specific language governing permissions and limitations | |
19 | * under the License. | |
20 | */ | |
21 | ||
22 | import java.io.UnsupportedEncodingException; | |
23 | import java.net.URLDecoder; | |
24 | import java.util.Arrays; | |
25 | import java.util.Collection; | |
26 | import java.util.Enumeration; | |
27 | import java.util.List; | |
28 | import java.util.StringTokenizer; | |
29 | import java.util.regex.Matcher; | |
30 | import java.util.regex.Pattern; | |
31 | import java.util.stream.Collectors; | |
32 | ||
33 | import javax.servlet.http.HttpServletRequest; | |
34 | import javax.servlet.http.Part; | |
35 | ||
36 | import org.apache.avalon.framework.service.ServiceException; | |
37 | import org.apache.commons.lang3.ArrayUtils; | |
38 | ||
39 | /** | |
40 | * DefaultParameterParser is a utility object to handle parsing and | |
41 | * retrieving the data passed via the GET/POST/PATH_INFO arguments. | |
42 | * | |
43 | * <p>NOTE: The name= portion of a name=value pair may be converted | |
44 | * to lowercase or uppercase when the object is initialized and when | |
45 | * new data is added. This behaviour is determined by the url.case.folding | |
46 | * property in TurbineResources.properties. Adding a name/value pair may | |
47 | * overwrite existing name=value pairs if the names match: | |
48 | * | |
49 | * <pre> | |
50 | * ParameterParser pp = data.getParameters(); | |
51 | * pp.add("ERROR",1); | |
52 | * pp.add("eRrOr",2); | |
53 | * int result = pp.getInt("ERROR"); | |
54 | * </pre> | |
55 | * | |
56 | * In the above example, result is 2. | |
57 | * | |
58 | * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a> | |
59 | * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a> | |
60 | * @author <a href="mailto:sean@informage.net">Sean Legassick</a> | |
61 | * @author <a href="mailto:jh@byteaction.de">Jürgen Hoffmann</a> | |
62 | * @version $Id: DefaultParameterParser.java 1862617 2019-07-05 13:42:22Z gk $ | |
63 | */ | |
64 | public class DefaultParameterParser | |
65 | extends BaseValueParser | |
66 | implements ParameterParser | |
67 | { | |
68 | /** | |
69 | * The servlet request to parse. | |
70 | */ | |
71 | 34 | private HttpServletRequest request = null; |
72 | ||
73 | /** | |
74 | * The raw data of a file upload. | |
75 | */ | |
76 | 34 | private byte[] uploadData = null; |
77 | ||
78 | /** | |
79 | * Create a new empty instance of ParameterParser. Uses the | |
80 | * default character encoding (US-ASCII). | |
81 | * | |
82 | * <p>To add name/value pairs to this set of parameters, use the | |
83 | * <code>add()</code> methods. | |
84 | * | |
85 | */ | |
86 | public DefaultParameterParser() | |
87 | { | |
88 | 34 | super(); |
89 | 34 | } |
90 | ||
91 | /** | |
92 | * Create a new empty instance of ParameterParser. Takes a | |
93 | * character encoding name to use when converting strings to | |
94 | * bytes. | |
95 | * | |
96 | * <p>To add name/value pairs to this set of parameters, use the | |
97 | * <code>add()</code> methods. | |
98 | * | |
99 | * @param characterEncoding The character encoding of strings. | |
100 | */ | |
101 | public DefaultParameterParser(String characterEncoding) | |
102 | { | |
103 | 0 | super (characterEncoding); |
104 | 0 | } |
105 | ||
106 | /** | |
107 | * Disposes the parser. | |
108 | */ | |
109 | @Override | |
110 | public void dispose() | |
111 | { | |
112 | 32 | this.request = null; |
113 | 32 | this.uploadData = null; |
114 | 32 | super.dispose(); |
115 | 32 | } |
116 | ||
117 | /** | |
118 | * Gets the parsed servlet request. | |
119 | * | |
120 | * @return the parsed servlet request or null. | |
121 | */ | |
122 | @Override | |
123 | public HttpServletRequest getRequest() | |
124 | { | |
125 | 32 | return request; |
126 | } | |
127 | ||
128 | /** | |
129 | * Sets the servlet request to the parser. This requires a | |
130 | * valid HttpServletRequest object. It will attempt to parse out | |
131 | * the GET/POST/PATH_INFO data and store the data into a Map. | |
132 | * There are convenience methods for retrieving the data as a | |
133 | * number of different datatypes. The PATH_INFO data must be a | |
134 | * URLEncoded() string. | |
135 | * <p> | |
136 | * Sets the request character encoding to the parser. | |
137 | * <p> | |
138 | * Sets the request encoding, if it is not set and {@link ParserService#getParameterEncoding()} | |
139 | * is set to a non-default value {@link ParserService#PARAMETER_ENCODING_DEFAULT} | |
140 | * (if {@link HttpServletRequest#getCharacterEncoding()} returns null, | |
141 | * it has the default set to ISO-8859-1, cft. Servlet 2.4, 2.5, 3.0, 3.1 Specs). | |
142 | * This will only succeed, if no data was read yet, cft. spec. | |
143 | * <p> | |
144 | * To add name/value pairs to this set of parameters, use the | |
145 | * <code>add()</code> methods. | |
146 | * | |
147 | * @param request An HttpServletRequest. | |
148 | */ | |
149 | @Override | |
150 | public void setRequest(HttpServletRequest request) | |
151 | { | |
152 | 10 | clear(); |
153 | ||
154 | 10 | uploadData = null; |
155 | ||
156 | 10 | handleEncoding( request ); |
157 | ||
158 | 10 | String contentType = request.getContentType(); |
159 | ||
160 | 10 | if (parserService.getAutomaticUpload() |
161 | && contentType != null | |
162 | 10 | && contentType.startsWith("multipart/form-data")) |
163 | { | |
164 | try | |
165 | { | |
166 | 2 | List<Part> parts = parserService.parseUpload(request); |
167 | ||
168 | 2 | if (parts != null) |
169 | { | |
170 | 2 | for (Part p : parts) |
171 | { | |
172 | 4 | getLogger().debug("Found an uploaded file: " + p.getName()); |
173 | 4 | getLogger().debug("It has " + p.getSize() + " Bytes"); |
174 | 4 | getLogger().debug("Adding Part as " + p.getName() + " to the params"); |
175 | 4 | add(p.getName(), p); |
176 | 4 | } |
177 | } | |
178 | } | |
179 | 0 | catch (ServiceException e) |
180 | { | |
181 | 0 | getLogger().error("File upload failed", e); |
182 | 2 | } |
183 | } | |
184 | ||
185 | 10 | for (Enumeration<?> names = request.getParameterNames(); |
186 | 10 | names.hasMoreElements();) |
187 | { | |
188 | 0 | String paramName = (String) names.nextElement(); |
189 | 0 | add(paramName, |
190 | 0 | request.getParameterValues(paramName)); |
191 | 0 | } |
192 | ||
193 | 10 | handlePathInfo( request ); |
194 | ||
195 | 10 | this.request = request; |
196 | ||
197 | 10 | if (getLogger().isDebugEnabled()) |
198 | { | |
199 | 0 | getLogger().debug("Parameters found in the Request:"); |
200 | 0 | for (String key : keySet()) |
201 | { | |
202 | 0 | getLogger().debug("Key: " + key + " -> " + getString(key)); |
203 | 0 | } |
204 | } | |
205 | 10 | } |
206 | ||
207 | private void handlePathInfo( HttpServletRequest request ) | |
208 | { | |
209 | // Also cache any pathinfo variables that are passed around as | |
210 | // if they are query string data. | |
211 | try | |
212 | { | |
213 | 10 | boolean isNameTok = true; |
214 | 10 | String paramName = null; |
215 | 10 | String paramValue = null; |
216 | ||
217 | 10 | for ( StringTokenizer st = |
218 | 10 | new StringTokenizer(request.getPathInfo(), "/"); |
219 | 20 | st.hasMoreTokens();) |
220 | { | |
221 | 10 | if (isNameTok) |
222 | { | |
223 | 10 | paramName = URLDecoder.decode(st.nextToken(), getCharacterEncoding()); |
224 | 10 | isNameTok = false; |
225 | } | |
226 | else | |
227 | { | |
228 | 0 | paramValue = URLDecoder.decode(st.nextToken(), getCharacterEncoding()); |
229 | 0 | if (paramName != null && paramName.length() > 0) |
230 | { | |
231 | 0 | add(paramName, paramValue); |
232 | } | |
233 | 0 | isNameTok = true; |
234 | } | |
235 | } | |
236 | } | |
237 | 0 | catch (Exception e) |
238 | { | |
239 | // If anything goes wrong above, don't worry about it. | |
240 | // Chances are that the path info was wrong anyways and | |
241 | // things that depend on it being right will fail later | |
242 | // and should be caught later. | |
243 | 10 | } |
244 | 10 | } |
245 | ||
246 | protected void handleEncoding( HttpServletRequest request ) | |
247 | { | |
248 | 10 | String enc = request.getCharacterEncoding(); |
249 | ||
250 | 10 | if (enc == null && !parserService.getParameterEncoding().equals(ParserService.PARAMETER_ENCODING_DEFAULT )) |
251 | { | |
252 | try | |
253 | { | |
254 | // no-op if data was read (parameter, POST..), see javadoc setCharacterEncoding | |
255 | 0 | request.setCharacterEncoding( parserService.getParameterEncoding() ); |
256 | // this is not (?) testable with mock | |
257 | 0 | enc = request.getCharacterEncoding(); |
258 | 0 | if (enc != null) |
259 | { | |
260 | 0 | getLogger().debug("Set the request encoding successfully to parameterEncoding of parser: "+enc ); |
261 | } | |
262 | else | |
263 | { | |
264 | 0 | getLogger().warn("Unsuccessfully (data read happened) tried to set the request encoding to "+ parserService.getParameterEncoding() ); |
265 | } | |
266 | } | |
267 | 0 | catch ( UnsupportedEncodingException e ) |
268 | { | |
269 | 0 | getLogger().error("Found only unsupported encoding "+ e.getMessage()); |
270 | 0 | } |
271 | } | |
272 | ||
273 | 20 | setCharacterEncoding(enc != null |
274 | ? enc | |
275 | 0 | : parserService.getParameterEncoding()); |
276 | 10 | } |
277 | ||
278 | /** | |
279 | * Sets the uploadData byte[] | |
280 | * | |
281 | * @param uploadData A byte[] with data. | |
282 | */ | |
283 | @Override | |
284 | public void setUploadData ( byte[] uploadData ) | |
285 | { | |
286 | // copy contents into our own representation for safety re: EI_EXPOSE_REP | |
287 | 0 | this.uploadData = Arrays.copyOf(uploadData, uploadData.length); |
288 | 0 | } |
289 | ||
290 | /** | |
291 | * Gets the uploadData byte[] | |
292 | * | |
293 | * @return uploadData A byte[] with data. | |
294 | */ | |
295 | @Override | |
296 | public byte[] getUploadData () | |
297 | { | |
298 | 0 | return this.uploadData; |
299 | } | |
300 | ||
301 | /** | |
302 | * Add a Part object as a parameters. If there are any | |
303 | * Parts already associated with the name, append to the | |
304 | * array. The reason for this is that RFC 1867 allows multiple | |
305 | * files to be associated with single HTML input element. | |
306 | * | |
307 | * @param name A String with the name. | |
308 | * @param value A Part with the value. | |
309 | */ | |
310 | @Override | |
311 | public void add( String name, Part value ) | |
312 | { | |
313 | 16 | Part[] items = this.getParts(name); |
314 | 16 | items = ArrayUtils.add(items, value); |
315 | 16 | parameters.put(convert(name), items); |
316 | 16 | } |
317 | ||
318 | /** | |
319 | * @see org.apache.fulcrum.parser.ParameterParser#getPart(java.lang.String) | |
320 | * | |
321 | * Return a Part object for the given name. If the name does | |
322 | * not exist or the object stored is not a Part, return null. | |
323 | * | |
324 | * @param name A String with the name. | |
325 | * @return A Part. | |
326 | */ | |
327 | @Override | |
328 | public Part getPart(String name) | |
329 | { | |
330 | try | |
331 | { | |
332 | 12 | Part value = null; |
333 | 12 | Object object = parameters.get(convert(name)); |
334 | 12 | if (object != null) |
335 | { | |
336 | 12 | value = ((Part[])object)[0]; |
337 | } | |
338 | 8 | return value; |
339 | } | |
340 | 4 | catch ( ClassCastException e ) |
341 | { | |
342 | 4 | return null; |
343 | } | |
344 | } | |
345 | ||
346 | /** | |
347 | * @see org.apache.fulcrum.parser.ParameterParser#getParts(java.lang.String) | |
348 | * | |
349 | * Return an array of Part objects for the given name. If the | |
350 | * name does not exist or the object stored is not a Part | |
351 | * array, return null. | |
352 | * | |
353 | * @param name A String with the name. | |
354 | * @return A Part[] | |
355 | */ | |
356 | @Override | |
357 | public Part[] getParts(String name) | |
358 | { | |
359 | try | |
360 | { | |
361 | 16 | return (Part[])parameters.get(convert(name)); |
362 | } | |
363 | 0 | catch ( ClassCastException e ) |
364 | { | |
365 | 0 | return new Part[0];// empty array |
366 | } | |
367 | } | |
368 | ||
369 | /* (non-Javadoc) | |
370 | * @see org.apache.fulcrum.parser.ParameterParser#getParts() | |
371 | */ | |
372 | @Override | |
373 | public Collection<Part> getParts() | |
374 | { | |
375 | 0 | return parameters.values().stream(). |
376 | 0 | filter( p-> p instanceof Part[]). |
377 | 0 | flatMap(c -> Arrays.stream( (Part[]) c )). |
378 | 0 | collect( Collectors.toList() ); |
379 | ||
380 | } | |
381 | ||
382 | /* (non-Javadoc) | |
383 | * @see org.apache.fulcrum.parser.ParameterParser#getFileName(javax.servlet.http.Part) | |
384 | */ | |
385 | @Override | |
386 | public String getFileName(Part part) | |
387 | { | |
388 | 4 | final String partHeader = part.getHeader("content-disposition"); |
389 | ||
390 | // rfc2183, rfc5987 quoted string, but attachments may have not? | |
391 | 4 | Pattern regex = Pattern.compile("filename\\*?=\"?(.[^\"]+)\"?"); |
392 | ||
393 | 12 | for (String content : partHeader.split(";")) |
394 | { | |
395 | // could also filename*=<encoding>''<value> | |
396 | 12 | if (content.trim().contains( "filename" )) |
397 | { | |
398 | 4 | String fnTmp = ""; |
399 | 4 | String srcStr = content.trim(); |
400 | 4 | Matcher regexMatcher = regex.matcher(srcStr); |
401 | 4 | if (regexMatcher.find()) |
402 | { | |
403 | 4 | fnTmp = regexMatcher.group(1); |
404 | 4 | if (getLogger().isDebugEnabled()) |
405 | { | |
406 | 0 | getLogger().debug( "matched fileName:" + fnTmp ); |
407 | } | |
408 | } else { | |
409 | // last resort | |
410 | 0 | fnTmp = srcStr.substring(srcStr.indexOf('=')+1).replace( "\"", "" ); |
411 | 0 | getLogger().debug( "second fileName match:" + fnTmp ); |
412 | } | |
413 | 4 | return fnTmp.trim(); |
414 | } | |
415 | } | |
416 | 0 | return null; |
417 | } | |
418 | } |