/Main_Page

::You must have ninja focus to complete your mission::NinjaFocus::

ReCaptcha

Views:

STOP SPAM, READ BOOKS

http://www.recaptcha.net/ will describe it better than I can, but basically a CAPTCHA is a test that can tell computers and humans apart. It is used to prevent automated spam messages, usually on blogs, etc. ReCaptcha is a nice twist on the typical CAPTHCA, it's reliable, accessible and actually helps Carnegie Mellon University to digitise old books.

It's a web service that your site talks to, the visitor completes a test sent from the ReCaptcha servers, then your site talks to the web service to find out the results.

It's pretty easy to get set up, but here are some notes and (I think) some improved PHP code over the stuff provided on the ReCaptcha site. This code is based on the code provided at ReCaptcha.net

I've set up a demo of this code over here - http://www.ninjafocus.net/demos/recaptcha-test/recaptcha-test.php

Contents

Requirements

To use the service, you need a public key from a public/private key pair, issued by domain name. You need to create an account on their site.

The code example here uses PHP on the server side. You will need to use javascript on the client side, ReCaptcha can work without Javascript but only if the visitors browser has javascript disabled.

Overview

The ReCaptcha code can slot in around your normal code, whatever it's for - comments, form mail, etc.

The code provided here is a simple demonstration that will display user submitted text only if they can complete the CAPTCHA.

Take your existing html form and a text area for collecting the user's answer to the CAPTCHA. By default let the non-javascript version of ReCaptcha run, use javascript code to get in there and load the Javascript version of ReCaptcha as soon as the page loads. When the user submits the form, check the response provided with ReCaptcha's servers, then process the submitted data accordingly.

Code

You can download the source code in a zip file media:recaptcha-demo.zip - code listings follow...

recaptcha_test.php

The main HTML / PHP file. It's only a basic demo so it's all lumped in to one file.

<?php

 /*************************************************************************
  * STOP SPAM, READ BOOKS                                                 *
  * http://www.recaptcha.net/                                             *
  *                                                                       *
  * A public key from a public/private key pair, issued by domain name.   *
  * You need to create an account on their site.                          *
  *************************************************************************/
 
 /*************************************************************************
  * Settings                                                              *
  *************************************************************************/
  
define('API_SECURE_SERVER', "https://api-secure.recaptcha.net");
define('API_SERVER', "http://api.recaptcha.net");

/*
 * Public Key
 *
 * E.g. "6LcmrAEAAAAFADIEzGfaorKL65zHufEwbXQcqnrp"
 */
define('RECAPTCHA_KEY', ""); 

/*
 * Private Key
 *
 * E.g. "6LcmrAEAAAAFANQUtJM1vCvdYPjJC_EUoadWC_J8"
 */
define('RECAPTCHA_PRIVATE_KEY', "");

/*
 * Theme / Colour Scheme
 *
 * One of: red, white, blackglass, clean, custom
 * see recaptcha.net to find out about setting a custome theme
 */
define('THEME', "white");



 /*************************************************************************
  * Initialise                                                            *
  *************************************************************************/

if (RECAPTCHA_KEY && RECAPTCHA_PRIVATE_KEY)
{
    require_once 'lib/ReCaptcha.php';
    $reCaptcha = new kReCaptcha(RECAPTCHA_KEY, RECAPTCHA_PRIVATE_KEY);
    $reCaptcha->setApiServer(API_SERVER);
    $reCaptcha->setTheme(THEME);
}



 /*************************************************************************
  * Process                                                               *
  *************************************************************************/

$error = null;
$human = null;
$comment = null;

if (isset($_POST['recaptcha_challenge_field']))
{
    $rcResponse = $reCaptcha->checkAnswer(
	    $_SERVER['REMOTE_ADDR'], 
	    $_POST['recaptcha_challenge_field'], 
	    $_POST['recaptcha_response_field']
	);
	$human = $rcResponse['is_valid'];
	$error = $rcResponse['error'];
	$comment = trim($_POST['comment']);
}

$iframeURL = API_SERVER."/noscript?k=".RECAPTCHA_KEY;
if ($error)
{
    $iframeURL .= "&error=".htmlentities($error, ENT_COMPAT, 'UTF-8');
}


 /*************************************************************************
  * Output                                                                *
  *************************************************************************/

?>
<!DOCTYPE html 
		PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
		"DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>reCaptcha Test</title> 
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />


<script type="text/javascript" src="lib/K.js"></script>
<script type="text/javascript">
//<![CDATA[

    var RECAPTCHA_KEY = <?php echo 
            (RECAPTCHA_KEY && strlen(RECAPTCHA_KEY)) ? "'".RECAPTCHA_KEY."'" : "null"
        ?>;
        
    DomLoaded.load(
        function() {
            // Place your initialisation code here (this is like body.onload)


                /*  
                    Load up and trigger the reCaptcha script from reCaptcha.net
                    Pass the public key code issued from your reCaptcha Account
                    and also the id of a div tag that you want to hold the 
                    captcha.
                 */
                K.reCaptcha(RECAPTCHA_KEY, 'reCaptchaHerePlease');

            // --------------------------------------------------------------
        }
    );

//]]>
</script>


</head>
<body class="{class}">
	<h1>reCaptcha Test</h1>
	<p><a href="http://recaptcha.net/">recaptcha.net</a></p>
	
	
<?php if (!RECAPTCHA_KEY || !RECAPTCHA_PRIVATE_KEY):?>
    
    <h2>Not Set Up Yet</h2>
	<p><strong>Go here to get a public and private key pair key for your domain: <a href="http://recaptcha.net/">recaptcha.net</a>.</strong></p>
	<p><strong>Add it to this file's source code.</strong></p>

<?php elseif ($human):?>
    
    <p>Some human said:</p>
    <pre><?php echo htmlentities($comment, ENT_COMPAT, 'UTF-8');?></pre>
    <p><a href="<?php echo $_SERVER['PHP_SELF'];?>">Do you want to say something?</a></p>

<?php else:?>

    <form action="<?php echo $_SERVER['PHP_SELF'];?>" method="post">
        
        <p><label for="comment">Comment</label></p>
        <p>
            <textarea name="comment" id="comment" rows="6" cols="80"><?php echo htmlentities($comment, ENT_COMPAT, 'UTF-8');?></textarea>
        </p>
        
        <?php if ($error):?>
            <h2>I Don't Think You're Human!</h2>
            <p>Error: <?php echo htmlentities($error, ENT_COMPAT, "UTF-8")?>
        <?php else:?>
            <h2>Are you Human?</h2>
        <?php endif;?>
        
        <!-- for those with Javascript on -->
        <!--     this gets filled in by a script loaded from reCaptcha.net -->
        <div id="reCaptchaHerePlease" style="margin:0;padding:0">

        </div>
    
        <!-- and for those without -->
        <noscript>
    		<iframe type="text/html"  src="<?php echo $iframeURL?>" id="recaptchaiframe" name="recaptchaiframe" height="240" width="800" frameborder="0">
		  
    		</iframe>
            <p><textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea></p>
            <input type="hidden" name="recaptcha_response_field" value="manual_challenge" />
        </noscript>
            <p><input type="submit" name="Submit" /></p>
    </form>

<?php endif;?>	
	
	
	
</body>
</html>
 

K.js

This is my general Javascript Utility Class library, you don't need all the functionality from it. Here are the relevant sections from the Javascript Utility Class:

var K = {
    loadScript: function (url, aFunction) {
        // Load an additional script after the domloaded event has already fired.
        // Safe for XHTML Strict web sites. 
        // url - the url to load, aFunction - function to call once script has loaded
        var e = document.createElement("script");
        e.onreadystatechange = function()
        {
            if ((this.readyState == 'completed' || this.readyState == 'loaded') && !this.loadScriptDone)
            {
                this.loadScriptDone = true;
                aFunction();
            }
        }
        e.onload = aFunction;
        e.type = "text/javascript";
        e.src = url;
        document.getElementsByTagName("head")[0].appendChild(e);
        return e;
    },
    reCaptcha: function (reCaptchaKey, reCaptchaPlaceholderId) {
        var placeHolder = document.getElementById(reCaptchaPlaceholderId);
        if (!reCaptchaKey || !placeHolder) 
        {
            return null;
        }
        K.loadScript(
            (("https:" == document.location.protocol) ? "https://api-secure" : "http://api") + '.recaptcha.net/js/recaptcha_ajax.js', function () {
                K.reCaptchaLoaded(reCaptchaKey, reCaptchaPlaceholderId);
            }
        );
    },
    reCaptchaLoaded: function (reCaptchaKey, reCaptchaPlaceholderId) {
        var placeHolder = document.getElementById(reCaptchaPlaceholderId);
        if (!reCaptchaKey || !placeHolder)
        {
            return null;
        }
        Recaptcha.create(reCaptchaKey, placeHolder);
    }
};
///////////////////////////////////
// DomLoaded - ondomdocumentready
/*! DomLoaded courtesy of Dean Edwards and contributors at http://dean.edwards.name
 * Released under an MIT License http://www.opensource.org/licenses/mit-license.php 
 * Modified by Kieran Whitbread */
var DomLoaded =
{
    onload: [],
    loaded: function()
    {
        var i;
        if (arguments.callee.done) return;
        arguments.callee.done = true;
        for (i = 0; i < DomLoaded.onload.length; i++) DomLoaded.onload[i]();
    },
    load: function(fireThis)
    {
        this.onload.push(fireThis);
        if (document.addEventListener)
        document.addEventListener("DOMContentLoaded", DomLoaded.loaded, null);
        if (/KHTML|WebKit/i.test(navigator.userAgent))
        {
            var _timer = setInterval(function()
            {
                if (/loaded|complete/.test(document.readyState))
                {
                    clearInterval(_timer);
                    delete _timer;
                    DomLoaded.loaded();
                }
            },
            10);
        }
/*@cc_on @*/
/*@if (@_win32)
var proto = "src='javascript:void(0)'";
if (location.protocol == "https:") proto = "src=//0";
document.write("<scr"+"ipt id=__ie_onload defer " + proto + "><\/scr"+"ipt>");
var script = document.getElementById("__ie_onload");
script.onreadystatechange = function() {
    if (this.readyState == "complete") {
        DomLoaded.loaded();
    }
};
/*@end @*/
        window.onload = DomLoaded.loaded;
    }
};
//DomLoaded
/////////////

ReCaptcha.php

This is the PHP ReCaptcha class that you'll need to process the CAPTCHA, it's "included" in to the main recapthca_test.php file

<?php
class kReCaptcha
{
    /**
     * Theme / Colour Scheme
     *
     * One of: red, white, blackglass, clean
     * see recaptcha.net to find out about setting a custom theme
     * (custom not supported in this class yet)
     *
     * @var string
     **/
    protected $theme = 'white';
    protected $allowedThemes = array("red", "white", "blackglass", "clean");
    
    /**
     * The protected key issued by recaptcha.net
     *
     * @var string
     **/
    protected $privateKey = null;
    
    /**
     * The place we are going to send the response submitted by the user, 
     * to have it validated
     *
     * @var string
     **/
    protected $verifyServer = "api-verify.recaptcha.net";
    
    /**
     * Server to use for http/plain communication
     *
     * @var string
     **/
    protected $apiServer = "http://api.recaptcha.net";
    
    /**
     * Server to use for https/secure communication
     *
     * @var string
     **/
    protected $apiSecureServer = "https://api-secure.recaptcha.net";
    
    /**
     * Not Implemented!!
     *
     * The language to use
     *
     * One of: en, nl, fr, de, pt, ru, es, tr
     *
     * @var string
     **/
    protected $lang = 'en';
    protected $allowedLangs = array('en', 'nl', 'fr', 'de', 'pt', 'ru', 'es', 'tr');
    
    public function __construct($publicKey, $privateKey)
    {
        if (!$publicKey || !strlen($publicKey) || !$privateKey || !strlen($privateKey))
        {
            throw new Exception("You must pass your public and private keys in to the constructor");
        }
        $this->privateKey = $privateKey;
        $this->publicKey = $publicKey;
    }
    public function setApiServer($server)
    {
        if ($server && strlen($server))
        {
            $this->apiServer = $server;
        }
    }
    public function setApiSecureServer($server)
    {
        if ($server && strlen($server))
        {
            $this->apiSecureServer = $server;
        }
    }
    public function setTheme($theme)
    {
        if ($theme && strlen($theme) && in_array($theme, $this->allowedThemes))
        {
            $this->theme = $theme;
            return true;
        }
        return false;
    }
    public function setVerifyServer($server)
    {
        if ($server && strlen($server))
        {
            $this->verifyServer = $server;
        }
    }
    
    
    protected function aesPad($val) 
    {
    	$block_size = 16;
    	$numpad = $block_size - (strlen ($val) % $block_size);
    	return str_pad($val, strlen ($val) + $numpad, chr($numpad));
    }
    /**
      * Calls an HTTP POST function to verify if the user's guess was correct
      * @param string $privkey
      * @param string $remoteip
      * @param string $challenge
      * @param string $response
      * @param array $extra_params an array of extra variables to post to the server
      * @return ReCaptchaResponse
      */
    function checkAnswer ($remoteip, $challenge, $response, $extra_params = array())
    {
        if ($remoteip == null || $remoteip == '') 
        {
            throw new InvalidArgumentException('For security reasons, you must pass the remote ip to reCAPTCHA');
        }
        if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) 
        {
            return array('is_valid' => false, 'error' => 'incorrect-captcha-sol');
        }
        $params = array(
            'privatekey' => $this->privateKey,
            'remoteip' => $remoteip,
            'challenge' => $challenge,
            'response' => $response,
        );
        $response = $this->httpPost ($this->verifyServer, "/verify", $params + $extra_params);
        $answers = explode("\n", $response[1]);
        $recaptcha_response = array();
        if (trim($answers[0]) == 'true') 
        {
            $recaptcha_response['is_valid'] = true;
            $recaptcha_response['error'] = false;
        }
        else 
        {
            $recaptcha_response['is_valid'] = false;
            $recaptcha_response['error'] = $answers[1];
        }
        return $recaptcha_response;
    }
    /**
     * Submits an HTTP POST to a reCAPTCHA server
     * @param string $host
     * @param string $path
     * @param array $data
     * @param int port
     * @return array response
     */
    protected function httpPost($host, $path, $data, $port = 80) 
    {
        $req = $this->qsencode ($data);
        $http_request  = "POST $path HTTP/1.0\r\n";
        $http_request .= "Host: $host\r\n";
        $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
        $http_request .= "Content-Length: " . strlen($req) . "\r\n";
        $http_request .= "User-Agent: reCAPTCHA/PHP\r\n";
        $http_request .= "\r\n";
        $http_request .= $req;
        $response = '';
        if(false == ($fs = @fsockopen($host, $port, $errno, $errstr, 10))) 
        {
            throw new RuntimeException('Could not open socket');
        }
        fwrite($fs, $http_request);
        while (!feof($fs)) $response .= fgets($fs, 1160); // One TCP-IP packet
        fclose($fs);
        $response = explode("\r\n\r\n", $response, 2);
        return $response;
    }
	/**
     * Encodes the given data into a query string format
     * @param $data - array of string elements to be encoded
     * @return string - encoded request
     */
    protected function qsencode ($data)
    {
        $req = "";
        foreach ($data as $key => $value)
        {
            $req .= $key . '=' . urlencode( stripslashes($value) ) . '&';
        }
        // Cut the last '&'
        $req=substr($req,0,strlen($req)-1);
        return $req;
    }
    
}

Main Menu

Personal tools

Toolbox