PradoSoft

Captcha Image Tutorial

From PRADO Wiki

Contents

Introduction

This is a tutorial about how to serve up CAPTCHA images using the PRADO framework. I am by no means a PRADO expert, so if you know a better way to do this, please suggest it!

We'll break this tutorial down into a couple parts. We'll first create a component that creates the raw CAPTCHA jpeg. After that, we'll create a custom service that services up CAPTCHA when the appropriate HTTP request comes in. Of course, we'll also describe how to set up this service in the application.xml file. And finally, just to put icing on the cake, we'll create a custom control that calls the CAPTCHA service for us and makes everything all nice and automatic. Ready to go?

TCaptchaImageComponent

Our first file is a PRADO component that will generate the raw CAPTCHA jpeg image. The code that generates the image was borrowed from here, courtesy of White Hat Web Design, and distributed under the GNU General Public License.

TCaptchaImageComponent.php Source

 
<?php
/**
 * JonHaywood <webmaster@thehomeofjon.net>
 * Code courtesy of http://www.white-hat-web-design.co.uk/articles/php-captcha.php
 */
 
class TCaptchaImageComponent extends TApplicationComponent {
 
	/**
	 * @var Location of font to use for image
	 */
	private $_FontFile;
	
	/**
	 * @param String number of random characters to generate
	 * @return String random security code
	 */
	protected function generateCode($characters) {
		/* list all possible characters, similar looking characters and vowels have been removed */
		$possible = '23456789bcdfghjkmnpqrstvwxyz';
		$code = '';
		$i = 0;
		while ($i < $characters) { 
			$code .= substr($possible, mt_rand(0, strlen($possible)-1), 1);
			$i++;
		}
		return $code;
	}
 
	/**
	 * Generates CAPTCHA image
	 *
	 * @param width - width of image
	 * @param height - height of image
	 * @param characters - length of security code
	 * @return CAPTCHA image. Use imagecreate to display.
	 */
	public function generateCaptchaImage($width, $height, $characters) {
		// ensure font has been specified
		if(!$this->_FontFile) 
			throw new TConfigurationException(
				"You must specify the location of a true-type font."
		);
		
		$code = $this->generateCode($characters);
		/* font size will be 75% of the image height */
		$font_size = $height * 0.75;
		$image = @imagecreate($width, $height) or die('Cannot initialize new GD image stream');
		
		/* set the colours */
		$background_color = imagecolorallocate($image, 255, 255, 255);
		$text_color = imagecolorallocate($image, 20, 40, 100);
		$noise_color = imagecolorallocate($image, 100, 120, 180);
 
		/* generate random dots in background */
		for( $i=0; $i<($width*$height)/3; $i++ ) {
			imagefilledellipse($image, mt_rand(0,$width), mt_rand(0,$height), 1, 1, $noise_color);
		}
 
		/* generate random lines in background */
		for( $i=0; $i<($width*$height)/150; $i++ ) {
			imageline($image, mt_rand(0,$width), mt_rand(0,$height), mt_rand(0,$width), mt_rand(0,$height), $noise_color);
		}
 
		/* create textbox and add text */
		$textbox = imagettfbbox($font_size, 0, $this->FontFile, $code) or die('Error in imagettfbbox function');
		$x = ($width - $textbox[4])/2;
		$y = ($height - $textbox[5])/2;
		imagettftext($image, $font_size, 0, $x, $y, $text_color, $this->FontFile , $code) or die('Error in imagettftext function');
 
		/* start session & store code */
		$this->Session['CAPTCHA_SECURITY_CODE'] = hash("sha256",$code);
 
		/* return image */
		return $image;
	}
	
	/**
	 * Sets the location of the true-type font
	 */
	public function setFontFile($value)
	{
		$this->_FontFile = $_SERVER['DOCUMENT_ROOT'] . TPropertyValue::ensureString($value);
	}
	
	/**
	 * Gets the location of the true-type font
	 */
	public function getFontFile()
	{
		return $this->_FontFile;
	}
 
}
 
?>

This class is fairly straightforward. We have a private property that stores the path to the True-type font that is required to generate the image (and the accessor methods that go with it).

The guts of this class resides in the generateCaptchaImage function. It accepts a width a height and the length of the CAPTCHA word. We don't really care about all the specifics of how the image is generated - but the code is readable if you care to try to understand it.

Note: This class has a dependency; you will need to have the GD (or GD2) extension activated for your installation of php in order for the image-generating functions to work. For a more automated and friendly component one can add a bit of code to ensure that the required extension is loaded. Right after the check to make sure we have a true type font specified.

 
    if(!extension_loaded('gd')) {
      if (!dl('gd.so')) {
        throw new TConfigurationException("You must have the 'gd' extension load
ed to use this component.");
      }
    }


Another important thing to notice is that once our random "security code" (the code that will be displayed on the CAPTCHA image) is generated, we store the sha256 (similar to sha1 and md5) hash of the code in Session. This way, the plain text version of the code never exists outside this function. sha256 is a highly-secure one-way 'encryption' method - no one will be able to decrypt the generated code.

TCaptchaService

Now that we have a method by which to generated CAPTCHA images, we need a way to request these images from Prado. The best Prado-way to do this is to create a service that serves up CAPTCHA images. Luckily, this is very easy to do.

TCaptchaService.php Source

 
<?php
/**
 * @author Jon Haywood <webmastert@thehomeofjon.net>
 */
 
Prado::using('System.TService');
Prado::using('Path.To.TCaptchaImageComponent');
 
/**
 * TCaptchaService class. Services up Captcha images
 */
class TCaptchaService extends TService {
 
	/**
	 * @var String length of captcha word
	 */
	private $_Length;	
	
	/**
	 * @var String width of captcha image
	 */
	private $_Width;	
	
	/**
	 * @var String height of captcha image
	 */
	private $_Height;	
	
	/**
	 * @var location of font
	 */
	private $_FontFile;	
	 
    /**
     * Initializes the service.
     * This method is required by IService interface and is invoked by application.
     * @param TXmlElement service configuration
     */
	public function init($config) {	
		// check that the required properties are set
		if (!$this->Length)
			throw new TConfigurationException(
				"You must specify the Length of the CAPTCHA Word in the Application configuration"						
		);			
		
		if (!$this->Width)
			throw new TConfigurationException(
				"You must specify the Width of the CAPTCHA image"						
		);		
		
		if (!$this->Height)
			throw new TConfigurationException(
				"You must specify the Height of the CAPTCHA image"						
		);			
	}
 
	/**
	 * Runs the service.
	 * 
	 * Sends Captcha Image to Browser
	 */
	public function run() {
		// Generate CAPTCHA image
		$imgcaptcha = new TCaptchaImageComponent;
		$imgcaptcha->FontFile = $this->FontFile;
		$image = $imgcaptcha->generateCaptchaImage($this->Width,$this->Height,$this->Length);
 
		// Send image to Browser
		header("Content-type: image/jpeg");
		imagejpeg($image);
		imagedestroy($image);
	}
	
	// Accessor Methods
	
	/**
	 * @return string Length
	 */
	public function getLength() {
		return $this->_Length;
	}
	
	/**
	 * @param string Length
	 */
	public function setLength($length) {
		$this->_Length = TPropertyValue::ensureInteger($length);
	}
	
	/**
	 * @return string Width
	 */
	public function getWidth() {
		return $this->_Width;
	}
	
	/**
	 * @param string Width
	 */
	public function setWidth($Width) {
		$this->_Width = TPropertyValue::ensureInteger($Width);
	}
	
	/**
	 * @return string Height
	 */
	public function getHeight() {
		return $this->_Height;
	}
	
	/**
	 * @param string Height
	 */
	public function setHeight($Height) {
		$this->_Height = TPropertyValue::ensureInteger($Height);
	}
	
	/**
	 * @return string Font location
	 */
	public function getFontFile() {
		return $this->_FontFile;
	}
	
	/**
	 * @param string Font location
	 */
	public function setFontFile($value) {
		$this->_FontFile = TPropertyValue::ensureString($value);
	}
	
}
 
?>

Just like the Prado Page service, our Captcha service will be called via a specific URL. If we wished, we could pass some of our image attributes (width, height, length) via the URL. However, in the interest of 1) minimizing the possibility of throwing an exception and 2) minimizing the risks of a security hole, we will have these attributes be properties of the service itself.

Also just like the Prado Page service, our Captcha service will be configured in the application settings file - the application.xml file. It is here where the width, height, and length will be set. I will provide an example of this in the next section.

The function that does all the work in our service is the run method. It does the following:

  1. Calls our CAPTCHA Compoment and sets the location of the font file
  2. Generates the CAPTCHA jpeg image using our custom component
  3. Send the appropriate headers and outputs the image to the browser

And that's it! We now have the code for a CAPTCHA service. Now on to configuring our service in the application settings.

application.xml

In the application.xml we will alert Prado to the existence of our service. After the modules section, in the services section of the file, we will enter something similar to this:

 
<services>
    <!-- CAPTCHA Service -->
    <service id="captcha" class="Path.To.TCaptchaService" Width="170" Height="30" Length="5" FontFile="/common/fonts/monofont.ttf" />
  </services>

You see that we assign all the attributes of the service here - the width, height, length, and location of the Font file. We also assign our service an ID of "captcha".

Now, in order to see our new image, enter a url in your browser similar to this:

 
http://myserver/path/to/prado/index.php?captcha=1

And viola! We see our wonderful CAPTCHA image.

Notice that in the URL instead of the regular "index.php?page=Home" syntax, we instead have "index.php?captcha=1". This tells Prado to forget about the normal Page service and instead look for a service with the ID of "captcha". This is the ID we specified for our service in the application.xml file.

CAPTCHA Custom Control

Now that we have a CAPTCHA image, we can load this image in any page we want by pointing the source attribute of an image tag to our service. However, since our URL doesn't change, it would make our lives easier if we created a custom control that handled calling the CAPTCHA service for us. Luckily, this is also easy with Prado.

TCaptchaImage.php Source

 
<?php
 
Prado::using("System.Web.UI.WebControls.TImage");
 
class TCaptchaImage extends TImage {
	
	/**
	 * OnInit method call service & init image url
	*/	
	public function onInit($param) {
		parent::onInit($param);
		if(!$this->Page->IsPostBack && !$this->Page->IsCallBack) {
			// call captcha service. Has ID of 'captcha'
			$url = $this->getRequest()->constructUrl("captcha", 1);	
			$this->ImageUrl = $url;
		}		
	}
	
	/**
	 * @param code - check if given code is valid
	 * @return boolean if code is valid
	 */
	protected function isCodeValid($code) {
		return hash("sha256",$code) == $this->Session['CAPTCHA_SECURITY_CODE'];
	}
 
}
 
?>

This is a simple control that extends TImage. On initialization we set the URL which calls our custom CAPTCHA service. Very simple!

Next we must worry about validating the code that the user enters and checking that against the code we generated when we generated the image. Remember that we stored sha256 hash of the security code in Session. Thus, to check user input against it, we must compare the sha256 hash. This is what the isCodeValid function does.

Using Our Custom Component

Using our custom component is very simple. Take a look at this example page:

Home.page

 
<html>
 
<head>
<title>Hello World Demo - Prado</title>
</head>
 
<body>
 
<com:TForm>
 
<com:TCaptchaImage ID="imgCaptchaImage" /><br />
<com:TTextBox ID="txtCaptchaText" />
<com:TCustomValidator
		ControlToValidate="txtCaptchaText"
		OnServerValidate="captchaImageValidate"
		Text="The code entered does not match the image" />
 
</com:TForm>
 
</body>
</html>

This page loads the CAPTCHA image and a textbox for the user to enter a code. We also have a custom validator to validate the user entered code. Now for the code-behind.

Home.php

 
<?php
 
class Home extends TPage
{
	public function captchaImageValidate($sender, $param) {
		if(!$this->imgCaptchaImage->isCodeValid($param->Value)) {
			$param->IsValid=false;
			$this->txtCaptchaText->Text = '';
		}
	}
}
 
?>

Again, very simple! In our validation function we get the text the user entered and pass it off to our isCodeValid function that we constructed in the last section.

Conclusion

So overall we needed just 2 files; a CAPTCHA-image generating component and a service to serve up that image. Our optional 3rd file, our custom CAPTCHA-image control just makes our life easier. Now we can tell the humans from the machines. For now.

Troubleshooting/Notes

Getting errors implementing this CAPTCHA solution? Check the following notes to ensure your system is configured correctly.

  1. Make sure to have the GD or GD2 extension enabled for PHP for this to work!
  2. Having a problem with session variables in the isCodeValid() method? Make sure Session is started in the application. You can do this by entering something similar to the following in your application.xml file:
 
<module id="session" class="THttpSession" AutoStart="true" />
Personal tools
Your user name:

Your password:

MediaWiki