From 903ae8a581fac1e6917fc3e31d2ad8fb91df80c3 Mon Sep 17 00:00:00 2001 From: ctrlaltca <> Date: Thu, 12 Jul 2012 11:21:01 +0000 Subject: standardize the use of unix eol; use svn properties to enforce native eol --- framework/Web/UI/WebControls/TCaptcha.php | 990 +++++++++++++++--------------- 1 file changed, 495 insertions(+), 495 deletions(-) (limited to 'framework/Web/UI/WebControls/TCaptcha.php') diff --git a/framework/Web/UI/WebControls/TCaptcha.php b/framework/Web/UI/WebControls/TCaptcha.php index 7bcf5643..5ec870ce 100644 --- a/framework/Web/UI/WebControls/TCaptcha.php +++ b/framework/Web/UI/WebControls/TCaptcha.php @@ -1,495 +1,495 @@ - - * @link http://www.pradosoft.com/ - * @copyright Copyright © 2005-2012 PradoSoft - * @license http://www.pradosoft.com/license/ - * @version $Id$ - * @package System.Web.UI.WebControls - */ - -Prado::using('System.Web.UI.WebControls.TImage'); - -/** - * TCaptcha class. - * - * Notice: while this class is easy to use and implement, it does not provide full security. - * In fact, it's easy to bypass the checks reusing old, already-validated tokens (reply attack). - * A better alternative is provided by {@link TReCaptcha}. - * - * TCaptcha displays a CAPTCHA (a token displayed as an image) that can be used - * to determine if the input is entered by a real user instead of some program. - * - * Unlike other CAPTCHA scripts, TCaptcha does not need session or cookie. - * - * The token (a string consisting of alphanumeric characters) displayed is automatically - * generated and can be configured in several ways. To specify the length of characters - * in the token, set {@link setMinTokenLength MinTokenLength} and {@link setMaxTokenLength MaxTokenLength}. - * To use case-insensitive comparison and generate upper-case-only token, set {@link setCaseSensitive CaseSensitive} - * to false. Advanced users can try to set {@link setTokenAlphabet TokenAlphabet}, which - * specifies what characters can appear in tokens. - * - * The validation of the token is related with two properties: {@link setTestLimit TestLimit} - * and {@link setTokenExpiry TokenExpiry}. The former specifies how many times a token can - * be tested with on the server side, and the latter says when a generated token will expire. - * - * To specify the appearance of the generated token image, set {@link setTokenImageTheme TokenImageTheme} - * to be an integer between 0 and 63. And to adjust the generated image size, set {@link setTokenFontSize TokenFontSize} - * (you may also set {@link TWebControl::setWidth Width}, but the scaled image may not look good.) - * By setting {@link setChangingTokenBackground ChangingTokenBackground} to true, the image background - * of the token will be variating even though the token is the same during postbacks. - * - * Upon postback, user input can be validated by calling {@link validate()}. - * The {@link TCaptchaValidator} control can also be used to do validation, which provides - * client-side validation besides the server-side validation. By default, the token will - * remain the same during multiple postbacks. A new one can be generated by calling - * {@link regenerateToken()} manually. - * - * The following template shows a typical use of TCaptcha control: - * - * - * - * - * - * - * @author Qiang Xue - * @version $Id$ - * @package System.Web.UI.WebControls - * @since 3.1.1 - */ -class TCaptcha extends TImage -{ - const MIN_TOKEN_LENGTH=2; - const MAX_TOKEN_LENGTH=40; - private $_privateKey; - private $_validated=false; - - /** - * @return integer the theme of the token image. Defaults to 0. - */ - public function getTokenImageTheme() - { - return $this->getViewState('TokenImageTheme',0); - } - - /** - * Sets the theme of the token image. - * You may test each theme to find out the one you like the most. - * Below is the explanation of the theme value: - * It is treated as a 5-bit integer. Each bit toggles a specific feature of the image. - * Bit 0 (the least significant): whether the image is opaque (1) or transparent (0). - * Bit 1: whether we should add white noise to the image (1) or not (0). - * Bit 2: whether we should add a grid to the image (1) or not (0). - * Bit 3: whether we should add some scribbles to the image (1) or not (0). - * Bit 4: whether the image background should be morphed (1) or not (0). - * Bit 5: whether the token text should cast a shadow (1) or not (0). - * @param integer the theme of the token image. It must be an integer between 0 and 63. - */ - public function setTokenImageTheme($value) - { - $value=TPropertyValue::ensureInteger($value); - if($value>=0 && $value<=63) - $this->setViewState('TokenImageTheme',$value,0); - else - throw new TConfigurationException('captcha_tokenimagetheme_invalid',0,63); - } - - /** - * @return integer the font size used for displaying the token in an image. Defaults to 30. - */ - public function getTokenFontSize() - { - return $this->getViewState('TokenFontSize',30); - } - - /** - * Sets the font size used for displaying the token in an image. - * This property affects the generated token image size. - * The image width is proportional to this font size. - * @param integer the font size used for displaying the token in an image. It must be an integer between 20 and 100. - */ - public function setTokenFontSize($value) - { - $value=TPropertyValue::ensureInteger($value); - if($value>=20 && $value<=100) - $this->setViewState('TokenFontSize',$value,30); - else - throw new TConfigurationException('captcha_tokenfontsize_invalid',20,100); - } - - /** - * @return integer the minimum length of the token. Defaults to 4. - */ - public function getMinTokenLength() - { - return $this->getViewState('MinTokenLength',4); - } - - /** - * @param integer the minimum length of the token. It must be between 2 and 40. - */ - public function setMinTokenLength($value) - { - $length=TPropertyValue::ensureInteger($value); - if($length>=self::MIN_TOKEN_LENGTH && $length<=self::MAX_TOKEN_LENGTH) - $this->setViewState('MinTokenLength',$length,4); - else - throw new TConfigurationException('captcha_mintokenlength_invalid',self::MIN_TOKEN_LENGTH,self::MAX_TOKEN_LENGTH); - } - - /** - * @return integer the maximum length of the token. Defaults to 6. - */ - public function getMaxTokenLength() - { - return $this->getViewState('MaxTokenLength',6); - } - - /** - * @param integer the maximum length of the token. It must be between 2 and 40. - */ - public function setMaxTokenLength($value) - { - $length=TPropertyValue::ensureInteger($value); - if($length>=self::MIN_TOKEN_LENGTH && $length<=self::MAX_TOKEN_LENGTH) - $this->setViewState('MaxTokenLength',$length,6); - else - throw new TConfigurationException('captcha_maxtokenlength_invalid',self::MIN_TOKEN_LENGTH,self::MAX_TOKEN_LENGTH); - } - - /** - * @return boolean whether the token should be treated as case-sensitive. Defaults to true. - */ - public function getCaseSensitive() - { - return $this->getViewState('CaseSensitive',true); - } - - /** - * @param boolean whether the token should be treated as case-sensitive. If false, only upper-case letters will appear in the token. - */ - public function setCaseSensitive($value) - { - $this->setViewState('CaseSensitive',TPropertyValue::ensureBoolean($value),true); - } - - /** - * @return string the characters that may appear in the token. Defaults to '234578adefhijmnrtABDEFGHJLMNRT'. - */ - public function getTokenAlphabet() - { - return $this->getViewState('TokenAlphabet','234578adefhijmnrtABDEFGHJLMNRT'); - } - - /** - * @param string the characters that may appear in the token. At least 2 characters must be specified. - */ - public function setTokenAlphabet($value) - { - if(strlen($value)<2) - throw new TConfigurationException('captcha_tokenalphabet_invalid'); - $this->setViewState('TokenAlphabet',$value,'234578adefhijmnrtABDEFGHJLMNRT'); - } - - /** - * @return integer the number of seconds that a generated token will remain valid. Defaults to 600 seconds (10 minutes). - */ - public function getTokenExpiry() - { - return $this->getViewState('TokenExpiry',600); - } - - /** - * @param integer the number of seconds that a generated token will remain valid. A value smaller than 1 means the token will not expire. - */ - public function setTokenExpiry($value) - { - $this->setViewState('TokenExpiry',TPropertyValue::ensureInteger($value),600); - } - - /** - * @return boolean whether the background of the token image should be variated during postbacks. Defaults to false. - */ - public function getChangingTokenBackground() - { - return $this->getViewState('ChangingTokenBackground',false); - } - - /** - * @param boolean whether the background of the token image should be variated during postbacks. - */ - public function setChangingTokenBackground($value) - { - $this->setViewState('ChangingTokenBackground',TPropertyValue::ensureBoolean($value),false); - } - - /** - * @return integer how many times a generated token can be tested. Defaults to 5. - */ - public function getTestLimit() - { - return $this->getViewState('TestLimit',5); - } - - /** - * @param integer how many times a generated token can be tested. For unlimited tests, set it to 0. - */ - public function setTestLimit($value) - { - $this->setViewState('TestLimit',TPropertyValue::ensureInteger($value),5); - } - - /** - * @return boolean whether the currently generated token has expired. - */ - public function getIsTokenExpired() - { - if(($expiry=$this->getTokenExpiry())>0 && ($start=$this->getViewState('TokenGenerated',0))>0) - return $expiry+$startgetViewState('PublicKey',''))==='') - { - $publicKey=$this->generateRandomKey(); - $this->setPublicKey($publicKey); - } - return $publicKey; - } - - /** - * @param string the public key used for generating the token. A random one will be generated if this is not set. - */ - public function setPublicKey($value) - { - $this->setViewState('PublicKey',$value,''); - } - - /** - * @return string the token that will be displayed - */ - public function getToken() - { - return $this->generateToken($this->getPublicKey(),$this->getPrivateKey(),$this->getTokenAlphabet(),$this->getTokenLength(),$this->getCaseSensitive()); - } - - /** - * @return integer the length of the token to be generated. - */ - protected function getTokenLength() - { - if(($tokenLength=$this->getViewState('TokenLength'))===null) - { - $minLength=$this->getMinTokenLength(); - $maxLength=$this->getMaxTokenLength(); - if($minLength>$maxLength) - $tokenLength=rand($maxLength,$minLength); - else if($minLength<$maxLength) - $tokenLength=rand($minLength,$maxLength); - else - $tokenLength=$minLength; - $this->setViewState('TokenLength',$tokenLength); - } - return $tokenLength; - } - - /** - * @return string the private key used for generating the token. This is randomly generated and kept in a file for persistency. - */ - public function getPrivateKey() - { - if($this->_privateKey===null) - { - $fileName=$this->generatePrivateKeyFile(); - $content=file_get_contents($fileName); - $matches=array(); - if(preg_match("/privateKey='(.*?)'/ms",$content,$matches)>0) - $this->_privateKey=$matches[1]; - else - throw new TConfigurationException('captcha_privatekey_unknown'); - } - return $this->_privateKey; - } - - /** - * Validates a user input with the token. - * @param string user input - * @return boolean if the user input is not the same as the token. - */ - public function validate($input) - { - $number=$this->getViewState('TestNumber',0); - if(!$this->_validated) - { - $this->setViewState('TestNumber',++$number); - $this->_validated=true; - } - if($this->getIsTokenExpired() || (($limit=$this->getTestLimit())>0 && $number>$limit)) - { - $this->regenerateToken(); - return false; - } - return ($this->getToken()===($this->getCaseSensitive()?$input:strtoupper($input))); - } - - /** - * Regenerates the token to be displayed. - * By default, a token, once generated, will remain the same during the following page postbacks. - * Calling this method will generate a new token. - */ - public function regenerateToken() - { - $this->clearViewState('TokenLength'); - $this->setPublicKey(''); - $this->clearViewState('TokenGenerated'); - $this->clearViewState('RandomSeed'); - $this->clearViewState('TestNumber',0); - } - - /** - * Configures the image URL that shows the token. - * @param mixed event parameter - */ - public function onPreRender($param) - { - parent::onPreRender($param); - if(!self::checkRequirements()) - throw new TConfigurationException('captcha_imagettftext_required'); - if(!$this->getViewState('TokenGenerated',0)) - { - $manager=$this->getApplication()->getAssetManager(); - $manager->publishFilePath($this->getFontFile()); - $url=$manager->publishFilePath($this->getCaptchaScriptFile()); - $url.='?options='.urlencode($this->getTokenImageOptions()); - $this->setImageUrl($url); - - $this->setViewState('TokenGenerated',time()); - } - } - - /** - * @return string the options to be passed to the token image generator - */ - protected function getTokenImageOptions() - { - $privateKey=$this->getPrivateKey(); // call this method to ensure private key is generated - $token=$this->getToken(); - $options=array(); - $options['publicKey']=$this->getPublicKey(); - $options['tokenLength']=strlen($token); - $options['caseSensitive']=$this->getCaseSensitive(); - $options['alphabet']=$this->getTokenAlphabet(); - $options['fontSize']=$this->getTokenFontSize(); - $options['theme']=$this->getTokenImageTheme(); - if(($randomSeed=$this->getViewState('RandomSeed',0))===0) - { - $randomSeed=(int)(microtime()*1000000); - $this->setViewState('RandomSeed',$randomSeed); - } - $options['randomSeed']=$this->getChangingTokenBackground()?0:$randomSeed; - $str=serialize($options); - return base64_encode(md5($privateKey.$str).$str); - } - - /** - * @return string the file path of the PHP script generating the token image - */ - protected function getCaptchaScriptFile() - { - return dirname(__FILE__).DIRECTORY_SEPARATOR.'assets'.DIRECTORY_SEPARATOR.'captcha.php'; - } - - protected function getFontFile() - { - return dirname(__FILE__).DIRECTORY_SEPARATOR.'assets'.DIRECTORY_SEPARATOR.'verase.ttf'; - } - - /** - * Generates a file with a randomly generated private key. - * @return string the path of the file keeping the private key - */ - protected function generatePrivateKeyFile() - { - $captchaScript=$this->getCaptchaScriptFile(); - $path=dirname($this->getApplication()->getAssetManager()->getPublishedPath($captchaScript)); - $fileName=$path.DIRECTORY_SEPARATOR.'captcha_key.php'; - if(!is_file($fileName)) - { - @mkdir($path); - $key=$this->generateRandomKey(); - $content=""; - file_put_contents($fileName,$content); - } - return $fileName; - } - - /** - * @return string a randomly generated key - */ - protected function generateRandomKey() - { - return md5(rand().rand().rand().rand()); - } - - /** - * Generates the token. - * @param string public key - * @param string private key - * @param integer the length of the token - * @param boolean whether the token is case sensitive - * @return string the token generated. - */ - protected function generateToken($publicKey,$privateKey,$alphabet,$tokenLength,$caseSensitive) - { - $token=substr($this->hash2string(md5($publicKey.$privateKey),$alphabet).$this->hash2string(md5($privateKey.$publicKey),$alphabet),0,$tokenLength); - return $caseSensitive?$token:strtoupper($token); - } - - /** - * Converts a hash string into a string with characters consisting of alphanumeric characters. - * @param string the hexadecimal representation of the hash string - * @param string the alphabet used to represent the converted string. If empty, it means '234578adefhijmnrtwyABDEFGHIJLMNQRTWY', which excludes those confusing characters. - * @return string the converted string - */ - protected function hash2string($hex,$alphabet='') - { - if(strlen($alphabet)<2) - $alphabet='234578adefhijmnrtABDEFGHJLMNQRT'; - $hexLength=strlen($hex); - $base=strlen($alphabet); - $result=''; - for($i=0;$i<$hexLength;$i+=6) - { - $number=hexdec(substr($hex,$i,6)); - while($number) - { - $result.=$alphabet[$number%$base]; - $number=floor($number/$base); - } - } - return $result; - } - - /** - * Checks the requirements needed for generating CAPTCHA images. - * TCaptach requires GD2 with TrueType font support and PNG image support. - * @return boolean whether the requirements are satisfied. - */ - public static function checkRequirements() - { - return extension_loaded('gd') && function_exists('imagettftext') && function_exists('imagepng'); - } -} - + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2012 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Web.UI.WebControls + */ + +Prado::using('System.Web.UI.WebControls.TImage'); + +/** + * TCaptcha class. + * + * Notice: while this class is easy to use and implement, it does not provide full security. + * In fact, it's easy to bypass the checks reusing old, already-validated tokens (reply attack). + * A better alternative is provided by {@link TReCaptcha}. + * + * TCaptcha displays a CAPTCHA (a token displayed as an image) that can be used + * to determine if the input is entered by a real user instead of some program. + * + * Unlike other CAPTCHA scripts, TCaptcha does not need session or cookie. + * + * The token (a string consisting of alphanumeric characters) displayed is automatically + * generated and can be configured in several ways. To specify the length of characters + * in the token, set {@link setMinTokenLength MinTokenLength} and {@link setMaxTokenLength MaxTokenLength}. + * To use case-insensitive comparison and generate upper-case-only token, set {@link setCaseSensitive CaseSensitive} + * to false. Advanced users can try to set {@link setTokenAlphabet TokenAlphabet}, which + * specifies what characters can appear in tokens. + * + * The validation of the token is related with two properties: {@link setTestLimit TestLimit} + * and {@link setTokenExpiry TokenExpiry}. The former specifies how many times a token can + * be tested with on the server side, and the latter says when a generated token will expire. + * + * To specify the appearance of the generated token image, set {@link setTokenImageTheme TokenImageTheme} + * to be an integer between 0 and 63. And to adjust the generated image size, set {@link setTokenFontSize TokenFontSize} + * (you may also set {@link TWebControl::setWidth Width}, but the scaled image may not look good.) + * By setting {@link setChangingTokenBackground ChangingTokenBackground} to true, the image background + * of the token will be variating even though the token is the same during postbacks. + * + * Upon postback, user input can be validated by calling {@link validate()}. + * The {@link TCaptchaValidator} control can also be used to do validation, which provides + * client-side validation besides the server-side validation. By default, the token will + * remain the same during multiple postbacks. A new one can be generated by calling + * {@link regenerateToken()} manually. + * + * The following template shows a typical use of TCaptcha control: + * + * + * + * + * + * + * @author Qiang Xue + * @version $Id$ + * @package System.Web.UI.WebControls + * @since 3.1.1 + */ +class TCaptcha extends TImage +{ + const MIN_TOKEN_LENGTH=2; + const MAX_TOKEN_LENGTH=40; + private $_privateKey; + private $_validated=false; + + /** + * @return integer the theme of the token image. Defaults to 0. + */ + public function getTokenImageTheme() + { + return $this->getViewState('TokenImageTheme',0); + } + + /** + * Sets the theme of the token image. + * You may test each theme to find out the one you like the most. + * Below is the explanation of the theme value: + * It is treated as a 5-bit integer. Each bit toggles a specific feature of the image. + * Bit 0 (the least significant): whether the image is opaque (1) or transparent (0). + * Bit 1: whether we should add white noise to the image (1) or not (0). + * Bit 2: whether we should add a grid to the image (1) or not (0). + * Bit 3: whether we should add some scribbles to the image (1) or not (0). + * Bit 4: whether the image background should be morphed (1) or not (0). + * Bit 5: whether the token text should cast a shadow (1) or not (0). + * @param integer the theme of the token image. It must be an integer between 0 and 63. + */ + public function setTokenImageTheme($value) + { + $value=TPropertyValue::ensureInteger($value); + if($value>=0 && $value<=63) + $this->setViewState('TokenImageTheme',$value,0); + else + throw new TConfigurationException('captcha_tokenimagetheme_invalid',0,63); + } + + /** + * @return integer the font size used for displaying the token in an image. Defaults to 30. + */ + public function getTokenFontSize() + { + return $this->getViewState('TokenFontSize',30); + } + + /** + * Sets the font size used for displaying the token in an image. + * This property affects the generated token image size. + * The image width is proportional to this font size. + * @param integer the font size used for displaying the token in an image. It must be an integer between 20 and 100. + */ + public function setTokenFontSize($value) + { + $value=TPropertyValue::ensureInteger($value); + if($value>=20 && $value<=100) + $this->setViewState('TokenFontSize',$value,30); + else + throw new TConfigurationException('captcha_tokenfontsize_invalid',20,100); + } + + /** + * @return integer the minimum length of the token. Defaults to 4. + */ + public function getMinTokenLength() + { + return $this->getViewState('MinTokenLength',4); + } + + /** + * @param integer the minimum length of the token. It must be between 2 and 40. + */ + public function setMinTokenLength($value) + { + $length=TPropertyValue::ensureInteger($value); + if($length>=self::MIN_TOKEN_LENGTH && $length<=self::MAX_TOKEN_LENGTH) + $this->setViewState('MinTokenLength',$length,4); + else + throw new TConfigurationException('captcha_mintokenlength_invalid',self::MIN_TOKEN_LENGTH,self::MAX_TOKEN_LENGTH); + } + + /** + * @return integer the maximum length of the token. Defaults to 6. + */ + public function getMaxTokenLength() + { + return $this->getViewState('MaxTokenLength',6); + } + + /** + * @param integer the maximum length of the token. It must be between 2 and 40. + */ + public function setMaxTokenLength($value) + { + $length=TPropertyValue::ensureInteger($value); + if($length>=self::MIN_TOKEN_LENGTH && $length<=self::MAX_TOKEN_LENGTH) + $this->setViewState('MaxTokenLength',$length,6); + else + throw new TConfigurationException('captcha_maxtokenlength_invalid',self::MIN_TOKEN_LENGTH,self::MAX_TOKEN_LENGTH); + } + + /** + * @return boolean whether the token should be treated as case-sensitive. Defaults to true. + */ + public function getCaseSensitive() + { + return $this->getViewState('CaseSensitive',true); + } + + /** + * @param boolean whether the token should be treated as case-sensitive. If false, only upper-case letters will appear in the token. + */ + public function setCaseSensitive($value) + { + $this->setViewState('CaseSensitive',TPropertyValue::ensureBoolean($value),true); + } + + /** + * @return string the characters that may appear in the token. Defaults to '234578adefhijmnrtABDEFGHJLMNRT'. + */ + public function getTokenAlphabet() + { + return $this->getViewState('TokenAlphabet','234578adefhijmnrtABDEFGHJLMNRT'); + } + + /** + * @param string the characters that may appear in the token. At least 2 characters must be specified. + */ + public function setTokenAlphabet($value) + { + if(strlen($value)<2) + throw new TConfigurationException('captcha_tokenalphabet_invalid'); + $this->setViewState('TokenAlphabet',$value,'234578adefhijmnrtABDEFGHJLMNRT'); + } + + /** + * @return integer the number of seconds that a generated token will remain valid. Defaults to 600 seconds (10 minutes). + */ + public function getTokenExpiry() + { + return $this->getViewState('TokenExpiry',600); + } + + /** + * @param integer the number of seconds that a generated token will remain valid. A value smaller than 1 means the token will not expire. + */ + public function setTokenExpiry($value) + { + $this->setViewState('TokenExpiry',TPropertyValue::ensureInteger($value),600); + } + + /** + * @return boolean whether the background of the token image should be variated during postbacks. Defaults to false. + */ + public function getChangingTokenBackground() + { + return $this->getViewState('ChangingTokenBackground',false); + } + + /** + * @param boolean whether the background of the token image should be variated during postbacks. + */ + public function setChangingTokenBackground($value) + { + $this->setViewState('ChangingTokenBackground',TPropertyValue::ensureBoolean($value),false); + } + + /** + * @return integer how many times a generated token can be tested. Defaults to 5. + */ + public function getTestLimit() + { + return $this->getViewState('TestLimit',5); + } + + /** + * @param integer how many times a generated token can be tested. For unlimited tests, set it to 0. + */ + public function setTestLimit($value) + { + $this->setViewState('TestLimit',TPropertyValue::ensureInteger($value),5); + } + + /** + * @return boolean whether the currently generated token has expired. + */ + public function getIsTokenExpired() + { + if(($expiry=$this->getTokenExpiry())>0 && ($start=$this->getViewState('TokenGenerated',0))>0) + return $expiry+$startgetViewState('PublicKey',''))==='') + { + $publicKey=$this->generateRandomKey(); + $this->setPublicKey($publicKey); + } + return $publicKey; + } + + /** + * @param string the public key used for generating the token. A random one will be generated if this is not set. + */ + public function setPublicKey($value) + { + $this->setViewState('PublicKey',$value,''); + } + + /** + * @return string the token that will be displayed + */ + public function getToken() + { + return $this->generateToken($this->getPublicKey(),$this->getPrivateKey(),$this->getTokenAlphabet(),$this->getTokenLength(),$this->getCaseSensitive()); + } + + /** + * @return integer the length of the token to be generated. + */ + protected function getTokenLength() + { + if(($tokenLength=$this->getViewState('TokenLength'))===null) + { + $minLength=$this->getMinTokenLength(); + $maxLength=$this->getMaxTokenLength(); + if($minLength>$maxLength) + $tokenLength=rand($maxLength,$minLength); + else if($minLength<$maxLength) + $tokenLength=rand($minLength,$maxLength); + else + $tokenLength=$minLength; + $this->setViewState('TokenLength',$tokenLength); + } + return $tokenLength; + } + + /** + * @return string the private key used for generating the token. This is randomly generated and kept in a file for persistency. + */ + public function getPrivateKey() + { + if($this->_privateKey===null) + { + $fileName=$this->generatePrivateKeyFile(); + $content=file_get_contents($fileName); + $matches=array(); + if(preg_match("/privateKey='(.*?)'/ms",$content,$matches)>0) + $this->_privateKey=$matches[1]; + else + throw new TConfigurationException('captcha_privatekey_unknown'); + } + return $this->_privateKey; + } + + /** + * Validates a user input with the token. + * @param string user input + * @return boolean if the user input is not the same as the token. + */ + public function validate($input) + { + $number=$this->getViewState('TestNumber',0); + if(!$this->_validated) + { + $this->setViewState('TestNumber',++$number); + $this->_validated=true; + } + if($this->getIsTokenExpired() || (($limit=$this->getTestLimit())>0 && $number>$limit)) + { + $this->regenerateToken(); + return false; + } + return ($this->getToken()===($this->getCaseSensitive()?$input:strtoupper($input))); + } + + /** + * Regenerates the token to be displayed. + * By default, a token, once generated, will remain the same during the following page postbacks. + * Calling this method will generate a new token. + */ + public function regenerateToken() + { + $this->clearViewState('TokenLength'); + $this->setPublicKey(''); + $this->clearViewState('TokenGenerated'); + $this->clearViewState('RandomSeed'); + $this->clearViewState('TestNumber',0); + } + + /** + * Configures the image URL that shows the token. + * @param mixed event parameter + */ + public function onPreRender($param) + { + parent::onPreRender($param); + if(!self::checkRequirements()) + throw new TConfigurationException('captcha_imagettftext_required'); + if(!$this->getViewState('TokenGenerated',0)) + { + $manager=$this->getApplication()->getAssetManager(); + $manager->publishFilePath($this->getFontFile()); + $url=$manager->publishFilePath($this->getCaptchaScriptFile()); + $url.='?options='.urlencode($this->getTokenImageOptions()); + $this->setImageUrl($url); + + $this->setViewState('TokenGenerated',time()); + } + } + + /** + * @return string the options to be passed to the token image generator + */ + protected function getTokenImageOptions() + { + $privateKey=$this->getPrivateKey(); // call this method to ensure private key is generated + $token=$this->getToken(); + $options=array(); + $options['publicKey']=$this->getPublicKey(); + $options['tokenLength']=strlen($token); + $options['caseSensitive']=$this->getCaseSensitive(); + $options['alphabet']=$this->getTokenAlphabet(); + $options['fontSize']=$this->getTokenFontSize(); + $options['theme']=$this->getTokenImageTheme(); + if(($randomSeed=$this->getViewState('RandomSeed',0))===0) + { + $randomSeed=(int)(microtime()*1000000); + $this->setViewState('RandomSeed',$randomSeed); + } + $options['randomSeed']=$this->getChangingTokenBackground()?0:$randomSeed; + $str=serialize($options); + return base64_encode(md5($privateKey.$str).$str); + } + + /** + * @return string the file path of the PHP script generating the token image + */ + protected function getCaptchaScriptFile() + { + return dirname(__FILE__).DIRECTORY_SEPARATOR.'assets'.DIRECTORY_SEPARATOR.'captcha.php'; + } + + protected function getFontFile() + { + return dirname(__FILE__).DIRECTORY_SEPARATOR.'assets'.DIRECTORY_SEPARATOR.'verase.ttf'; + } + + /** + * Generates a file with a randomly generated private key. + * @return string the path of the file keeping the private key + */ + protected function generatePrivateKeyFile() + { + $captchaScript=$this->getCaptchaScriptFile(); + $path=dirname($this->getApplication()->getAssetManager()->getPublishedPath($captchaScript)); + $fileName=$path.DIRECTORY_SEPARATOR.'captcha_key.php'; + if(!is_file($fileName)) + { + @mkdir($path); + $key=$this->generateRandomKey(); + $content=""; + file_put_contents($fileName,$content); + } + return $fileName; + } + + /** + * @return string a randomly generated key + */ + protected function generateRandomKey() + { + return md5(rand().rand().rand().rand()); + } + + /** + * Generates the token. + * @param string public key + * @param string private key + * @param integer the length of the token + * @param boolean whether the token is case sensitive + * @return string the token generated. + */ + protected function generateToken($publicKey,$privateKey,$alphabet,$tokenLength,$caseSensitive) + { + $token=substr($this->hash2string(md5($publicKey.$privateKey),$alphabet).$this->hash2string(md5($privateKey.$publicKey),$alphabet),0,$tokenLength); + return $caseSensitive?$token:strtoupper($token); + } + + /** + * Converts a hash string into a string with characters consisting of alphanumeric characters. + * @param string the hexadecimal representation of the hash string + * @param string the alphabet used to represent the converted string. If empty, it means '234578adefhijmnrtwyABDEFGHIJLMNQRTWY', which excludes those confusing characters. + * @return string the converted string + */ + protected function hash2string($hex,$alphabet='') + { + if(strlen($alphabet)<2) + $alphabet='234578adefhijmnrtABDEFGHJLMNQRT'; + $hexLength=strlen($hex); + $base=strlen($alphabet); + $result=''; + for($i=0;$i<$hexLength;$i+=6) + { + $number=hexdec(substr($hex,$i,6)); + while($number) + { + $result.=$alphabet[$number%$base]; + $number=floor($number/$base); + } + } + return $result; + } + + /** + * Checks the requirements needed for generating CAPTCHA images. + * TCaptach requires GD2 with TrueType font support and PNG image support. + * @return boolean whether the requirements are satisfied. + */ + public static function checkRequirements() + { + return extension_loaded('gd') && function_exists('imagettftext') && function_exists('imagepng'); + } +} + -- cgit v1.2.3