/*
* Copyright (C) 2000-2015 aw2.0 LTD
*
* This file is part of Open BlueDragon (OpenBD) CFML Server Engine.
*
* OpenBD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* Free Software Foundation,version 3.
*
* OpenBD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenBD. If not, see http://www.gnu.org/licenses/
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or combining
* it with any of the JARS listed in the README.txt (or a modified version of
* (that library), containing parts covered by the terms of that JAR, the
* licensors of this Program grant you additional permission to convey the
* resulting work.
* README.txt @ http://www.openbluedragon.org/license/README.txt
*
* http://www.openbd.org/
* $Id: cfFormData.java 2471 2015-01-11 23:37:49Z alan $
*/
package com.naryx.tagfusion.cfm.engine;
/**
* Handles the incoming HTTP data that may originate from either a URL or a FORM submission.
* In addition this class holds the body of the incoming request for POST only. This allows
* the function 'GetHttpRequestData' to be able to poll it.
*
* This class can also handle the combined scopes to allow 'form' and 'url' to act the same.
*/
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.List;
import javax.servlet.ServletInputStream;
import org.apache.oro.text.regex.MalformedPatternException;
import com.nary.util.SequencedHashMap;
import com.nary.util.string;
import com.naryx.tagfusion.cfm.tag.cfERROR;
import com.naryx.tagfusion.cfm.tag.cfPARAM;
import com.naryx.tagfusion.util.RequestUtil;
public class cfFormData extends cfUrlData implements java.io.Serializable{
static final long serialVersionUID = 1;
private boolean combined;
private byte[] incomingRequest = null;
private boolean isMultipartData = false;
public cfFormData( cfSession _session ){
this(_session,false);
}
public cfFormData( cfSession _session, boolean combinedScopes ){
combined = combinedScopes;
String enc = _session.REQ.getCharacterEncoding();
if ( enc == null ){
enc = cfEngine.getDefaultEncoding();
}
try{
init( _session, enc );
}catch ( UnsupportedEncodingException ue ) {
cfEngine.log( ue.getMessage() ); // what should we do here?
}
}
protected void reinit( cfSession _session, String enc ) throws UnsupportedEncodingException{
encoding = enc;
// if combined, then decode the URI string first of all
if ( combined )
decodeURIString( _session );
int contentLength = _session.REQ.getContentLength();
if ( // _session.REQ.getMethod().equals( "POST" ) && // see bug #2696 for why this is commented-out
( contentLength > 0 ) &&
( _session.REQ.getContentType().indexOf( "application/x-www-form-urlencoded" ) == 0 ) ) {
readParams();
}
}
public boolean isMultipartData(){
return isMultipartData;
}
private void readParams() throws UnsupportedEncodingException{
SequencedHashMap formParms = new SequencedHashMap();
int len = incomingRequest.length;
// note we're making a copy of the request data to pass to RequestUtil.parseParameters()
// because that method edits the contents of the byte array. This is not desirable since
// we'll need the original contents if we wish to reinit() with a different encoding
byte [] reqCopy = new byte[len];
System.arraycopy( incomingRequest,0,reqCopy,0,len);
RequestUtil.parseParameters( formParms, reqCopy, encoding );
Iterator<?> iter = formParms.keySet().iterator();
while ( iter.hasNext() ){
String key = (String)iter.next();
String[] valArray = (String[])formParms.get( key );
String value = valArray[ 0 ];
// create a comma-separated list for multiple values
if ( valArray.length > 1 ) {
StringBuilder valBuffer = new StringBuilder();
for ( int i = 0; i < valArray.length; i++ ){
if ( valArray[ i ].length() > 0 ) {
valBuffer.append( valArray[ i ] );
valBuffer.append( ',' );
}
}
if ( valBuffer.length() > 0 ) {
// remove trailing ','
value = valBuffer.toString().substring( 0, valBuffer.length() - 1 );
}
}
//-- Check to see if this value hasn't already been added with a URI
if ( combined ){
cfData data;
if ( (data=this.getData(key)) != null ){
try{
value = value + "," + data.getString();
}catch(Exception ignoreE){}
}
}
this.setData( key, new cfStringData( value ) );
}
this.setData( "fieldnames", new cfStringData( this.getKeyList( "," ) ) );
}
protected void init( cfSession _session, String enc ) throws UnsupportedEncodingException {
encoding = enc;
// --[ if combined, then decode the URI string first of all
if ( combined ) {
decodeURIString( _session );
}
int contentLength = _session.REQ.getContentLength();
String contentType = _session.REQ.getContentType();
if ( contentType == null ) {
contentType = "";
}
isMultipartData = (contentType.indexOf( "multipart/form-data" ) == 0 );
if ( // _session.REQ.getMethod().equals( "POST" ) && // see bug #2696 for why this is commented-out
( contentLength > 0 )
&& ( !isMultipartData ) ) {
try {
int max = contentLength;
int len = 0;
incomingRequest = new byte[ contentLength ];
ServletInputStream is = _session.REQ.getInputStream();
while ( len < max ) {
int next = is.read( incomingRequest, len, max - len );
if (next < 0) {
break;
}
len += next;
}
is.close();
} catch ( IOException ignoreIOexcption ) {}
if ( contentType.indexOf( "application/x-www-form-urlencoded" ) == 0 ) {
readParams();
}
}
}
public void setEncoding( cfSession _session, String _encoding ) throws UnsupportedEncodingException {
String currEncoding = encoding;
encoding = _encoding;
cfDecodedInput di = (cfDecodedInput) _session.getDataBin( cfDecodedInput.DATA_BIN_KEY );
if ( di != null ){ // if cfDecodedInput is present then form data is multipart
di.reencodeParameters( _session, currEncoding, _encoding );
}else{ // otherwise use different method to reset the encoding of the form data
clear();
reinit( _session, _encoding );
}
}
public String getEncoding(){
return encoding;
}
public byte[] getRequestData(){
return incomingRequest;
}
public void validateFormFields( cfSession parentSession ) throws cfmAbortException {
Object [] keys = keys();
String parameter;
String type;
int typeStartIndx;
StringBuilder errorText = new StringBuilder();
boolean bError = false;
boolean subError;
for ( int i = 0; i < keys.length; i++ ){
parameter = (String) keys[i];
subError = false;
typeStartIndx = parameter.toLowerCase().lastIndexOf( "_cfform" );
if ( typeStartIndx == -1 ){
typeStartIndx = parameter.lastIndexOf( '_' );
// legacy support for _eurodate, _range, _float, _date, _time, _integer only as per cfmx
if ( typeStartIndx != -1 ){
type = parameter.substring( typeStartIndx+1 ).toLowerCase();;
if ( !( type.equals( "integer" ) || type.equals( "time" ) || type.equals( "date" ) || type.equals( "float" ) || type.equals( "range" ) || type.equals( "eurodate" ) ) ){
continue;
}
}else{
continue;
}
}else{
type = parameter.substring( typeStartIndx+7 ).toLowerCase();
}
//--[ Check for the required
if ( type.equals( "required" ) && !isVariablePresent(parameter) ){
subError = true;
} else if ( isVariablePresent( parameter ) ){
if ( type.equals( "integer" ) ){
subError = !isInteger(parameter);
}else if ( type.equals( "float" ) || type.equals( "numeric" ) ){
subError = !isFloat(parameter);
}else if ( ( type.equals( "date" ) || type.equals( "usdate" ) || type.equals( "eurodate" ) || type.equals( "time" ) ) ){
subError = !isTimeDate(parameter);
}else if ( type.equals( "ssn") || type.equals( "social_security_number" ) ){
subError = !isSSN( parameter );
}else if ( type.equals( "uuid") ){
subError = !isUUID( parameter );
}else if ( type.equals( "guid") ){
subError = !isGUID( parameter );
}else if ( type.equals( "zipcode") ){
subError = !isZipCode( parameter );
}else if ( type.equals( "telephone") ){
subError = !isTelephone( parameter );
}else if ( type.equals( "creditcard") ){
subError = !isCreditCard( parameter );
}else if ( type.equals( "boolean") ){
subError = !isBoolean( parameter );
}else if ( type.equals( "email") ){
subError = !isEmail( parameter );
} else if ( type.equals( "maxlength" ) ){
subError = !isLengthLessThan(parameter);
} else if ( type.equals( "noblanks" ) ){
subError = getParameterValue( parameter ).trim().length() == 0;
} else if ( type.equals( "range" ) ){
subError = !isInRange(parameter);
} else if ( type.equals( "url" ) ){
subError = !isUrl(parameter);
} else if ( type.equals( "regex" ) ){
subError = !isMatch(parameter);
}
}
// if it failed then add the error
if ( subError ){
bError = true;
errorText.append( "<LI>" );
errorText.append( getError( type, parameter ) );
errorText.append( "</LI>" );
}
}
//--[ If an error then display and throw an error
if ( bError ){
//--[ Check to see if the user has specified a CFERROR tag for this error
cfErrorData errorData = (cfErrorData)parentSession.getDataBin( cfERROR.DATA_BIN_KEY );
if ( errorData == null || (errorData != null && !errorData.handleValidationError( parentSession, errorText.toString())) ){
parentSession.clearCfSettings();
parentSession.reset();
parentSession.write( "<HTML><HEAD><TITLE>Form Entries Incomplete or Invalid</TITLE></HEAD><BODY><HR><H3>Form Entries Incomplete or Invalid</H3>One or more problems exist with the data you have entered.<UL>" );
parentSession.write( errorText.toString() );
parentSession.write( "</UL>Use the <I>Back</I> button on your web browser to return to the previous page and correct the listed problems.<P><HR></BODY></HTML>" );
}
parentSession.abortPageProcessing();
}
}
protected String getParameter( String parameter ) {
try {
cfData data = getData( parameter );
return ( data == null ? null : data.getString() );
} catch ( dataNotSupportedException e ) {
return null;
}
}
protected String getParameterName( String field ) {
return field.substring( 0, field.lastIndexOf("_") );
}
private boolean isVariablePresent( String field ){
String tmp = getParameter( getParameterName(field) );
return ( tmp == null || tmp.length() == 0 ) ? false : true;
}
// returns the appropriate error message for the given field type
private String getError( String type, String field ){
String errorValue = getParameter( field );
// if the type is maxlength, noblanks or regex - the VALUE doesn't contain the error message to use
if ( type.equals( "maxlength" ) ){
return "Data entered in the <B>" + getParameterName( field ) + "</B> field must be, at most, " + getParameter( field ) + " characters in length (you entered '" + getParameter( getParameterName(field) ) + "').";
}else if ( type.equals( "regex" ) ){
return "Data entered in the <B>" + getParameterName( field ) + "</B> field must match the regular expression " + getParameter( field ) + " (you entered '" + getParameter( getParameterName(field) ) + "').";
}else if ( type.equals( "range" ) ){
return "Data entered in the <B>" + getParameterName( field ) + "</B> field must be in the range [" + getParameter( field ) + "] (you entered '" + getParameter( getParameterName(field) ) + "').";
}else if ( errorValue != null && errorValue.length() > 0 ){ // if the VALUE is present and not blank
return errorValue;
}else if ( type.equals( "required" ) ){
return "A value must be entered for the <B>" + getParameterName( field ) + "</B> field.";
}else if ( type.equals( "noblanks" ) ){
return "Data entered in the <B>" + getParameterName( field ) + "</B> field must not be blank (you entered '" + getParameter( getParameterName(field) ) + "').";
}else if ( type.equals( "integer" ) ){
return "Data entered in the <B>" + getParameterName( field ) + "</B> field must be an integer (you entered '" + getParameter( getParameterName(field) ) + "').";
}else{
return "Data entered in the <B>" + getParameterName( field ) + "</B> field must be a " + type + " (you entered '" + getParameter( getParameterName(field) ) + "').";
}
}
private boolean isMatch( String field ){
try{
return com.nary.util.string.regexMatches( getParameterValue( field ), getParameter( field ) );
}catch( Exception e ){
return false;
}
}
private boolean isInteger( String field ){
try{
Integer.valueOf( getParameterValue( field ) );
return true;
}catch( NumberFormatException E){
return false;
}
}
private boolean isFloat( String field ){
try{
Double.valueOf( getParameterValue( field ) );
return true;
}catch( NumberFormatException E){
return false;
}
}
private boolean isBoolean( String field ){
String bool = getParameterValue( field ).toLowerCase();
if ( bool.equalsIgnoreCase( "yes" ) || bool.equals( "true" ) || bool.equals( "no" ) || bool.equals( "false" ) ){
return true;
}
try{
Integer.parseInt( bool );
return true;
}catch( NumberFormatException E){}
return false;
}
private boolean isUrl( String field ){
try {
return cfPARAM.isUrl( getParameterValue( field ) );
} catch (MalformedPatternException e) { // shouldn't happen
return false;
}
}
private boolean isSSN( String field ){
try{
return cfPARAM.validateRE( cfPARAM.SSN_RE, getParameterValue( field ) );
} catch (MalformedPatternException e) { // shouldn't happen
return false;
}
}
private boolean isUUID( String field ){
try{
return cfPARAM.validateRE( cfPARAM.UUID_RE, getParameterValue( field ).toLowerCase() );
} catch (MalformedPatternException e) { // shouldn't happen
return false;
}
}
private boolean isZipCode( String field ){
try{
return cfPARAM.validateRE( cfPARAM.ZIPCODE_RE, getParameterValue( field ) );
} catch (MalformedPatternException e) { // shouldn't happen
return false;
}
}
private boolean isTelephone( String field ){
try{
return cfPARAM.validateRE( cfPARAM.PHONE_RE, getParameterValue( field ) );
} catch (MalformedPatternException e) { // shouldn't happen
return false;
}
}
private boolean isCreditCard( String field ){
try{
return cfPARAM.isCreditCard( getParameterValue( field ) );
} catch (MalformedPatternException e) { // shouldn't happen
return false;
}
}
private boolean isGUID( String field ){
try{
return cfPARAM.validateRE( cfPARAM.GUID_RE, getParameterValue( field ).toLowerCase() );
} catch (MalformedPatternException e) { // shouldn't happen
return false;
}
}
private boolean isEmail( String field ){
try{
return cfPARAM.validateRE( cfPARAM.EMAIL_RE, getParameterValue( field ) );
} catch (MalformedPatternException e) { // shouldn't happen
return false;
}
}
private boolean isLengthLessThan( String field ){
String maxlenStr = getParameter( field );
try{
int maxLen = Integer.parseInt( maxlenStr );
if ( maxLen < 0 ){
return true;
}
return getParameterValue( field ).length() <= maxLen;
}catch( NumberFormatException E){
return true;
}
}
private String getParameterValue( String field ){
String value = getParameter( getParameterName( field ) );
value = com.nary.util.string.replaceString( value, ",", "" );
value = com.nary.util.string.replaceString( value, "$", "" );
return value.trim();
}
private boolean isInRange( String field ){
String minmax = getParameter( field ).toLowerCase();
if ( minmax.indexOf("min=") == -1 && minmax.indexOf("max=") == -1 )
return false;
try{
double valueD = Double.valueOf( getParameterValue( field ) ).doubleValue();
List<String> tokens = string.split( minmax, " " );
for ( int i = 0; i < tokens.size(); i++ ){
String mm = tokens.get(i).toString();
int c1 = mm.indexOf( "=" );
if ( c1 == -1 )
return false;
String mmType = mm.substring( 0, c1 );
double mmValue = Double.valueOf( mm.substring( c1+1 ) ).doubleValue();
if ( mmType.equals("min") && (valueD < mmValue) )
return false;
else if ( mmType.equals("max") && (valueD > mmValue) )
return false;
}
}catch( NumberFormatException E){
return false;
}
return true;
}
private boolean isTimeDate( String field ){
String DD = getParameter( getParameterName( field ) );
if ( DD == null || DD.trim().length() == 0 )
return false;
com.nary.util.date.dateTimeTokenizer DT = new com.nary.util.date.dateTimeTokenizer( DD );
if ( ( !DT.validateStructure() ) || ( DT.getDate() == null ) )
return false;
else
return true;
}
}