3 Does emit a CAPTCHA graphic and form fields, which allows to tell real
5 Though a textual description is generated as well, this sort of access
6 restriction will knock out visually impaired users, and frustrate all
7 others anyhow. Therefore this should only be used as last resort for
8 defending against spambots. Because of the readable text and the used
9 colorspaces this is a weak implementation, not completely OCR-secure.
11 captcha::form() will return a html string to be inserted into textarea/
12 [save] <forms> and alike. User input is veryfied with captcha::check().
13 You should leave the sample COLLEGE.ttf next to this script, else you
14 have to define the _FONT_DIR constant correctly. Use only one type.
16 Includes a sluggish workaround for Internet Explorer; but this script
17 must reside in a www-accessible directory then.
19 Public Domain, available via http://freshmeat.net/p/captchaphp
24 define("EWIKI_FONT_DIR", dirname(__FILE__)); // which fonts to use
25 define("CAPTCHA_INVERSE", 0); // white or black(=1)
26 define("CAPTCHA_TIMEOUT", 5000); // in seconds (=max 4 hours)
29 /* static - (you could instantiate it, but...) */
33 /* gets parameter from $_REQUEST[] array (POST vars) and so can
34 verify input, @returns boolean
37 if (($hash = $_REQUEST["captcha_hash"])
38 and ($pw = trim($_REQUEST["captcha_input"]))) {
39 return((captcha::hash($pw)==$hash) || (captcha::hash($pw,-1)==$hash));
44 /* yields <input> fields html string (no complete form), with captcha
45 image already embedded as data:-URI
47 function form($title="→ retype that here", $more="<small>Enter the correct letters and numbers from the image into the text box. This small test serves as access restriction against malicious bots. Simply reload the page if this graphic is too hard to read.</small>") {
48 $pw = captcha::mkpass();
49 $hash = captcha::hash($pw);
50 $maxsize = (strpos("MSIE", $_SERVER["HTTP_USER_AGENT"]) ? 3000 : 6000);
51 @header("Vary: User-Agent");
52 $img = "data:image/jpeg;base64,"
53 . base64_encode(captcha::image($pw, 200, 60, CAPTCHA_INVERSE, $maxsize));
54 $alt = htmlentities(captcha::textual_riddle($pw));
56 '<table border="0" summary="captcha input"><tr>'
57 . '<td><img name="captcha_image" id="captcha_image" src="'.$img. '" height="60" width="200" alt="'.$alt. '" /></td>'
58 . '<td>'.$title. '<br/><input name="captcha_hash" type="hidden" value="'.$hash. '" />'
59 . '<input name="captcha_input" type="text" size="7" maxlength="16" style="height:46px; font-size:34px; font-weight:450;" />'
60 . '</td><td>'.$more.'</td>'
62 #-- js/html fix if ("MSIE")
64 $base = "http://$_SERVER[SERVER_NAME]:$_SERVER[SERVER_PORT]/"
65 . substr(realpath(__FILE__), strlen($_SERVER["DOCUMENT_ROOT"]));
67 <script language="Javascript"><!--
68 if (/Microsoft/.test(navigator.appName)) {
69 var img = document.captcha_image;
70 img.src = "$base?_ddu=" + img.src.substr(23);
74 $html = "<div class=\"captcha\">$html</div>";
79 /* generates alternative (non-graphic), human-understandable
80 representation of the passphrase
82 function textual_riddle($phrase) {
83 $symbols0 = '"\'-/_:';
84 $symbols1 = array("\n,", "\n;", ";", "\n&", "\n-", ",", ",", "\nand then", "\nfollowed by", "\nand", "\nand not a\n\"".chr(65+rand(0,26))."\",\nbut");
85 $s = "Guess the letters and numbers\n(passphrase riddle)\n--\n";
86 for ($p=0; $p<strlen($phrase); $p++) {
91 $i = $symbols0[rand(0,strlen($symbols0)-1)];
96 $type = ($c >= 'a' ? "small " : "");
99 $c2 = chr((ord($c) & 0x5F) + $n);
101 while (($c2 < 'A') || ($c2 > 'Z'));
104 $add .= "$type'$c2' +$n letters";
107 $add .= "$n chars before $type$c2";
115 do { $x = rand(1, 10); } while (!$x);
118 $add = "($add * $x)"; $n *= $x;
122 $add = "($add / $x)"; $n /= $x;
125 $add = "($add + $x)"; $n += $x;
128 $add = "($add - $x)"; $n -= $x;
135 $s .= $symbols1[rand(0,count($symbols1)-1)] . "\n";
141 /* returns jpeg file stream with unscannable letters encoded
142 in front of colorful disturbing background
144 function image($phrase, $width=200, $height=60, $inverse=0, $maxsize=0xFFFFF) {
146 #-- initialize in-memory image with gd library
147 srand(microtime()*21017);
148 $img = imagecreatetruecolor($width, $height);
149 $R = $inverse ? 0xFF : 0x00;
150 imagefilledrectangle($img, 0,0, $width,$height, captcha::random_color($img, 222^$R, 255^$R));
151 $c1 = rand(150^$R, 185^$R);
152 $c2 = rand(195^$R, 230^$R);
158 $fonts += glob(EWIKI_FONT_DIR."/*.ttf");
163 while ($x < $width) {
164 imagefilledrectangle($img, $x, 0, $x+=$wd, $height, captcha::random_color($img, 222^$R, 255^$R));
165 $wd += max(10, rand(0, 20) - 10);
168 #-- make interesting background I, lines
172 for ($x=0; $x<$width; $x+=(int)$wd) {
173 if ($x < $width) { // verical
174 imageline($img, $x+$w1, 0, $x+$w2, $height-1, captcha::random_color($img,$c1,$c2));
176 if ($x < $height) { // horizontally ("y")
177 imageline($img, 0, $x-$w2, $width-1, $x-$w1, captcha::random_color($img,$c1,$c2));
179 $wd += rand(0,8) - 4;
180 if ($wd < 1) { $wd = 2; }
181 $w1 += rand(0,8) - 4;
182 $w2 += rand(0,8) - 4;
183 if (($x > $height) && ($y > $height)) {
188 #-- more disturbing II, random letters
189 $limit = rand(30,90);
190 for ($n=0; $n<$limit; $n++) {
193 $letter .= chr(rand(31,125)); // random symbol
195 $size = rand(5, $height/2);
196 $half = (int) ($size / 2);
197 $x = rand(-$half, $width+$half);
198 $y = rand(+$half, $height);
199 $rotation = rand(60, 300);
200 $c1 = captcha::random_color($img, 130^$R, 240^$R);
201 $font = $fonts[rand(0, count($fonts)-1)];
202 imagettftext($img, $size, $rotation, $x, $y, $c1, $font, $letter);
205 #-- add the real text to it
206 $len = strlen($phrase);
208 $w2 = $width / ($len+1);
209 for ($p=0; $p<$len; $p++) {
210 $letter = $phrase[$p];
211 $size = rand(18, $height/2.2);
212 $half = (int) $size / 2;
213 $rotation = rand(-33, 33);
214 $y = rand($size+3, $height-3);
216 $w1 += rand(-$width/90, $width/40); // @BUG: last char could be +30 pixel outside of image
217 $font = $fonts[rand(0, count($fonts)-1)];
218 $r=rand(30,99); $g=rand(30,99); $b=rand(30,99); // two colors for shadow
219 $c1 = imagecolorallocate($img, $r*1^$R, $g*1^$R, $b*1^$R);
220 $c2 = imagecolorallocate($img, $r*2^$R, $g*2^$R, $b*2^$R);
221 imagettftext($img, $size, $rotation, $x+1, $y, $c2, $font, $letter);
222 imagettftext($img, $size, $rotation, $x, $y-1, $c1, $font, $letter);
225 #-- let JFIF stream be generated
229 ob_start(); ob_implicit_flush(0);
230 imagejpeg($img, "", (int)$quality);
231 $jpeg = ob_get_contents(); ob_end_clean();
232 $size = strlen($jpeg);
233 $s_debug[] = ((int)($quality*10)/10) . "%=$size";
234 $quality = $quality * ($maxsize/$size) * 0.93 - 1.7; // -($quality/7.222)*
236 while (($size > $maxsize) && ($quality >= 16));
244 function random_color($img, $a,$b) {
245 return imagecolorallocate($img, rand($a,$b), rand($a,$b), rand($a,$b));
249 /* unreversable hash from passphrase, with time() slice encoded */
250 function hash($text, $dtime=0) {
251 $text = strtolower($text);
252 $pfix = (int) (time() / CAPTCHA_TIMEOUT) + $dtime;
253 return md5("captcha::$pfix:$text::".__FILE__.":$_SERVER[SERVER_NAME]:80");
257 /* makes string of random letters for embedding into image and for
258 encoding as hash, later verification
262 for ($n=0; $n<10; $n++) {
263 $s .= chr(rand(0, 255));
265 $s = base64_encode($s); // base64-set, but filter out unwanted chars
266 $s = preg_replace("/[+\/=IG0ODQR]/i", "", $s); // (depends on YOUR font)
267 $s = substr($s, 0, rand(5,7));
274 if (isset($_REQUEST["_ddu"])) {
275 header("Content-Type: image/jpeg");
276 die(base64_decode(substr($_REQUEST["_ddu"], 0)));