Changeset 26554 for extensions/CryptograPHP/securimage/securimage.php
- Timestamp:
- Jan 9, 2014, 1:36:32 PM (10 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
extensions/CryptograPHP/securimage/securimage.php
r12617 r26554 1 1 <?php 2 3 // error_reporting(E_ALL); ini_set('display_errors', 1); // uncomment this line for debugging 4 2 5 /** 3 * Project: Securimage: A PHP class for creating and managing form CAPTCHA images 4 * File: securimage.php 6 * Project: Securimage: A PHP class for creating and managing form CAPTCHA images<br /> 7 * File: securimage.php<br /> 5 8 * 6 * Copyright (c) 201 1, Drew Phillips9 * Copyright (c) 2013, Drew Phillips 7 10 * All rights reserved. 8 * 11 * 9 12 * Redistribution and use in source and binary forms, with or without modification, 10 13 * are permitted provided that the following conditions are met: 11 * 14 * 12 15 * - Redistributions of source code must retain the above copyright notice, 13 16 * this list of conditions and the following disclaimer. … … 15 18 * this list of conditions and the following disclaimer in the documentation 16 19 * and/or other materials provided with the distribution. 17 * 20 * 18 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE … … 29 32 * 30 33 * Any modifications to the library should be indicated clearly in the source code 31 * to inform users that the changes are not a part of the original software. 34 * to inform users that the changes are not a part of the original software.<br /><br /> 32 35 * 33 * If you found this script useful, please take a quick moment to rate it. 36 * If you found this script useful, please take a quick moment to rate it.<br /> 34 37 * http://www.hotscripts.com/rate/49400.html Thanks. 35 38 * … … 37 40 * @link http://www.phpcaptcha.org/latest.zip Download Latest Version 38 41 * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation 39 * @copyright 201 1Drew Phillips42 * @copyright 2013 Drew Phillips 40 43 * @author Drew Phillips <drew@drew-phillips.com> 41 * @version 3. 0 (October 2011)44 * @version 3.5.1 (June 21, 2013) 42 45 * @package Securimage 43 46 * … … 46 49 /** 47 50 ChangeLog 48 51 52 3.5.1 53 - Fix XSS vulnerability in example_form.php (discovered by Gjoko Krstic - <gjoko@zeroscience.mk>) 54 55 3.5 56 - Release new version 57 - MB string support for charlist 58 - Modify audio file path to use language directories 59 - Changed default captcha appearance 60 61 3.2RC4 62 - Add MySQL, PostgreSQL, and SQLite3 support for database storage 63 - Deprecate "use_sqlite_db" option and remove SQLite2/sqlite_* functions 64 - Add new captcha type that displays 2 dictionary words on one image 65 - Update examples 66 67 3.2RC3 68 - Fix canSendHeaders() check which was breaking if a PHP startup error was issued 69 70 3.2RC2 71 - Add error handler (https://github.com/dapphp/securimage/issues/15) 72 - Fix flash examples to use the correct value name for audio parameter 73 74 3.2RC1 75 - New audio captcha code. Faster, fully dynamic audio, full WAV support 76 (Paul Voegler, Drew Phillips) <http://voegler.eu/pub/audio> 77 - New Flash audio streaming button. User defined image and size supported 78 - Additional options for customizing captcha (noise_level, send_headers, 79 no_exit, no_session, display_value 80 - Add captcha ID support. Uses sqlite and unique captcha IDs to track captchas, 81 no session used 82 - Add static methods for creating and validating captcha by ID 83 - Automatic clearing of old codes from SQLite database 84 85 3.0.3Beta 86 - Add improved mixing function to WavFile class (Paul Voegler) 87 - Improve performance and security of captcha audio (Paul Voegler, Drew Phillips) 88 - Add option to use random file as background noise in captcha audio 89 - Add new securimage options for audio files 90 91 3.0.2Beta 92 - Fix issue with session variables when upgrading from 2.0 - 3.0 93 - Improve audio captcha, switch to use WavFile class, make mathematical captcha audio work 94 95 3.0.1 96 - Bugfix: removed use of deprecated variable in addSignature method that would cause errors with display_errors on 97 49 98 3.0 50 99 - Rewrite class using PHP5 OOP … … 78 127 - Audio output is mp3 format by default 79 128 - Change font to AlteHaasGrotesk by yann le coroller 80 - Some code cleanup 129 - Some code cleanup 81 130 82 131 1.0.4 (unreleased) … … 108 157 * Securimage CAPTCHA Class. 109 158 * 110 * @version 3. 0159 * @version 3.5 111 160 * @package Securimage 112 161 * @subpackage classes … … 116 165 class Securimage 117 166 { 118 119 120 121 167 // All of the public variables below are securimage options 168 // They can be passed as an array to the Securimage constructor, set below, 169 // or set from securimage_show.php and securimage_play.php 170 122 171 /** 123 172 * Renders captcha as a JPEG image … … 135 184 */ 136 185 const SI_IMAGE_GIF = 3; 137 186 138 187 /** 139 188 * Create a normal alphanumeric captcha … … 146 195 */ 147 196 const SI_CAPTCHA_MATHEMATIC = 1; 148 197 /** 198 * Create a word based captcha using 2 words 199 * @var int 200 */ 201 const SI_CAPTCHA_WORDS = 2; 202 203 /** 204 * MySQL option identifier for database storage option 205 * 206 * @var string 207 */ 208 const SI_DRIVER_MYSQL = 'mysql'; 209 210 /** 211 * PostgreSQL option identifier for database storage option 212 * 213 * @var string 214 */ 215 const SI_DRIVER_PGSQL = 'pgsql'; 216 217 /** 218 * SQLite option identifier for database storage option 219 * 220 * @var string 221 */ 222 const SI_DRIVER_SQLITE3 = 'sqlite'; 223 224 /*%*********************************************************************%*/ 225 // Properties 226 149 227 /** 150 228 * The width of the captcha image … … 180 258 /** 181 259 * The color of the noise that is drawn 182 * @var Securimage_Color 260 * @var Securimage_Color 183 261 */ 184 262 public $noise_color = '#707070'; 185 263 186 264 /** 187 265 * How transparent to make the text 0 = completely opaque, 100 = invisible 188 266 * @var int 189 267 */ 190 public $text_transparency_percentage = 50;268 public $text_transparency_percentage = 20; 191 269 /** 192 270 * Whether or not to draw the text transparently, true = use transparency, false = no transparency 193 271 * @var bool 194 272 */ 195 public $use_transparent_text = false;196 273 public $use_transparent_text = true; 274 197 275 /** 198 276 * The length of the captcha code … … 215 293 */ 216 294 public $expiry_time = 900; 217 295 218 296 /** 219 297 * The session name securimage should use, only set this if your application uses a custom session name … … 222 300 */ 223 301 public $session_name = null; 224 302 225 303 /** 226 304 * true to use the wordlist file, false to generate random captcha codes … … 233 311 * @var double 234 312 */ 235 public $perturbation = 0. 75;313 public $perturbation = 0.85; 236 314 /** 237 315 * How many lines to draw over the captcha code to increase security 238 316 * @var int 239 317 */ 240 public $num_lines = 8;318 public $num_lines = 5; 241 319 /** 242 320 * The level of noise (random dots) to place on the image, 0-10 243 321 * @var int 244 322 */ 245 public $noise_level = 0;246 323 public $noise_level = 2; 324 247 325 /** 248 326 * The signature text to draw on the bottom corner of the image … … 260 338 */ 261 339 public $signature_font; 262 263 /** 340 341 /** 342 * DO NOT USE!!! 264 343 * Use an SQLite database to store data (for users that do not support cookies) 265 344 * @var bool 345 * @see Securimage::$use_sqlite_db 346 * @deprecated 3.2RC4 266 347 */ 267 348 public $use_sqlite_db = false; 268 349 350 /** 351 * Use a database backend for code storage. 352 * Provides a fallback to users with cookies disabled. 353 * Required when using captcha IDs. 354 * 355 * @see Securimage::$database_driver 356 * @var bool 357 */ 358 public $use_database = false; 359 360 /** 361 * Database driver to use for database support. 362 * Allowable values: 'mysql', 'pgsql', 'sqlite'. 363 * Default: sqlite 364 * 365 * @var string 366 */ 367 public $database_driver = self::SI_DRIVER_SQLITE3; 368 369 /** 370 * Database host to connect to when using mysql or postgres 371 * On Linux use "localhost" for Unix domain socket, otherwise uses TCP/IP 372 * Does not apply to SQLite 373 * 374 * @var string 375 */ 376 public $database_host = 'localhost'; 377 378 /** 379 * Database username for connection (mysql, postgres only) 380 * Default is an empty string 381 * 382 * @var string 383 */ 384 public $database_user = ''; 385 386 /** 387 * Database password for connection (mysql, postgres only) 388 * Default is empty string 389 * 390 * @var string 391 */ 392 public $database_pass = ''; 393 394 /** 395 * Name of the database to select (mysql, postgres only) 396 * 397 * @see Securimage::$database_file for SQLite 398 * @var string 399 */ 400 public $database_name = ''; 401 402 /** 403 * Database table where captcha codes are stored 404 * Note: Securimage will attempt to create this table for you if it does 405 * not exist. If the table cannot be created, an E_USER_WARNING is emitted. 406 * 407 * @var string 408 */ 409 public $database_table = 'captcha_codes'; 410 411 /** 412 * Fully qualified path to the database file when using SQLite3. 413 * This value is only used when $database_driver == sqlite3 and does 414 * not apply when no database is used, or when using MySQL or PostgreSQL. 415 * 416 * @var string 417 */ 418 public $database_file; 419 269 420 /** 270 421 * The type of captcha to create, either alphanumeric, or a math problem<br /> … … 272 423 * @var int 273 424 */ 274 public $captcha_type = self::SI_CAPTCHA_STRING; 275 425 public $captcha_type = self::SI_CAPTCHA_STRING; // or self::SI_CAPTCHA_MATHEMATIC; 426 276 427 /** 277 428 * The captcha namespace, use this if you have multiple forms on a single page, blank if you do not use multiple forms on one page … … 281 432 * // in securimage_show.php (create one show script for each form) 282 433 * $img->namespace = 'contact_form'; 283 * 434 * 284 435 * // in form validator 285 436 * $img->namespace = 'contact_form'; … … 290 441 */ 291 442 public $namespace; 292 443 293 444 /** 294 445 * The font file to use to draw the captcha code, leave blank for default font AHGBold.ttf … … 308 459 /** 309 460 * The path to the SQLite database file to use, if $use_sqlite_database = true, should be chmod 666 461 * @deprecated 3.2RC4 310 462 * @var string 311 463 */ … … 315 467 * @var string 316 468 * <code> 317 * $img->audio_path = '/home/yoursite/public_html/securimage/audio/ ';469 * $img->audio_path = '/home/yoursite/public_html/securimage/audio/en/'; 318 470 * </code> 319 471 */ 320 472 public $audio_path; 321 322 323 473 /** 474 * The path to the directory containing audio files that will be selected 475 * randomly and mixed with the captcha audio. 476 * 477 * @var string 478 */ 479 public $audio_noise_path; 480 /** 481 * Whether or not to mix background noise files into captcha audio (true = mix, false = no) 482 * Mixing random background audio with noise can help improve security of audio captcha. 483 * Default: securimage/audio/noise 484 * 485 * @since 3.0.3 486 * @see Securimage::$audio_noise_path 487 * @var bool 488 */ 489 public $audio_use_noise; 490 /** 491 * The method and threshold (or gain factor) used to normalize the mixing with background noise. 492 * See http://www.voegler.eu/pub/audio/ for more information. 493 * 494 * Valid: <ul> 495 * <li> >= 1 - Normalize by multiplying by the threshold (boost - positive gain). <br /> 496 * A value of 1 in effect means no normalization (and results in clipping). </li> 497 * <li> <= -1 - Normalize by dividing by the the absolute value of threshold (attenuate - negative gain). <br /> 498 * A factor of 2 (-2) is about 6dB reduction in volume.</li> 499 * <li> [0, 1) - (open inverval - not including 1) - The threshold 500 * above which amplitudes are comressed logarithmically. <br /> 501 * e.g. 0.6 to leave amplitudes up to 60% "as is" and compress above. </li> 502 * <li> (-1, 0) - (open inverval - not including -1 and 0) - The threshold 503 * above which amplitudes are comressed linearly. <br /> 504 * e.g. -0.6 to leave amplitudes up to 60% "as is" and compress above. </li></ul> 505 * 506 * Default: 0.6 507 * 508 * @since 3.0.4 509 * @var float 510 */ 511 public $audio_mix_normalization = 0.6; 512 /** 513 * Whether or not to degrade audio by introducing random noise (improves security of audio captcha) 514 * Default: true 515 * 516 * @since 3.0.3 517 * @var bool 518 */ 519 public $degrade_audio; 520 /** 521 * Minimum delay to insert between captcha audio letters in milliseconds 522 * 523 * @since 3.0.3 524 * @var float 525 */ 526 public $audio_gap_min = 0; 527 /** 528 * Maximum delay to insert between captcha audio letters in milliseconds 529 * 530 * @since 3.0.3 531 * @var float 532 */ 533 public $audio_gap_max = 600; 534 535 /** 536 * Captcha ID if using static captcha 537 * @var string Unique captcha id 538 */ 539 protected static $_captchaId = null; 540 324 541 protected $im; 325 542 protected $tmpimg; 326 543 protected $bgimg; 327 544 protected $iscale = 5; 328 329 protected $securimage_path = null; 330 545 546 public $securimage_path = null; 547 548 /** 549 * The captcha challenge value (either the case-sensitive/insensitive word captcha, or the solution to the math captcha) 550 * 551 * @var string Captcha challenge value 552 */ 331 553 protected $code; 554 555 /** 556 * The display value of the captcha to draw on the image (the word captcha, or the math equation to present to the user) 557 * 558 * @var string Captcha display value to draw on the image 559 */ 332 560 protected $code_display; 333 561 562 /** 563 * A value that can be passed to the constructor that can be used to generate a captcha image with a given value 564 * This value does not get stored in the session or database and is only used when calling Securimage::show(). 565 * If a display_value was passed to the constructor and the captcha image is generated, the display_value will be used 566 * as the string to draw on the captcha image. Used only if captcha codes are generated and managed by a 3rd party app/library 567 * 568 * @var string Captcha code value to display on the image 569 */ 570 public $display_value; 571 572 /** 573 * Captcha code supplied by user [set from Securimage::check()] 574 * 575 * @var string 576 */ 334 577 protected $captcha_code; 335 protected $sqlite_handle; 336 578 579 /** 580 * Flag that can be specified telling securimage not to call exit after generating a captcha image or audio file 581 * 582 * @var bool If true, script will not terminate; if false script will terminate (default) 583 */ 584 protected $no_exit; 585 586 /** 587 * Flag indicating whether or not a PHP session should be started and used 588 * 589 * @var bool If true, no session will be started; if false, session will be started and used to store data (default) 590 */ 591 protected $no_session; 592 593 /** 594 * Flag indicating whether or not HTTP headers will be sent when outputting captcha image/audio 595 * 596 * @var bool If true (default) headers will be sent, if false, no headers are sent 597 */ 598 protected $send_headers; 599 600 /** 601 * PDO connection when a database is used 602 * 603 * @var resource 604 */ 605 protected $pdo_conn; 606 607 // gd color resources that are allocated for drawing the image 337 608 protected $gdbgcolor; 338 609 protected $gdtextcolor; 339 610 protected $gdlinecolor; 340 611 protected $gdsignaturecolor; 341 612 342 613 /** 343 614 * Create a new securimage object, pass options to set in the constructor.<br /> … … 352 623 * 'font_file' => Securimage::getPath() . '/custom.ttf' 353 624 * ); 354 * 625 * 355 626 * $img = new Securimage($options); 356 627 * </code> … … 359 630 { 360 631 $this->securimage_path = dirname(__FILE__); 361 632 362 633 if (is_array($options) && sizeof($options) > 0) { 363 634 foreach($options as $prop => $val) { 364 $this->$prop = $val; 635 if ($prop == 'captchaId') { 636 Securimage::$_captchaId = $val; 637 $this->use_database = true; 638 } else if ($prop == 'use_sqlite_db') { 639 trigger_error("The use_sqlite_db option is deprecated, use 'use_database' instead", E_USER_NOTICE); 640 } else { 641 $this->$prop = $val; 642 } 365 643 } 366 644 } … … 372 650 $this->signature_color = $this->initColor($this->signature_color, '#616161'); 373 651 374 if ( $this->ttf_file == null) {652 if (is_null($this->ttf_file)) { 375 653 $this->ttf_file = $this->securimage_path . '/AHGBold.ttf'; 376 654 } 377 655 378 656 $this->signature_font = $this->ttf_file; 379 380 if ( $this->wordlist_file == null) {657 658 if (is_null($this->wordlist_file)) { 381 659 $this->wordlist_file = $this->securimage_path . '/words/words.txt'; 382 660 } 383 384 if ($this->sqlite_database == null) { 385 $this->sqlite_database = $this->securimage_path . '/database/securimage.sqlite'; 386 } 387 388 if ($this->audio_path == null) { 389 $this->audio_path = $this->securimage_path . '/audio/'; 390 } 391 392 if ($this->code_length == null || $this->code_length < 1) { 661 662 if (is_null($this->database_file)) { 663 $this->database_file = $this->securimage_path . '/database/securimage.sq3'; 664 } 665 666 if (is_null($this->audio_path)) { 667 $this->audio_path = $this->securimage_path . '/audio/en/'; 668 } 669 670 if (is_null($this->audio_noise_path)) { 671 $this->audio_noise_path = $this->securimage_path . '/audio/noise/'; 672 } 673 674 if (is_null($this->audio_use_noise)) { 675 $this->audio_use_noise = true; 676 } 677 678 if (is_null($this->degrade_audio)) { 679 $this->degrade_audio = true; 680 } 681 682 if (is_null($this->code_length) || (int)$this->code_length < 1) { 393 683 $this->code_length = 6; 394 684 } 395 396 if ( $this->perturbation == null|| !is_numeric($this->perturbation)) {685 686 if (is_null($this->perturbation) || !is_numeric($this->perturbation)) { 397 687 $this->perturbation = 0.75; 398 688 } 399 400 if ( $this->namespace == null|| !is_string($this->namespace)) {689 690 if (is_null($this->namespace) || !is_string($this->namespace)) { 401 691 $this->namespace = 'default'; 402 692 } 403 693 404 // Initialize session or attach to existing 405 if ( session_id() == '' ) { // no session has been started yet, which is needed for validation 406 if ($this->session_name != null && trim($this->session_name) != '') { 407 session_name(trim($this->session_name)); // set session name if provided 408 } 409 session_start(); 410 } 411 } 412 694 if (is_null($this->no_exit)) { 695 $this->no_exit = false; 696 } 697 698 if (is_null($this->no_session)) { 699 $this->no_session = false; 700 } 701 702 if (is_null($this->send_headers)) { 703 $this->send_headers = true; 704 } 705 706 if ($this->no_session != true) { 707 // Initialize session or attach to existing 708 if ( session_id() == '' ) { // no session has been started yet, which is needed for validation 709 if (!is_null($this->session_name) && trim($this->session_name) != '') { 710 session_name(trim($this->session_name)); // set session name if provided 711 } 712 session_start(); 713 } 714 } 715 } 716 413 717 /** 414 718 * Return the absolute path to the Securimage directory … … 419 723 return dirname(__FILE__); 420 724 } 421 725 726 /** 727 * Generate a new captcha ID or retrieve the current ID 728 * 729 * @param $new bool If true, generates a new challenge and returns and ID 730 * @param $options array Additional options to be passed to Securimage. 731 * Must include database options if not set directly in securimage.php 732 * 733 * @return null|string Returns null if no captcha id set and new was false, or string captcha ID 734 */ 735 public static function getCaptchaId($new = true, array $options = array()) 736 { 737 if (is_null($new) || (bool)$new == true) { 738 $id = sha1(uniqid($_SERVER['REMOTE_ADDR'], true)); 739 $opts = array('no_session' => true, 740 'use_database' => true); 741 if (sizeof($options) > 0) $opts = array_merge($options, $opts); 742 $si = new self($opts); 743 Securimage::$_captchaId = $id; 744 $si->createCode(); 745 746 return $id; 747 } else { 748 return Securimage::$_captchaId; 749 } 750 } 751 752 /** 753 * Validate a captcha code input against a captcha ID 754 * 755 * @param string $id The captcha ID to check 756 * @param string $value The captcha value supplied by the user 757 * @param array $options Array of options to construct Securimage with. 758 * Options must include database options if they are not set in securimage.php 759 * 760 * @see Securimage::$database_driver 761 * @return bool true if the code was valid for the given captcha ID, false if not or if database failed to open 762 */ 763 public static function checkByCaptchaId($id, $value, array $options = array()) 764 { 765 $opts = array('captchaId' => $id, 766 'no_session' => true, 767 'use_database' => true); 768 769 if (sizeof($options) > 0) $opts = array_merge($options, $opts); 770 771 $si = new self($opts); 772 773 if ($si->openDatabase()) { 774 $code = $si->getCodeFromDatabase(); 775 776 if (is_array($code)) { 777 $si->code = $code['code']; 778 $si->code_display = $code['code_disp']; 779 } 780 781 if ($si->check($value)) { 782 $si->clearCodeFromDatabase(); 783 784 return true; 785 } else { 786 return false; 787 } 788 } else { 789 return false; 790 } 791 } 792 793 422 794 /** 423 795 * Used to serve a captcha image to the browser 424 796 * @param string $background_image The path to the background image to use 425 * <code> 797 * <code> 426 798 * $img = new Securimage(); 427 799 * $img->code_length = 6; 428 800 * $img->num_lines = 5; 429 801 * $img->noise_level = 5; 430 * 802 * 431 803 * $img->show(); // sends the image to browser 432 804 * exit; … … 435 807 public function show($background_image = '') 436 808 { 809 set_error_handler(array(&$this, 'errorHandler')); 810 437 811 if($background_image != '' && is_readable($background_image)) { 438 812 $this->bgimg = $background_image; … … 441 815 $this->doImage(); 442 816 } 443 817 444 818 /** 445 819 * Check a submitted code against the stored value … … 461 835 return $this->correct_code; 462 836 } 463 837 464 838 /** 465 839 * Output a wav file of the captcha code to the browser 466 * 840 * 467 841 * <code> 468 842 * $img = new Securimage(); … … 473 847 public function outputAudioFile() 474 848 { 475 $ext = 'wav'; // force wav - mp3 is insecure 476 477 header("Content-Disposition: attachment; filename=\"securimage_audio.{$ext}\""); 478 header('Cache-Control: no-store, no-cache, must-revalidate'); 479 header('Expires: Sun, 1 Jan 2000 12:00:00 GMT'); 480 header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT'); 481 header('Content-type: audio/x-wav'); 482 483 $audio = $this->getAudibleCode($ext); 484 485 header('Content-Length: ' . strlen($audio)); 486 487 echo $audio; 488 exit; 489 } 490 849 set_error_handler(array(&$this, 'errorHandler')); 850 851 require_once dirname(__FILE__) . '/WavFile.php'; 852 853 try { 854 $audio = $this->getAudibleCode(); 855 } catch (Exception $ex) { 856 if (($fp = @fopen(dirname(__FILE__) . '/si.error_log', 'a+')) !== false) { 857 fwrite($fp, date('Y-m-d H:i:s') . ': Securimage audio error "' . $ex->getMessage() . '"' . "\n"); 858 fclose($fp); 859 } 860 861 $audio = $this->audioError(); 862 } 863 864 if ($this->canSendHeaders() || $this->send_headers == false) { 865 if ($this->send_headers) { 866 $uniq = md5(uniqid(microtime())); 867 header("Content-Disposition: attachment; filename=\"securimage_audio-{$uniq}.wav\""); 868 header('Cache-Control: no-store, no-cache, must-revalidate'); 869 header('Expires: Sun, 1 Jan 2000 12:00:00 GMT'); 870 header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT'); 871 header('Content-type: audio/x-wav'); 872 873 if (extension_loaded('zlib')) { 874 ini_set('zlib.output_compression', true); // compress output if supported by browser 875 } else { 876 header('Content-Length: ' . strlen($audio)); 877 } 878 } 879 880 echo $audio; 881 } else { 882 echo '<hr /><strong>' 883 .'Failed to generate audio file, content has already been ' 884 .'output.<br />This is most likely due to misconfiguration or ' 885 .'a PHP error was sent to the browser.</strong>'; 886 } 887 888 restore_error_handler(); 889 890 if (!$this->no_exit) exit; 891 } 892 893 /** 894 * Return the code from the session or sqlite database if used. If none exists yet, an empty string is returned 895 * 896 * @param $array bool True to receive an array containing the code and properties 897 * @return array|string Array if $array = true, otherwise a string containing the code 898 */ 899 public function getCode($array = false, $returnExisting = false) 900 { 901 $code = ''; 902 $time = 0; 903 $disp = 'error'; 904 905 if ($returnExisting && strlen($this->code) > 0) { 906 if ($array) { 907 return array('code' => $this->code, 908 'display' => $this->code_display, 909 'code_display' => $this->code_display, 910 'time' => 0); 911 } else { 912 return $this->code; 913 } 914 } 915 916 if ($this->no_session != true) { 917 if (isset($_SESSION['securimage_code_value'][$this->namespace]) && 918 trim($_SESSION['securimage_code_value'][$this->namespace]) != '') { 919 if ($this->isCodeExpired( 920 $_SESSION['securimage_code_ctime'][$this->namespace]) == false) { 921 $code = $_SESSION['securimage_code_value'][$this->namespace]; 922 $time = $_SESSION['securimage_code_ctime'][$this->namespace]; 923 $disp = $_SESSION['securimage_code_disp'] [$this->namespace]; 924 } 925 } 926 } 927 928 if (empty($code) && $this->use_database) { 929 // no code in session - may mean user has cookies turned off 930 $this->openDatabase(); 931 $code = $this->getCodeFromDatabase(); 932 } else { /* no code stored in session or sqlite database, validation will fail */ } 933 934 if ($array == true) { 935 return array('code' => $code, 'ctime' => $time, 'display' => $disp); 936 } else { 937 return $code; 938 } 939 } 940 491 941 /** 492 942 * The main image drawing routing, responsible for constructing the entire image and serving it … … 499 949 $imagecreate = 'imagecreate'; 500 950 } 501 951 502 952 $this->im = $imagecreate($this->image_width, $this->image_height); 503 953 $this->tmpimg = $imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale); 504 954 505 955 $this->allocateColors(); 506 956 imagepalettecopy($this->tmpimg, $this->im); … … 508 958 $this->setBackground(); 509 959 510 $this->createCode(); 511 512 $this->drawWord(); 960 $code = ''; 961 962 if ($this->getCaptchaId(false) !== null) { 963 // a captcha Id was supplied 964 965 // check to see if a display_value for the captcha image was set 966 if (is_string($this->display_value) && strlen($this->display_value) > 0) { 967 $this->code_display = $this->display_value; 968 $this->code = ($this->case_sensitive) ? 969 $this->display_value : 970 strtolower($this->display_value); 971 $code = $this->code; 972 } else if ($this->openDatabase()) { 973 // no display_value, check the database for existing captchaId 974 $code = $this->getCodeFromDatabase(); 975 976 // got back a result from the database with a valid code for captchaId 977 if (is_array($code)) { 978 $this->code = $code['code']; 979 $this->code_display = $code['code_disp']; 980 $code = $code['code']; 981 } 982 } 983 } 984 985 if ($code == '') { 986 // if the code was not set using display_value or was not found in 987 // the database, create a new code 988 $this->createCode(); 989 } 513 990 514 991 if ($this->noise_level > 0) { 515 992 $this->drawNoise(); 516 993 } 517 994 995 $this->drawWord(); 996 518 997 if ($this->perturbation > 0 && is_readable($this->ttf_file)) { 519 998 $this->distortedCopy(); … … 530 1009 $this->output(); 531 1010 } 532 1011 533 1012 /** 534 1013 * Allocate the colors to be used for the image … … 541 1020 $this->image_bg_color->g, 542 1021 $this->image_bg_color->b); 543 1022 544 1023 $alpha = intval($this->text_transparency_percentage / 100 * 127); 545 1024 546 1025 if ($this->use_transparent_text == true) { 547 1026 $this->gdtextcolor = imagecolorallocatealpha($this->im, … … 574 1053 $this->noise_color->b); 575 1054 } 576 1055 577 1056 $this->gdsignaturecolor = imagecolorallocate($this->im, 578 1057 $this->signature_color->r, … … 581 1060 582 1061 } 583 1062 584 1063 /** 585 1064 * The the background color, or background image to be used … … 594 1073 $this->image_width * $this->iscale, $this->image_height * $this->iscale, 595 1074 $this->gdbgcolor); 596 1075 597 1076 if ($this->bgimg == '') { 598 if ($this->background_directory != null && 1077 if ($this->background_directory != null && 599 1078 is_dir($this->background_directory) && 600 1079 is_readable($this->background_directory)) … … 606 1085 } 607 1086 } 608 1087 609 1088 if ($this->bgimg == '') { 610 1089 return; … … 612 1091 613 1092 $dat = @getimagesize($this->bgimg); 614 if($dat == false) { 1093 if($dat == false) { 615 1094 return; 616 1095 } … … 629 1108 imagesx($newim), imagesy($newim)); 630 1109 } 631 1110 632 1111 /** 633 1112 * Scan the directory for a background image to use … … 645 1124 646 1125 if (sizeof($images) > 0) { 647 return rtrim($this->background_directory, '/') . '/' . $images[ rand(0, sizeof($images)-1)];1126 return rtrim($this->background_directory, '/') . '/' . $images[mt_rand(0, sizeof($images)-1)]; 648 1127 } 649 1128 } … … 651 1130 return false; 652 1131 } 653 1132 654 1133 /** 655 1134 * Generates the code or math problem and saves the value to the session 656 1135 */ 657 p rotectedfunction createCode()1136 public function createCode() 658 1137 { 659 1138 $this->code = false; … … 662 1141 case self::SI_CAPTCHA_MATHEMATIC: 663 1142 { 664 $signs = array('+', '-', 'x'); 665 $left = rand(1, 10); 666 $right = rand(1, 5); 667 $sign = $signs[rand(0, 2)]; 668 669 switch($sign) { 670 case 'x': $c = $left * $right; break; 671 case '-': $c = $left - $right; break; 672 default: $c = $left + $right; break; 673 } 674 1143 do { 1144 $signs = array('+', '-', 'x'); 1145 $left = mt_rand(1, 10); 1146 $right = mt_rand(1, 5); 1147 $sign = $signs[mt_rand(0, 2)]; 1148 1149 switch($sign) { 1150 case 'x': $c = $left * $right; break; 1151 case '-': $c = $left - $right; break; 1152 default: $c = $left + $right; break; 1153 } 1154 } while ($c <= 0); // no negative #'s or 0 1155 675 1156 $this->code = $c; 676 1157 $this->code_display = "$left $sign $right"; 677 1158 break; 678 1159 } 679 1160 1161 case self::SI_CAPTCHA_WORDS: 1162 $words = $this->readCodeFromFile(2); 1163 $this->code = implode(' ', $words); 1164 $this->code_display = $this->code; 1165 break; 1166 680 1167 default: 681 1168 { … … 687 1174 $this->code = $this->generateCode($this->code_length); 688 1175 } 689 1176 690 1177 $this->code_display = $this->code; 691 1178 $this->code = ($this->case_sensitive) ? $this->code : strtolower($this->code); 692 1179 } // default 693 1180 } 694 1181 695 1182 $this->saveData(); 696 1183 } 697 1184 698 1185 /** 699 1186 * Draws the captcha code on the image … … 703 1190 $width2 = $this->image_width * $this->iscale; 704 1191 $height2 = $this->image_height * $this->iscale; 705 1192 706 1193 if (!is_readable($this->ttf_file)) { 707 1194 imagestring($this->im, 4, 10, ($this->image_height / 2) - 5, 'Failed to load TTF font file!', $this->gdtextcolor); … … 727 1214 } 728 1215 } 729 1216 730 1217 // DEBUG 731 1218 //$this->im = $this->tmpimg; 732 1219 //$this->output(); 733 734 } 735 1220 1221 } 1222 736 1223 /** 737 1224 * Copies the captcha image to the final image with distortion applied … … 742 1229 // make array of poles AKA attractor points 743 1230 for ($i = 0; $i < $numpoles; ++ $i) { 744 $px[$i] = rand($this->image_width * 0.2, $this->image_width * 0.8);745 $py[$i] = rand($this->image_height * 0.2, $this->image_height * 0.8);746 $rad[$i] = rand($this->image_height * 0.2, $this->image_height * 0.8);1231 $px[$i] = mt_rand($this->image_width * 0.2, $this->image_width * 0.8); 1232 $py[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8); 1233 $rad[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8); 747 1234 $tmp = ((- $this->frand()) * 0.15) - .15; 748 1235 $amp[$i] = $this->perturbation * $tmp; 749 1236 } 750 1237 751 1238 $bgCol = imagecolorat($this->tmpimg, 0, 0); 752 1239 $width2 = $this->iscale * $this->image_width; … … 784 1271 } 785 1272 } 786 1273 787 1274 /** 788 1275 * Draws distorted lines on the image … … 793 1280 $x = $this->image_width * (1 + $line) / ($this->num_lines + 1); 794 1281 $x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines; 795 $y = rand($this->image_height * 0.1, $this->image_height * 0.9);796 1282 $y = mt_rand($this->image_height * 0.1, $this->image_height * 0.9); 1283 797 1284 $theta = ($this->frand() - 0.5) * M_PI * 0.7; 798 1285 $w = $this->image_width; 799 $len = rand($w * 0.4, $w * 0.7);800 $lwid = rand(0, 2);801 1286 $len = mt_rand($w * 0.4, $w * 0.7); 1287 $lwid = mt_rand(0, 2); 1288 802 1289 $k = $this->frand() * 0.6 + 0.2; 803 1290 $k = $k * $k * 0.5; … … 810 1297 $x0 = $x - 0.5 * $len * cos($theta); 811 1298 $y0 = $y - 0.5 * $len * sin($theta); 812 1299 813 1300 $ldx = round(- $dy * $lwid); 814 1301 $ldy = round($dx * $lwid); 815 1302 816 1303 for ($i = 0; $i < $n; ++ $i) { 817 1304 $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi); … … 821 1308 } 822 1309 } 823 1310 824 1311 /** 825 1312 * Draws random noise on the image … … 834 1321 835 1322 $t0 = microtime(true); 836 1323 837 1324 $noise_level *= 125; // an arbitrary number that works well on a 1-10 scale 838 1325 839 1326 $points = $this->image_width * $this->image_height * $this->iscale; 840 1327 $height = $this->image_height * $this->iscale; 841 1328 $width = $this->image_width * $this->iscale; 842 1329 for ($i = 0; $i < $noise_level; ++$i) { 843 $x = rand(10, $width);844 $y = rand(10, $height);845 $size = rand(7, 10);1330 $x = mt_rand(10, $width); 1331 $y = mt_rand(10, $height); 1332 $size = mt_rand(7, 10); 846 1333 if ($x - $size <= 0 && $y - $size <= 0) continue; // dont cover 0,0 since it is used by imagedistortedcopy 847 1334 imagefilledarc($this->tmpimg, $x, $y, $size, $size, 0, 360, $this->gdnoisecolor, IMG_ARC_PIE); 848 1335 } 849 1336 850 1337 $t1 = microtime(true); 851 1338 852 1339 $t = $t1 - $t0; 853 1340 854 1341 /* 855 1342 // DEBUG … … 860 1347 */ 861 1348 } 862 863 864 865 1349 1350 /** 1351 * Print signature text on image 1352 */ 866 1353 protected function addSignature() 867 1354 { 868 if ($this->use_gd_font) { 869 imagestring($this->im, 5, $this->image_width - (strlen($this->image_signature) * 10), $this->image_height - 20, $this->image_signature, $this->gdsignaturecolor); 1355 $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature); 1356 $textlen = $bbox[2] - $bbox[0]; 1357 $x = $this->image_width - $textlen - 5; 1358 $y = $this->image_height - 3; 1359 1360 imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature); 1361 } 1362 1363 /** 1364 * Sends the appropriate image and cache headers and outputs image to the browser 1365 */ 1366 protected function output() 1367 { 1368 if ($this->canSendHeaders() || $this->send_headers == false) { 1369 if ($this->send_headers) { 1370 // only send the content-type headers if no headers have been output 1371 // this will ease debugging on misconfigured servers where warnings 1372 // may have been output which break the image and prevent easily viewing 1373 // source to see the error. 1374 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); 1375 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT"); 1376 header("Cache-Control: no-store, no-cache, must-revalidate"); 1377 header("Cache-Control: post-check=0, pre-check=0", false); 1378 header("Pragma: no-cache"); 1379 } 1380 1381 switch ($this->image_type) { 1382 case self::SI_IMAGE_JPEG: 1383 if ($this->send_headers) header("Content-Type: image/jpeg"); 1384 imagejpeg($this->im, null, 90); 1385 break; 1386 case self::SI_IMAGE_GIF: 1387 if ($this->send_headers) header("Content-Type: image/gif"); 1388 imagegif($this->im); 1389 break; 1390 default: 1391 if ($this->send_headers) header("Content-Type: image/png"); 1392 imagepng($this->im); 1393 break; 1394 } 870 1395 } else { 871 872 $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature); 873 $textlen = $bbox[2] - $bbox[0]; 874 $x = $this->image_width - $textlen - 5; 875 $y = $this->image_height - 3; 876 877 imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature); 878 } 879 } 880 881 /** 882 * Sends the appropriate image and cache headers and outputs image to the browser 883 */ 884 protected function output() 885 { 886 header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); 887 header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT"); 888 header("Cache-Control: no-store, no-cache, must-revalidate"); 889 header("Cache-Control: post-check=0, pre-check=0", false); 890 header("Pragma: no-cache"); 891 892 switch ($this->image_type) { 893 case self::SI_IMAGE_JPEG: 894 header("Content-Type: image/jpeg"); 895 imagejpeg($this->im, null, 90); 896 break; 897 case self::SI_IMAGE_GIF: 898 header("Content-Type: image/gif"); 899 imagegif($this->im); 900 break; 901 default: 902 header("Content-Type: image/png"); 903 imagepng($this->im); 904 break; 905 } 906 1396 echo '<hr /><strong>' 1397 .'Failed to generate captcha image, content has already been ' 1398 .'output.<br />This is most likely due to misconfiguration or ' 1399 .'a PHP error was sent to the browser.</strong>'; 1400 } 1401 907 1402 imagedestroy($this->im); 908 exit(); 909 } 910 1403 restore_error_handler(); 1404 1405 if (!$this->no_exit) exit; 1406 } 1407 911 1408 /** 912 1409 * Gets the code and returns the binary audio file for the stored captcha code 913 * @param string $format WAV only 914 */ 915 protected function getAudibleCode($format = 'wav') 916 { 917 // override any format other than wav for now 918 // this is due to security issues with MP3 files 919 $format = 'wav'; 920 1410 * 1411 * @return The audio representation of the captcha in Wav format 1412 */ 1413 protected function getAudibleCode() 1414 { 921 1415 $letters = array(); 922 $code = $this->getCode(); 923 924 if ($code == '') { 925 $this->createCode(); 926 $code = $this->getCode(); 927 } 928 929 for($i = 0; $i < strlen($code); ++$i) { 930 $letters[] = $code{$i}; 931 } 932 933 if ($format == 'mp3') { 934 return $this->generateMP3($letters); 1416 $code = $this->getCode(true, true); 1417 1418 if ($code['code'] == '') { 1419 if (strlen($this->display_value) > 0) { 1420 $code = array('code' => $this->display_value, 'display' => $this->display_value); 1421 } else { 1422 $this->createCode(); 1423 $code = $this->getCode(true); 1424 } 1425 } 1426 1427 if (preg_match('/(\d+) (\+|-|x) (\d+)/i', $code['display'], $eq)) { 1428 $math = true; 1429 1430 $left = $eq[1]; 1431 $sign = str_replace(array('+', '-', 'x'), array('plus', 'minus', 'times'), $eq[2]); 1432 $right = $eq[3]; 1433 1434 $letters = array($left, $sign, $right); 935 1435 } else { 1436 $math = false; 1437 1438 $length = strlen($code['display']); 1439 1440 for($i = 0; $i < $length; ++$i) { 1441 $letter = $code['display']{$i}; 1442 $letters[] = $letter; 1443 } 1444 } 1445 1446 try { 936 1447 return $this->generateWAV($letters); 1448 } catch(Exception $ex) { 1449 throw $ex; 937 1450 } 938 1451 } … … 941 1454 * Gets a captcha code from a wordlist 942 1455 */ 943 protected function readCodeFromFile( )944 { 945 $fp = @fopen($this->wordlist_file, 'rb');1456 protected function readCodeFromFile($numWords = 1) 1457 { 1458 $fp = fopen($this->wordlist_file, 'rb'); 946 1459 if (!$fp) return false; 947 1460 … … 949 1462 if ($fsize < 128) return false; // too small of a list to be effective 950 1463 951 fseek($fp, rand(0, $fsize - 64), SEEK_SET); // seek to a random position of file from 0 to filesize-64 952 $data = fread($fp, 64); // read a chunk from our random position 1464 if ((int)$numWords < 1 || (int)$numWords > 5) $numWords = 1; 1465 1466 $words = array(); 1467 $i = 0; 1468 do { 1469 fseek($fp, mt_rand(0, $fsize - 64), SEEK_SET); // seek to a random position of file from 0 to filesize-64 1470 $data = fread($fp, 64); // read a chunk from our random position 1471 $data = preg_replace("/\r?\n/", "\n", $data); 1472 1473 $start = @strpos($data, "\n", mt_rand(0, 56)) + 1; // random start position 1474 $end = @strpos($data, "\n", $start); // find end of word 1475 1476 if ($start === false) { 1477 // picked start position at end of file 1478 continue; 1479 } else if ($end === false) { 1480 $end = strlen($data); 1481 } 1482 1483 $word = strtolower(substr($data, $start, $end - $start)); // return a line of the file 1484 $words[] = $word; 1485 } while (++$i < $numWords); 1486 953 1487 fclose($fp); 954 $data = preg_replace("/\r?\n/", "\n", $data); 955 956 $start = @strpos($data, "\n", rand(0, 56)) + 1; // random start position 957 $end = @strpos($data, "\n", $start); // find end of word 958 959 if ($start === false) { 960 return false; 961 } else if ($end === false) { 962 $end = strlen($data); 963 } 964 965 return strtolower(substr($data, $start, $end - $start)); // return a line of the file 966 } 967 1488 1489 if ($numWords < 2) { 1490 return $words[0]; 1491 } else { 1492 return $words; 1493 } 1494 } 1495 968 1496 /** 969 1497 * Generates a random captcha code from the set character set … … 973 1501 $code = ''; 974 1502 975 for($i = 1, $cslen = strlen($this->charset); $i <= $this->code_length; ++$i) { 976 $code .= $this->charset{rand(0, $cslen - 1)}; 977 } 978 979 //return 'testing'; // debug, set the code to given string 980 1503 if (function_exists('mb_strlen')) { 1504 for($i = 1, $cslen = mb_strlen($this->charset); $i <= $this->code_length; ++$i) { 1505 $code .= mb_substr($this->charset, mt_rand(0, $cslen - 1), 1, 'UTF-8'); 1506 } 1507 } else { 1508 for($i = 1, $cslen = strlen($this->charset); $i <= $this->code_length; ++$i) { 1509 $code .= substr($this->charset, mt_rand(0, $cslen - 1), 1); 1510 } 1511 } 1512 981 1513 return $code; 982 1514 } 983 1515 984 1516 /** 985 1517 * Checks the entered code against the value stored in the session or sqlite database, handles case sensitivity … … 988 1520 protected function validate() 989 1521 { 990 $code = $this->getCode(); 991 // returns stored code, or an empty string if no stored code was found 992 // checks the session and sqlite database if enabled 993 1522 if (!is_string($this->code) || strlen($this->code) == 0) { 1523 $code = $this->getCode(); 1524 // returns stored code, or an empty string if no stored code was found 1525 // checks the session and database if enabled 1526 } else { 1527 $code = $this->code; 1528 } 1529 994 1530 if ($this->case_sensitive == false && preg_match('/[A-Z]/', $code)) { 995 1531 // case sensitive was set from securimage_show.php but not in class … … 997 1533 $this->case_sensitive = true; 998 1534 } 999 1535 1000 1536 $code_entered = trim( (($this->case_sensitive) ? $this->code_entered 1001 1537 : strtolower($this->code_entered)) 1002 1538 ); 1003 1539 $this->correct_code = false; 1004 1540 1005 1541 if ($code != '') { 1542 if (strpos($code, ' ') !== false) { 1543 // for multi word captchas, remove more than once space from input 1544 $code_entered = preg_replace('/\s+/', ' ', $code_entered); 1545 $code_entered = strtolower($code_entered); 1546 } 1547 1006 1548 if ($code == $code_entered) { 1007 1549 $this->correct_code = true; 1008 $_SESSION['securimage_code_value'][$this->namespace] = ''; 1009 $_SESSION['securimage_code_ctime'][$this->namespace] = ''; 1550 if ($this->no_session != true) { 1551 $_SESSION['securimage_code_value'][$this->namespace] = ''; 1552 $_SESSION['securimage_code_ctime'][$this->namespace] = ''; 1553 } 1010 1554 $this->clearCodeFromDatabase(); 1011 1555 } 1012 1556 } 1013 1557 } 1014 1015 /** 1016 * Return the code from the session or sqlite database if used. If none exists yet, an empty string is returned 1017 */ 1018 protected function getCode() 1558 1559 /** 1560 * Save data to session namespace and database if used 1561 */ 1562 protected function saveData() 1563 { 1564 if ($this->no_session != true) { 1565 if (isset($_SESSION['securimage_code_value']) && is_scalar($_SESSION['securimage_code_value'])) { 1566 // fix for migration from v2 - v3 1567 unset($_SESSION['securimage_code_value']); 1568 unset($_SESSION['securimage_code_ctime']); 1569 } 1570 1571 $_SESSION['securimage_code_disp'] [$this->namespace] = $this->code_display; 1572 $_SESSION['securimage_code_value'][$this->namespace] = $this->code; 1573 $_SESSION['securimage_code_ctime'][$this->namespace] = time(); 1574 } 1575 1576 if ($this->use_database) { 1577 $this->saveCodeToDatabase(); 1578 } 1579 } 1580 1581 /** 1582 * Saves the code to the sqlite database 1583 */ 1584 protected function saveCodeToDatabase() 1585 { 1586 $success = false; 1587 $this->openDatabase(); 1588 1589 if ($this->use_database && $this->pdo_conn) { 1590 $id = $this->getCaptchaId(false); 1591 $ip = $_SERVER['REMOTE_ADDR']; 1592 1593 if (empty($id)) { 1594 $id = $ip; 1595 } 1596 1597 $time = time(); 1598 $code = $this->code; 1599 $code_disp = $this->code_display; 1600 1601 // This is somewhat expensive in PDO Sqlite3 (when there is something to delete) 1602 $this->clearCodeFromDatabase(); 1603 1604 $query = "INSERT INTO {$this->database_table} (" 1605 ."id, code, code_display, namespace, created) " 1606 ."VALUES(?, ?, ?, ?, ?)"; 1607 1608 $stmt = $this->pdo_conn->prepare($query); 1609 $success = $stmt->execute(array($id, $code, $code_disp, $this->namespace, $time)); 1610 1611 if (!$success) { 1612 $err = $stmt->errorInfo(); 1613 trigger_error("Failed to insert code into database. {$err[1]}: {$err[2]}", E_USER_WARNING); 1614 } 1615 } 1616 1617 return $success !== false; 1618 } 1619 1620 /** 1621 * Open sqlite database 1622 */ 1623 protected function openDatabase() 1624 { 1625 $this->pdo_conn = false; 1626 1627 if ($this->use_database) { 1628 $pdo_extension = 'PDO_' . strtoupper($this->database_driver); 1629 1630 if (!extension_loaded($pdo_extension)) { 1631 trigger_error("Database support is turned on in Securimage, but the chosen extension $pdo_extension is not loaded in PHP.", E_USER_WARNING); 1632 return false; 1633 } 1634 } 1635 1636 if ($this->database_driver == self::SI_DRIVER_SQLITE3) { 1637 if (!file_exists($this->database_file)) { 1638 $fp = fopen($this->database_file, 'w+'); 1639 if (!$fp) { 1640 $err = error_get_last(); 1641 trigger_error("Securimage failed to create SQLite3 database file '{$this->database_file}'. Reason: {$err['message']}", E_USER_WARNING); 1642 return false; 1643 } 1644 fclose($fp); 1645 chmod($this->database_file, 0666); 1646 } else if (!is_writeable($this->database_file)) { 1647 trigger_error("Securimage does not have read/write access to database file '{$this->database_file}. Make sure permissions are 0666 and writeable by user '" . get_current_user() . "'", E_USER_WARNING); 1648 return false; 1649 } 1650 } 1651 1652 $dsn = $this->getDsn(); 1653 1654 try { 1655 $options = array(); 1656 $this->pdo_conn = new PDO($dsn, $this->database_user, $this->database_pass, $options); 1657 } catch (PDOException $pdoex) { 1658 trigger_error("Database connection failed: " . $pdoex->getMessage(), E_USER_WARNING); 1659 return false; 1660 } 1661 1662 try { 1663 if (!$this->checkTablesExist()) { 1664 // create tables... 1665 $this->createDatabaseTables(); 1666 } 1667 } catch (Exception $ex) { 1668 trigger_error($ex->getMessage(), E_USER_WARNING); 1669 $this->pdo_conn = null; 1670 return false; 1671 } 1672 1673 if (mt_rand(0, 100) / 100.0 == 1.0) { 1674 $this->purgeOldCodesFromDatabase(); 1675 } 1676 1677 return $this->pdo_conn; 1678 } 1679 1680 protected function getDsn() 1681 { 1682 $dsn = sprintf('%s:', $this->database_driver); 1683 1684 switch($this->database_driver) { 1685 case self::SI_DRIVER_SQLITE3: 1686 $dsn .= $this->database_file; 1687 break; 1688 1689 case self::SI_DRIVER_MYSQL: 1690 case self::SI_DRIVER_PGSQL: 1691 $dsn .= sprintf('host=%s;dbname=%s', 1692 $this->database_host, 1693 $this->database_name); 1694 break; 1695 1696 } 1697 1698 return $dsn; 1699 } 1700 1701 protected function checkTablesExist() 1702 { 1703 $table = $this->pdo_conn->quote($this->database_table); 1704 1705 switch($this->database_driver) { 1706 case self::SI_DRIVER_SQLITE3: 1707 // query row count for sqlite, PRAGMA queries seem to return no 1708 // rowCount using PDO even if there are rows returned 1709 $query = "SELECT COUNT(id) FROM $table"; 1710 break; 1711 1712 case self::SI_DRIVER_MYSQL: 1713 $query = "SHOW TABLES LIKE $table"; 1714 break; 1715 1716 case self::SI_DRIVER_PGSQL: 1717 $query = "SELECT * FROM information_schema.columns WHERE table_name = $table;"; 1718 break; 1719 } 1720 1721 $result = $this->pdo_conn->query($query); 1722 1723 if (!$result) { 1724 $err = $this->pdo_conn->errorInfo(); 1725 1726 if ($this->database_driver == self::SI_DRIVER_SQLITE3 && 1727 $err[1] === 1 && strpos($err[2], 'no such table') !== false) 1728 { 1729 return false; 1730 } 1731 1732 throw new Exception("Failed to check tables: {$err[0]} - {$err[1]}: {$err[2]}"); 1733 } else if ($this->database_driver == self::SI_DRIVER_SQLITE3) { 1734 // successful here regardless of row count for sqlite 1735 return true; 1736 } else if ($result->rowCount() == 0) { 1737 return false; 1738 } else { 1739 return true; 1740 } 1741 } 1742 1743 protected function createDatabaseTables() 1744 { 1745 $queries = array(); 1746 1747 switch($this->database_driver) { 1748 case self::SI_DRIVER_SQLITE3: 1749 $queries[] = "CREATE TABLE \"{$this->database_table}\" ( 1750 id VARCHAR(40), 1751 namespace VARCHAR(32) NOT NULL, 1752 code VARCHAR(32) NOT NULL, 1753 code_display VARCHAR(32) NOT NULL, 1754 created INTEGER NOT NULL, 1755 PRIMARY KEY(id, namespace) 1756 )"; 1757 1758 $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created)"; 1759 break; 1760 1761 case self::SI_DRIVER_MYSQL: 1762 $queries[] = "CREATE TABLE `{$this->database_table}` ( 1763 `id` VARCHAR(40) NOT NULL, 1764 `namespace` VARCHAR(32) NOT NULL, 1765 `code` VARCHAR(32) NOT NULL, 1766 `code_display` VARCHAR(32) NOT NULL, 1767 `created` INT NOT NULL, 1768 PRIMARY KEY(id, namespace), 1769 INDEX(created) 1770 )"; 1771 break; 1772 1773 case self::SI_DRIVER_PGSQL: 1774 $queries[] = "CREATE TABLE {$this->database_table} ( 1775 id character varying(40) NOT NULL, 1776 namespace character varying(32) NOT NULL, 1777 code character varying(32) NOT NULL, 1778 code_display character varying(32) NOT NULL, 1779 created integer NOT NULL, 1780 CONSTRAINT pkey_id_namespace PRIMARY KEY (id, namespace) 1781 )"; 1782 1783 $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created);"; 1784 break; 1785 } 1786 1787 $this->pdo_conn->beginTransaction(); 1788 1789 foreach($queries as $query) { 1790 $result = $this->pdo_conn->query($query); 1791 1792 if (!$result) { 1793 $err = $this->pdo_conn->errorInfo(); 1794 trigger_error("Failed to create table. {$err[1]}: {$err[2]}", E_USER_WARNING); 1795 $this->pdo_conn->rollBack(); 1796 $this->pdo_conn = false; 1797 return false; 1798 } 1799 } 1800 1801 $this->pdo_conn->commit(); 1802 1803 return true; 1804 } 1805 1806 /** 1807 * Get a code from the sqlite database for ip address/captchaId. 1808 * 1809 * @return string|array Empty string if no code was found or has expired, 1810 * otherwise returns the stored captcha code. If a captchaId is set, this 1811 * returns an array with indices "code" and "code_disp" 1812 */ 1813 protected function getCodeFromDatabase() 1019 1814 { 1020 1815 $code = ''; 1021 1022 if (isset($_SESSION['securimage_code_value'][$this->namespace]) && 1023 trim($_SESSION['securimage_code_value'][$this->namespace]) != '') { 1024 if ($this->isCodeExpired( 1025 $_SESSION['securimage_code_ctime'][$this->namespace]) == false) { 1026 $code = $_SESSION['securimage_code_value'][$this->namespace]; 1027 } 1028 } else if ($this->use_sqlite_db == true && function_exists('sqlite_open')) { 1029 // no code in session - may mean user has cookies turned off 1030 $this->openDatabase(); 1031 $code = $this->getCodeFromDatabase(); 1032 } else { /* no code stored in session or sqlite database, validation will fail */ } 1033 1816 1817 if ($this->use_database == true && $this->pdo_conn) { 1818 if (Securimage::$_captchaId !== null) { 1819 $query = "SELECT * FROM {$this->database_table} WHERE id = ?"; 1820 $stmt = $this->pdo_conn->prepare($query); 1821 $result = $stmt->execute(array(Securimage::$_captchaId)); 1822 } else { 1823 $ip = $_SERVER['REMOTE_ADDR']; 1824 $ns = $this->namespace; 1825 1826 // ip is stored in id column when no captchaId 1827 $query = "SELECT * FROM {$this->database_table} WHERE id = ? AND namespace = ?"; 1828 $stmt = $this->pdo_conn->prepare($query); 1829 $result = $stmt->execute(array($ip, $ns)); 1830 } 1831 1832 if (!$result) { 1833 $err = $this->pdo_conn->errorInfo(); 1834 trigger_error("Failed to select code from database. {$err[0]}: {$err[1]}", E_USER_WARNING); 1835 } else { 1836 if ( ($row = $stmt->fetch()) !== false ) { 1837 if (false == $this->isCodeExpired($row['created'])) { 1838 if (Securimage::$_captchaId !== null) { 1839 // return an array when using captchaId 1840 $code = array('code' => $row['code'], 1841 'code_disp' => $row['code_display']); 1842 } else { 1843 $code = $row['code']; 1844 } 1845 } 1846 } 1847 } 1848 } 1849 1034 1850 return $code; 1035 1851 } 1036 1037 /** 1038 * Save data to session namespace and database if used 1039 */ 1040 protected function saveData() 1041 { 1042 $_SESSION['securimage_code_value'][$this->namespace] = $this->code; 1043 $_SESSION['securimage_code_ctime'][$this->namespace] = time(); 1044 1045 $this->saveCodeToDatabase(); 1046 } 1047 1048 /** 1049 * Saves the code to the sqlite database 1050 */ 1051 protected function saveCodeToDatabase() 1052 { 1053 $success = false; 1054 1055 $this->openDatabase(); 1056 1057 if ($this->use_sqlite_db && $this->sqlite_handle !== false) { 1058 $ip = $_SERVER['REMOTE_ADDR']; 1059 $time = time(); 1060 $code = $_SESSION['securimage_code_value'][$this->namespace]; // if cookies are disabled the session still exists at this point 1061 $success = sqlite_query($this->sqlite_handle, 1062 "INSERT OR REPLACE INTO codes(ip, code, namespace, created) 1063 VALUES('$ip', '$code', '{$this->namespace}', $time)"); 1064 } 1065 1066 return $success !== false; 1067 } 1068 1069 /** 1070 * Open sqlite database 1071 */ 1072 protected function openDatabase() 1073 { 1074 $this->sqlite_handle = false; 1075 1076 if ($this->use_sqlite_db && function_exists('sqlite_open')) { 1077 $this->sqlite_handle = sqlite_open($this->sqlite_database, 0666, $error); 1078 1079 if ($this->sqlite_handle !== false) { 1080 $res = sqlite_query($this->sqlite_handle, "PRAGMA table_info(codes)"); 1081 if (sqlite_num_rows($res) == 0) { 1082 sqlite_query($this->sqlite_handle, "CREATE TABLE codes (ip VARCHAR(32) PRIMARY KEY, code VARCHAR(32) NOT NULL, namespace VARCHAR(32) NOT NULL, created INTEGER)"); 1083 } 1084 } 1085 1086 return $this->sqlite_handle != false; 1087 } 1088 1089 return $this->sqlite_handle; 1090 } 1091 1092 /** 1093 * Get a code from the sqlite database for ip address 1094 */ 1095 protected function getCodeFromDatabase() 1096 { 1097 $code = ''; 1098 1099 if ($this->use_sqlite_db && $this->sqlite_handle !== false) { 1852 1853 /** 1854 * Remove an entered code from the database 1855 */ 1856 protected function clearCodeFromDatabase() 1857 { 1858 if ($this->pdo_conn) { 1100 1859 $ip = $_SERVER['REMOTE_ADDR']; 1101 $ns = sqlite_escape_string($this->namespace); 1102 1103 $res = sqlite_query($this->sqlite_handle, "SELECT * FROM codes WHERE ip = '$ip' AND namespace = '$ns'"); 1104 if ($res && sqlite_num_rows($res) > 0) { 1105 $res = sqlite_fetch_array($res); 1106 1107 if ($this->isCodeExpired($res['created']) == false) { 1108 $code = $res['code']; 1109 } 1110 } 1111 } 1112 return $code; 1113 } 1114 1115 /** 1116 * Remove an entered code from the database 1117 */ 1118 protected function clearCodeFromDatabase() 1119 { 1120 if (is_resource($this->sqlite_handle)) { 1121 $ip = $_SERVER['REMOTE_ADDR']; 1122 $ns = sqlite_escape_string($this->namespace); 1123 1124 sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE ip = '$ip' AND namespace = '$ns'"); 1125 } 1126 } 1127 1860 $ns = $this->pdo_conn->quote($this->namespace); 1861 $id = Securimage::$_captchaId; 1862 1863 if (empty($id)) { 1864 $id = $ip; // if no captchaId set, IP address is captchaId. 1865 } 1866 1867 $id = $this->pdo_conn->quote($id); 1868 1869 $query = sprintf("DELETE FROM %s WHERE id = %s AND namespace = %s", 1870 $this->database_table, $id, $ns); 1871 1872 $result = $this->pdo_conn->query($query); 1873 if (!$result) { 1874 trigger_error("Failed to delete code from database.", E_USER_WARNING); 1875 } 1876 } 1877 } 1878 1128 1879 /** 1129 1880 * Deletes old codes from sqlite database … … 1131 1882 protected function purgeOldCodesFromDatabase() 1132 1883 { 1133 if ($this->use_ sqlite_db && $this->sqlite_handle !== false) {1884 if ($this->use_database && $this->pdo_conn) { 1134 1885 $now = time(); 1135 1886 $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time; 1136 1137 sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE $now - created > $limit"); 1138 } 1139 } 1140 1887 1888 $query = sprintf("DELETE FROM %s WHERE %s - created > %s", 1889 $this->database_table, 1890 $this->pdo_conn->quote($now, PDO::PARAM_INT), 1891 $this->pdo_conn->quote($limit, PDO::PARAM_INT)); 1892 1893 $result = $this->pdo_conn->query($query); 1894 } 1895 } 1896 1141 1897 /** 1142 1898 * Checks to see if the captcha code has expired and cannot be used … … 1146 1902 { 1147 1903 $expired = true; 1148 1904 1149 1905 if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) { 1150 1906 $expired = false; … … 1152 1908 $expired = false; 1153 1909 } 1154 1910 1155 1911 return $expired; 1156 1912 } 1157 1158 /** 1159 * 1160 * Generate an MP3 audio file of the captcha image 1161 * 1162 * @deprecated 3.0 1163 */ 1164 protected function generateMP3() 1165 { 1166 return false; 1167 } 1168 1913 1169 1914 /** 1170 1915 * Generate a wav file given the $letters in the code … … 1175 1920 protected function generateWAV($letters) 1176 1921 { 1177 $data_len = 0; 1178 $files = array(); 1179 $out_data = ''; 1180 $out_channels = 0; 1181 $out_samplert = 0; 1182 $out_bpersample = 0; 1183 $numSamples = 0; 1184 $removeChunks = array('LIST', 'DISP', 'NOTE'); 1185 1186 for ($i = 0; $i < sizeof($letters); ++$i) { 1187 $letter = $letters[$i]; 1188 $filename = $this->audio_path . strtoupper($letter) . '.wav'; 1189 $file = array(); 1190 $data = @file_get_contents($filename); 1191 1192 if ($data === false) { 1193 // echo "Failed to read $filename"; 1194 return $this->audioError(); 1195 } 1196 1197 $header = substr($data, 0, 36); 1198 $info = unpack('NChunkID/VChunkSize/NFormat/NSubChunk1ID/' 1199 .'VSubChunk1Size/vAudioFormat/vNumChannels/' 1200 .'VSampleRate/VByteRate/vBlockAlign/vBitsPerSample', 1201 $header); 1202 1203 $dataPos = strpos($data, 'data'); 1204 $out_channels = $info['NumChannels']; 1205 $out_samplert = $info['SampleRate']; 1206 $out_bpersample = $info['BitsPerSample']; 1207 1208 if ($dataPos === false) { 1209 // wav file with no data? 1210 // echo "Failed to find DATA segment in $filename"; 1211 return $this->audioError(); 1212 } 1213 1214 if ($info['AudioFormat'] != 1) { 1215 // only work with PCM audio 1216 // echo "$filename was not PCM audio, only PCM is supported"; 1217 return $this->audioError(); 1218 } 1219 1220 if ($info['SubChunk1Size'] != 16 && $info['SubChunk1Size'] != 18) { 1221 // probably unsupported extension 1222 // echo "Bad SubChunk1Size in $filename - Size was {$info['SubChunk1Size']}"; 1223 return $this->audioError(); 1224 } 1225 1226 if ($info['SubChunk1Size'] > 16) { 1227 $header .= substr($data, 36, $info['SubChunk1Size'] - 16); 1228 } 1229 1230 if ($i == 0) { 1231 // create the final file's header, size will be adjusted later 1232 $out_data = $header . 'data'; 1233 } 1234 1235 $removed = 0; 1236 1237 foreach($removeChunks as $chunk) { 1238 $chunkPos = strpos($data, $chunk); 1239 if ($chunkPos !== false) { 1240 $listSize = unpack('VSize', substr($data, $chunkPos + 4, 4)); 1241 1242 $data = substr($data, 0, $chunkPos) . 1243 substr($data, $chunkPos + 8 + $listSize['Size']); 1244 1245 $removed += $listSize['Size'] + 8; 1922 $wavCaptcha = new WavFile(); 1923 $first = true; // reading first wav file 1924 1925 foreach ($letters as $letter) { 1926 $letter = strtoupper($letter); 1927 1928 try { 1929 $l = new WavFile($this->audio_path . '/' . $letter . '.wav'); 1930 1931 if ($first) { 1932 // set sample rate, bits/sample, and # of channels for file based on first letter 1933 $wavCaptcha->setSampleRate($l->getSampleRate()) 1934 ->setBitsPerSample($l->getBitsPerSample()) 1935 ->setNumChannels($l->getNumChannels()); 1936 $first = false; 1246 1937 } 1247 } 1248 1249 $dataSize = unpack('VSubchunk2Size', substr($data, $dataPos + 4, 4)); 1250 $dataSize['Subchunk2Size'] -= $removed; 1251 $out_data .= substr($data, $dataPos + 8, $dataSize['Subchunk2Size'] * ($out_bpersample / 8)); 1252 $numSamples += $dataSize['Subchunk2Size']; 1253 } 1254 1255 $filesize = strlen($out_data); 1256 $chunkSize = $filesize - 8; 1257 $dataCSize = $numSamples; 1258 1259 $out_data = substr_replace($out_data, pack('V', $chunkSize), 4, 4); 1260 $out_data = substr_replace($out_data, pack('V', $numSamples), 40 + ($info['SubChunk1Size'] - 16), 4); 1261 1262 $this->scrambleAudioData($out_data, 'wav'); 1263 1264 return $out_data; 1265 } 1266 1267 /** 1268 * Randomizes the audio data to add noise and prevent binary recognition 1269 * @param string $data The binary audio file data 1270 * @param string $format The format of the sound file (wav only) 1271 */ 1272 protected function scrambleAudioData(&$data, $format) 1273 { 1274 $start = strpos($data, 'data') + 4; // look for "data" indicator 1275 if ($start === false) $start = 44; // if not found assume 44 byte header 1276 1277 $start += rand(1, 4); // randomize starting offset 1278 $datalen = strlen($data) - $start; 1279 $step = 1; 1280 1281 for ($i = $start; $i < $datalen; $i += $step) { 1282 $ch = ord($data{$i}); 1283 if ($ch == 0 || $ch == 255) continue; 1284 1285 if ($ch < 16 || $ch > 239) { 1286 $ch += rand(-6, 6); 1287 } else { 1288 $ch += rand(-12, 12); 1289 } 1290 1291 if ($ch < 0) $ch = 0; else if ($ch > 255) $ch = 255; 1292 1293 $data{$i} = chr($ch); 1294 1295 $step = rand(1,4); 1296 } 1297 1298 return $data; 1299 } 1300 1938 1939 // append letter to the captcha audio 1940 $wavCaptcha->appendWav($l); 1941 1942 // random length of silence between $audio_gap_min and $audio_gap_max 1943 if ($this->audio_gap_max > 0 && $this->audio_gap_max > $this->audio_gap_min) { 1944 $wavCaptcha->insertSilence( mt_rand($this->audio_gap_min, $this->audio_gap_max) / 1000.0 ); 1945 } 1946 } catch (Exception $ex) { 1947 // failed to open file, or the wav file is broken or not supported 1948 // 2 wav files were not compatible, different # channels, bits/sample, or sample rate 1949 throw $ex; 1950 } 1951 } 1952 1953 /********* Set up audio filters *****************************/ 1954 $filters = array(); 1955 1956 if ($this->audio_use_noise == true) { 1957 // use background audio - find random file 1958 $noiseFile = $this->getRandomNoiseFile(); 1959 1960 if ($noiseFile !== false && is_readable($noiseFile)) { 1961 try { 1962 $wavNoise = new WavFile($noiseFile, false); 1963 } catch(Exception $ex) { 1964 throw $ex; 1965 } 1966 1967 // start at a random offset from the beginning of the wavfile 1968 // in order to add more randomness 1969 $randOffset = 0; 1970 if ($wavNoise->getNumBlocks() > 2 * $wavCaptcha->getNumBlocks()) { 1971 $randBlock = mt_rand(0, $wavNoise->getNumBlocks() - $wavCaptcha->getNumBlocks()); 1972 $wavNoise->readWavData($randBlock * $wavNoise->getBlockAlign(), $wavCaptcha->getNumBlocks() * $wavNoise->getBlockAlign()); 1973 } else { 1974 $wavNoise->readWavData(); 1975 $randOffset = mt_rand(0, $wavNoise->getNumBlocks() - 1); 1976 } 1977 1978 1979 $mixOpts = array('wav' => $wavNoise, 1980 'loop' => true, 1981 'blockOffset' => $randOffset); 1982 1983 $filters[WavFile::FILTER_MIX] = $mixOpts; 1984 $filters[WavFile::FILTER_NORMALIZE] = $this->audio_mix_normalization; 1985 } 1986 } 1987 1988 if ($this->degrade_audio == true) { 1989 // add random noise. 1990 // any noise level below 95% is intensely distorted and not pleasant to the ear 1991 $filters[WavFile::FILTER_DEGRADE] = mt_rand(95, 98) / 100.0; 1992 } 1993 1994 if (!empty($filters)) { 1995 $wavCaptcha->filter($filters); // apply filters to captcha audio 1996 } 1997 1998 return $wavCaptcha->__toString(); 1999 } 2000 2001 public function getRandomNoiseFile() 2002 { 2003 $return = false; 2004 2005 if ( ($dh = opendir($this->audio_noise_path)) !== false ) { 2006 $list = array(); 2007 2008 while ( ($file = readdir($dh)) !== false ) { 2009 if ($file == '.' || $file == '..') continue; 2010 if (strtolower(substr($file, -4)) != '.wav') continue; 2011 2012 $list[] = $file; 2013 } 2014 2015 closedir($dh); 2016 2017 if (sizeof($list) > 0) { 2018 $file = $list[array_rand($list, 1)]; 2019 $return = $this->audio_noise_path . DIRECTORY_SEPARATOR . $file; 2020 } 2021 } 2022 2023 return $return; 2024 } 2025 1301 2026 /** 1302 2027 * Return a wav file saying there was an error generating file 1303 * 2028 * 1304 2029 * @return string The binary audio contents 1305 2030 */ 1306 2031 protected function audioError() 1307 2032 { 1308 return @file_get_contents(dirname(__FILE__) . '/audio/error.wav'); 1309 } 1310 2033 return @file_get_contents(dirname(__FILE__) . '/audio/en/error.wav'); 2034 } 2035 2036 /** 2037 * Checks to see if headers can be sent and if any error has been output to the browser 2038 * 2039 * @return bool true if headers haven't been sent and no output/errors will break audio/images, false if unsafe 2040 */ 2041 protected function canSendHeaders() 2042 { 2043 if (headers_sent()) { 2044 // output has been flushed and headers have already been sent 2045 return false; 2046 } else if (strlen((string)ob_get_contents()) > 0) { 2047 // headers haven't been sent, but there is data in the buffer that will break image and audio data 2048 return false; 2049 } 2050 2051 return true; 2052 } 2053 2054 /** 2055 * Return a random float between 0 and 0.9999 2056 * 2057 * @return float Random float between 0 and 0.9999 2058 */ 1311 2059 function frand() 1312 2060 { 1313 return 0.0001 * rand(0,9999);1314 } 1315 2061 return 0.0001 * mt_rand(0,9999); 2062 } 2063 1316 2064 /** 1317 2065 * Convert an html color code to a Securimage_Color … … 1334 2082 return new Securimage_Color($default); 1335 2083 } 2084 } 2085 2086 /** 2087 * Error handler used when outputting captcha image or audio. 2088 * This error handler helps determine if any errors raised would 2089 * prevent captcha image or audio from displaying. If they have 2090 * no effect on the output buffer or headers, true is returned so 2091 * the script can continue processing. 2092 * See https://github.com/dapphp/securimage/issues/15 2093 * 2094 * @param int $errno 2095 * @param string $errstr 2096 * @param string $errfile 2097 * @param int $errline 2098 * @param array $errcontext 2099 * @return boolean true if handled, false if PHP should handle 2100 */ 2101 public function errorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array()) 2102 { 2103 // get the current error reporting level 2104 $level = error_reporting(); 2105 2106 // if error was supressed or $errno not set in current error level 2107 if ($level == 0 || ($level & $errno) == 0) { 2108 return true; 2109 } 2110 2111 return false; 1336 2112 } 1337 2113 } … … 1360 2136 * $color = new Securimage_Color('#0080FF') or <br /> 1361 2137 * $color = new Securimage_Color(0, 128, 255) 1362 * 2138 * 1363 2139 * @param string $color 1364 2140 * @throws Exception … … 1367 2143 { 1368 2144 $args = func_get_args(); 1369 2145 1370 2146 if (sizeof($args) == 0) { 1371 2147 $this->r = 255; … … 1377 2153 $color = substr($color, 1); 1378 2154 } 1379 2155 1380 2156 if (strlen($color) != 3 && strlen($color) != 6) { 1381 2157 throw new InvalidArgumentException( … … 1383 2159 ); 1384 2160 } 1385 2161 1386 2162 $this->constructHTML($color); 1387 2163 } else if (sizeof($args) == 3) { … … 1393 2169 } 1394 2170 } 1395 2171 1396 2172 /** 1397 2173 * Construct from an rgb triplet … … 1408 2184 if ($blue < 0) $blue = 0; 1409 2185 if ($blue > 255) $blue = 255; 1410 2186 1411 2187 $this->r = $red; 1412 2188 $this->g = $green; 1413 2189 $this->b = $blue; 1414 2190 } 1415 2191 1416 2192 /** 1417 2193 * Construct from an html hex color code … … 1427 2203 $red = substr($color, 0, 2); 1428 2204 $green = substr($color, 2, 2); 1429 $blue = substr($color, 4, 2); 1430 } 1431 2205 $blue = substr($color, 4, 2); 2206 } 2207 1432 2208 $this->r = hexdec($red); 1433 2209 $this->g = hexdec($green); … … 1435 2211 } 1436 2212 } 1437 ?>
Note: See TracChangeset
for help on using the changeset viewer.