package hirondelle.web4j.request;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.servlet.ServletConfig;
import hirondelle.web4j.action.Action;
import hirondelle.web4j.model.ModelCtorException;
import hirondelle.web4j.model.ModelFromRequest;
import hirondelle.web4j.model.ModelUtil;
import hirondelle.web4j.readconfig.InitParam;
import hirondelle.web4j.security.ApplicationFirewall;
import hirondelle.web4j.util.Util;
/**
<span class="highlight">Request parameter as a name and
(usually) an associated <em>regular expression</em>.</span>
<P>This class does not <em>directly</em> provide access to the parameter <em>value</em>.
For such services, please see {@link RequestParser} and {@link ModelFromRequest}.
<P>This class separates request parameters into two kinds : file upload request
parameters, and all others (here called "regular" request parameters).
<P><b><a name="Regular">Regular Request Parameters</a></b><br>
Regular request parameters are associated with :
<ul>
<li>a name, corresponding to both an underlying HTTP request parameter name, and
an underlying control name - see <a href="#NamingConvention">naming convention</a>.
<li>a regular expression, used by {@link ApplicationFirewall} to perform
<a href="ApplicationFirewall.html#HardValidation">hard validation</a>
</ul>
<P><b><a name="FileUpload">File Upload Request Parameters</a></b><br>
Files are uploaded using forms having :
<ul>
<li> <tt>method="POST"</tt>
<li> <tt>enctype="multipart/form-data"</tt>
<li> an <tt><INPUT type="file"></tt> control
</ul>
<P>In addition, note that the Servlet API does <em>not</em> have extensive services for
processing file upload parameters. It is likely best to use a third party tool for
that task.
<P>File upload request parameters, <em>as represented by this class</em>, have only a
name associated with them, and no regular expression. This is because WEB4J
cannot perform <a href="ApplicationFirewall.html#HardValidation">hard validation</a>
on the value of a file upload parameter - since the user may select any file whatsoever,
validation of file contents can only be treated
as a <a href="ApplicationFirewall.html#SoftValidation">soft validation</a>. If there is a
problem, the response to the user must be polished, as part of the normal operation of
the application.
<P>As an example, an {@link Action} might perform
<a href="ApplicationFirewall.html#SoftValidation">soft validation</a> on a file upload parameter
for these items :
<ul>
<li>file size does not exceed a maximum value
<li>MIME type matches a regular expression
<li>file name matches a regular expression
<li>text file content may be matched to a regular expression
</ul>
<P><b><a name="NamingConvention">Naming Convention</a></b><br>
<span class="highlight">Parameter names are usually not arbitrary in WEB4J.</span>
Instead, a simple convention is used which allows for automated mapping between
request parameter names and corresponding <tt>getXXX</tt> methods of Model Objects
(see {@link hirondelle.web4j.ui.tag.Populate}). For example, a parameter
named <tt>'Birth Date'</tt> (or <tt>'birthDate'</tt>) is mapped to a method named
<tt>getBirthDate()</tt> when prepopulating a form with the contents of
a Model Object. (The <tt>'Birth Date'</tt> naming style is recommended, since it
has this advantage : when messages regarding form input are presented to the user,
the control name may be used directly, without trivial mapping
of a 'coder-friendly' parameter name into more user-friendly text.)
<P> Some parameters - notably those passed to <tt>Template.jsp</tt> - are not
processed at all by the <tt>Controller</tt>, but are used directly in JSPs
instead. Such parameters do not undergo
<a href="ApplicationFirewall.html#HardValidation">hard validation</a> by the
{@link hirondelle.web4j.security.ApplicationFirewall}, and are not represented by this class.
<P> See {@link java.util.regex.Pattern} for more information on regular expressions.
*/
public final class RequestParameter {
/**
<P>Called by the framework upon startup.
<P>Fetches the setting named <tt>MaxRequestParamValueSize</tt> in <tt>web.xml</tt>.
This setting is used by {@link #withLengthCheck(String)}.
*/
public static void init(ServletConfig aConfig){
MAX_SIZE = getMaxSize(fMAX_SIZE, aConfig);
fLogger.fine("Max size of request parameter values, from web.xml : " + MAX_SIZE);
}
/**
Return a <a href="#Regular">regular parameter</a> hard-validated only for
name and size.
<P>The size is taken from the <tt>MaxRequestParamValueSize</tt> setting in <tt>web.xml</tt>.
@param aName name of the underlying HTTP request parameter. See
<a href="#NamingConvention">naming convention</a>.
*/
public static RequestParameter withLengthCheck(String aName){
String regex = "(.){0," + MAX_SIZE + "}";
Pattern lengthPattern = Pattern.compile(regex, Pattern.DOTALL);
return withRegexCheck(aName, lengthPattern);
}
/**
Return a <a href="#Regular">regular parameter</a> hard-validated for name and
for value matching a regular expression.
@param aName name of the underlying HTTP request parameter. See
<a href="#NamingConvention">naming convention</a>.
@param aValueRegex regular expression for doing hard validation of the request
parameter value(s).
*/
public static RequestParameter withRegexCheck(String aName, Pattern aValueRegex){
return new RequestParameter(aName, aValueRegex);
}
/**
Return a <a href="#Regular">regular parameter</a> hard-validated for name and
for value matching a regular expression.
@param aName name of the underlying HTTP request parameter. See
<a href="#NamingConvention">naming convention</a>.
@param aValueRegex regular expression for doing hard validation of the request
parameter value(s).
*/
public static RequestParameter withRegexCheck(String aName, String aValueRegex){
return new RequestParameter(aName, aValueRegex);
}
/**
Constructor for a <a href="#FileUpload">file upload</a> request parameter.
@param aName name of the underlying HTTP request parameter. See
<a href="#NamingConvention">naming convention</a>.
*/
public static RequestParameter forFileUpload(String aName){
return new RequestParameter(aName);
}
/** Return the request parameter name. */
public String getName(){
return fName;
}
/**
Return the regular expression associated with this <tt>RequestParameter</tt>.
<P>This regular expression is used to perform
<a href="ApplicationFirewall.html#HardValidation">hard validation</a> of this parameter's value(s).
<P>This method will return <tt>null</tt> only for <a href="#FileUpload">file upload</a> parameters.
*/
public Pattern getRegex(){
return fRegex;
}
/**
Return <tt>true</tt> only if {@link #forFileUpload} was used to build this object.
*/
public boolean isFileUploadParameter() {
return ! fIsRegularParameter;
}
/**
Return <tt>true</tt> only if <tt>aRawParamValue</tt> satisfies the regular expression
{@link #getRegex()}, <em>or</em> if this is a <a href="#FileUpload">file upload</a>
request parameter.
<P>Always represents a <a href="ApplicationFirewall.html#HardValidation">hard validation</a>, not a
soft validation.
*/
public boolean isValidParamValue(String aRawParamValue){
boolean result = false;
if ( isFileUploadParameter() ){
result = true;
}
else {
result = Util.matches(fRegex, aRawParamValue);
}
return result;
}
@Override public boolean equals(Object aThat){
if (this == aThat) return true;
if ( !(aThat instanceof RequestParameter) ) return false;
RequestParameter that = (RequestParameter)aThat;
return ModelUtil.equalsFor(this.getSignificantFields(), that.getSignificantFields());
}
@Override public int hashCode(){
return ModelUtil.hashCodeFor(getSignificantFields());
}
/** Intended for debugging only. */
@Override public String toString() {
String result = null;
if ( isFileUploadParameter() ) {
result = "Name[File Upload]: " + fName;
}
else {
result = "Name:" + fName + " Regex:" + fRegex;
}
return result;
}
// PRIVATE //
private final String fName;
private Pattern fRegex; //Patterns are immutable
private final boolean fIsRegularParameter;
/**
Max size of a valid HTTP request parameter value, in bytes. Pulled in from web.xml.
*/
private static int MAX_SIZE;
private static final InitParam fMAX_SIZE = new InitParam("MaxRequestParamValueSize", "51200");
private static final Logger fLogger = Util.getLogger(RequestParameter.class);
private static int getMaxSize(InitParam aInitParam, ServletConfig aConfig){
int result = Integer.parseInt(
aInitParam.fetch(aConfig).getValue()
);
if ( result < 1000 ) {
throw new IllegalArgumentException(
"Configured value of " + result + " in web.xml for " +
aInitParam.getName() +
" is too low. Please see web.xml for more information."
);
}
return result;
}
/**
Constructor for a <a href="#Regular">"regular"</a> request parameter.
@param aName name of the underlying HTTP request parameter. See
<a href="#NamingConvention">naming convention</a>.
@param aRegex regular expression for doing hard validation of the request
parameter value(s).
*/
private RequestParameter(String aName, String aRegex) {
this(aName, Pattern.compile(aRegex));
validateState();
}
/**
Constructor for a <a href="#Regular">"regular"</a> request parameter.
@param aName name of the underlying HTTP request parameter. See
<a href="#NamingConvention">naming convention</a>.
@param aRegex regular expression for doing hard validation of the request
parameter value(s).
*/
private RequestParameter(String aName, Pattern aRegex) {
fName = aName;
fRegex = aRegex;
fIsRegularParameter = true;
validateState();
}
/**
Constructor for a <a href="#FileUpload">file upload</a> request parameter.
@param aName name of the underlying HTTP request parameter. See
<a href="#NamingConvention">naming convention</a>.
*/
private RequestParameter(String aName) {
fName = aName;
fIsRegularParameter = false;
validateState();
}
private void validateState(){
//use the model ctor exception only to gather errors together
//it is never actually thrown.
ModelCtorException ex = new ModelCtorException();
if ( ! Util.textHasContent(fName) ){
ex.add("Name must have content.");
}
if ( fIsRegularParameter && (fRegex == null || ! Util.textHasContent(fRegex.pattern()) ) ){
ex.add("For regular request parameters, regex pattern must be present.");
}
if ( ! fIsRegularParameter && fRegex != null ){
ex.add("For file upload parameters, regex pattern must be null.");
}
if ( ex.isNotEmpty() ) {
throw new IllegalArgumentException(ex.getMessages().toString());
}
}
private Object[] getSignificantFields(){
return new Object[] {fName, fRegex};
}
}