A few years ago I wrote a quick blog entry on working with CAPTCHA and ColdFusion. (Quick and dirty CAPTCHA Guide - for ColdFusion 8) A reader on that entry asked if it was possible to add a "reload if you can't read feature" to the CAPTCHA. We've all seen CAPTCHAs before that are impossible to read. For many of these forms you have no choice but to submit the form and hope the next one is better. But more and more forms are allowing you to reload just the CAPTCHA. Here is a quick example of how I modified the earlier blog entry to support this.
To start off - I modified my code from the old entry to avoid using a hash of the CAPTCHA for validation. Instead I stored the value in the session scope and verified it there. Here is the modified template. I'm not going to explain it since it's 95% the same as the old blog entry, but as you can see I now don't bother with a hash.
<cfscript>
for(i=1; i <= length; i++) {
char = mid(chars, randRange(1, len(chars)),1);
result&=char;
}
</cfscript> <cfreturn result>
</cffunction> <cfset showForm = true>
<cfparam name="form.name" default="">
<cfparam name="form.comments" default="">
<cfparam name="form.captcha" default=""> <cfif isDefined("form.send")>
<cfset errors = ""> <cfif not len(trim(form.name))>
<cfset errors = errors & "You must include your name.<br />">
</cfif> <cfif not len(trim(form.comments))>
<cfset errors = errors & "You must include your comments.<br />">
</cfif> <cfif form.captcha neq session.captcha>
<cfset errors = errors & "You did not enter the right text. Are you a spammer?<br />">
</cfif> <cfif errors is "">
<!--- do something here --->
<cfset showForm = false>
</cfif> </cfif> <cfif showForm> <cfset session.captcha = makeRandomString()> <cfoutput>
<p>
Please fill the form below.
</p> <cfif isDefined("errors")>
<p>
<b>Correct these errors:<br />#errors#</b>
</p>
</cfif> <form action="#cgi.script_name#" method="post" >
<table>
<tr>
<td>Name:</td>
<td><input name="name" type="text" value="#form.name#"></td>
</tr>
<tr>
<td>Comments:</td>
<td><textarea name="comments">#form.comments#</textarea></td>
</tr>
<tr>
<td>Enter Text Below:</td>
<td><input type="text" name="captcha"></td>
</tr>
<tr>
<td colspan="2">
<cfimage action="captcha" width="300" height="75" text="#session.captcha#">
</td>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="send" value="Send Comments"></td>
</tr>
</table>
</form>
</cfoutput> <cfelse> <cfoutput>
<p>
Thank you for submitting your information, #form.name#. We really do care
about your comments. Seriously. We care a lot.
</p>
</cfoutput> </cfif>
<cffunction name="makeRandomString" returnType="string" output="false">
<cfset var chars = "23456789ABCDEFGHJKMNPQRS">
<cfset var length = randRange(4,7)>
<cfset var result = "">
<cfset var i = "">
<cfset var char = "">
If you want to run this, you can do so here. But that isn't the interesting one. Let's look at a nicer version.
In order to support reloading just the CAPTCHA, I moved the CAPTCHA display into a DIV I'll be loading via Ajax. So where before I had the cfimage tag, now I have just this:
<div id="captchaDiv"></div>
I used some jQuery to load it:
$("#captchaDiv").load("showcaptcha.cfm");
showcaptcha.cfm now includes the logic to generate a random CAPTCHA string and to store/render it.
<cfscript>
for(i=1; i <= length; i++) {
char = mid(chars, randRange(1, len(chars)),1);
result&=char;
}
</cfscript> <cfreturn result>
</cffunction> <cfset session.captcha = makeRandomString()>
<cfimage action="captcha" text="#session.captcha#" width="300" height="75">
<cffunction name="makeRandomString" returnType="string" output="false">
<cfset var chars = "23456789ABCDEFGHJKMNPQRS">
<cfset var length = randRange(4,7)>
<cfset var result = "">
<cfset var i = "">
<cfset var char = "">
Ok, so how do we handle reload? After my div, I added a quick link:
Can't read? <a href="" id="reloadLink">Reload</a>
And then wrote a lot of JavaScript to handle clicks there. (OK, maybe not a lot of code - but jQuery saves me such much time I've got to pretend I actually work!)
$("#reloadLink").click(function(e) {
$("#captchaDiv").load("showcaptcha.cfm");
e.preventDefault();
});
And that's it. The form page has changed quite a bit so I'll display it completely below. Before that you can demo it here:
<cfif isDefined("form.send")>
<cfset errors = ""> <cfif not len(trim(form.name))>
<cfset errors = errors & "You must include your name.<br />">
</cfif> <cfif not len(trim(form.comments))>
<cfset errors = errors & "You must include your comments.<br />">
</cfif> <cfif form.captcha neq session.captcha>
<cfset errors = errors & "You did not enter the right text. Are you a spammer?<br />">
</cfif> <cfif errors is "">
<!--- do something here --->
<cfset showForm = false>
</cfif> </cfif> <cfif showForm> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$("#captchaDiv").load("showcaptcha.cfm"); $("#reloadLink").click(function(e) {
$("#captchaDiv").load("showcaptcha.cfm");
e.preventDefault();
});
})
</script> <cfoutput>
<p>
Please fill the form below.
</p> <cfif isDefined("errors")>
<p>
<b>Correct these errors:<br />#errors#</b>
</p>
</cfif> <form action="#cgi.script_name#" method="post" >
<table>
<tr>
<td>Name:</td>
<td><input name="name" type="text" value="#form.name#"></td>
</tr>
<tr>
<td>Comments:</td>
<td><textarea name="comments">#form.comments#</textarea></td>
</tr>
<tr>
<td>Enter Text Below:</td>
<td><input type="text" name="captcha"></td>
</tr>
<tr>
<td colspan="2">
<div id="captchaDiv"></div>
Can't read? <a href="" id="reloadLink">Reload</a>
</td>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="send" value="Send Comments"></td>
</tr>
</table>
</form>
</cfoutput> <cfelse> <cfoutput>
<p>
Thank you for submitting your information, #form.name#. We really do care
about your comments. Seriously. We care a lot.
</p>
</cfoutput> </cfif>
<cfset showForm = true>
<cfparam name="form.name" default="">
<cfparam name="form.comments" default="">
<cfparam name="form.captcha" default="">
Archived Comments
Nice :-)
I'm just using --- onclick="window.location.reload();return false;" --- on an anchor next to the image ...
Hi ray, I had also created the one before long, You can check this tutorial out!
http://tutorial571.easycfm....
Hi Ray, you placed the captcha string in session, that means only one captcha at a time per session... so the user can't process a different form on a different tab in the browser. I was thinking you can return the captcha image in xml, and the xmlroot includes the captcha string. and when the xml is returned, the JS creates a hidden input with the value captcha string. what do you think?
@Jonathan: You could easily support N captchas and N forms by using different names. So if this form was a contact form, I could have used session.contactcaptcha.
Ray, Your demo link doesn't work (for me) using IE7. Safari for windows and Firefox was OK.
Works in IE8. Can't test in 7. If you figure out why, please let us know.
I have an instance of IE7 also -- it may be an image caching issue -- Even a forced refresh of the page doesn't change the captcha graphic <shrug>
Hard to tell without Firebug support...
Brian - mod my JS to add a ?x= random number to the end. I bet that fixes it.
An added piece to this puzzle... clearing the cookie, forced a refresh.
Works in IE7 if temporary internet files set to 'Every time I visit the webpage', but not if set to 'Automatically'
Yep, adding a random # to the URL would fix that. I haven't done it in a while, but I used to add a "iesucks" to the URL for stuff like this where the value was random.
It's most likely a caching issue... try adding a random URL variable to the showcaptcha.cfm request... like:
x = Math.floor(Math.random()*100000);
"showcaptcha.cfm?x=" + x;
heh I keep saying "add a random # to the url" - is no one seeing that?? ;)
Good one!
@Ray yup, adding a random # to the URL does make the diff. You rat. You forced me to go read some jQuery docs. I've never played with it before -- not a desired interface at client sites that are remote and bandwidth throttled. Still not something I can use here, but an interesting exercise, nonetheless.
Sorry - what can't you use? jQuery? I made use of the remote library but you don't have to. You can download the jQuery library to your local web server.
Since I've never found a time where I'd want a browser to cache my ajax requests, I add this line to the top of my main page.
$.ajaxSetup ({
// Disable caching of ANY AJAX responses.
cache: false
});
This tells jquery to add the current date string to the end of the url each time a request is made. Simple and you never have to think about request caching again.
Darn nice tip there Josh. Thanks!
Have you thought about accessibility?
Not yet.
I started experiencing on one of my load balanced CF9 servers with captcha. and the cfimage tag.
One Server: coldfusion.image.ImageWriter$ImageWritingException: An exception occurred while trying to write the image.
The other one rendered it fine.
My simple test:
<cfimage action="captcha" width="300" height="75" text="abcd3">
Does anyone have suggestions on why this happens or have they seen it before.
A Google turned up this:
http://www.bennadel.com/blo...
Are you up to date with your hot fixes?
Hi Ray -
Where does
$("#captchaDiv").load("showcaptcha.cfm");
go in the form so the captcha image loads?
I'm confused. Do you not see it in the code template above? The last one in the blog entry?
I do, however, the captcha image is not loading. I was asking for verification. I have simply copied and pasted all the code, but the image isn't showing. I was previous using the quick and dirty version with no problem and only wanted to add in the element refresh.
When you first hit the form does it load ok? When you do the refresh - what do you see with Firebug/Chrome Dev Tools?
Looks like:
The requested scope session has not been enabled.
Before session variables can be used, the session state management system must be enabled using the cfapplication tag.
Should this be enabled in the CF Admin ?
Add an App.cfc/App.cfm to your folder and turn it on. I should have been more clear that it would be required for this demo.
I added an application.cfm file with:
<cfapplication name = "captcha"
sessiontimeout = #CreateTimeSpan(0, 0, 0, 60)#
sessionmanagement = "Yes">
and the captcha now shows. Thanks for the help!
Actually, Ray, on your old quick and dirty captcha, if the viewer simply submits the form with an incorrect or empty captcha text field, it refreshes the captcha without changing any text they have entered in the other forms. I still prefer the hash anyway.
Sorry - what? What I read you saying is "If I submit the form and do not put the right captcha in, a new one is made." That's desired is it not?
@JP, @Ray -
Regarding the random URL variable, how do I incorporate the code
x = Math.floor(Math.random()*100000);
"showcaptcha.cfm?x=" + x;
into the showCaptcha.cfm code?
I'm not sure what your question means. You don't add anything to showcaptcha.cfm. The random number is there to just trick IE into not using the cache.
Thanks, Ray. I read JP's response about adding the URL variable and thought the solution was to add a variable to the showCaptcha code in order to make the variable appear in the URL. How do I make the random URL variable appear? This method is working in every browser except IE. Manually adding the variable works, but users won't be able to do it, so I need to code it to plug in automatically.
"How do I make the random URL variable appear?"
What do you mean? It isn't "appearing" - it is simply being added to the end of the main url. It's a way to trick IE into thinking you are asking for something it hasnt asked for before. (Technically it isn't a trick - it IS a new url.)
"This method is working in every browser except IE."
How is it not working for you? Remember that this fixes an issue where IE users would see the same captcha. So are your IE users seeing the same captcha? If so - can you share the URL of where this is so I can test it myself?
Yes, IE users see the same captcha and are unable to reload the captcha. The page in question is http://academicaffairs.iupu...
Um... because you didn't add the code. The code that appends the random crap. This is your JS:
$("#reloadLink").click(function(e) {
$("#captchaDiv").load("showcaptcha.cfm");
e.preventDefault();
});
So in adjusting that part of the code, this allows for the captcha to be reloaded, but only once:
$("#reloadLink").click(function(e) {
x = Math.floor(Math.random()*100000);
$("#captchaDiv").load("showcaptcha.cfm?x="+x);
e.preventDefault();
});
Is this incorrect?
That got it. Thanks for the assistance, Ray.
<!---MAKE A CAPTCHA--->
<cffunction name="getCaptchaContact" access="remote" output="false" returnType="string" returnFormat="JSON">
<cfset local.fileName = CreateUUID() />
<cfset local.captchaString = makeRandomString() />
<cfset session.captchaContact = local.captchaString />
<cfimage action="captcha" difficulty="low" text="#local.captchaString#" destination = "#local.fileName#.png" />
<cffile action="readbinary" file="/_cfc/#local.fileName#.png" variable="local.binimage">
<cffile action="delete" file="/_cfc/#local.fileName#.png" />
<cfset local.encodedImg = BinaryEncode(binimage, "Base64") />
<cfreturn local.encodedImg />
</cffunction>
This is probably lame. But this is how I have been making captcha's. I just ajax the captcha were ever I need it on my site.
Not lame - kinda cool. :)
Ray,
Your demo does not display captcha image any more. Do you have any suggestions? Is it anything to do with CF 10 upgrade?
Thanks for your help.
Nan
Yeah - the code should work fine if run on your own server.
Ray,
I've a strange problem. Your code works as such on CF 8 server on all 3 major browsers (FF, Chrome and IE 7, 8, 9). But it doesn't work on IE (8 and 9 tested) after I upgrade our dev server to CF 10. (we jumped CF9). Both production and dev environments are under SSL behind F5. Unfortunately it is an internal site so the URL won't load from outside.
Have you seen it before?
thanks
One more thing,
If I put the complete path on IE it loads the image as such.
CFFileServlet/_cf_captcha/_captcha_img1637508603629842317.png
I tried both --cfimage giving a name and destination as well as cfimage action=captcha, the result is same. the image doesn't load on IE and loads fine on FF and Chrome on CF 10 server.
Thanks for your help.
Not sure what to suggest. Do you see anything in IE's dev tools?
Figured it is something to do with JQery AJAX call to showcaptcha.cfm.
The dev server had debug output to the screen FF and Chrome didn't mind them. IE couldn't handle that but no errors anywhere. Simply no image displayed.
When I added <cfsetting showdebugoutput="no"> on top of showcaptcha.cfm. IE now loads it fine.
Sorry for the false alarm.
Ahhh - duh! :)