Ask Ben: Form Processing Demo With Anti-Spam Features
There is not a specific question here. Someone contacted me and was having trouble implementing one of the ColdFusion anti-spam examples that I put up. I have created this single page demo that includes both display and form processing so that people can see how it all fits together.
This is NOT meant to be bullet a proof anti-spam solution. This is meant as a demo to show some people how this might work. I hope that it helps. I have tried to put in good comments. The form makes use of a time stamp (an hour) for which the form is active. The idea is not to permanently stop forms from being submitted after an hour; the objective of the time stamp is to force a form refresh if the form is old. This is just meant to prevent spam bots from automatically submitting cached forms.
Hope this helps:
<!--- Kill extra output. --->
<cfsilent>
<!--- Param form variables. --->
<cfparam
name="FORM.first_name"
type="string"
default=""
/>
<cfparam
name="FORM.last_name"
type="string"
default=""
/>
<cfparam
name="FORM.email"
type="string"
default=""
/>
<cfparam
name="FORM.comments"
type="string"
default=""
/>
<cftry>
<cfparam
name="FORM.submitted"
type="numeric"
default="0"
/>
<cfcatch>
<cfset FORM.submitted = 0 />
</cfcatch>
</cftry>
<!---
Param the anti-spam form values. These are the values
that we want the spam bots to submit by accident. These
are not going to be used for standard form useage.
--->
<cfparam
name="FORM.notes"
type="string"
default=""
/>
<cfparam
name="FORM.referrer"
type="string"
default=""
/>
<cfparam
name="FORM.spam_key1"
type="string"
default=""
/>
<cfparam
name="FORM.spam_key2"
type="string"
default=""
/>
<!---
Create an array to keep track of the form submissions
errors. This will help us determine if the form is
valid for submission.
--->
<cfset arrErrors = ArrayNew( 1 ) />
<!---
Set up an anti-spam key. This is what will be used
to encrypt and decrypt the spam key time stamp and
additional anti spam keys.
--->
<cfset strEncryptionKey = "azure_is_a_mega_babe!" />
<!---
Clean up the form data. While this is clearly not
necessary for any non-form posts, I like to do it here
just cause (I usually don't include in the form
processing page - it is a site-wide feature).
--->
<cfloop
item="strKey"
collection="#FORM#">
<!--- Trim all form values. --->
<cfset FORM[ strKey ] = Trim( FORM[ strKey ] ) />
<!--- Unescape any quotes. --->
<cfset FORM[ strKey ] = Replace(
FORM[ strKey ],
""",
"""",
"ALL"
) />
<!---
You could also do things here like replace out
Microsoft quotes with standard web quotes or
strip out M/N-dashes and replace with
standard dashes.
--->
</cfloop>
<!--- Check to see if the form has been submitted. --->
<cfif FORM.submitted>
<!---
The form has been submitted. Validate the form
data to make sure we have everything we need and
that this was NOT a Spam submission.
--->
<cfif NOT Len( FORM.first_name )>
<cfset ArrayAppend(
arrErrors,
"Please enter your first name."
) />
</cfif>
<cfif NOT Len( FORM.last_name )>
<cfset ArrayAppend(
arrErrors,
"Please enter your last name."
) />
</cfif>
<cfif NOT IsValid( "email", FORM.email )>
<cfset ArrayAppend(
arrErrors,
"Please enter a valid email address."
) />
</cfif>
<!---
Check for spam bot red flags. Remember, since our
spam fields were hidden, standard users shoudl NOT
have seen them and therefore should not have filled
them out. However, as Spam bots see things that
standard users do not, they might fill them out. We
are also going to be checking our spam key. Since
this involved decryption let's put it in a CFTry /
CFCatch to handle any errors.
--->
<cftry>
<!--- Decrypt the encryption key. --->
<cfset FORM.spam_key2 = Decrypt(
FORM.spam_key2,
strEncryptionKey,
"CFMX_COMPAT",
"HEX"
) />
<!--- Decrypt the time stamp. --->
<cfset FORM.spam_key1 = Decrypt(
FORM.spam_key1,
FORM.spam_key2,
"CFMX_COMPAT",
"HEX"
) />
<!--- Check for spam. --->
<cfif (
Len( FORM.notes ) OR
Len( FORM.referrer ) OR
(NOT IsNumericDate( FORM.spam_key1 )) OR
(FORM.spam_key1 LT Now())
)>
<cfset ArrayAppend(
arrErrors,
"There was a problem with your form submission."
) />
</cfif>
<!--- Handle any anti-spam errors. --->
<cfcatch>
<cfset ArrayAppend(
arrErrors,
"There was a problem with your form submission."
) />
<!--- For Debugging: ---
<cfdump var="#CFCATCH#" />
<cfabort />
--->
</cfcatch>
</cftry>
<!---
Check to see if we have any form errors from data
validation. If we do, then the form errors array
will have a length. If this is the case, we do NOT
want to process any further. Instead, skip the rest
and then just re-show the form with the previous
data values.
--->
<cfif NOT ArrayLen( arrErrors )>
<!---
The form data is valid and found to have been
submitted by a standard user (not a spam bot).
At this point you can put it in the database,
send out emails, do what ever you want.
--->
</cfif>
<cfelse>
<!---
The form has NOT been submitted yet. Do any sort
of form initialization here.
--->
</cfif>
<!---
The actions here (after we have checed to see if the
form has been submitted) will take place no matter
what (unless processing has been haulted).
--->
<!---
Let's create a random key to encrypt the spam time
stamp (created next).
--->
<cfset FORM.spam_key2 = RepeatString(
ToString( Rand() ).ReplaceFirst( "^(\d+\.)?", "" ),
2
) />
<!---
Let's create a time stamp for this form so that if
it is cached, it cannot be submitted directly.
--->
<cfset FORM.spam_key1 = (
Now() +
CreateTimeSpan(
0, <!--- Days. --->
1, <!--- Hours. --->
0, <!--- Minutes. --->
0 <!--- Seconds. --->
)
) />
<!--- Encrypt the spam key using our random number. --->
<cfset FORM.spam_key1 = Encrypt(
FORM.spam_key1,
FORM.spam_key2,
"CFMX_COMPAT",
"HEX"
) />
<!---
Encrypt the encryption key using our global
encryption key. We will need this value AND the
global encryption key to decrypt the time stamp above.
--->
<cfset FORM.spam_key2 = Encrypt(
FORM.spam_key2,
strEncryptionKey,
"CFMX_COMPAT",
"HEX"
) />
<!---
As one final thing before we render a page, we want to
make sure that our form data will NOT break the forms.
If any of our "Text"-based form values have quotes in
them, it will break the HTML. Escape all quotes.
--->
<cfloop
item="strKey"
collection="#FORM#">
<!--- Unescape any quotes. --->
<cfset FORM[ strKey ] = Replace(
FORM[ strKey ],
"""",
""",
"ALL"
) />
</cfloop>
</cfsilent>
<cfoutput>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Anti-Spam Form Posting</title>
</head>
<body>
<!---
Check to see if we have any form errors to display.
We can check this no matter what as the variable
always exists. If the form has not been submitted
then the errors array is empty and will not show up.
--->
<cfif ArrayLen( arrErrors )>
<h3>
Please review the following errors:
</h3>
<ul>
<cfloop
index="intError"
from="1"
to="#ArrayLen( arrErrors )#"
step="1">
<li>
#arrErrors[ intError ]#
</li>
</cfloop>
</ul>
</cfif>
<form
action="#CGI.script_name#"
method="post">
<!---
This hidden form field is meant to flag that
the form has been submitted on the next
page submit.
--->
<input
type="hidden"
name="submitted"
value="1"
/>
<!---
This is the time stamp before which the form
is still valid.
--->
<input
type="hidden"
name="spam_key1"
value="#FORM.spam_key1#"
/>
<!---
This is the encrypted key that we used to
encrypt the timestamp.
--->
<input
type="hidden"
name="spam_key2"
value="#FORM.spam_key2#"
/>
<label for="first_name">
First Name:
<input
type="text"
name="first_name"
id="first_name"
value="#FORM.first_name#"
maxlength="30"
/><br />
</label>
<br />
<label for="last_name">
Last Name:
<input
type="text"
name="last_name"
id="last_name"
value="#FORM.last_name#"
maxlength="40"
/><br />
</label>
<br />
<label for="email">
Email:
<input
type="text"
name="email"
id="email"
value="#FORM.email#"
maxlength="75"
/><br />
</label>
<br />
<input type="submit" value="Submit Form" /><br />
<!---
Now, here's where we put the anti-spam form
fields. These are referred to as honey pots.
They are hidden from the rest of the users.
I also like to put a comment here so that blind
people know the difference as well.
It also helps in case CSS is not working on
a given browswer.
--->
<div style="height: 1px ; overflow: hidden ; width: 1px ;">
<p>
Do NOT fill in the form fields below. They
are not meant to be used by people.
</p>
<label for="notes">
Notes:
<textarea
name="notes"
id="notes"
cols="40"
rows="10"
>#FORM.notes#</textarea><br />
</label>
<br />
Referrer:
<label>
<input
type="radio"
name="referrer"
value="Google"
/>
Google
</label>
<label>
<input
type="radio"
name="referrer"
value="Word of mouth"
/>
Word of mouth
</label>
</div>
</form>
</body>
</html>
</cfoutput>
Please let me know if anyone else would like demo of anything.
Want to use code from this post? Check out the license.
Reader Comments