Last night a reader of mine wrote with an interesting issue. His error handler was firing off quite a few emails (this would help!) but the error wasn't quite clear. I've recreated the error below and I'll explain how I went about diagnosing it. The short summary is that - once again - ColdFusion's ancient automatic form checker was the culprit, which luckily is easy to get around in ColdFusion 9. But first - let's look at the errors that were generated.
Ok, first - here is a snapshot of what the error looked like:
When I saw this, I immediately noticed that both the Message and the StackTrace talked about form validation. This was my first clue that something with ColdFusion's built in form validation was being triggered. You can read about this feature here. For the most part, no one uses this nor do they even think about it until they accidentally name a field something_required and accidentally trigger it.
Now I asked the developer if he had anything named like that and he said not. But this is an important point. Even if you don't name anything something_required, if a remote request sends that, it will trigger the error. So to be clear, I can force that error on your server right now by just writing my own form. It isn't a big deal for sure, but it is something to remember. Here is where things got weird though. In his error handler he was dumping out the form and it was empty! I noticed his code looked like so:
<cfif isDefined("form.fieldnames")> <cfdump var="#form#" label="FORM"> </cfif>
So - at this point - I was still pretty lost. I asked him to add this to his error handler:
This then revealed form data within the content of the request. I then realized the issue. ColdFusion wasn't populating form.fieldnames. If he had just dumped the form scope itself it would have worked. So for some reason, when a form validation error occurs, form.fieldnames will not exist. Confused yet? Let's look at a complete example. First, my Application.cfc.
<cfset this.name = "demo2"> <cfset this.sessionManagement = true> <cffunction name="onApplicationStart" returnType="boolean" output="false"> <cfreturn true> </cffunction> <cffunction name="onError"> <cfargument name="exception"> <cfargument name="event"> <cfdump var="#arguments#" abort> </cffunction>
Next - this is the file being hit by the remote server.
Testing from test3.cfm
Right now it just echos back data. And finally, here is my tester:
<h1>testing from test1</h1>
<cfhttp url="http://localhost/test3.cfm" method="post" result="result"> <cfhttpparam type="formfield" name="stuff" value="ray"> </cfhttp>
Notice the form field is just stuff. Ok, so here is the result of a good post.
And now - let's break it. Here is my new tester:
<h1>testing from test1</h1>
<cfhttp url="http://localhost/test3.cfm" method="post" result="result"> <cfhttpparam type="formfield" name="stuff_required" value="ray"> </cfhttp>
As you can see - I just renamed the field to include something to trip up ColdFusion's validation.
As you can see - even this is a bit confusing. Why is my form value in there? No idea. So what to do?
First off - in ColdFusion 9 you can fix this in two seconds:
<cfset this.serverSideFormValidation = false>
I see no reason to never use this line of code - unless you actually are using that old feature and if so... um... stop. Now unfortunately the user was still on ColdFusion 8. Luckily you can just look at the exception and not email if it happens. Here is an example.
<cffunction name="onError"> <cfargument name="exception"> <cfargument name="event"> <cfif arguments.exception.message is not "Form entries are incomplete or invalid."> <cfdump var="#arguments#" abort> </cfif> </cffunction>
Since the remote site wasn't doing anything valid on the server anyway, this is probably a fine response. (Although I'd also consider noting the IP and blocking it at the network level.)
Anyway - I hope this helps others!