Coverage Report - org.apache.fulcrum.parser.DefaultParameterParser
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultParameterParser
63%
68/107
47%
20/42
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&#252;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  
 }