Skip to content
This repository
Browse code

API CHANGE Using curl instead of fsockopen() for HTTP communication i…

…nside RecaptchaField

ENHANCEMENT Making RecaptchaField http requests mockable by introducing RecaptchaField_HTTPClient. Added unit tests
MINOR Added package phpdoc info
  • Loading branch information...
commit cedae82b46159bd6f2954e6964216f322471c7ab 1 parent b1cfbae
Ingo Schommer authored May 14, 2010
1  README.md
Source Rendered
@@ -13,6 +13,7 @@ by giving them a challenge to decrypt an image.
13 13
 ## Requirements
14 14
 
15 15
  * SilverStripe 2.3 or newer
  16
+ * curl PHP module
16 17
  * Requires [SpamProtectionModule](http://silverstripe.org/spam-protection-module/)
17 18
 
18 19
 ## Developer Documentation
4  _config.php
... ...
@@ -1,3 +1,5 @@
1 1
 <?php
2  
-
  2
+/**
  3
+ * @package recaptcha
  4
+ */
3 5
 ?>
94  code/RecaptchaField.php
... ...
@@ -1,5 +1,9 @@
1 1
 <?php
2 2
 /**
  3
+ * @package recaptcha
  4
+ */
  5
+
  6
+/**
3 7
  * Provides an {@link FormField} which allows form to validate for non-bot submissions
4 8
  * by giving them a challenge to decrypt an image.
5 9
  * Generation and validation of captchas is handled on external server.
@@ -16,8 +20,6 @@
16 20
  * 
17 21
  * @see http://recaptcha.net
18 22
  * @see http://recaptcha.net/api/getkey
19  
- * 
20  
- * @module recaptcha
21 23
  */
22 24
 class RecaptchaField extends SpamProtectorField {
23 25
 	
@@ -110,6 +112,11 @@ class RecaptchaField extends SpamProtectorField {
110 112
 	public static $recaptcha_ajax_url = "http://api.recaptcha.net/js/recaptcha_ajax.js";
111 113
 	
112 114
 	/**
  115
+	 * @var string
  116
+	 */
  117
+	public static $httpclient_class = 'RecaptchaField_HTTPClient';
  118
+	
  119
+	/**
113 120
 	 * All languages in which the recaptcha widget is available.
114 121
 	 *
115 122
 	 * @see http://recaptcha.net/apidocs/captcha/client.html
@@ -280,9 +287,7 @@ public function validate($validator) {
280 287
 		}
281 288
 		
282 289
 		// get the payload of the response and split it by newlines
283  
-		$response = explode("\r\n\r\n", $response, 2);
284  
-		
285  
-		list($isValid, $error) = explode("\n", $response[1]);
  290
+		list($isValid, $error) = explode("\n", $response, 2);
286 291
 
287 292
 		if($isValid != 'true') {
288 293
 			if(trim($error) != 'incorrect-captcha-sol') {
@@ -319,43 +324,62 @@ public function validate($validator) {
319 324
 	 * @return string Raw HTTP-response
320 325
 	 */
321 326
 	protected function recaptchaHTTPPost($challengeStr, $responseStr) {
322  
-		$host = self::$api_verify_server;
323  
-		$port = 80;
324  
-		$path = '/verify';
325  
-		$req = http_build_query(array(
  327
+		$postVars = array(
326 328
 			'privatekey' => self::$private_api_key,
327 329
 			'remoteip' => $_SERVER["REMOTE_ADDR"],
328 330
 			'challenge' => $challengeStr,
329 331
 			'response' => $responseStr,
330  
-		));
331  
-
332  
-		$http_request  = "POST $path HTTP/1.0\r\n";
333  
-		$http_request .= "Host: $host\r\n";
334  
-		$http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
335  
-		$http_request .= "Content-Length: " . strlen($req) . "\r\n";
336  
-		$http_request .= "User-Agent: reCAPTCHA/PHP\r\n";
337  
-		$http_request .= "\r\n";
338  
-		$http_request .= $req;
339  
-
340  
-		$fs = fsockopen($host, $port, $errno, $errstr, 10);
341  
-		if(!$fs) {
342  
-			user_error('RecaptchaField::recaptchaHTTPPost(): Could not open socket');
343  
-			return false;
  332
+		);
  333
+		$client = $this->getHTTPClient();
  334
+		$response = $client->post(self::$api_verify_server . '/verify', $postVars);
  335
+		
  336
+		return $response->getBody();
  337
+	}
  338
+	
  339
+	/**
  340
+	 * @param RecaptchaField_HTTPClient
  341
+	 */
  342
+	function setHTTPClient($client) {
  343
+		$this->client = $client;
  344
+	}
  345
+	
  346
+	/**
  347
+	 * @return RecaptchaField_HTTPClient
  348
+	 */
  349
+	function getHTTPClient() {
  350
+		if(!$this->client) {
  351
+			$class = self::$httpclient_class;
  352
+			$this->client = new $class();
344 353
 		}
  354
+		
  355
+		return $this->client;
  356
+	}
  357
+}
345 358
 
346  
-		stream_set_timeout($fs, 10); // time out after 10 seconds for read/write 
347  
-		fwrite($fs, $http_request);
  359
+/**
  360
+ * Simple HTTP client, mainly to make it mockable.
  361
+ */
  362
+class RecaptchaField_HTTPClient extends Object {
  363
+	
  364
+	/**
  365
+	 * @param String $url
  366
+	 * @param Array $data
  367
+	 * @return String HTTPResponse
  368
+	 */
  369
+	function post($url, $postVars) {
  370
+		$ch = curl_init($url);
  371
+		curl_setopt($ch, CURLOPT_TIMEOUT, 10);
  372
+		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  373
+		curl_setopt($ch, CURLOPT_USERAGENT, 'reCAPTCHA/PHP');
  374
+		// we need application/x-www-form-urlencoded
  375
+		curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postVars)); 
  376
+		$response = curl_exec($ch);
348 377
 
349  
-		$response = '';
350  
-		$timed_out = false;
351  
-		while(!$timed_out && !feof($fs)) {
352  
-			$response .= fgets($fs, 1160); // One TCP-IP packet
353  
-			$timed_out = stream_get_meta_data($fs);
354  
-			$timed_out = $timed_out['timed_out'];
  378
+		if(class_exists('SS_HTTPResponse')) {
  379
+			return new SS_HTTPResponse($response);
  380
+		} else {
  381
+			// 2.3 backwards compat
  382
+			return new HTTPResponse($response);
355 383
 		}
356  
-
357  
-		fclose($fs);
358  
-
359  
-		return $response;
360 384
 	}
361 385
 }
6  code/RecaptchaProtector.php
... ...
@@ -1,11 +1,11 @@
1 1
 <?php
2  
-
3 2
 /**
4  
- * Protecter class to handle spam protection interface 
5  
- *
6 3
  * @package recaptcha
7 4
  */
8 5
 
  6
+/**
  7
+ * Protecter class to handle spam protection interface 
  8
+ */
9 9
 class RecaptchaProtector implements SpamProtector {
10 10
 	
11 11
 	/**
40  tests/unit/RecatpchaFieldTest.php
... ...
@@ -0,0 +1,40 @@
  1
+<?php
  2
+/**
  3
+ * @package recaptcha
  4
+ */
  5
+
  6
+class RecatpchaFieldTest extends SapphireTest {
  7
+	
  8
+	function testValidate() {
  9
+		$form = new Form(new Controller(), 'Form', new FieldSet(), new FieldSet());
  10
+		$f = new RecaptchaField('MyField');
  11
+		$f->setHTTPClient(new RecatpchaFieldTest_HTTPClient());
  12
+		$f->setForm($form);
  13
+		$v = new RequiredFields();
  14
+		
  15
+		$_REQUEST['recaptcha_challenge_field'] = 'valid';
  16
+		$_REQUEST['recaptcha_response_field'] = 'response';
  17
+		$this->assertTrue($f->validate($v));
  18
+		
  19
+		$_REQUEST['recaptcha_challenge_field'] = 'invalid';
  20
+		$_REQUEST['recaptcha_response_field'] = 'response';
  21
+		$this->assertFalse($f->validate($v));
  22
+		
  23
+		unset($_REQUEST['recaptcha_challenge_field']);
  24
+		unset($_REQUEST['recaptcha_response_field']);
  25
+	}
  26
+}
  27
+
  28
+class RecatpchaFieldTest_HTTPClient extends RecaptchaField_HTTPClient implements TestOnly {
  29
+	function post($url, $postVars) {
  30
+		if($postVars['challenge'] == 'valid') {
  31
+			return new SS_HTTPResponse("true\nNo errors");
  32
+		}
  33
+		
  34
+		if($postVars['challenge'] == 'invalid') {
  35
+			return new SS_HTTPResponse("false\nincorrect-captcha-sol");
  36
+		}
  37
+		
  38
+		return new SS_HTTPResponse();
  39
+	}
  40
+}

0 notes on commit cedae82

Please sign in to comment.
Something went wrong with that request. Please try again.