Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Ask Ben: Form Processing Demo With Anti-Spam Features

By Ben Nadel on

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>

				&nbsp;&nbsp;&nbsp;

				<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.



Reader Comments