recaptcha.class.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. <?php
  2. /**
  3. * reCaptcha integration
  4. *
  5. * Copyright 2010 by Shaun McCormick <shaun@modxcms.com>
  6. *
  7. * Register is free software; you can redistribute it and/or modify it under the
  8. * terms of the GNU General Public License as published by the Free Software
  9. * Foundation; either version 2 of the License, or (at your option) any later
  10. * version.
  11. *
  12. * Register is distributed in the hope that it will be useful, but WITHOUT ANY
  13. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  14. * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along with
  17. * Register; if not, write to the Free Software Foundation, Inc., 59 Temple
  18. * Place, Suite 330, Boston, MA 02111-1307 USA
  19. *
  20. * @package login
  21. */
  22. /**
  23. * reCaptcha modX service class.
  24. *
  25. * Based off of recaptchalib.php by Mike Crawford and Ben Maurer. Changes include converting to OOP and making a class.
  26. *
  27. * @package login
  28. * @subpackage recaptcha
  29. */
  30. if (!class_exists('reCaptcha')) {
  31. class reCaptcha {
  32. const API_SERVER = 'http://www.google.com/recaptcha/api/';
  33. const API_SECURE_SERVER = 'https://www.google.com/recaptcha/api/';
  34. const VERIFY_SERVER = 'www.google.com';
  35. const OPT_PRIVATE_KEY = 'privateKey';
  36. const OPT_PUBLIC_KEY = 'publicKey';
  37. const OPT_USE_SSL = 'use_ssl';
  38. function __construct(modX &$modx,array $config = array()) {
  39. $this->modx =& $modx;
  40. $this->config = array_merge(array(
  41. reCaptcha::OPT_PRIVATE_KEY => $this->modx->getOption('recaptcha.private_key',$config,''),
  42. reCaptcha::OPT_PUBLIC_KEY => $this->modx->getOption('recaptcha.public_key',$config,''),
  43. reCaptcha::OPT_USE_SSL => $this->modx->getOption('recaptcha.use_ssl',$config,false),
  44. ),$config);
  45. }
  46. /**
  47. * Encodes the given data into a query string format
  48. * @param $data - array of string elements to be encoded
  49. * @return string - encoded request
  50. */
  51. protected function qsencode($data) {
  52. $req = '';
  53. foreach ($data as $key => $value) {
  54. $req .= $key . '=' . urlencode( stripslashes($value) ) . '&';
  55. }
  56. // Cut the last '&'
  57. $req=substr($req,0,strlen($req)-1);
  58. return $req;
  59. }
  60. /**
  61. * Submits an HTTP POST to a reCAPTCHA server
  62. * @param $host
  63. * @param $path
  64. * @param array $data
  65. * @param int $port
  66. * @return string
  67. */
  68. protected function httpPost($host, $path, array $data = array(), $port = 80) {
  69. $data['privatekey'] = $this->config[reCaptcha::OPT_PRIVATE_KEY];
  70. $req = $this->qsencode($data);
  71. $http_request = "POST $path HTTP/1.0\r\n";
  72. $http_request .= "Host: $host\r\n";
  73. $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
  74. $http_request .= "Content-Length: " . strlen($req) . "\r\n";
  75. $http_request .= "User-Agent: reCAPTCHA/PHP\r\n";
  76. $http_request .= "\r\n";
  77. $http_request .= $req;
  78. $response = '';
  79. if(false == ($fs = @fsockopen($host, $port, $errno, $errstr, 10))) {
  80. return 'Could not open socket';
  81. }
  82. fwrite($fs, $http_request);
  83. while (!feof($fs)) {
  84. $response .= fgets($fs, 1160); // One TCP-IP packet
  85. }
  86. fclose($fs);
  87. $response = explode("\r\n\r\n", $response, 2);
  88. return $response;
  89. }
  90. /**
  91. * Gets the challenge HTML (javascript and non-javascript version).
  92. * This is called from the browser, and the resulting reCAPTCHA HTML widget
  93. * is embedded within the HTML form it was called from.
  94. *
  95. * @param string $theme
  96. * @param int $width
  97. * @param int $height
  98. * @param null $error
  99. * @return string The HTML to be embedded in the user's form.
  100. */
  101. public function getHtml($theme = 'clean',$width = 500,$height = 300,$error = null) {
  102. if (empty($this->config[reCaptcha::OPT_PUBLIC_KEY])) {
  103. return $this->error($this->modx->lexicon('recaptcha.no_api_key'));
  104. }
  105. /* use ssl or not */
  106. $server = !empty($this->config[reCaptcha::OPT_USE_SSL]) ? reCaptcha::API_SECURE_SERVER : reCaptcha::API_SERVER;
  107. $errorpart = '';
  108. if ($error) {
  109. $errorpart = "&amp;error=" . $error;
  110. }
  111. $opt = array(
  112. 'theme' => $theme,
  113. 'width' => $width,
  114. 'height' => $height,
  115. 'lang' => $this->modx->getOption('cultureKey',null,'en'),
  116. );
  117. return '<script type="text/javascript">var RecaptchaOptions = '.$this->modx->toJSON($opt).';</script><script type="text/javascript" src="'. $server . 'challenge?k=' . $this->config[reCaptcha::OPT_PUBLIC_KEY] . $errorpart . '"></script>
  118. <noscript>
  119. <iframe src="'. $server . 'noscript?k=' . $this->config[reCaptcha::OPT_PUBLIC_KEY] . $errorpart . '" height="'.$height.'" width="'.$width.'" frameborder="0" style="width: '.$width.'px; height: '.$height.'px;"></iframe><br />
  120. <textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
  121. <input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
  122. </noscript>';
  123. }
  124. protected function error($message = '') {
  125. $response = new reCaptchaResponse();
  126. $response->is_valid = false;
  127. $response->error = $message;
  128. return $message;
  129. }
  130. /**
  131. * Calls an HTTP POST function to verify if the user's guess was correct
  132. * @param $remoteIp
  133. * @param $challenge
  134. * @param $responseField
  135. * @param array $extraParams
  136. * @return ReCaptchaResponse
  137. */
  138. public function checkAnswer ($remoteIp, $challenge, $responseField, $extraParams = array()) {
  139. if (empty($this->config[reCaptcha::OPT_PRIVATE_KEY])) {
  140. return $this->error($this->modx->lexicon('recaptcha.no_api_key'));
  141. }
  142. if (empty($remoteIp)) {
  143. return $this->error($this->modx->lexicon('recaptcha.no_remote_ip'));
  144. }
  145. //discard spam submissions
  146. if (empty($challenge) || empty($responseField)) {
  147. return $this->error($this->modx->lexicon('recaptcha.empty_answer'));
  148. }
  149. $response = $this->httpPost(reCaptcha::VERIFY_SERVER,"/recaptcha/api/verify",array (
  150. 'remoteip' => $remoteIp,
  151. 'challenge' => $challenge,
  152. 'response' => $responseField,
  153. ) + $extraParams);
  154. $answers = explode("\n",$response[1]);
  155. $response = new reCaptchaResponse();
  156. if (trim($answers[0]) == 'true') {
  157. $response->is_valid = true;
  158. } else {
  159. $response->is_valid = false;
  160. $response->error = $answers [1];
  161. }
  162. return $response;
  163. }
  164. /**
  165. * gets a URL where the user can sign up for reCAPTCHA. If your application
  166. * has a configuration page where you enter a key, you should provide a link
  167. * using this function.
  168. * @param null $domain
  169. * @param null $appname
  170. * @return string
  171. */
  172. public function getSignupUrl ($domain = null, $appname = null) {
  173. return "http://www.google.com/recaptcha/api/getkey?" . $this->qsencode(array ('domain' => $domain, 'app' => $appname));
  174. }
  175. protected function aesPad($val) {
  176. $block_size = 16;
  177. $numpad = $block_size - (strlen ($val) % $block_size);
  178. return str_pad($val, strlen ($val) + $numpad, chr($numpad));
  179. }
  180. /* Mailhide related code */
  181. protected function aesEncrypt($val,$ky) {
  182. if (!function_exists("mcrypt_encrypt")) {
  183. return $this->error($this->modx->lexicon('recaptcha.mailhide_no_mcrypt'));
  184. }
  185. $mode=MCRYPT_MODE_CBC;
  186. $enc=MCRYPT_RIJNDAEL_128;
  187. $val= $this->aesPad($val);
  188. return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
  189. }
  190. protected function mailhideUrlbase64 ($x) {
  191. return strtr(base64_encode ($x), '+/', '-_');
  192. }
  193. /* gets the reCAPTCHA Mailhide url for a given email, public key and private key */
  194. public function mailhideUrl($email) {
  195. if (empty($this->config[reCaptcha::OPT_PUBLIC_KEY]) || empty($this->config[reCaptcha::OPT_PRIVATE_KEY])) {
  196. return $this->error($this->modx->lexicon('recaptcha.mailhide_no_api_key'));
  197. }
  198. $ky = pack('H*',$this->config[reCaptcha::OPT_PRIVATE_KEY]);
  199. $cryptmail = $this->aesEncrypt($email, $ky);
  200. return 'http://mailhide.recaptcha.net/d?k='
  201. . $this->config[reCaptcha::OPT_PUBLIC_KEY]
  202. . '&c=' . $this->mailhideUrlbase64($cryptmail);
  203. }
  204. /**
  205. * gets the parts of the email to expose to the user.
  206. * eg, given johndoe@example,com return ["john", "example.com"].
  207. * the email is then displayed as john...@example.com
  208. *
  209. * @param $email
  210. * @return array
  211. */
  212. public function mailhideEmailParts($email) {
  213. $arr = preg_split("/@/", $email);
  214. if (strlen($arr[0]) <= 4) {
  215. $arr[0] = substr($arr[0], 0, 1);
  216. } else if (strlen ($arr[0]) <= 6) {
  217. $arr[0] = substr($arr[0], 0, 3);
  218. } else {
  219. $arr[0] = substr($arr[0], 0, 4);
  220. }
  221. return $arr;
  222. }
  223. /**
  224. * Gets html to display an email address given a public an private key.
  225. * to get a key, go to:
  226. *
  227. * http://mailhide.recaptcha.net/apikey
  228. *
  229. * @param $email
  230. * @return string
  231. */
  232. public function mailhideHtml($email) {
  233. $emailparts = $this->mailhideEmailParts($email);
  234. $url = $this->mailhideUrl($email);
  235. return htmlentities($emailparts[0]) . "<a href='" . htmlentities ($url) .
  236. "' onclick=\"window.open('" . htmlentities ($url) . "', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;\" title=\"Reveal this e-mail address\">...</a>@" . htmlentities ($emailparts [1]);
  237. }
  238. }
  239. /**
  240. * A reCaptchaResponse is returned from reCaptcha::check_answer()
  241. */
  242. class reCaptchaResponse {
  243. public $is_valid;
  244. public $error;
  245. }
  246. }