|
MAIN
DOCS
DOWNLOAD
|
<?php
/* * CAPicTCHA.php v0.3 * (c) 2004 Wil Langford * Licensed under the Academic Free License version 2.1
* A CAPTCHA generator function. Takes four arguments via GET or POST:
* text = the text you want to output as a CAPTCHA graphic * height = the approximate height in pixels of the output graphic (default=50) * output = png, jpeg or text (for debugging) (default=png) * special = <hex md5 hash> (default=<unset>)
* If special is set and appears to be an MD5 hash, the program attempts to lookup * the related value in a separate file.
************************************************************** * Credits (examples, documentation, etc. that really helped) * **************************************************************
***** Turing String Image Generator (the original foundation code for this program) * http://viebrock.ca/downloads/turing-image.phps * Copyright (c) 2004 Colin Viebrock
***** Jeff Knight from NYPHP's "Introduction to PHP Image Functions" (excellent guide) * http://www.nyphp.org/content/presentations/GDintro/
***** MK12 (sweet design company - graphics, animation, fonts, etc. check them out) * http://www.mk12.com * The studscratch font is theirs, and is used and redistributed with permission. * This permission is conditional and remains in force only as long as the * project remains Open Source.
***** Ray Larabie (professional font designer) * http://www.larabiefonts.com * Crystal Radio Kit is his, and it's the reliable, workhorse font of capictcha. * I like studscratch, but the Crystal Radio Kit font is much more complete, and * is also a lot cleaner looking for cases where a studscratch character renders * particularly unclearly (e.g. lowercase "a").
***** See ABOUT-FONTS for additional font credits.
***** FontForge (for font conversions while I was hunting for the right font) * http://fontforge.sourceforge.net/
* And, of course, PHP and other fine open source projects. */
/************************************************** START CONFIGURATION SECTION aka YE OLDE CONSTANTS **************************************************/
// where to find necessary files define('BASEDIR',dirname($_SERVER['SCRIPT_FILENAME']) . "/"); define('FONTBASEDIR',BASEDIR); // where is the lookup hashfile stored, if we are using one. must be readable by web server, but should // NOT BE SERVED BY THE WEB SERVER. it would kind of defeat the purpose to do so, because anyone would be // able to grab the list of things you are trying to obscure from machines define('HASHFILE',BASEDIR . "../../.capictcha.hash"); // what font file to use - MUST be an absolute path unless the font in a GD library defined font path define('FONT',FONTBASEDIR . "MK12.studscratch.ttf"); // what chars can we use from FONT and still have them be readable? define('FONTCHARS'," ._+!$0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); // if a character is not in FONTCHARS, which font should we use? If FONT is a standard font with all the // punctuation, numbers, etc. in it, then you should be able to set STABLEFONT = FONT. If the FONT you use // doesn't handle all characters you need, use a more standard font here to catch all the oddball chars. define('STABLEFONT',FONTBASEDIR . "LARABIE.crystal-radio-kit.ttf");
// input defaults define('DEFAULT_HEIGHT',100); define('DEFAULT_TYPE',"png"); define('DEFAULT_TEXT',"NOT SO RANDOM");
// ********************** // ye olde various limits // **********************
// what's the longest text string we want to accept? define('MAX_TEXTLEN',100);
// limits on the **approximate** height of the overall graphic displayed // the actual graphic size is calculated after characters are placed define('MIN_GRAPHICHEIGHT',20); define('MAX_GRAPHICHEIGHT',400);
// set range for font size relative to graphic_height minus padding // actual max font size is approximately = $graphic_height * 1/Y_PADDING_FACTOR * MAX_FSIZE define('MAX_FSIZE', 1.0); // actual min font size is approximately = $graphic_height * 1/Y_PADDING_FACTOR * MAX_FSIZE * MIN_FSIZE define('MIN_FSIZE', 0.7);
// background lower and upper limits for each color component (i.e. R, G and B) out of 255 define('MIN_BGCOMPCOL', 60); define('MAX_BGCOMPCOL', 160);
// font lower and upper limits for each color component (i.e. R, G and B) out of 255 define('MIN_FCOMPCOL', 100); define('MAX_FCOMPCOL', 200);
// minimum and maximum rotation limits for each character define('MIN_ROT', -20); define('MAX_ROT', -MIN_ROT);
// number of overstrikes. zero disables. one is about the max you really want to use, but hey, go nuts if you want. define('OVERSTRIKES',1); // if this is greater than zero, then each character is printed twice. the // second time it is printed, it is offset +- MAX_OVERSTRIKE_OFFSET both // horizontally and vertically this quickly makes the text very hard to read, // so low values are preferred. define('MAX_OVERSTRIKE_OFFSET', 3); // +- pixels // same as above, except rotates the overstruck character define('MAX_OVERSTRIKE_ROTATE', 15); // +- degrees // same as above, except resizes the overstruck character define('MAX_OVERSTRIKE_RESIZE', 22); // +- percentage of size
// maximum number of random lines to draw // also limited by the number of characters define('MAX_LINES',10); define('MAX_LINE_THICKNESS', 6);
// pad the whole graphic output define('X_PADDING_FACTOR', 1.05); define('Y_PADDING_FACTOR', 1.4);
// range [0-100] define('JPEG_QUALITY', 90);
// A setting of 1.0 uses the standard ranges of color for foreground and background. // higher values mean higher contrast and more readable text // lower values mean lower contrast and more secure text // note that the difference between 1.0 and 1.2 is very noticeable define('CONTRAST_MULTIPLIER', 1.24);
/************************************************** END CONFIGURATION SECTION **************************************************/
define('TRYDEBUG', FALSE); define('LOG', TRUE); define('LOGFILE',"/tmp/wil-www/capictcha/debug-log");
// ye olde debugging stuff define('LIMITDEBUGBYIP', TRUE); define('DEBUG_IPS', "127.0.0.1");
// decide whether to enter DEBUG mode if(TRYDEBUG and (!LIMITDEBUGBYIP or (preg_match("/" . preg_quote(getenv("REMOTE_ADDR"), "/") . "/", DEBUG_IPS)))) { define('DEBUG',TRUE); } else { define('DEBUG',FALSE); }
// if we're debugging, set some stuff up if(DEBUG) { error_reporting(E_ALL); // error_reporting(E_USER_NOTICE); } else { error_reporting(E_NONE); }
// if we're logging, set some stuff up if(LOG) { ini_set('log_errors',TRUE); ini_set('error_log',LOGFILE); // mylog("Remote IP", getenv("REMOTE_ADDR")); } else { if(file_exists(LOGFILE)) { unlink( LOGFILE ); } }
// are we using character specific tweaks? if so, set up the tweaks data structure // y_pos is a fraction of the char_height to add (positive values mean lower char position) // char_width is a multiplier for the char_width define('TWEAKS',TRUE); if (TWEAKS) { $tweaks = array( // whole tweaks array (by character) 'MK12.studscratch.ttf' => array( 'i' => array( 'char_width'=>0.54), '-' => array( 'y_pos'=>0.5), ), ); }
// scan for inputs foreach (array("text","height","output","special") as $varname) { ${"in_" . $varname}=global_capsule($varname); }
// if we're not debugging, then disallow text output if(!DEBUG) { $in_output = ($in_output === "text") ? "png" : $in_output; } else { // if we are debugging, set up some stuff mylog("DEBUGGING ACTIVE"); // for convenience, this is a hash that gets created, used once and then destroyed. it could have // been more efficient as a switch/case construct, but this looks prettier. // ... plus, special_hash sounds like something nasty your parents made you eat as a child }
// set up specials $special_hash = array( 'punk' => "'" . '"/,.!@#$%^&*()_-=\|+ ', 'hello' => 'Hi there!', 'person' => 'SSGTAC', 'random' => '12345789abdefghijmnqrtuyABCDEFGHIJKLMNOPQRSTUVWXYZ', ); if(isset($special_hash[$in_special])) { $in_text = $special_hash[$in_special]; }
// if the special value is random with a number after it, make a random string the length of the number if(strlen($in_special)>6 and substr($in_special,0,6) === "random") { $len_rand = substr($in_special,6); mylog ("LEN_RAND",$len_rand); $in_text=''; for($i=0; $i<$len_rand; $i++) { $in_text .= $special_hash['random']{mt_rand(0,strlen($special_hash['random'])-1)}; } // $in_text = "random not happy"; }
// if the special value looks like a hex MD5 hash, try looking it up if (strlen(preg_replace("/[^0-9a-fA-F]/","",$in_special)) === 32) { mylog("md5",$in_special); $lookupcmd="grep -i " . $in_special . " " . HASHFILE . " | cut -f 1 -d ':'"; $in_text=exec($lookupcmd); if (strlen($in_text)<2) { $in_text="NOT FOUND"; } }
captcha_anything($in_text,$in_height,$in_output);
function captcha_anything($text = DEFAULT_TEXT,$graphic_height = DEFAULT_HEIGHT,$output = DEFAULT_TYPE) {
// use font specific tweaks? if(TWEAKS) {global $tweaks;} // argument reality checks // truncate text string to MAX_TEXTLEN chars if longer - provide default value for text if (strlen($text) > MAX_TEXTLEN) { $text = substr($text,0,MAX_TEXTLEN); } if (strlen($text) == 0) { $text = "NOT SO RANDOM"; } // graphic height can range from MIN_GRAPHICHEIGHT to MAX_GRAPHICHEIGHT if ((int)$graphic_height < MIN_GRAPHICHEIGHT or (int)$graphic_height > MAX_GRAPHICHEIGHT) { $graphic_height = DEFAULT_HEIGHT; } // output types are png, jpeg, and text if ($output !== "png" and $output !== "jpeg" and $output !== "text") { $output = "png"; }
// init vars $data = array(); $graphic_width = 0;
// set y padding // NOTE: Y padding is mostly allocated on the bottom to allow characters with tails some room (e.g. "gpqyj") define('Y_PADDING',$graphic_height*(Y_PADDING_FACTOR-1)); // this is a fraction of the Y_PADDING to allocate on the bottom define('Y_PADDING_BOTTOM',0.7); // build the data array of the characters, size, placement, etc. for($i=0; $i<strlen($text); $i++) { $char = substr($text, $i, 1); $size = mt_rand(($graphic_height - Y_PADDING) * MIN_FSIZE * MAX_FSIZE, ($graphic_height - Y_PADDING) * MAX_FSIZE); $angle = mt_rand(MIN_ROT, MAX_ROT); $font = (preg_match("/" . preg_quote("$char","/") . "/", FONTCHARS )) ? FONT : STABLEFONT; $fontbase = basename($font); $bbox = imagettfbbox( (float)$size, (float)$angle, $font, $char ); $char_width = max($bbox[2],$bbox[4]) - min($bbox[0],$bbox[6]); $char_height = max($bbox[1],$bbox[3]) - min($bbox[7],$bbox[5]); $pos_x = ($i>0) ? $data[$i-1]['pos_x'] + $data[$i-1]['char_width']:0; $pos_y = $graphic_height - Y_PADDING*Y_PADDING_BOTTOM ; if(TWEAKS) { if(isset($tweaks[$fontbase][$char])){ foreach($tweaks[$fontbase][$char] as $attribute => $adjust) { switch ($attribute) { case 'char_width': $$attribute*=$adjust; break; case 'char_height': $$attribute*=$adjust; break; case 'pos_y': $$attribute+=$char_height*$adjust; break; } } } } $graphic_width += $char_width; $data[$i] = array( 'char' => $char, 'size' => $size, 'angle' => $angle, 'font' => $font, 'fontbase' => $fontbase, 'char_height' => $char_height, 'char_width' => $char_width, 'pos_x' => $pos_x, 'pos_y' => $pos_y, 'color' => 0, // we can't allocate colors yet, because the image doesn't exist yet - 0 is placeholder value ); if(OVERSTRIKES>0) { for($j=0; $j<OVERSTRIKES; $j++) { $data[$i]['OS'][$j] = array ( 'size' => $size*(1+(mt_rand(-MAX_OVERSTRIKE_RESIZE,MAX_OVERSTRIKE_RESIZE)/100)), 'angle' => $angle+mt_rand(-MAX_OVERSTRIKE_ROTATE,MAX_OVERSTRIKE_ROTATE), 'pos_x' => $pos_x+mt_rand(-MAX_OVERSTRIKE_OFFSET,MAX_OVERSTRIKE_OFFSET), 'pos_y' => $pos_y+mt_rand(-MAX_OVERSTRIKE_OFFSET,MAX_OVERSTRIKE_OFFSET), ); } } }
// calculate the final image size, adding some padding define('X_PADDING',$graphic_width*(X_PADDING_FACTOR-1)); $graphic_width += X_PADDING;
// build image $im = imagecreate($graphic_width, $graphic_height);
// allocate the colors $color_bg=imagecolorallocate($im, contrast_color("bg"),contrast_color("bg"),contrast_color("bg")); if(DEBUG) { $color_white=imagecolorallocate($im, 255,255,255); $color_black=imagecolorallocate($im, 0,0,0); } $color_border=imagecolorallocate($im, 0,0,0); for($i=0; $i<strlen($text); $i++) { $data[$i]['color'] = imagecolorallocate($im, contrast_color("fg"), contrast_color("fg"), contrast_color("fg")); $line_color[$i]=imagecolorallocate($im, contrast_color("bg"), contrast_color("bg"), contrast_color("bg")); }
// make the random background lines for($l=0; $l<min(MAX_LINES,strlen($text)-1); $l++) { $thick = mt_rand(1,MAX_LINE_THICKNESS); if($l % 2 == 1) { // alternate between top-to-bottom and side-to-side lines $line_data[$l] = array( // horizontal 0,mt_rand(0,$graphic_height), $graphic_width,mt_rand(0,$graphic_height), $graphic_width,0, 0,0, ); $line_data[$l][5] = $line_data[$l][3] + $thick; $line_data[$l][7] = $line_data[$l][1] + $thick; } else { $line_data[$l] = array( // vertical mt_rand(0,$graphic_width),0, mt_rand(0,$graphic_width),$graphic_height, 0,$graphic_height, 0,0, ); $line_data[$l][4] = $line_data[$l][2] + $thick; $line_data[$l][6] = $line_data[$l][0] + $thick; } imagefilledpolygon($im, $line_data[$l], 4, $line_color[$l]); }
// output each character $l=0; foreach($data as $d) { // calculate absolute character position $d['pos_x'] += X_PADDING/2; // actually place each char into image if(DEBUG) { imagettftext($im, $graphic_height*.1, 0, $d['pos_x'], $graphic_height*.9, $color_black, STABLEFONT, $d['char'] ); } imagettftext($im, $d['size'], $d['angle'], $d['pos_x'], $d['pos_y'], $d['color'], $d['font'], $d['char'] ); // imagefilledpolygon($im, $line_data[$l], 4, $line_color[$l++]); // interleave images and lines - looks bad - disabline for now if(OVERSTRIKES>0) { for($j=0; $j<OVERSTRIKES; $j++) { // calculate absolute character position $d['OS'][$j]['pos_x'] += X_PADDING/2; // actually place each char into image imagettftext($im, $d['OS'][$j]['size'], $d['OS'][$j]['angle'], $d['OS'][$j]['pos_x'], $d['OS'][$j]['pos_y'], $d['color'], $d['font'], $d['char'] ); } } }
// a nice border imagerectangle($im, 0, 0, $graphic_width-1, $graphic_height-1, $color_border);
// display it switch ($output) { case 'text': if(DEBUG) { echo "<pre>\n"; echo "DEBUGGING OUTPUT\n"; print_r($data); print_r($line_data); echo "\n</pre>\n"; } break; case 'jpeg': header('Content-type: image/jpeg'); imagejpeg($im,"",JPEG_QUALITY); break; case 'png': default: header('Content-type: image/png'); imagepng($im); break; } imagedestroy($im); }
function contrast_color($style="none") { $mincol=0; $maxcol=255; switch ($style) { case "bg": $mincol=round(MIN_BGCOMPCOL/CONTRAST_MULTIPLIER); $maxcol=round(MAX_BGCOMPCOL/CONTRAST_MULTIPLIER); break; case "fg": $mincol=round(MIN_FCOMPCOL*CONTRAST_MULTIPLIER); $maxcol=round(MAX_FCOMPCOL*CONTRAST_MULTIPLIER); break; case "bgno": $mincol=MIN_BGCOMPCOL; $maxcol=MAX_BGCOMPCOL; break; case "fgno": $mincol=MIN_FCOMPCOL; $maxcol=MAX_FCOMPCOL; break; } $mincol=max(0,min(255,$mincol)); $maxcol=max(0,min(255,$maxcol)); $out=gen_color($mincol,$maxcol); return $out; }
function gen_color($mincol,$maxcol) { $out=mt_rand($mincol,$maxcol); return $out; }
function global_capsule($globalname) { $filtered_global = "";
if(isset($_POST[$globalname])) { $filtered_global = strip_tags(urldecode($_POST[$globalname])); }
if(isset($_GET[$globalname])) { $filtered_global = strip_tags(urldecode($_GET[$globalname])); }
$filtered_global=substr($filtered_global,0,255);
return $filtered_global; }
function mylog() { $logthis=func_get_args(); if(DEBUG) {trigger_error( (is_array($logthis) ? implode(":", $logthis) : $logthis) , E_USER_NOTICE);} } ?>
|
|