From 669beed5392d22421d03b2a2d1bf12f5f0db9012 Mon Sep 17 00:00:00 2001 From: xue <> Date: Tue, 28 Aug 2007 22:05:18 +0000 Subject: added TCaptcha, TCaptchaValidator. --- framework/Web/UI/WebControls/TCaptcha.php | 241 +++++++++++++++++++++ framework/Web/UI/WebControls/TCaptchaValidator.php | 117 ++++++++++ framework/Web/UI/WebControls/assets/captcha.php | 35 +++ 3 files changed, 393 insertions(+) create mode 100644 framework/Web/UI/WebControls/TCaptcha.php create mode 100644 framework/Web/UI/WebControls/TCaptchaValidator.php create mode 100644 framework/Web/UI/WebControls/assets/captcha.php (limited to 'framework/Web/UI') diff --git a/framework/Web/UI/WebControls/TCaptcha.php b/framework/Web/UI/WebControls/TCaptcha.php new file mode 100644 index 00000000..a9467333 --- /dev/null +++ b/framework/Web/UI/WebControls/TCaptcha.php @@ -0,0 +1,241 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2007 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Web.UI.WebControls + */ + +Prado::using('System.Web.UI.WebControls.TImage'); + +/** + * TCaptcha class. + * + * 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. + * + * 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 setTokenLength TokenLength}. To use case-insensitive + * comparison and generate upper-case-only token, set {@link setCaseSensitive CaseSensitive} + * to false. + * + * 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. + * + * @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; + + /** + * @return integer the length of the token. Defaults to 6. + */ + public function getTokenLength() + { + return $this->getViewState('TokenLength',6); + } + + /** + * @param integer the length of the token. It must be between 2 and 40. + */ + public function setTokenLength($value) + { + $length=TPropertyValue::ensureInteger($value); + if($length>=self::MIN_TOKEN_LENGTH && $length<=self::MAX_TOKEN_LENGTH) + $this->setViewState('TokenLength',$length,6); + else + throw new TConfigurationException('captcha_TokenLength_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 public key used for generating the token. A random one will be generated if this is not set. + */ + public function getPublicKey() + { + if(($publicKey=$this->getViewState('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->getTokenLength(),$this->getCaseSensitive()); return $this->generateToken(); + } + + /** + * @return string the private key used for generating the token. This is randomly generated and kept in a file for persistency. + */ + public function getPrivateKey() + { + $fileName=$this->generatePrivateKeyFile(); + $content=file_get_contents($fileName); + $matches=array(); + if(preg_match("/privateKey='(.*?)'/ms",$content,$matches)>0) + return $matches[1]; + else + throw new TConfigurationException('captcha_private_unknown'); + } + + /** + * 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) + { + 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('RegenerateToken'); + $this->setPublicKey(''); + } + + /** + * Configures the image URL that shows the token. + * @param mixed event parameter + */ + public function onPreRender($param) + { + parent::onPreRender($param); + if($this->getViewState('RegenerateToken',true)) + { + $this->getToken(); // call to ensure the token is regenerated + $url=$this->getApplication()->getAssetManager()->publishFilePath($this->getCaptchaScriptFile()); + $url.='?pk='.urlencode($this->getPublicKey()); + $url.='&length='.$this->getTokenLength(); + $url.='&case='.($this->getCaseSensitive()?'1':'0'); + $this->setImageUrl($url); + $this->generatePrivateKeyFile(); + + $this->setViewState('RegenerateToken',false); + } + } + + /** + * @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'; + } + + /** + * 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,$tokenLength,$caseSensitive) + { + $token=substr($this->hash2string(md5($publicKey.$privateKey)).$this->hash2string(md5($privateKey.$publicKey)),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 0-9, a-z and A-Z. + * @return string the converted string + */ + protected function hash2string($hex,$alphabet='') + { + if(strlen($alphabet)<2) + $alphabet='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $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; + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/WebControls/TCaptchaValidator.php b/framework/Web/UI/WebControls/TCaptchaValidator.php new file mode 100644 index 00000000..4385bbfe --- /dev/null +++ b/framework/Web/UI/WebControls/TCaptchaValidator.php @@ -0,0 +1,117 @@ + + * @link http://www.pradosoft.com/ + * @copyright Copyright © 2005-2007 PradoSoft + * @license http://www.pradosoft.com/license/ + * @version $Id$ + * @package System.Web.UI.WebControls + */ + +Prado::using('System.Web.UI.WebControls.TBaseValidator'); +Prado::using('System.Web.UI.WebControls.TCaptcha'); + +/** + * TCaptchaValidator class + * + * TCaptchaValidator validates user input against a CAPTCHA represented by + * a {@link TCaptcha} control. The input control fails validation if its value + * is not the same as the token displayed in CAPTCHA. Note, if the user does + * not enter any thing, it is still considered as failing the validation. + * + * To use TCaptchaValidator, specify the {@link setControlToValidate ControlToValidate} + * to be the ID path of the input control (usually a {@link TTextBox} control}. + * Also specify the {@link setCaptchaControl CaptchaControl} to be the ID path of + * the CAPTCHA control that the user input should be compared with. + * + * @author Qiang Xue + * @version $Id$ + * @package System.Web.UI.WebControls + * @since 3.1.1 + */ +class TCaptchaValidator extends TBaseValidator +{ + /** + * Gets the name of the javascript class responsible for performing validation for this control. + * This method overrides the parent implementation. + * @return string the javascript class name + */ + protected function getClientClassName() + { + return 'Prado.WebUI.TCaptchaValidator'; + } + + /** + * @return string the ID path of the CAPTCHA control to validate + */ + public function getCaptchaControl() + { + return $this->getViewState('CaptchaControl',''); + } + + /** + * Sets the ID path of the CAPTCHA control to validate. + * The ID path is the dot-connected IDs of the controls reaching from + * the validator's naming container to the target control. + * @param string the ID path + */ + public function setCaptchaControl($value) + { + $this->setViewState('CaptchaControl',TPropertyValue::ensureString($value),''); + } + + /** + * This method overrides the parent's implementation. + * The validation succeeds if the input control has the same value + * as the one displayed in the corresponding CAPTCHA control. + * + * @return boolean whether the validation succeeds + */ + protected function evaluateIsValid() + { + $value=$this->getValidationValue($this->getValidationTarget()); + $control=$this->findCaptchaControl(); + return $control->validate(trim($value)); + } + + /** + * @return TCaptchaControl the CAPTCHA control to be validated against + * @throws TConfigurationException if the CAPTCHA cannot be found according to {@link setCaptchaControl CaptchaControl} + */ + protected function findCaptchaControl() + { + if(($id=$this->getCaptchaControl())==='') + throw new TConfigurationException('captchavalidator_captchacontrol_required'); + else if(($control=$this->findControl($id))===null) + throw new TConfigurationException('captchavalidator_captchacontrol_inexistent',$id); + else if(!($control instanceof TCaptcha)) + throw new TConfigurationException('captchavalidator_captchacontrol_invalid',$id); + else + return $control; + } + + /** + * Returns an array of javascript validator options. + * @return array javascript validator options. + */ + protected function getClientScriptOptions() + { + $options=parent::getClientScriptOptions(); + $control=$this->findCaptchaControl(); + if($control->getCaseSensitive()) + { + $options['TokenHash']=crc32($control->getToken()); + $options['CaseSensitive']=true; + } + else + { + $options['TokenHash']=crc32(strtoupper($control->getToken())); + $options['CaseSensitive']=false; + } + return $options; + } +} + +?> \ No newline at end of file diff --git a/framework/Web/UI/WebControls/assets/captcha.php b/framework/Web/UI/WebControls/assets/captcha.php new file mode 100644 index 00000000..aa3fdcc0 --- /dev/null +++ b/framework/Web/UI/WebControls/assets/captcha.php @@ -0,0 +1,35 @@ + \ No newline at end of file -- cgit v1.2.3