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><INPUT type="file"></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 }