001    package hirondelle.web4j.request;
002    
003    import java.util.logging.Logger;
004    import java.util.regex.Pattern;
005    import javax.servlet.ServletConfig;
006    
007    import hirondelle.web4j.action.Action;
008    import hirondelle.web4j.model.ModelCtorException;
009    import hirondelle.web4j.model.ModelFromRequest;
010    import hirondelle.web4j.model.ModelUtil;
011    import hirondelle.web4j.readconfig.InitParam;
012    import hirondelle.web4j.security.ApplicationFirewall;
013    import hirondelle.web4j.util.Util;
014    
015    /**
016     <span class="highlight">Request parameter as a name and 
017     (usually) an associated <em>regular expression</em>.</span>
018    
019     <P>This class does not <em>directly</em> provide access to the parameter <em>value</em>.
020     For such services, please see {@link RequestParser} and {@link ModelFromRequest}. 
021      
022     <P>This class separates request parameters into two kinds : file upload request 
023     parameters, and all others (here called "regular" request parameters).
024      
025     <P><b><a name="Regular">Regular Request Parameters</a></b><br>
026     Regular request parameters are associated with :
027     <ul>
028     <li>a name, corresponding to both an underlying HTTP request parameter name, and 
029     an underlying control name - see <a href="#NamingConvention">naming convention</a>. 
030     <li>a regular expression, used by {@link ApplicationFirewall} to perform 
031     <a href="ApplicationFirewall.html#HardValidation">hard validation</a>  
032     </ul> 
033     
034     <P><b><a name="FileUpload">File Upload Request Parameters</a></b><br>
035     Files are uploaded using forms having : 
036    <ul>
037     <li> <tt>method="POST"</tt>
038     <li> <tt>enctype="multipart/form-data"</tt>
039     <li> an <tt>&lt;INPUT type="file"&gt;</tt> control
040    </ul>
041     
042     <P>In addition, note that the Servlet API does <em>not</em> have extensive services for 
043     processing file upload parameters. It is likely best to use a third party tool for 
044     that task. 
045     
046     <P>File upload request parameters, <em>as represented by this class</em>, have only a
047     name associated with them, and no regular expression. This is because WEB4J 
048     cannot perform <a href="ApplicationFirewall.html#HardValidation">hard validation</a>  
049     on the value of a file upload parameter - since the user may select any file whatsoever, 
050     validation of file contents can only be treated 
051     as a <a href="ApplicationFirewall.html#SoftValidation">soft validation</a>. If there is a 
052     problem, the response to the user must be polished, as part of the normal operation of 
053     the application.  
054     
055     <P>As an example, an {@link Action} might perform 
056     <a href="ApplicationFirewall.html#SoftValidation">soft validation</a> on a file upload parameter 
057     for these items : 
058     <ul>   
059     <li>file size does not exceed a maximum value
060     <li>MIME type matches a regular expression  
061     <li>file name matches a regular expression
062     <li>text file content may be matched to a regular expression
063     </ul> 
064     
065     <P><b><a name="NamingConvention">Naming Convention</a></b><br>
066     <span class="highlight">Parameter names are usually not arbitrary in WEB4J.</span>
067     Instead, a simple convention is used which allows for automated mapping between 
068     request parameter names and corresponding <tt>getXXX</tt> methods of Model Objects  
069     (see {@link hirondelle.web4j.ui.tag.Populate}). For example, a parameter 
070     named <tt>'Birth Date'</tt> (or <tt>'birthDate'</tt>) is mapped to a method named 
071     <tt>getBirthDate()</tt> when prepopulating a form with the contents of 
072     a Model Object. (The <tt>'Birth Date'</tt> naming style is recommended, since it 
073     has this advantage : when messages regarding form input are presented to the user, 
074     the control name may be used directly, without trivial mapping  
075     of a 'coder-friendly' parameter name into more user-friendly text.)
076      
077     <P> Some parameters - notably those passed to <tt>Template.jsp</tt> - are not
078     processed at all by the <tt>Controller</tt>, but are used directly in JSPs 
079     instead. Such parameters do not undergo 
080     <a href="ApplicationFirewall.html#HardValidation">hard validation</a> by the 
081     {@link hirondelle.web4j.security.ApplicationFirewall}, and are not represented by this class.
082     
083     <P> See {@link java.util.regex.Pattern} for more information on regular expressions.
084    */
085    public final class RequestParameter { 
086      
087      /**
088       <P>Called by the framework upon startup.
089       
090       <P>Fetches the setting named <tt>MaxRequestParamValueSize</tt> in <tt>web.xml</tt>.
091       This setting is used by {@link #withLengthCheck(String)}. 
092      */
093      public static void init(ServletConfig aConfig){
094        MAX_SIZE = getMaxSize(fMAX_SIZE, aConfig);
095        fLogger.fine("Max size of request parameter values, from web.xml : " + MAX_SIZE);
096      }
097    
098      /**
099       Return a <a href="#Regular">regular parameter</a> hard-validated only for 
100       name and size. 
101       
102       <P>The size is taken from the <tt>MaxRequestParamValueSize</tt> setting in <tt>web.xml</tt>.
103        
104       @param aName name of the underlying HTTP request parameter. See 
105       <a href="#NamingConvention">naming convention</a>. 
106      */
107      public static RequestParameter withLengthCheck(String aName){
108        String regex = "(.){0," + MAX_SIZE + "}";
109        Pattern lengthPattern = Pattern.compile(regex, Pattern.DOTALL);
110        return withRegexCheck(aName, lengthPattern);
111      }
112      
113      /**
114       Return a <a href="#Regular">regular parameter</a> hard-validated for name and 
115       for value matching a regular expression.
116       
117       @param aName name of the underlying HTTP request parameter. See 
118       <a href="#NamingConvention">naming convention</a>. 
119       @param aValueRegex regular expression for doing hard validation of the request 
120       parameter value(s).
121      */
122      public static RequestParameter withRegexCheck(String aName, Pattern aValueRegex){
123        return new RequestParameter(aName, aValueRegex);    
124      }
125      
126      /**
127       Return a <a href="#Regular">regular parameter</a> hard-validated for name and 
128       for value matching a regular expression.
129       
130       @param aName name of the underlying HTTP request parameter. See 
131       <a href="#NamingConvention">naming convention</a>. 
132       @param aValueRegex regular expression for doing hard validation of the request 
133       parameter value(s).
134      */
135      public static RequestParameter withRegexCheck(String aName, String aValueRegex){
136        return new RequestParameter(aName, aValueRegex);    
137      }
138      
139      /**
140       Constructor for a <a href="#FileUpload">file upload</a> request parameter. 
141       
142       @param aName name of the underlying HTTP request parameter. See 
143       <a href="#NamingConvention">naming convention</a>. 
144      */
145      public static RequestParameter forFileUpload(String aName){
146        return new RequestParameter(aName); 
147      }
148      
149      /** Return the request parameter name.  */
150      public String getName(){
151        return fName;
152      }
153       
154      /**
155       Return the regular expression associated with this <tt>RequestParameter</tt>. 
156       
157       <P>This regular expression is used to perform 
158       <a href="ApplicationFirewall.html#HardValidation">hard validation</a> of this parameter's value(s).
159       
160       <P>This method will return <tt>null</tt> only for <a href="#FileUpload">file upload</a> parameters.
161      */
162      public Pattern getRegex(){
163        return fRegex;
164      }
165      
166      /**
167       Return <tt>true</tt> only if {@link #forFileUpload} was used to build this object. 
168      */
169      public boolean isFileUploadParameter() {
170        return ! fIsRegularParameter;
171      }
172      
173      /**
174       Return <tt>true</tt> only if <tt>aRawParamValue</tt> satisfies the regular expression 
175       {@link #getRegex()}, <em>or</em> if this is a <a href="#FileUpload">file upload</a> 
176       request parameter.
177       
178       <P>Always represents a <a href="ApplicationFirewall.html#HardValidation">hard validation</a>, not a 
179       soft validation.
180      */
181      public boolean isValidParamValue(String aRawParamValue){
182        boolean result = false;
183        if ( isFileUploadParameter() ){
184          result = true;   
185        }
186        else {
187          result = Util.matches(fRegex, aRawParamValue);
188        }
189        return result;
190      }
191      
192      @Override public boolean equals(Object aThat){
193        if (this == aThat) return true;
194        if ( !(aThat instanceof RequestParameter) ) return false;
195        RequestParameter that = (RequestParameter)aThat;
196        return ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
197      }
198      
199      @Override public int hashCode(){
200        return ModelUtil.hashCodeFor(getSignificantFields());
201      }
202    
203      /** Intended for debugging only.  */
204      @Override public String toString() {
205        String result = null;
206        if ( isFileUploadParameter() ) {
207          result = "Name[File Upload]: " + fName;  
208        }
209        else {
210          result = "Name:" + fName + " Regex:" + fRegex;  
211        }
212        return result;
213      } 
214      
215      // PRIVATE // 
216      private final String fName;
217      private Pattern fRegex; //Patterns are immutable
218      private final boolean fIsRegularParameter;
219      
220      /**
221       Max size of a valid HTTP request parameter value, in bytes. Pulled in from web.xml.
222      */
223      private static int MAX_SIZE;
224      private static final InitParam fMAX_SIZE = new InitParam("MaxRequestParamValueSize", "51200");
225      private static final Logger fLogger = Util.getLogger(RequestParameter.class);
226      
227      private static int getMaxSize(InitParam aInitParam, ServletConfig aConfig){
228        int result = Integer.parseInt(
229          aInitParam.fetch(aConfig).getValue()
230        );
231        if ( result < 1000 ) {
232          throw new IllegalArgumentException(
233            "Configured value of " + result + " in web.xml for " + 
234            aInitParam.getName() + 
235            " is too low. Please see web.xml for more information." 
236          );
237        }
238        return result;
239      }
240      
241      /**
242       Constructor for a <a href="#Regular">"regular"</a> request parameter. 
243       
244       @param aName name of the underlying HTTP request parameter. See 
245       <a href="#NamingConvention">naming convention</a>. 
246       @param aRegex regular expression for doing hard validation of the request 
247       parameter value(s).
248      */
249      private RequestParameter(String aName, String aRegex) { 
250        this(aName, Pattern.compile(aRegex));
251        validateState();
252      }
253       
254      /**
255       Constructor for a <a href="#Regular">"regular"</a> request parameter.
256        
257       @param aName name of the underlying HTTP request parameter. See 
258       <a href="#NamingConvention">naming convention</a>. 
259       @param aRegex regular expression for doing hard validation of the request 
260       parameter value(s).
261      */
262      private RequestParameter(String aName, Pattern aRegex) {
263        fName = aName;
264        fRegex = aRegex;
265        fIsRegularParameter = true;
266        validateState();
267      }
268      
269      /**
270       Constructor for a <a href="#FileUpload">file upload</a> request parameter.
271        
272       @param aName name of the underlying HTTP request parameter. See 
273       <a href="#NamingConvention">naming convention</a>. 
274      */
275      private RequestParameter(String aName) {
276        fName = aName;
277        fIsRegularParameter = false;
278        validateState();
279      }
280     
281      private void validateState(){
282        //use the model ctor exception only to gather errors together
283        //it is never actually thrown.
284        ModelCtorException ex = new ModelCtorException();
285        if ( ! Util.textHasContent(fName) ){
286          ex.add("Name must have content.");
287        }
288        if ( fIsRegularParameter && (fRegex == null || ! Util.textHasContent(fRegex.pattern()) ) ){
289          ex.add("For regular request parameters, regex pattern must be present.");
290        }
291        if ( ! fIsRegularParameter && fRegex != null  ){
292          ex.add("For file upload parameters, regex pattern must be null.");
293        }
294        if ( ex.isNotEmpty() ) {
295          throw new IllegalArgumentException(ex.getMessages().toString());
296        }
297      }
298      
299      private Object[] getSignificantFields(){
300        return new Object[] {fName, fRegex};
301      }
302    }