Rick Strahl's Weblog
Rick Strahl's FoxPro and Web Connection Weblog
White Papers | Products | Message Board | News |

A Captcha Image generator for FoxPro


3 comments
October 01, 2006 •

A couple of areas of my FoxPro WebSite – specifically the wiki – are getting plastered with SPAM these days so I’ve been meaning to create some code to handle wiki:CAPTCHA:CAPTCHA: style validation. CAPTCHA basically displays a verification image next to a textbox that has to be filled out to validate the current request. It’s not a foolproof approach for validation and it has some issues with accessibility but it seems to be a common solution to this problem.

 

There are a quite a few CAPTCHA controls out there for .NET, but in FoxPro this is a little more tricky to do. You can actually use the FFC classes to do the GDI+ code, but I really didn’t want to drag this 400+k library into my project, so tonight I decided to write a small GDI+ routine that does this for me and well, since I already have a small library of GDI+ routines this doesn’t add any overhead to the project.

 

And it’s actually easier to write this code in C++ than in Fox <g>.

 

Here’s the rudimentary code that you can compile with Visual Studio and C++:

 

DWORD WINAPI GetCaptchaImage(WCHAR *lcText,WCHAR* FontName,

                             int FontSize, WCHAR *lcOutputFile)

{

      GdiplusStartupInput gdiplusStartupInput;

      ULONG_PTR gdiplusToken;

      try

      {

      GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

      }

      catch(...)

      { return 0; }

 

      // *** Start by figuring out size of the image

      Bitmap *tBitmap = new Bitmap(1000,400);

      Graphics *graphics = new Graphics(tBitmap);

 

      Font *font = new Font(FontName,(REAL) FontSize);

      PointF Origin(0,0);  

 

      RectF Box;

      graphics->MeasureString(lcText,lstrlenW(lcText),font,Origin,&Box);

 

      int lnWidth = int(Box.Width);

      int lnHeight =  int(Box.Height * 1.02);

 

     

      // *** Now create the real thing

      tBitmap = new Bitmap(lnWidth,lnHeight );

      graphics = new Graphics(tBitmap);

 

      // *** DarkGray Text

      SolidBrush *Black = new SolidBrush( Color(102,102,102 ) ); 

 

      HatchBrush *hBrush = new HatchBrush(  HatchStyleSmallConfetti,Color(208,208,208),Color(255,255,255) );

      graphics->FillRectangle( hBrush,0,0,lnWidth,lnHeight );

 

      graphics->SetSmoothingMode(SmoothingModeAntiAlias);

      graphics->SetTextRenderingHint(TextRenderingHintAntiAlias);

 

      //// *** Draw at 1% angle

      Matrix *transformMatrix = new Matrix();

      transformMatrix->Rotate(1.0f);

      graphics->SetTransform(transformMatrix);

 

      // *** Keep resizing until it fits

      while (Box.Width < lnWidth - 10)

      {

            FontSize++;

            delete font;

            font = new Font(FontName,(REAL) FontSize);

            graphics->MeasureString(lcText,lstrlenW(lcText),font,Origin,&Box);

      }

      while (Box.Width >= lnWidth)

      {

            FontSize--;

            delete font;

            font = new Font(FontName,(REAL) FontSize);

            graphics->MeasureString(lcText,lstrlenW(lcText),font,Origin,&Box);

      }

 

      graphics->DrawString(lcText,lstrlenW(lcText)+1,font,Origin,Black);

 

 

      bool Error = false;

 

      try

      {

            DeleteFile(lcOutputFile); // must delete file or size may not be updated

 

 

      CLSID loEncoderId;

      if (GetEncoderClsid(lcOutputFile,&loEncoderId) == -1) 

      {

            GdiplusShutdown(gdiplusToken);

            return 0;

      }

            tBitmap->Save(lcOutputFile,&loEncoderId,NULL);

      }

      catch(...)

      {

            Error = true;

      }

 

      delete graphics;

      delete tBitmap;

      GdiplusShutdown(gdiplusToken);

 

      if (Error)

            return 0;

 

      return 1;

}

 

There’s one dependency function GetEncoderClsId which translates the file extension into a ClsId that GDI+ needs to figure out which encoder to use to save a file. You can find that function in the source code provided.

 

Now this code is pretty basic – it doesn’t do fancy text warping or randomizing the backgrounds etc. For the moment I’m not too worried about SPAMMERS on my site actually hacking the image generated. If it gets to that point adding additional checks might be worthwhile.

 

My C++ skills are fast fading. It took me a couple of hours to write this code, primarily because I ran into a problem witht GdiplusShutdown crashing Visual FoxPro. As it turns out if you create hard instances of any GDI+ objects – rather than reference objects created with the new operator – the object stick around before the method is done processing. This in turn causes GdiplusShutdown fire an exception that gets passed back to VFP. It took a while of tweaking to figure this one out.

 

You can see the images in the Wiki here and in the WebLog here.

 

You can download the DLL, Fox wrappers and the VC project from here.

Integration in Web Connection

I’ve also integrated this into Web Connection as part of the wwImaging.dll and the wwAPI function class. There’s a new function called GetCaptureImage() that looks like this:

 

lcText = RIGHT(SYS(2015),5)  && Random number

lcFileName = SYS(2003) + "\" + lcText + ".jpg"

GetCaptchaImage(lcText,lcFilename,"Verdana",30)

 

CAPTCHA makes sense only in Web Applications so here’s a more complete example that uses Captcha to check for user validity in a typical Web Connection Process method:

 

************************************************************************

* wwAPI ::  GetCaptchaImage

****************************************

***  Function: Returns an image for the given text

***    Assume:

***      Pass:

***    Return:

************************************************************************

FUNCTION GetCaptchaImage(lcText,lcOutputFile,lcFont,lnFontSize)

 

IF EMPTY(lcFont)

   lcFont = "Arial"

ENDIF

IF EMPTY(lnFontSize)

   lnFontSize = 28

ENDIF     

 

DECLARE INTEGER GetCaptchaImage ;

   IN wwImaging.dll  as _GetCaptchaImage ;

   STRING Text,  STRING FONTNAME, integer FontSize, STRING lcOutputFile

 

lcText = STRCONV(lcText,5) + CHR(0)

lcFont = STRCONV(lcFont,5) + CHR(0)

lcOutputFile = STRCONV(lcOutputFile,5) + CHR(0)  

  

RETURN IIF( _GetCaptchaImage(lcText,lcFont,lnFontSize,lcOutputFile) = 1,;

           .T.,.F.)

ENDFUNC

*  wwAPI ::  GetCaptchaImage

 

 

In addition there’s a new default Process method that can automatically serve a CAPTCHA image. You basically submit the CAPTCHA

 

Here’s what this looks like in a Process method which demonstrates the logic of the process best:

 

FUNCTION CaptchaTest

 

lcResult = ""

 

IF Request.IsPostBack()

      *** Captcha Value is stored in a fixed SessionVar

      lcCaptcha = Session.GetSessionVar("__CAPTCHA")

 

      IF LOWER(lcCaptcha) == LOWER(Request.Form("txtCaptcha"))

         lcResult = "You matched it!"

      ELSE

         lcResult = "Bing: Try again sucker"

      ENDIF      

ENDIF

 

*** Generate a new CAPTCHA AFTER you've checked it!

*** CaptchaImage is a new Process method that handles

*** both ‘submitting’ of the CAPTCHA as well as serving it

Process.CaptchaImage(RIGHT(SYS(2015),5),"Verdana",30)

 

TEXT TO lcHTML TEXTMERGE NOSHOW

<form method="POST" action="">

Here it is: <img src='CaptchaImage.wwd'> <input name="txtCaptcha" >

<input type="submit"  name="btnSubmit" value="save">

</form>

<<lcResult>>

ENDTEXT

 

this.StandardPage("Output",lcHtml)

 

ENDFUNC

 

That’s pretty straight forward, however, it’s even easier with the Web Control Framework. I added a new wwWebCapture control that  can be dropped onto any WCF style form like this:

 

<ww:wwWebCaptcha runat="server" id="Captcha" CaptchaChars="ABCDEF0123456789"

                CaptchaLength="5" FontName="Tahoma" FontSize="24" ></ww:wwWebCaptcha>

 

Then in the CodeBehind code:

 

FUNCTION btnPostComment_Click()

 

this.oComment.New()

 

IF THIS.BindingErrors.Count > 0

   this.ErrorDisplay.Text = this.BindingErrors.ToHtml()

   RETURN

ENDIF

 

 

IF !this.Captcha.IsValidated

      this.ErrorDisplay.ShowError("Invalid Validation Code")

      this.SetFocus(this.txtComment)

      RETURN

ENDIF

 

ENDFUNC

 

The control manages all the Session settings, the image url creation so there’s really only a single line of code required to check whether the Captcha was validated. That’s it.

 

Captcha this, Captcha that

Captcha isn’t perfect – it can be hacked and this implementation is as minimal as it can be. There’s no text warping, no variable angles and fonts etc. to make it a little harder to crack. But the reality is that most spamming won’t go through the lengths to create even the simplest of CAPTCHAs – it’s just not worth the effort. If you find that a CAPTCHA gets cracked an relatively easy way to throw it off again is to rename some of your form vars.

 

Another preventative measure might be to add an alert() box to your form’s onsubmit() functionality. I think most of the spamming is done through some sort of browser automation rather than through HTTP clients and adding an alert() box stops a robot dead. Couple that with something that requires JavaScript to handle none browser clients and you’re reducing the amount of access considerably. There will always be those that actually manually spam – and that’s much, much harder to fight. But labor is cheap in China <s>… (no offense to any far eastern folks, but 90% of the spam I’ve seen over the last few years comes from China.

 

Also remember Captcha is not accessible. If you have a vision impaired user they may not be able to get past a capture because there’s no text that can ‘be read’ out by the various accessibility features.

Posted in:

Feedback for this Weblog Entry


Re: A Captcha Image generator for FoxPro



Cesar Chalom
December 06, 2006

Hi Rick Great post !

I decided to aply some of your ideas using the new GdiPlus-X library http://weblogs.foxite.com/cesarchalom/archive/2006/12/06/2998.aspx

I know the new library is "heavy", but I think that people will start using more and more GDI+. Anyway, there's a really light GDI+ class, GPIMAGE2, originally created by Alexander Golovlev, and updated by me some years ago. Basically I added some graphics functions to that library. It can do all the tasks that you presented here and a lot more ! The latest version size is about 70k, and can be reached from here: http://cchalom.sites.uol.com.br/GPIMAGE/index.htm

Best regards Cesar

Re: A Captcha Image generator for FoxPro



Rick Strahl
December 06, 2006

That's great Cesar. Have to take a look at your library. 70k is lot more palatable than 400k. Even 400k is worth it IF you use it extensively - my reasoning here for the C++ code was that I don't use it anywhere else...

Re: A Captcha Image generator for FoxPro



Cesar Chalom
December 07, 2006

Feel free to call me if you need any help to make it work. All the commands are almost the same from the other classes. There are many things that I wanted to update on this library, but at this moment I'm concentrating my efforts (and free time) to the GdiPlus-X library.

 
© Rick Strahl, West Wind Technologies, 2003 - 2024