diff options
Diffstat (limited to 'includes/securimage/securimage.php')
| -rw-r--r-- | includes/securimage/securimage.php | 1584 |
1 files changed, 1584 insertions, 0 deletions
| diff --git a/includes/securimage/securimage.php b/includes/securimage/securimage.php new file mode 100644 index 0000000..ebabab0 --- /dev/null +++ b/includes/securimage/securimage.php | |||
| @@ -0,0 +1,1584 @@ | |||
| 1 | <?php | ||
| 2 | |||
| 3 | /** | ||
| 4 | * Project: Securimage: A PHP class for creating and managing form CAPTCHA images<br /> | ||
| 5 | * File: securimage.php<br /> | ||
| 6 | * | ||
| 7 | * This library is free software; you can redistribute it and/or | ||
| 8 | * modify it under the terms of the GNU Lesser General Public | ||
| 9 | * License as published by the Free Software Foundation; either | ||
| 10 | * version 2.1 of the License, or any later version.<br /><br /> | ||
| 11 | * | ||
| 12 | * This library is distributed in the hope that it will be useful, | ||
| 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 15 | * Lesser General Public License for more details.<br /><br /> | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU Lesser General Public | ||
| 18 | * License along with this library; if not, write to the Free Software | ||
| 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA<br /><br /> | ||
| 20 | * | ||
| 21 | * Any modifications to the library should be indicated clearly in the source code | ||
| 22 | * to inform users that the changes are not a part of the original software.<br /><br /> | ||
| 23 | * | ||
| 24 | * If you found this script useful, please take a quick moment to rate it.<br /> | ||
| 25 | * http://www.hotscripts.com/rate/49400.html Thanks. | ||
| 26 | * | ||
| 27 | * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA | ||
| 28 | * @link http://www.phpcaptcha.org/latest.zip Download Latest Version | ||
| 29 | * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation | ||
| 30 | * @copyright 2009 Drew Phillips | ||
| 31 | * @author Drew Phillips <drew@drew-phillips.com> | ||
| 32 | * @version 2.0.1 BETA (December 6th, 2009) | ||
| 33 | * @package Securimage | ||
| 34 | * | ||
| 35 | */ | ||
| 36 | |||
| 37 | /** | ||
| 38 | ChangeLog | ||
| 39 | |||
| 40 | 2.0.1 | ||
| 41 | - Add support for browsers with cookies disabled (requires php5, sqlite) maps users to md5 hashed ip addresses and md5 hashed codes for security | ||
| 42 | - Add fallback to gd fonts if ttf support is not enabled or font file not found (Mike Challis http://www.642weather.com/weather/scripts.php) | ||
| 43 | - Check for previous definition of image type constants (Mike Challis) | ||
| 44 | - Fix mime type settings for audio output | ||
| 45 | - Fixed color allocation issues with multiple colors and background images, consolidate allocation to one function | ||
| 46 | - Ability to let codes expire after a given length of time | ||
| 47 | - Allow HTML color codes to be passed to Securimage_Color (suggested by Mike Challis) | ||
| 48 | |||
| 49 | 2.0.0 | ||
| 50 | - Add mathematical distortion to characters (using code from HKCaptcha) | ||
| 51 | - Improved session support | ||
| 52 | - Added Securimage_Color class for easier color definitions | ||
| 53 | - Add distortion to audio output to prevent binary comparison attack (proposed by Sven "SavageTiger" Hagemann [insecurity.nl]) | ||
| 54 | - Flash button to stream mp3 audio (Douglas Walsh www.douglaswalsh.net) | ||
| 55 | - Audio output is mp3 format by default | ||
| 56 | - Change font to AlteHaasGrotesk by yann le coroller | ||
| 57 | - Some code cleanup | ||
| 58 | |||
| 59 | 1.0.4 (unreleased) | ||
| 60 | - Ability to output audible codes in mp3 format to stream from flash | ||
| 61 | |||
| 62 | 1.0.3.1 | ||
| 63 | - Error reading from wordlist in some cases caused words to be cut off 1 letter short | ||
| 64 | |||
| 65 | 1.0.3 | ||
| 66 | - Removed shadow_text from code which could cause an undefined property error due to removal from previous version | ||
| 67 | |||
| 68 | 1.0.2 | ||
| 69 | - Audible CAPTCHA Code wav files | ||
| 70 | - Create codes from a word list instead of random strings | ||
| 71 | |||
| 72 | 1.0 | ||
| 73 | - Added the ability to use a selected character set, rather than a-z0-9 only. | ||
| 74 | - Added the multi-color text option to use different colors for each letter. | ||
| 75 | - Switched to automatic session handling instead of using files for code storage | ||
| 76 | - Added GD Font support if ttf support is not available. Can use internal GD fonts or load new ones. | ||
| 77 | - Added the ability to set line thickness | ||
| 78 | - Added option for drawing arced lines over letters | ||
| 79 | - Added ability to choose image type for output | ||
| 80 | |||
| 81 | */ | ||
| 82 | |||
| 83 | /** | ||
| 84 | * Output images in JPEG format | ||
| 85 | */ | ||
| 86 | if (!defined('SI_IMAGE_JPEG')) | ||
| 87 | define('SI_IMAGE_JPEG', 1); | ||
| 88 | /** | ||
| 89 | * Output images in PNG format | ||
| 90 | */ | ||
| 91 | if (!defined('SI_IMAGE_PNG')) | ||
| 92 | define('SI_IMAGE_PNG', 2); | ||
| 93 | /** | ||
| 94 | * Output images in GIF format (not recommended) | ||
| 95 | * Must have GD >= 2.0.28! | ||
| 96 | */ | ||
| 97 | if (!defined('SI_IMAGE_GIF')) | ||
| 98 | define('SI_IMAGE_GIF', 3); | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Securimage CAPTCHA Class. | ||
| 102 | * | ||
| 103 | * @package Securimage | ||
| 104 | * @subpackage classes | ||
| 105 | * | ||
| 106 | */ | ||
| 107 | class Securimage { | ||
| 108 | |||
| 109 | /** | ||
| 110 | * The desired width of the CAPTCHA image. | ||
| 111 | * | ||
| 112 | * @var int | ||
| 113 | */ | ||
| 114 | var $image_width; | ||
| 115 | |||
| 116 | /** | ||
| 117 | * The desired width of the CAPTCHA image. | ||
| 118 | * | ||
| 119 | * @var int | ||
| 120 | */ | ||
| 121 | var $image_height; | ||
| 122 | |||
| 123 | /** | ||
| 124 | * The image format for output.<br /> | ||
| 125 | * Valid options: SI_IMAGE_PNG, SI_IMAGE_JPG, SI_IMAGE_GIF | ||
| 126 | * | ||
| 127 | * @var int | ||
| 128 | */ | ||
| 129 | var $image_type; | ||
| 130 | |||
| 131 | /** | ||
| 132 | * The length of the code to generate. | ||
| 133 | * | ||
| 134 | * @var int | ||
| 135 | */ | ||
| 136 | var $code_length; | ||
| 137 | |||
| 138 | /** | ||
| 139 | * The character set for individual characters in the image.<br /> | ||
| 140 | * Letters are converted to uppercase.<br /> | ||
| 141 | * The font must support the letters or there may be problematic substitutions. | ||
| 142 | * | ||
| 143 | * @var string | ||
| 144 | */ | ||
| 145 | var $charset; | ||
| 146 | |||
| 147 | /** | ||
| 148 | * Create codes using this word list | ||
| 149 | * | ||
| 150 | * @var string The path to the word list to use for creating CAPTCHA codes | ||
| 151 | */ | ||
| 152 | var $wordlist_file; | ||
| 153 | |||
| 154 | /** | ||
| 155 | * Use wordlist of not | ||
| 156 | * | ||
| 157 | * @var bool true to use wordlist file, false to use random code | ||
| 158 | */ | ||
| 159 | var $use_wordlist = false; | ||
| 160 | |||
| 161 | /** | ||
| 162 | * Note: Use of GD fonts is not recommended as many distortion features are not available<br /> | ||
| 163 | * The GD font to use.<br /> | ||
| 164 | * Internal gd fonts can be loaded by their number.<br /> | ||
| 165 | * Alternatively, a file path can be given and the font will be loaded from file. | ||
| 166 | * | ||
| 167 | * @var mixed | ||
| 168 | */ | ||
| 169 | var $gd_font_file; | ||
| 170 | |||
| 171 | /** | ||
| 172 | * The approximate size of the font in pixels.<br /> | ||
| 173 | * This does not control the size of the font because that is determined by the GD font itself.<br /> | ||
| 174 | * This is used to aid the calculations of positioning used by this class.<br /> | ||
| 175 | * | ||
| 176 | * @var int | ||
| 177 | */ | ||
| 178 | var $gd_font_size; | ||
| 179 | |||
| 180 | /** | ||
| 181 | * Use a gd font instead of TTF | ||
| 182 | * | ||
| 183 | * @var bool true for gd font, false for TTF | ||
| 184 | */ | ||
| 185 | var $use_gd_font; | ||
| 186 | |||
| 187 | // Note: These font options below do not apply if you set $use_gd_font to true with the exception of $text_color | ||
| 188 | |||
| 189 | /** | ||
| 190 | * The path to the TTF font file to load. | ||
| 191 | * | ||
| 192 | * @var string | ||
| 193 | */ | ||
| 194 | var $ttf_file; | ||
| 195 | |||
| 196 | /** | ||
| 197 | * How much to distort image, higher = more distortion.<br /> | ||
| 198 | * Distortion is only available when using TTF fonts.<br /> | ||
| 199 | * | ||
| 200 | * @var float | ||
| 201 | */ | ||
| 202 | var $perturbation; | ||
| 203 | |||
| 204 | /** | ||
| 205 | * The minimum angle in degrees, with 0 degrees being left-to-right reading text.<br /> | ||
| 206 | * Higher values represent a counter-clockwise rotation.<br /> | ||
| 207 | * For example, a value of 90 would result in bottom-to-top reading text.<br /> | ||
| 208 | * This value along with maximum angle distance do not need to be very high with perturbation | ||
| 209 | * | ||
| 210 | * @var int | ||
| 211 | */ | ||
| 212 | var $text_angle_minimum; | ||
| 213 | |||
| 214 | /** | ||
| 215 | * The minimum angle in degrees, with 0 degrees being left-to-right reading text.<br /> | ||
| 216 | * Higher values represent a counter-clockwise rotation.<br /> | ||
| 217 | * For example, a value of 90 would result in bottom-to-top reading text. | ||
| 218 | * | ||
| 219 | * @var int | ||
| 220 | */ | ||
| 221 | var $text_angle_maximum; | ||
| 222 | |||
| 223 | /** | ||
| 224 | * The X-Position on the image where letter drawing will begin.<br /> | ||
| 225 | * This value is in pixels from the left side of the image. | ||
| 226 | * | ||
| 227 | * @var int | ||
| 228 | * @deprecated 2.0 | ||
| 229 | */ | ||
| 230 | var $text_x_start; | ||
| 231 | |||
| 232 | /** | ||
| 233 | * The background color for the image as a Securimage_Color.<br /> | ||
| 234 | * | ||
| 235 | * @var Securimage_Color | ||
| 236 | */ | ||
| 237 | var $image_bg_color; | ||
| 238 | |||
| 239 | /** | ||
| 240 | * Scan this directory for gif, jpg, and png files to use as background images.<br /> | ||
| 241 | * A random image file will be picked each time.<br /> | ||
| 242 | * Change from null to the full path to your directory.<br /> | ||
| 243 | * i.e. var $background_directory = $_SERVER['DOCUMENT_ROOT'] . '/securimage/backgrounds'; | ||
| 244 | * Make sure not to pass a background image to the show function, otherwise this directive is ignored. | ||
| 245 | * | ||
| 246 | * @var string | ||
| 247 | */ | ||
| 248 | var $background_directory = null; //'./backgrounds'; | ||
| 249 | |||
| 250 | /** | ||
| 251 | * The text color to use for drawing characters as a Securimage_Color.<br /> | ||
| 252 | * This value is ignored if $use_multi_text is set to true.<br /> | ||
| 253 | * Make sure this contrasts well with the background color or image.<br /> | ||
| 254 | * | ||
| 255 | * @see Securimage::$use_multi_text | ||
| 256 | * @var Securimage_Color | ||
| 257 | */ | ||
| 258 | var $text_color; | ||
| 259 | |||
| 260 | /** | ||
| 261 | * Set to true to use multiple colors for each character. | ||
| 262 | * | ||
| 263 | * @see Securimage::$multi_text_color | ||
| 264 | * @var boolean | ||
| 265 | */ | ||
| 266 | var $use_multi_text; | ||
| 267 | |||
| 268 | /** | ||
| 269 | * Array of Securimage_Colors which will be randomly selected for each letter.<br /> | ||
| 270 | * | ||
| 271 | * @var array | ||
| 272 | */ | ||
| 273 | var $multi_text_color; | ||
| 274 | |||
| 275 | /** | ||
| 276 | * Set to true to make the characters appear transparent. | ||
| 277 | * | ||
| 278 | * @see Securimage::$text_transparency_percentage | ||
| 279 | * @var boolean | ||
| 280 | */ | ||
| 281 | var $use_transparent_text; | ||
| 282 | |||
| 283 | /** | ||
| 284 | * The percentage of transparency, 0 to 100.<br /> | ||
| 285 | * A value of 0 is completely opaque, 100 is completely transparent (invisble) | ||
| 286 | * | ||
| 287 | * @see Securimage::$use_transparent_text | ||
| 288 | * @var int | ||
| 289 | */ | ||
| 290 | var $text_transparency_percentage; | ||
| 291 | |||
| 292 | |||
| 293 | // Line options | ||
| 294 | /** | ||
| 295 | * Draw vertical and horizontal lines on the image. | ||
| 296 | * | ||
| 297 | * @see Securimage::$line_color | ||
| 298 | * @see Securimage::$draw_lines_over_text | ||
| 299 | * @var boolean | ||
| 300 | */ | ||
| 301 | var $num_lines; | ||
| 302 | |||
| 303 | /** | ||
| 304 | * Color of lines drawn over text | ||
| 305 | * | ||
| 306 | * @var string | ||
| 307 | */ | ||
| 308 | var $line_color; | ||
| 309 | |||
| 310 | /** | ||
| 311 | * Draw the lines over the text.<br /> | ||
| 312 | * If fales lines will be drawn before putting the text on the image. | ||
| 313 | * | ||
| 314 | * @var boolean | ||
| 315 | */ | ||
| 316 | var $draw_lines_over_text; | ||
| 317 | |||
| 318 | /** | ||
| 319 | * Text to write at the bottom corner of captcha image | ||
| 320 | * | ||
| 321 | * @since 2.0 | ||
| 322 | * @var string Signature text | ||
| 323 | */ | ||
| 324 | var $image_signature; | ||
| 325 | |||
| 326 | /** | ||
| 327 | * Color to use for writing signature text | ||
| 328 | * | ||
| 329 | * @since 2.0 | ||
| 330 | * @var Securimage_Color | ||
| 331 | */ | ||
| 332 | var $signature_color; | ||
| 333 | |||
| 334 | /** | ||
| 335 | * Full path to the WAV files to use to make the audio files, include trailing /.<br /> | ||
| 336 | * Name Files [A-Z0-9].wav | ||
| 337 | * | ||
| 338 | * @since 1.0.1 | ||
| 339 | * @var string | ||
| 340 | */ | ||
| 341 | var $audio_path; | ||
| 342 | |||
| 343 | /** | ||
| 344 | * Type of audio file to generate (mp3 or wav) | ||
| 345 | * | ||
| 346 | * @var string | ||
| 347 | */ | ||
| 348 | var $audio_format; | ||
| 349 | |||
| 350 | /** | ||
| 351 | * The session name to use if not the default. Blank for none | ||
| 352 | * | ||
| 353 | * @see http://php.net/session_name | ||
| 354 | * @since 2.0 | ||
| 355 | * @var string | ||
| 356 | */ | ||
| 357 | var $session_name = ''; | ||
| 358 | |||
| 359 | /** | ||
| 360 | * The amount of time in seconds that a code remains valid.<br /> | ||
| 361 | * Any code older than this number will be considered invalid even if entered correctly.<br /> | ||
| 362 | * Any non-numeric or value less than 1 disables this functionality. | ||
| 363 | * | ||
| 364 | * @var int | ||
| 365 | */ | ||
| 366 | var $expiry_time; | ||
| 367 | |||
| 368 | /** | ||
| 369 | * Path to the file to use for storing codes for users.<br /> | ||
| 370 | * THIS FILE MUST ABSOLUTELY NOT BE ACCESSIBLE FROM A WEB BROWSER!!<br /> | ||
| 371 | * Put this file in a directory below the web root or one that is restricted (i.e. an apache .htaccess file with deny from all)<br /> | ||
| 372 | * If you cannot meet those requirements your forms may not be completely protected.<br /> | ||
| 373 | * You could obscure the database file name but this is also not recommended. | ||
| 374 | * | ||
| 375 | * @var string | ||
| 376 | */ | ||
| 377 | var $sqlite_database; | ||
| 378 | |||
| 379 | /** | ||
| 380 | * Use an SQLite database for storing codes as a backup to sessions.<br /> | ||
| 381 | * Note: Sessions will still be used | ||
| 382 | */ | ||
| 383 | var $use_sqlite_db; | ||
| 384 | |||
| 385 | |||
| 386 | //END USER CONFIGURATION | ||
| 387 | //There should be no need to edit below unless you really know what you are doing. | ||
| 388 | |||
| 389 | /** | ||
| 390 | * The gd image resource. | ||
| 391 | * | ||
| 392 | * @access private | ||
| 393 | * @var resource | ||
| 394 | */ | ||
| 395 | var $im; | ||
| 396 | |||
| 397 | /** | ||
| 398 | * Temporary image for rendering | ||
| 399 | * | ||
| 400 | * @access private | ||
| 401 | * @var resource | ||
| 402 | */ | ||
| 403 | var $tmpimg; | ||
| 404 | |||
| 405 | /** | ||
| 406 | * Internal scale factor for anti-alias @hkcaptcha | ||
| 407 | * | ||
| 408 | * @access private | ||
| 409 | * @since 2.0 | ||
| 410 | * @var int | ||
| 411 | */ | ||
| 412 | var $iscale; // internal scale factor for anti-alias @hkcaptcha | ||
| 413 | |||
| 414 | /** | ||
| 415 | * The background image resource | ||
| 416 | * | ||
| 417 | * @access private | ||
| 418 | * @var resource | ||
| 419 | */ | ||
| 420 | var $bgimg; | ||
| 421 | |||
| 422 | /** | ||
| 423 | * The code generated by the script | ||
| 424 | * | ||
| 425 | * @access private | ||
| 426 | * @var string | ||
| 427 | */ | ||
| 428 | var $code; | ||
| 429 | |||
| 430 | /** | ||
| 431 | * The code that was entered by the user | ||
| 432 | * | ||
| 433 | * @access private | ||
| 434 | * @var string | ||
| 435 | */ | ||
| 436 | var $code_entered; | ||
| 437 | |||
| 438 | /** | ||
| 439 | * Whether or not the correct code was entered | ||
| 440 | * | ||
| 441 | * @access private | ||
| 442 | * @var boolean | ||
| 443 | */ | ||
| 444 | var $correct_code; | ||
| 445 | |||
| 446 | /** | ||
| 447 | * Handle to SQLite database | ||
| 448 | * | ||
| 449 | * @access private | ||
| 450 | * @var resource | ||
| 451 | */ | ||
| 452 | var $sqlite_handle; | ||
| 453 | |||
| 454 | /** | ||
| 455 | * Color resource for image line color | ||
| 456 | * | ||
| 457 | * @access private | ||
| 458 | * @var int | ||
| 459 | */ | ||
| 460 | var $gdlinecolor; | ||
| 461 | |||
| 462 | /** | ||
| 463 | * Array of colors for multi colored codes | ||
| 464 | * | ||
| 465 | * @access private | ||
| 466 | * @var array | ||
| 467 | */ | ||
| 468 | var $gdmulticolor; | ||
| 469 | |||
| 470 | /** | ||
| 471 | * Color resource for image font color | ||
| 472 | * | ||
| 473 | * @access private | ||
| 474 | * @var int | ||
| 475 | */ | ||
| 476 | var $gdtextcolor; | ||
| 477 | |||
| 478 | /** | ||
| 479 | * Color resource for image signature color | ||
| 480 | * | ||
| 481 | * @access private | ||
| 482 | * @var int | ||
| 483 | */ | ||
| 484 | var $gdsignaturecolor; | ||
| 485 | |||
| 486 | /** | ||
| 487 | * Color resource for image background color | ||
| 488 | * | ||
| 489 | * @access private | ||
| 490 | * @var int | ||
| 491 | */ | ||
| 492 | var $gdbgcolor; | ||
| 493 | |||
| 494 | |||
| 495 | /** | ||
| 496 | * Class constructor.<br /> | ||
| 497 | * Because the class uses sessions, this will attempt to start a session if there is no previous one.<br /> | ||
| 498 | * If you do not start a session before calling the class, the constructor must be called before any | ||
| 499 | * output is sent to the browser. | ||
| 500 | * | ||
| 501 | * <code> | ||
| 502 | * $securimage = new Securimage(); | ||
| 503 | * </code> | ||
| 504 | * | ||
| 505 | */ | ||
| 506 | function Securimage() | ||
| 507 | { | ||
| 508 | // Initialize session or attach to existing | ||
| 509 | if ( session_id() == '' ) { // no session has been started yet, which is needed for validation | ||
| 510 | if (trim($this->session_name) != '') { | ||
| 511 | session_name($this->session_name); // set session name if provided | ||
| 512 | } | ||
| 513 | session_start(); | ||
| 514 | } | ||
| 515 | |||
| 516 | // Set Default Values | ||
| 517 | $this->image_width = 230; | ||
| 518 | $this->image_height = 80; | ||
| 519 | $this->image_type = SI_IMAGE_PNG; | ||
| 520 | |||
| 521 | $this->code_length = 6; | ||
| 522 | $this->charset = 'ABCDEFGHKLMNPRSTUVWYZabcdefghklmnprstuvwyz23456789'; | ||
| 523 | $this->wordlist_file = './words/words.txt'; | ||
| 524 | $this->use_wordlist = false; | ||
| 525 | |||
| 526 | $this->gd_font_file = 'gdfonts/automatic.gdf'; | ||
| 527 | $this->use_gd_font = false; | ||
| 528 | $this->gd_font_size = 24; | ||
| 529 | $this->text_x_start = 15; | ||
| 530 | |||
| 531 | $this->ttf_file = './AHGBold.ttf'; | ||
| 532 | |||
| 533 | $this->perturbation = 0.75; | ||
| 534 | $this->iscale = 5; | ||
| 535 | $this->text_angle_minimum = 0; | ||
| 536 | $this->text_angle_maximum = 0; | ||
| 537 | |||
| 538 | $this->image_bg_color = new Securimage_Color(0xff, 0xff, 0xff); | ||
| 539 | $this->text_color = new Securimage_Color(0x3d, 0x3d, 0x3d); | ||
| 540 | $this->multi_text_color = array(new Securimage_Color(0x0, 0x20, 0xCC), | ||
| 541 | new Securimage_Color(0x0, 0x30, 0xEE), | ||
| 542 | new Securimage_color(0x0, 0x40, 0xCC), | ||
| 543 | new Securimage_Color(0x0, 0x50, 0xEE), | ||
| 544 | new Securimage_Color(0x0, 0x60, 0xCC)); | ||
| 545 | $this->use_multi_text = false; | ||
| 546 | |||
| 547 | $this->use_transparent_text = false; | ||
| 548 | $this->text_transparency_percentage = 30; | ||
| 549 | |||
| 550 | $this->num_lines = 10; | ||
| 551 | $this->line_color = new Securimage_Color(0x3d, 0x3d, 0x3d); | ||
| 552 | $this->draw_lines_over_text = true; | ||
| 553 | |||
| 554 | $this->image_signature = ''; | ||
| 555 | $this->signature_color = new Securimage_Color(0x20, 0x50, 0xCC); | ||
| 556 | $this->signature_font = './AHGBold.ttf'; | ||
| 557 | |||
| 558 | $this->audio_path = './audio/'; | ||
| 559 | $this->audio_format = 'mp3'; | ||
| 560 | $this->session_name = ''; | ||
| 561 | $this->expiry_time = 900; | ||
| 562 | |||
| 563 | $this->sqlite_database = 'database/securimage.sqlite'; | ||
| 564 | $this->use_sqlite_db = false; | ||
| 565 | |||
| 566 | $this->sqlite_handle = false; | ||
| 567 | } | ||
| 568 | |||
| 569 | /** | ||
| 570 | * Generate a code and output the image to the browser. | ||
| 571 | * | ||
| 572 | * <code> | ||
| 573 | * <?php | ||
| 574 | * include 'securimage.php'; | ||
| 575 | * $securimage = new Securimage(); | ||
| 576 | * $securimage->show('bg.jpg'); | ||
| 577 | * ?> | ||
| 578 | * </code> | ||
| 579 | * | ||
| 580 | * @param string $background_image The path to an image to use as the background for the CAPTCHA | ||
| 581 | */ | ||
| 582 | function show($background_image = "") | ||
| 583 | { | ||
| 584 | if($background_image != "" && is_readable($background_image)) { | ||
| 585 | $this->bgimg = $background_image; | ||
| 586 | } | ||
| 587 | |||
| 588 | $this->doImage(); | ||
| 589 | } | ||
| 590 | |||
| 591 | /** | ||
| 592 | * Validate the code entered by the user. | ||
| 593 | * | ||
| 594 | * <code> | ||
| 595 | * $code = $_POST['code']; | ||
| 596 | * if ($securimage->check($code) == false) { | ||
| 597 | * die("Sorry, the code entered did not match."); | ||
| 598 | * } else { | ||
| 599 | * $valid = true; | ||
| 600 | * } | ||
| 601 | * </code> | ||
| 602 | * @param string $code The code the user entered | ||
| 603 | * @return boolean true if the code was correct, false if not | ||
| 604 | */ | ||
| 605 | function check($code) | ||
| 606 | { | ||
| 607 | $this->code_entered = $code; | ||
| 608 | $this->validate(); | ||
| 609 | return $this->correct_code; | ||
| 610 | } | ||
| 611 | |||
| 612 | /** | ||
| 613 | * Output audio file with HTTP headers to browser | ||
| 614 | * | ||
| 615 | * <code> | ||
| 616 | * $sound = new Securimage(); | ||
| 617 | * $sound->audio_format = 'mp3'; | ||
| 618 | * $sound->outputAudioFile(); | ||
| 619 | * </code> | ||
| 620 | * | ||
| 621 | * @since 2.0 | ||
| 622 | */ | ||
| 623 | function outputAudioFile() | ||
| 624 | { | ||
| 625 | if (strtolower($this->audio_format) == 'wav') { | ||
| 626 | header('Content-type: audio/x-wav'); | ||
| 627 | $ext = 'wav'; | ||
| 628 | } else { | ||
| 629 | header('Content-type: audio/mpeg'); // default to mp3 | ||
| 630 | $ext = 'mp3'; | ||
| 631 | } | ||
| 632 | |||
| 633 | header("Content-Disposition: attachment; filename=\"securimage_audio.{$ext}\""); | ||
| 634 | header('Cache-Control: no-store, no-cache, must-revalidate'); | ||
| 635 | header('Expires: Sun, 1 Jan 2000 12:00:00 GMT'); | ||
| 636 | header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT'); | ||
| 637 | |||
| 638 | $audio = $this->getAudibleCode($ext); | ||
| 639 | |||
| 640 | header('Content-Length: ' . strlen($audio)); | ||
| 641 | |||
| 642 | echo $audio; | ||
| 643 | exit; | ||
| 644 | } | ||
| 645 | |||
| 646 | /** | ||
| 647 | * Generate and output the image | ||
| 648 | * | ||
| 649 | * @access private | ||
| 650 | * | ||
| 651 | */ | ||
| 652 | function doImage() | ||
| 653 | { | ||
| 654 | if ($this->use_gd_font == true) { | ||
| 655 | $this->iscale = 1; | ||
| 656 | } | ||
| 657 | if($this->use_transparent_text == true || $this->bgimg != "") { | ||
| 658 | $this->im = imagecreatetruecolor($this->image_width, $this->image_height); | ||
| 659 | $this->tmpimg = imagecreatetruecolor($this->image_width * $this->iscale, $this->image_height * $this->iscale); | ||
| 660 | |||
| 661 | } else { //no transparency | ||
| 662 | $this->im = imagecreate($this->image_width, $this->image_height); | ||
| 663 | $this->tmpimg = imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale); | ||
| 664 | } | ||
| 665 | |||
| 666 | $this->allocateColors(); | ||
| 667 | imagepalettecopy($this->tmpimg, $this->im); | ||
| 668 | |||
| 669 | $this->setBackground(); | ||
| 670 | |||
| 671 | $this->createCode(); | ||
| 672 | |||
| 673 | if (!$this->draw_lines_over_text && $this->num_lines > 0) $this->drawLines(); | ||
| 674 | |||
| 675 | $this->drawWord(); | ||
| 676 | if ($this->use_gd_font == false && is_readable($this->ttf_file)) $this->distortedCopy(); | ||
| 677 | |||
| 678 | if ($this->draw_lines_over_text && $this->num_lines > 0) $this->drawLines(); | ||
| 679 | |||
| 680 | if (trim($this->image_signature) != '') $this->addSignature(); | ||
| 681 | |||
| 682 | $this->output(); | ||
| 683 | |||
| 684 | } | ||
| 685 | |||
| 686 | /** | ||
| 687 | * Allocate all colors that will be used in the CAPTCHA image | ||
| 688 | * | ||
| 689 | * @since 2.0.1 | ||
| 690 | * @access private | ||
| 691 | */ | ||
| 692 | function allocateColors() | ||
| 693 | { | ||
| 694 | // allocate bg color first for imagecreate | ||
| 695 | $this->gdbgcolor = imagecolorallocate($this->im, $this->image_bg_color->r, $this->image_bg_color->g, $this->image_bg_color->b); | ||
| 696 | |||
| 697 | $alpha = intval($this->text_transparency_percentage / 100 * 127); | ||
| 698 | |||
| 699 | if ($this->use_transparent_text == true) { | ||
| 700 | $this->gdtextcolor = imagecolorallocatealpha($this->im, $this->text_color->r, $this->text_color->g, $this->text_color->b, $alpha); | ||
| 701 | $this->gdlinecolor = imagecolorallocatealpha($this->im, $this->line_color->r, $this->line_color->g, $this->line_color->b, $alpha); | ||
| 702 | } else { | ||
| 703 | $this->gdtextcolor = imagecolorallocate($this->im, $this->text_color->r, $this->text_color->g, $this->text_color->b); | ||
| 704 | $this->gdlinecolor = imagecolorallocate($this->im, $this->line_color->r, $this->line_color->g, $this->line_color->b); | ||
| 705 | } | ||
| 706 | |||
| 707 | $this->gdsignaturecolor = imagecolorallocate($this->im, $this->signature_color->r, $this->signature_color->g, $this->signature_color->b); | ||
| 708 | |||
| 709 | if ($this->use_multi_text == true) { | ||
| 710 | $this->gdmulticolor = array(); | ||
| 711 | |||
| 712 | foreach($this->multi_text_color as $color) { | ||
| 713 | if ($this->use_transparent_text == true) { | ||
| 714 | $this->gdmulticolor[] = imagecolorallocatealpha($this->im, $color->r, $color->g, $color->b, $alpha); | ||
| 715 | } else { | ||
| 716 | $this->gdmulticolor[] = imagecolorallocate($this->im, $color->r, $color->g, $color->b); | ||
| 717 | } | ||
| 718 | } | ||
| 719 | } | ||
| 720 | } | ||
| 721 | |||
| 722 | /** | ||
| 723 | * Set the background of the CAPTCHA image | ||
| 724 | * | ||
| 725 | * @access private | ||
| 726 | * | ||
| 727 | */ | ||
| 728 | function setBackground() | ||
| 729 | { | ||
| 730 | imagefilledrectangle($this->im, 0, 0, $this->image_width * $this->iscale, $this->image_height * $this->iscale, $this->gdbgcolor); | ||
| 731 | imagefilledrectangle($this->tmpimg, 0, 0, $this->image_width * $this->iscale, $this->image_height * $this->iscale, $this->gdbgcolor); | ||
| 732 | |||
| 733 | if ($this->bgimg == '') { | ||
| 734 | if ($this->background_directory != null && is_dir($this->background_directory) && is_readable($this->background_directory)) { | ||
| 735 | $img = $this->getBackgroundFromDirectory(); | ||
| 736 | if ($img != false) { | ||
| 737 | $this->bgimg = $img; | ||
| 738 | } | ||
| 739 | } | ||
| 740 | } | ||
| 741 | |||
| 742 | $dat = @getimagesize($this->bgimg); | ||
| 743 | if($dat == false) { | ||
| 744 | return; | ||
| 745 | } | ||
| 746 | |||
| 747 | switch($dat[2]) { | ||
| 748 | case 1: $newim = @imagecreatefromgif($this->bgimg); break; | ||
| 749 | case 2: $newim = @imagecreatefromjpeg($this->bgimg); break; | ||
| 750 | case 3: $newim = @imagecreatefrompng($this->bgimg); break; | ||
| 751 | case 15: $newim = @imagecreatefromwbmp($this->bgimg); break; | ||
| 752 | case 16: $newim = @imagecreatefromxbm($this->bgimg); break; | ||
| 753 | default: return; | ||
| 754 | } | ||
| 755 | |||
| 756 | if(!$newim) return; | ||
| 757 | |||
| 758 | imagecopyresized($this->im, $newim, 0, 0, 0, 0, $this->image_width, $this->image_height, imagesx($newim), imagesy($newim)); | ||
| 759 | } | ||
| 760 | |||
| 761 | /** | ||
| 762 | * Return the full path to a random gif, jpg, or png from the background directory. | ||
| 763 | * | ||
| 764 | * @access private | ||
| 765 | * @see Securimage::$background_directory | ||
| 766 | * @return mixed false if none found, string $path if found | ||
| 767 | */ | ||
| 768 | function getBackgroundFromDirectory() | ||
| 769 | { | ||
| 770 | $images = array(); | ||
| 771 | |||
| 772 | if ($dh = opendir($this->background_directory)) { | ||
| 773 | while (($file = readdir($dh)) !== false) { | ||
| 774 | if (preg_match('/(jpg|gif|png)$/i', $file)) $images[] = $file; | ||
| 775 | } | ||
| 776 | |||
| 777 | closedir($dh); | ||
| 778 | |||
| 779 | if (sizeof($images) > 0) { | ||
| 780 | return rtrim($this->background_directory, '/') . '/' . $images[rand(0, sizeof($images)-1)]; | ||
| 781 | } | ||
| 782 | } | ||
| 783 | |||
| 784 | return false; | ||
| 785 | } | ||
| 786 | |||
| 787 | /** | ||
| 788 | * Draw random curvy lines over the image<br /> | ||
| 789 | * Modified code from HKCaptcha | ||
| 790 | * | ||
| 791 | * @since 2.0 | ||
| 792 | * @access private | ||
| 793 | * | ||
| 794 | */ | ||
| 795 | function drawLines() | ||
| 796 | { | ||
| 797 | for ($line = 0; $line < $this->num_lines; ++$line) { | ||
| 798 | $x = $this->image_width * (1 + $line) / ($this->num_lines + 1); | ||
| 799 | $x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines; | ||
| 800 | $y = rand($this->image_height * 0.1, $this->image_height * 0.9); | ||
| 801 | |||
| 802 | $theta = ($this->frand()-0.5) * M_PI * 0.7; | ||
| 803 | $w = $this->image_width; | ||
| 804 | $len = rand($w * 0.4, $w * 0.7); | ||
| 805 | $lwid = rand(0, 2); | ||
| 806 | |||
| 807 | $k = $this->frand() * 0.6 + 0.2; | ||
| 808 | $k = $k * $k * 0.5; | ||
| 809 | $phi = $this->frand() * 6.28; | ||
| 810 | $step = 0.5; | ||
| 811 | $dx = $step * cos($theta); | ||
| 812 | $dy = $step * sin($theta); | ||
| 813 | $n = $len / $step; | ||
| 814 | $amp = 1.5 * $this->frand() / ($k + 5.0 / $len); | ||
| 815 | $x0 = $x - 0.5 * $len * cos($theta); | ||
| 816 | $y0 = $y - 0.5 * $len * sin($theta); | ||
| 817 | |||
| 818 | $ldx = round(-$dy * $lwid); | ||
| 819 | $ldy = round($dx * $lwid); | ||
| 820 | |||
| 821 | for ($i = 0; $i < $n; ++$i) { | ||
| 822 | $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi); | ||
| 823 | $y = $y0 + $i * $dy - $amp * $dx * sin($k * $i * $step + $phi); | ||
| 824 | imagefilledrectangle($this->im, $x, $y, $x + $lwid, $y + $lwid, $this->gdlinecolor); | ||
| 825 | } | ||
| 826 | } | ||
| 827 | } | ||
| 828 | |||
| 829 | /** | ||
| 830 | * Draw the CAPTCHA code over the image | ||
| 831 | * | ||
| 832 | * @access private | ||
| 833 | * | ||
| 834 | */ | ||
| 835 | function drawWord() | ||
| 836 | { | ||
| 837 | $width2 = $this->image_width * $this->iscale; | ||
| 838 | $height2 = $this->image_height * $this->iscale; | ||
| 839 | |||
| 840 | if ($this->use_gd_font == true || !is_readable($this->ttf_file)) { | ||
| 841 | if (!is_int($this->gd_font_file)) { //is a file name | ||
| 842 | $font = @imageloadfont($this->gd_font_file); | ||
| 843 | if ($font == false) { | ||
| 844 | trigger_error("Failed to load GD Font file {$this->gd_font_file} ", E_USER_WARNING); | ||
| 845 | return; | ||
| 846 | } | ||
| 847 | } else { //gd font identifier | ||
| 848 | $font = $this->gd_font_file; | ||
| 849 | } | ||
| 850 | |||
| 851 | imagestring($this->im, $font, $this->text_x_start, ($this->image_height / 2) - ($this->gd_font_size / 2), $this->code, $this->gdtextcolor); | ||
| 852 | } else { //ttf font | ||
| 853 | $font_size = $height2 * .35; | ||
| 854 | $bb = imagettfbbox($font_size, 0, $this->ttf_file, $this->code); | ||
| 855 | $tx = $bb[4] - $bb[0]; | ||
| 856 | $ty = $bb[5] - $bb[1]; | ||
| 857 | $x = floor($width2 / 2 - $tx / 2 - $bb[0]); | ||
| 858 | $y = round($height2 / 2 - $ty / 2 - $bb[1]); | ||
| 859 | |||
| 860 | $strlen = strlen($this->code); | ||
| 861 | if (!is_array($this->multi_text_color)) $this->use_multi_text = false; | ||
| 862 | |||
| 863 | |||
| 864 | if ($this->use_multi_text == false && $this->text_angle_minimum == 0 && $this->text_angle_maximum == 0) { // no angled or multi-color characters | ||
| 865 | imagettftext($this->tmpimg, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code); | ||
| 866 | } else { | ||
| 867 | for($i = 0; $i < $strlen; ++$i) { | ||
| 868 | $angle = rand($this->text_angle_minimum, $this->text_angle_maximum); | ||
| 869 | $y = rand($y - 5, $y + 5); | ||
| 870 | if ($this->use_multi_text == true) { | ||
| 871 | $font_color = $this->gdmulticolor[rand(0, sizeof($this->gdmulticolor) - 1)]; | ||
| 872 | } else { | ||
| 873 | $font_color = $this->gdtextcolor; | ||
| 874 | } | ||
| 875 | |||
| 876 | $ch = $this->code{$i}; | ||
| 877 | |||
| 878 | imagettftext($this->tmpimg, $font_size, $angle, $x, $y, $font_color, $this->ttf_file, $ch); | ||
| 879 | |||
| 880 | // estimate character widths to increment $x without creating spaces that are too large or too small | ||
| 881 | // these are best estimates to align text but may vary between fonts | ||
| 882 | // for optimal character widths, do not use multiple text colors or character angles and the complete string will be written by imagettftext | ||
| 883 | if (strpos('abcdeghknopqsuvxyz', $ch) !== false) { | ||
| 884 | $min_x = $font_size - ($this->iscale * 6); | ||
| 885 | $max_x = $font_size - ($this->iscale * 6); | ||
| 886 | } else if (strpos('ilI1', $ch) !== false) { | ||
| 887 | $min_x = $font_size / 5; | ||
| 888 | $max_x = $font_size / 3; | ||
| 889 | } else if (strpos('fjrt', $ch) !== false) { | ||
| 890 | $min_x = $font_size - ($this->iscale * 12); | ||
| 891 | $max_x = $font_size - ($this->iscale * 12); | ||
| 892 | } else if ($ch == 'wm') { | ||
| 893 | $min_x = $font_size; | ||
| 894 | $max_x = $font_size + ($this->iscale * 3); | ||
| 895 | } else { // numbers, capitals or unicode | ||
| 896 | $min_x = $font_size + ($this->iscale * 2); | ||
| 897 | $max_x = $font_size + ($this->iscale * 5); | ||
| 898 | } | ||
| 899 | |||
| 900 | $x += rand($min_x, $max_x); | ||
| 901 | } //for loop | ||
| 902 | } // angled or multi-color | ||
| 903 | } //else ttf font | ||
| 904 | //$this->im = $this->tmpimg; | ||
| 905 | //$this->output(); | ||
| 906 | } //function | ||
| 907 | |||
| 908 | /** | ||
| 909 | * Warp text from temporary image onto final image.<br /> | ||
| 910 | * Modified for securimage | ||
| 911 | * | ||
| 912 | * @access private | ||
| 913 | * @since 2.0 | ||
| 914 | * @author Han-Kwang Nienhuys modified | ||
| 915 | * @copyright Han-Kwang Neinhuys | ||
| 916 | * | ||
| 917 | */ | ||
| 918 | function distortedCopy() | ||
| 919 | { | ||
| 920 | $numpoles = 3; // distortion factor | ||
| 921 | |||
| 922 | // make array of poles AKA attractor points | ||
| 923 | for ($i = 0; $i < $numpoles; ++$i) { | ||
| 924 | $px[$i] = rand($this->image_width * 0.3, $this->image_width * 0.7); | ||
| 925 | $py[$i] = rand($this->image_height * 0.3, $this->image_height * 0.7); | ||
| 926 | $rad[$i] = rand($this->image_width * 0.4, $this->image_width * 0.7); | ||
| 927 | $tmp = -$this->frand() * 0.15 - 0.15; | ||
| 928 | $amp[$i] = $this->perturbation * $tmp; | ||
| 929 | } | ||
| 930 | |||
| 931 | $bgCol = imagecolorat($this->tmpimg, 0, 0); | ||
| 932 | $width2 = $this->iscale * $this->image_width; | ||
| 933 | $height2 = $this->iscale * $this->image_height; | ||
| 934 | |||
| 935 | imagepalettecopy($this->im, $this->tmpimg); // copy palette to final image so text colors come across | ||
| 936 | |||
| 937 | // loop over $img pixels, take pixels from $tmpimg with distortion field | ||
| 938 | for ($ix = 0; $ix < $this->image_width; ++$ix) { | ||
| 939 | for ($iy = 0; $iy < $this->image_height; ++$iy) { | ||
| 940 | $x = $ix; | ||
| 941 | $y = $iy; | ||
| 942 | |||
| 943 | for ($i = 0; $i < $numpoles; ++$i) { | ||
| 944 | $dx = $ix - $px[$i]; | ||
| 945 | $dy = $iy - $py[$i]; | ||
| 946 | if ($dx == 0 && $dy == 0) continue; | ||
| 947 | |||
| 948 | $r = sqrt($dx * $dx + $dy * $dy); | ||
| 949 | if ($r > $rad[$i]) continue; | ||
| 950 | |||
| 951 | $rscale = $amp[$i] * sin(3.14 * $r / $rad[$i]); | ||
| 952 | $x += $dx * $rscale; | ||
| 953 | $y += $dy * $rscale; | ||
| 954 | } | ||
| 955 | |||
| 956 | $c = $bgCol; | ||
| 957 | $x *= $this->iscale; | ||
| 958 | $y *= $this->iscale; | ||
| 959 | |||
| 960 | if ($x >= 0 && $x < $width2 && $y >= 0 && $y < $height2) { | ||
| 961 | $c = imagecolorat($this->tmpimg, $x, $y); | ||
| 962 | } | ||
| 963 | |||
| 964 | if ($c != $bgCol) { // only copy pixels of letters to preserve any background image | ||
| 965 | imagesetpixel($this->im, $ix, $iy, $c); | ||
| 966 | } | ||
| 967 | } | ||
| 968 | } | ||
| 969 | } | ||
| 970 | |||
| 971 | /** | ||
| 972 | * Create a code and save to the session | ||
| 973 | * | ||
| 974 | * @access private | ||
| 975 | * @since 1.0.1 | ||
| 976 | * | ||
| 977 | */ | ||
| 978 | function createCode() | ||
| 979 | { | ||
| 980 | $this->code = false; | ||
| 981 | |||
| 982 | if ($this->use_wordlist && is_readable($this->wordlist_file)) { | ||
| 983 | $this->code = $this->readCodeFromFile(); | ||
| 984 | } | ||
| 985 | |||
| 986 | if ($this->code == false) { | ||
| 987 | $this->code = $this->generateCode($this->code_length); | ||
| 988 | } | ||
| 989 | |||
| 990 | $this->saveData(); | ||
| 991 | } | ||
| 992 | |||
| 993 | /** | ||
| 994 | * Generate a code | ||
| 995 | * | ||
| 996 | * @access private | ||
| 997 | * @param int $len The code length | ||
| 998 | * @return string | ||
| 999 | */ | ||
| 1000 | function generateCode($len) | ||
| 1001 | { | ||
| 1002 | $code = ''; | ||
| 1003 | |||
| 1004 | for($i = 1, $cslen = strlen($this->charset); $i <= $len; ++$i) { | ||
| 1005 | $code .= $this->charset{rand(0, $cslen - 1)}; | ||
| 1006 | } | ||
| 1007 | return $code; | ||
| 1008 | } | ||
| 1009 | |||
| 1010 | /** | ||
| 1011 | * Reads a word list file to get a code | ||
| 1012 | * | ||
| 1013 | * @access private | ||
| 1014 | * @since 1.0.2 | ||
| 1015 | * @return mixed false on failure, a word on success | ||
| 1016 | */ | ||
| 1017 | function readCodeFromFile() | ||
| 1018 | { | ||
| 1019 | $fp = @fopen($this->wordlist_file, 'rb'); | ||
| 1020 | if (!$fp) return false; | ||
| 1021 | |||
| 1022 | $fsize = filesize($this->wordlist_file); | ||
| 1023 | if ($fsize < 32) return false; // too small of a list to be effective | ||
| 1024 | |||
| 1025 | if ($fsize < 128) { | ||
| 1026 | $max = $fsize; // still pretty small but changes the range of seeking | ||
| 1027 | } else { | ||
| 1028 | $max = 128; | ||
| 1029 | } | ||
| 1030 | |||
| 1031 | fseek($fp, rand(0, $fsize - $max), SEEK_SET); | ||
| 1032 | $data = fread($fp, 128); // read a random 128 bytes from file | ||
| 1033 | fclose($fp); | ||
| 1034 | $data = preg_replace("/\r?\n/", "\n", $data); | ||
| 1035 | |||
| 1036 | $start = strpos($data, "\n", rand(0, 100)) + 1; // random start position | ||
| 1037 | $end = strpos($data, "\n", $start); // find end of word | ||
| 1038 | |||
| 1039 | return strtolower(substr($data, $start, $end - $start)); // return substring in 128 bytes | ||
| 1040 | } | ||
| 1041 | |||
| 1042 | /** | ||
| 1043 | * Output image to the browser | ||
| 1044 | * | ||
| 1045 | * @access private | ||
| 1046 | * | ||
| 1047 | */ | ||
| 1048 | function output() | ||
| 1049 | { | ||
| 1050 | header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); | ||
| 1051 | header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT"); | ||
| 1052 | header("Cache-Control: no-store, no-cache, must-revalidate"); | ||
| 1053 | header("Cache-Control: post-check=0, pre-check=0", false); | ||
| 1054 | header("Pragma: no-cache"); | ||
| 1055 | |||
| 1056 | switch($this->image_type) | ||
| 1057 | { | ||
| 1058 | case SI_IMAGE_JPEG: | ||
| 1059 | header("Content-Type: image/jpeg"); | ||
| 1060 | imagejpeg($this->im, null, 90); | ||
| 1061 | break; | ||
| 1062 | |||
| 1063 | case SI_IMAGE_GIF: | ||
| 1064 | header("Content-Type: image/gif"); | ||
| 1065 | imagegif($this->im); | ||
| 1066 | break; | ||
| 1067 | |||
| 1068 | default: | ||
| 1069 | header("Content-Type: image/png"); | ||
| 1070 | imagepng($this->im); | ||
| 1071 | break; | ||
| 1072 | } | ||
| 1073 | |||
| 1074 | imagedestroy($this->im); | ||
| 1075 | exit; | ||
| 1076 | } | ||
| 1077 | |||
| 1078 | /** | ||
| 1079 | * Get WAV or MP3 file data of the spoken code.<br /> | ||
| 1080 | * This is appropriate for output to the browser as audio/x-wav or audio/mpeg | ||
| 1081 | * | ||
| 1082 | * @since 1.0.1 | ||
| 1083 | * @return string WAV or MP3 data | ||
| 1084 | * | ||
| 1085 | */ | ||
| 1086 | function getAudibleCode($format = 'wav') | ||
| 1087 | { | ||
| 1088 | $letters = array(); | ||
| 1089 | $code = $this->getCode(); | ||
| 1090 | |||
| 1091 | if ($code == '') { | ||
| 1092 | $this->createCode(); | ||
| 1093 | $code = $this->getCode(); | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | for($i = 0; $i < strlen($code); ++$i) { | ||
| 1097 | $letters[] = $code{$i}; | ||
| 1098 | } | ||
| 1099 | |||
| 1100 | if ($format == 'mp3') { | ||
| 1101 | return $this->generateMP3($letters); | ||
| 1102 | } else { | ||
| 1103 | return $this->generateWAV($letters); | ||
| 1104 | } | ||
| 1105 | } | ||
| 1106 | |||
| 1107 | /** | ||
| 1108 | * Set the path to the audio directory.<br /> | ||
| 1109 | * | ||
| 1110 | * @since 1.0.4 | ||
| 1111 | * @return bool true if the directory exists and is readble, false if not | ||
| 1112 | */ | ||
| 1113 | function setAudioPath($audio_directory) | ||
| 1114 | { | ||
| 1115 | if (is_dir($audio_directory) && is_readable($audio_directory)) { | ||
| 1116 | $this->audio_path = $audio_directory; | ||
| 1117 | return true; | ||
| 1118 | } else { | ||
| 1119 | return false; | ||
| 1120 | } | ||
| 1121 | } | ||
| 1122 | |||
| 1123 | /** | ||
| 1124 | * Save the code in the session | ||
| 1125 | * | ||
| 1126 | * @access private | ||
| 1127 | * | ||
| 1128 | */ | ||
| 1129 | function saveData() | ||
| 1130 | { | ||
| 1131 | $_SESSION['securimage_code_value'] = strtolower($this->code); | ||
| 1132 | $_SESSION['securimage_code_ctime'] = time(); | ||
| 1133 | |||
| 1134 | $this->saveCodeToDatabase(); | ||
| 1135 | } | ||
| 1136 | |||
| 1137 | /** | ||
| 1138 | * Validate the code to the user code | ||
| 1139 | * | ||
| 1140 | * @access private | ||
| 1141 | * | ||
| 1142 | */ | ||
| 1143 | function validate() | ||
| 1144 | { | ||
| 1145 | // retrieve code from session, if no code exists check sqlite database if supported. | ||
| 1146 | |||
| 1147 | if (isset($_SESSION['securimage_code_value']) && trim($_SESSION['securimage_code_value']) != '') { | ||
| 1148 | if ($this->isCodeExpired($_SESSION['securimage_code_ctime']) == false) { | ||
| 1149 | $code = $_SESSION['securimage_code_value']; | ||
| 1150 | } | ||
| 1151 | } else if ($this->use_sqlite_db == true && function_exists('sqlite_open')) { // no code in session - may mean user has cookies turned off | ||
| 1152 | $this->openDatabase(); | ||
| 1153 | $code = $this->getCodeFromDatabase(); | ||
| 1154 | } else { | ||
| 1155 | // session code invalid or non-existant and code not found in sqlite db or sqlite is not available | ||
| 1156 | $code = ''; | ||
| 1157 | } | ||
| 1158 | |||
| 1159 | $code = trim(strtolower($code)); | ||
| 1160 | $code_entered = trim(strtolower($this->code_entered)); | ||
| 1161 | $this->correct_code = false; | ||
| 1162 | |||
| 1163 | if ($code != '') { | ||
| 1164 | if ($code == $code_entered) { | ||
| 1165 | $this->correct_code = true; | ||
| 1166 | $_SESSION['securimage_code_value'] = ''; | ||
| 1167 | $_SESSION['securimage_code_ctime'] = ''; | ||
| 1168 | $this->clearCodeFromDatabase(); | ||
| 1169 | } | ||
| 1170 | } | ||
| 1171 | } | ||
| 1172 | |||
| 1173 | /** | ||
| 1174 | * Get the captcha code | ||
| 1175 | * | ||
| 1176 | * @since 1.0.1 | ||
| 1177 | * @return string | ||
| 1178 | */ | ||
| 1179 | function getCode() | ||
| 1180 | { | ||
| 1181 | if (isset($_SESSION['securimage_code_value']) && !empty($_SESSION['securimage_code_value'])) { | ||
| 1182 | return strtolower($_SESSION['securimage_code_value']); | ||
| 1183 | } else { | ||
| 1184 | if ($this->sqlite_handle == false) $this->openDatabase(); | ||
| 1185 | |||
| 1186 | return $this->getCodeFromDatabase(); // attempt to get from database, returns empty string if sqlite is not available or disabled | ||
| 1187 | } | ||
| 1188 | } | ||
| 1189 | |||
| 1190 | /** | ||
| 1191 | * Check if the user entered code was correct | ||
| 1192 | * | ||
| 1193 | * @access private | ||
| 1194 | * @return boolean | ||
| 1195 | */ | ||
| 1196 | function checkCode() | ||
| 1197 | { | ||
| 1198 | return $this->correct_code; | ||
| 1199 | } | ||
| 1200 | |||
| 1201 | /** | ||
| 1202 | * Generate a wav file by concatenating individual files | ||
| 1203 | * | ||
| 1204 | * @since 1.0.1 | ||
| 1205 | * @access private | ||
| 1206 | * @param array $letters Array of letters to build a file from | ||
| 1207 | * @return string WAV file data | ||
| 1208 | */ | ||
| 1209 | function generateWAV($letters) | ||
| 1210 | { | ||
| 1211 | $data_len = 0; | ||
| 1212 | $files = array(); | ||
| 1213 | $out_data = ''; | ||
| 1214 | |||
| 1215 | foreach ($letters as $letter) { | ||
| 1216 | $filename = $this->audio_path . strtoupper($letter) . '.wav'; | ||
| 1217 | |||
| 1218 | $fp = fopen($filename, 'rb'); | ||
| 1219 | |||
| 1220 | $file = array(); | ||
| 1221 | |||
| 1222 | $data = fread($fp, filesize($filename)); // read file in | ||
| 1223 | |||
| 1224 | $header = substr($data, 0, 36); | ||
| 1225 | $body = substr($data, 44); | ||
| 1226 | |||
| 1227 | |||
| 1228 | $data = unpack('NChunkID/VChunkSize/NFormat/NSubChunk1ID/VSubChunk1Size/vAudioFormat/vNumChannels/VSampleRate/VByteRate/vBlockAlign/vBitsPerSample', $header); | ||
| 1229 | |||
| 1230 | $file['sub_chunk1_id'] = $data['SubChunk1ID']; | ||
| 1231 | $file['bits_per_sample'] = $data['BitsPerSample']; | ||
| 1232 | $file['channels'] = $data['NumChannels']; | ||
| 1233 | $file['format'] = $data['AudioFormat']; | ||
| 1234 | $file['sample_rate'] = $data['SampleRate']; | ||
| 1235 | $file['size'] = $data['ChunkSize'] + 8; | ||
| 1236 | $file['data'] = $body; | ||
| 1237 | |||
| 1238 | if ( ($p = strpos($file['data'], 'LIST')) !== false) { | ||
| 1239 | // If the LIST data is not at the end of the file, this will probably break your sound file | ||
| 1240 | $info = substr($file['data'], $p + 4, 8); | ||
| 1241 | $data = unpack('Vlength/Vjunk', $info); | ||
| 1242 | $file['data'] = substr($file['data'], 0, $p); | ||
| 1243 | $file['size'] = $file['size'] - (strlen($file['data']) - $p); | ||
| 1244 | } | ||
| 1245 | |||
| 1246 | $files[] = $file; | ||
| 1247 | $data = null; | ||
| 1248 | $header = null; | ||
| 1249 | $body = null; | ||
| 1250 | |||
| 1251 | $data_len += strlen($file['data']); | ||
| 1252 | |||
| 1253 | fclose($fp); | ||
| 1254 | } | ||
| 1255 | |||
| 1256 | $out_data = ''; | ||
| 1257 | for($i = 0; $i < sizeof($files); ++$i) { | ||
| 1258 | if ($i == 0) { // output header | ||
| 1259 | $out_data .= pack('C4VC8', ord('R'), ord('I'), ord('F'), ord('F'), $data_len + 36, ord('W'), ord('A'), ord('V'), ord('E'), ord('f'), ord('m'), ord('t'), ord(' ')); | ||
| 1260 | |||
| 1261 | $out_data .= pack('VvvVVvv', | ||
| 1262 | 16, | ||
| 1263 | $files[$i]['format'], | ||
| 1264 | $files[$i]['channels'], | ||
| 1265 | $files[$i]['sample_rate'], | ||
| 1266 | $files[$i]['sample_rate'] * (($files[$i]['bits_per_sample'] * $files[$i]['channels']) / 8), | ||
| 1267 | ($files[$i]['bits_per_sample'] * $files[$i]['channels']) / 8, | ||
| 1268 | $files[$i]['bits_per_sample'] ); | ||
| 1269 | |||
| 1270 | $out_data .= pack('C4', ord('d'), ord('a'), ord('t'), ord('a')); | ||
| 1271 | |||
| 1272 | $out_data .= pack('V', $data_len); | ||
| 1273 | } | ||
| 1274 | |||
| 1275 | $out_data .= $files[$i]['data']; | ||
| 1276 | } | ||
| 1277 | |||
| 1278 | $this->scrambleAudioData($out_data, 'wav'); | ||
| 1279 | return $out_data; | ||
| 1280 | } | ||
| 1281 | |||
| 1282 | /** | ||
| 1283 | * Randomly modify the audio data to scramble sound and prevent binary recognition.<br /> | ||
| 1284 | * Take care not to "break" the audio file by leaving the header data intact. | ||
| 1285 | * | ||
| 1286 | * @since 2.0 | ||
| 1287 | * @access private | ||
| 1288 | * @param $data Sound data in mp3 of wav format | ||
| 1289 | */ | ||
| 1290 | function scrambleAudioData(&$data, $format) | ||
| 1291 | { | ||
| 1292 | if ($format == 'wav') { | ||
| 1293 | $start = strpos($data, 'data') + 4; // look for "data" indicator | ||
| 1294 | if ($start === false) $start = 44; // if not found assume 44 byte header | ||
| 1295 | } else { // mp3 | ||
| 1296 | $start = 4; // 4 byte (32 bit) frame header | ||
| 1297 | } | ||
| 1298 | |||
| 1299 | $start += rand(1, 64); // randomize starting offset | ||
| 1300 | $datalen = strlen($data) - $start - 256; // leave last 256 bytes unchanged | ||
| 1301 | |||
| 1302 | for ($i = $start; $i < $datalen; $i += 64) { | ||
| 1303 | $ch = ord($data{$i}); | ||
| 1304 | if ($ch < 9 || $ch > 119) continue; | ||
| 1305 | |||
| 1306 | $data{$i} = chr($ch + rand(-8, 8)); | ||
| 1307 | } | ||
| 1308 | } | ||
| 1309 | |||
| 1310 | /** | ||
| 1311 | * Generate an mp3 file by concatenating individual files | ||
| 1312 | * @since 1.0.4 | ||
| 1313 | * @access private | ||
| 1314 | * @param array $letters Array of letters to build a file from | ||
| 1315 | * @return string MP3 file data | ||
| 1316 | */ | ||
| 1317 | function generateMP3($letters) | ||
| 1318 | { | ||
| 1319 | $data_len = 0; | ||
| 1320 | $files = array(); | ||
| 1321 | $out_data = ''; | ||
| 1322 | |||
| 1323 | foreach ($letters as $letter) { | ||
| 1324 | $filename = $this->audio_path . strtoupper($letter) . '.mp3'; | ||
| 1325 | |||
| 1326 | $fp = fopen($filename, 'rb'); | ||
| 1327 | $data = fread($fp, filesize($filename)); // read file in | ||
| 1328 | |||
| 1329 | $this->scrambleAudioData($data, 'mp3'); | ||
| 1330 | $out_data .= $data; | ||
| 1331 | |||
| 1332 | fclose($fp); | ||
| 1333 | } | ||
| 1334 | |||
| 1335 | |||
| 1336 | return $out_data; | ||
| 1337 | } | ||
| 1338 | |||
| 1339 | /** | ||
| 1340 | * Generate random number less than 1 | ||
| 1341 | * @since 2.0 | ||
| 1342 | * @access private | ||
| 1343 | * @return float | ||
| 1344 | */ | ||
| 1345 | function frand() | ||
| 1346 | { | ||
| 1347 | return 0.0001*rand(0,9999); | ||
| 1348 | } | ||
| 1349 | |||
| 1350 | /** | ||
| 1351 | * Print signature text on image | ||
| 1352 | * | ||
| 1353 | * @since 2.0 | ||
| 1354 | * @access private | ||
| 1355 | * | ||
| 1356 | */ | ||
| 1357 | function addSignature() | ||
| 1358 | { | ||
| 1359 | if ($this->use_gd_font) { | ||
| 1360 | imagestring($this->im, 5, $this->image_width - (strlen($this->image_signature) * 10), $this->image_height - 20, $this->image_signature, $this->gdsignaturecolor); | ||
| 1361 | } else { | ||
| 1362 | |||
| 1363 | $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature); | ||
| 1364 | $textlen = $bbox[2] - $bbox[0]; | ||
| 1365 | $x = $this->image_width - $textlen - 5; | ||
| 1366 | $y = $this->image_height - 3; | ||
| 1367 | |||
| 1368 | imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature); | ||
| 1369 | } | ||
| 1370 | } | ||
| 1371 | |||
| 1372 | /** | ||
| 1373 | * Get hashed IP address of remote user | ||
| 1374 | * | ||
| 1375 | * @access private | ||
| 1376 | * @since 2.0.1 | ||
| 1377 | * @return string | ||
| 1378 | */ | ||
| 1379 | function getIPHash() | ||
| 1380 | { | ||
| 1381 | return strtolower(md5($_SERVER['REMOTE_ADDR'])); | ||
| 1382 | } | ||
| 1383 | |||
| 1384 | /** | ||
| 1385 | * Open SQLite database | ||
| 1386 | * | ||
| 1387 | * @access private | ||
| 1388 | * @since 2.0.1 | ||
| 1389 | * @return bool true if database was opened successfully | ||
| 1390 | */ | ||
| 1391 | function openDatabase() | ||
| 1392 | { | ||
| 1393 | $this->sqlite_handle = false; | ||
| 1394 | |||
| 1395 | if ($this->use_sqlite_db && function_exists('sqlite_open')) { | ||
| 1396 | $this->sqlite_handle = sqlite_open($this->sqlite_database, 0666, $error); | ||
| 1397 | |||
| 1398 | if ($this->sqlite_handle !== false) { | ||
| 1399 | $res = sqlite_query($this->sqlite_handle, "PRAGMA table_info(codes)"); | ||
| 1400 | if (sqlite_num_rows($res) == 0) { | ||
| 1401 | sqlite_query($this->sqlite_handle, "CREATE TABLE codes (iphash VARCHAR(32) PRIMARY KEY, code VARCHAR(32) NOT NULL, created INTEGER)"); | ||
| 1402 | } | ||
| 1403 | } | ||
| 1404 | |||
| 1405 | return $this->sqlite_handle != false; | ||
| 1406 | } | ||
| 1407 | |||
| 1408 | return $this->sqlite_handle; | ||
| 1409 | } | ||
| 1410 | |||
| 1411 | /** | ||
| 1412 | * Save captcha code to sqlite database | ||
| 1413 | * | ||
| 1414 | * @access private | ||
| 1415 | * @since 2.0.1 | ||
| 1416 | * @return bool true if code was saved, false if not | ||
| 1417 | */ | ||
| 1418 | function saveCodeToDatabase() | ||
| 1419 | { | ||
| 1420 | $success = false; | ||
| 1421 | |||
| 1422 | $this->openDatabase(); | ||
| 1423 | |||
| 1424 | if ($this->use_sqlite_db && $this->sqlite_handle !== false) { | ||
| 1425 | $ip = $this->getIPHash(); | ||
| 1426 | $time = time(); | ||
| 1427 | $code = $_SESSION['securimage_code_value']; // hash code for security - if cookies are disabled the session still exists at this point | ||
| 1428 | $success = sqlite_query($this->sqlite_handle, "INSERT OR REPLACE INTO codes(iphash, code, created) VALUES('$ip', '$code', $time)"); | ||
| 1429 | } | ||
| 1430 | |||
| 1431 | return $success !== false; | ||
| 1432 | } | ||
| 1433 | |||
| 1434 | /** | ||
| 1435 | * Get stored captcha code from sqlite database based on ip address hash | ||
| 1436 | * | ||
| 1437 | * @access private | ||
| 1438 | * @since 2.0.1 | ||
| 1439 | * @return string captcha code | ||
| 1440 | */ | ||
| 1441 | function getCodeFromDatabase() | ||
| 1442 | { | ||
| 1443 | $code = ''; | ||
| 1444 | |||
| 1445 | if ($this->use_sqlite_db && $this->sqlite_handle !== false) { | ||
| 1446 | $ip = $this->getIPHash(); | ||
| 1447 | |||
| 1448 | $res = sqlite_query($this->sqlite_handle, "SELECT * FROM codes WHERE iphash = '$ip'"); | ||
| 1449 | if ($res && sqlite_num_rows($res) > 0) { | ||
| 1450 | $res = sqlite_fetch_array($res); | ||
| 1451 | |||
| 1452 | if ($this->isCodeExpired($res['created']) == false) { | ||
| 1453 | $code = $res['code']; | ||
| 1454 | } | ||
| 1455 | } | ||
| 1456 | } | ||
| 1457 | |||
| 1458 | return $code; | ||
| 1459 | } | ||
| 1460 | |||
| 1461 | /** | ||
| 1462 | * Delete a code from the database by ip address hash | ||
| 1463 | * | ||
| 1464 | * @access private | ||
| 1465 | * @since 2.0.1 | ||
| 1466 | */ | ||
| 1467 | function clearCodeFromDatabase() | ||
| 1468 | { | ||
| 1469 | if ($this->sqlite_handle !== false) { | ||
| 1470 | $ip = $this->getIPHash(); | ||
| 1471 | |||
| 1472 | sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE iphash = '$ip'"); | ||
| 1473 | } | ||
| 1474 | } | ||
| 1475 | |||
| 1476 | /** | ||
| 1477 | * Purge codes over a day old from database | ||
| 1478 | * | ||
| 1479 | * @access private | ||
| 1480 | * @since 2.0.1 | ||
| 1481 | */ | ||
| 1482 | function purgeOldCodesFromDatabase() | ||
| 1483 | { | ||
| 1484 | if ($this->use_sqlite_db && $this->sqlite_handle !== false) { | ||
| 1485 | $now = time(); | ||
| 1486 | $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time; | ||
| 1487 | |||
| 1488 | sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE $now - created > $limit"); | ||
| 1489 | } | ||
| 1490 | } | ||
| 1491 | |||
| 1492 | /** | ||
| 1493 | * Check a code to see if it is expired based on creation time | ||
| 1494 | * | ||
| 1495 | * @access private | ||
| 1496 | * @since 2.0.1 | ||
| 1497 | * @param $creation_time unix timestamp of code creation time | ||
| 1498 | * @return bool true if code has expired, false if not | ||
| 1499 | */ | ||
| 1500 | function isCodeExpired($creation_time) | ||
| 1501 | { | ||
| 1502 | $expired = true; | ||
| 1503 | |||
| 1504 | if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) { | ||
| 1505 | $expired = false; | ||
| 1506 | } else if (time() - $creation_time < $this->expiry_time) { | ||
| 1507 | $expired = false; | ||
| 1508 | } | ||
| 1509 | |||
| 1510 | return $expired; | ||
| 1511 | } | ||
| 1512 | |||
| 1513 | } /* class Securimage */ | ||
| 1514 | |||
| 1515 | |||
| 1516 | /** | ||
| 1517 | * Color object for Securimage CAPTCHA | ||
| 1518 | * | ||
| 1519 | * @since 2.0 | ||
| 1520 | * @package Securimage | ||
| 1521 | * @subpackage classes | ||
| 1522 | * | ||
| 1523 | */ | ||
| 1524 | class Securimage_Color { | ||
| 1525 | /** | ||
| 1526 | * Red component: 0-255 | ||
| 1527 | * | ||
| 1528 | * @var int | ||
| 1529 | */ | ||
| 1530 | var $r; | ||
| 1531 | /** | ||
| 1532 | * Green component: 0-255 | ||
| 1533 | * | ||
| 1534 | * @var int | ||
| 1535 | */ | ||
| 1536 | var $g; | ||
| 1537 | /** | ||
| 1538 | * Blue component: 0-255 | ||
| 1539 | * | ||
| 1540 | * @var int | ||
| 1541 | */ | ||
| 1542 | var $b; | ||
| 1543 | |||
| 1544 | /** | ||
| 1545 | * Create a new Securimage_Color object.<br /> | ||
| 1546 | * Specify the red, green, and blue components using their HTML hex code equivalent.<br /> | ||
| 1547 | * Example: The code for the HTML color #4A203C is:<br /> | ||
| 1548 | * $color = new Securimage_Color(0x4A, 0x20, 0x3C); | ||
| 1549 | * | ||
| 1550 | * @param $red Red component 0-255 | ||
| 1551 | * @param $green Green component 0-255 | ||
| 1552 | * @param $blue Blue component 0-255 | ||
| 1553 | */ | ||
| 1554 | function Securimage_Color($red, $green = null, $blue = null) | ||
| 1555 | { | ||
| 1556 | if ($green == null && $blue == null && preg_match('/^#[a-f0-9]{3,6}$/i', $red)) { | ||
| 1557 | $col = substr($red, 1); | ||
| 1558 | if (strlen($col) == 3) { | ||
| 1559 | $red = str_repeat(substr($col, 0, 1), 2); | ||
| 1560 | $green = str_repeat(substr($col, 1, 1), 2); | ||
| 1561 | $blue = str_repeat(substr($col, 2, 1), 2); | ||
| 1562 | } else { | ||
| 1563 | $red = substr($col, 0, 2); | ||
| 1564 | $green = substr($col, 2, 2); | ||
| 1565 | $blue = substr($col, 4, 2); | ||
| 1566 | } | ||
| 1567 | |||
| 1568 | $red = hexdec($red); | ||
| 1569 | $green = hexdec($green); | ||
| 1570 | $blue = hexdec($blue); | ||
| 1571 | } else { | ||
| 1572 | if ($red < 0) $red = 0; | ||
| 1573 | if ($red > 255) $red = 255; | ||
| 1574 | if ($green < 0) $green = 0; | ||
| 1575 | if ($green > 255) $green = 255; | ||
| 1576 | if ($blue < 0) $blue = 0; | ||
| 1577 | if ($blue > 255) $blue = 255; | ||
| 1578 | } | ||
| 1579 | |||
| 1580 | $this->r = $red; | ||
| 1581 | $this->g = $green; | ||
| 1582 | $this->b = $blue; | ||
| 1583 | } | ||
| 1584 | } | ||
