PageRenderTime 67ms CodeModel.GetById 17ms app.highlight 37ms RepoModel.GetById 1ms app.codeStats 1ms

/components/com_uniform/libraries/3rd-party/securimage/securimage.php

https://gitlab.com/Yishagerew/GDA
PHP | 2412 lines | 1473 code | 214 blank | 725 comment | 224 complexity | 34242af9d60ff732114d2c6dcf665241 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<?php
   2
   3// error_reporting(E_ALL); ini_set('display_errors', 1); // uncomment this line for debugging
   4
   5/**
   6 * Project:     Securimage: A PHP class for creating and managing form CAPTCHA images<br />
   7 * File:        securimage.php<br />
   8 *
   9 * Copyright (c) 2013, Drew Phillips
  10 * All rights reserved.
  11 *
  12 * Redistribution and use in source and binary forms, with or without modification,
  13 * are permitted provided that the following conditions are met:
  14 *
  15 *  - Redistributions of source code must retain the above copyright notice,
  16 *    this list of conditions and the following disclaimer.
  17 *  - Redistributions in binary form must reproduce the above copyright notice,
  18 *    this list of conditions and the following disclaimer in the documentation
  19 *    and/or other materials provided with the distribution.
  20 *
  21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  31 * POSSIBILITY OF SUCH DAMAGE.
  32 *
  33 * Any modifications to the library should be indicated clearly in the source code
  34 * to inform users that the changes are not a part of the original software.<br /><br />
  35 *
  36 * If you found this script useful, please take a quick moment to rate it.<br />
  37 * http://www.hotscripts.com/rate/49400.html  Thanks.
  38 *
  39 * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
  40 * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
  41 * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
  42 * @copyright 2013 Drew Phillips
  43 * @author Drew Phillips <drew@drew-phillips.com>
  44 * @version 3.5 (April 2013)
  45 * @package Securimage
  46 *
  47 */
  48
  49/**
  50ChangeLog
  51
  523.5
  53- Release new version
  54- MB string support for charlist
  55- Modify audio file path to use language directories
  56- Changed default captcha appearance
  57
  583.2RC4
  59- Add MySQL, PostgreSQL, and SQLite3 support for database storage
  60- Deprecate "use_sqlite_db" option and remove SQLite2/sqlite_* functions
  61- Add new captcha type that displays 2 dictionary words on one image
  62- Update examples
  63
  643.2RC3
  65- Fix canSendHeaders() check which was breaking if a PHP startup error was issued
  66
  673.2RC2
  68- Add error handler (https://github.com/dapphp/securimage/issues/15)
  69- Fix flash examples to use the correct value name for audio parameter
  70
  713.2RC1
  72- New audio captcha code.  Faster, fully dynamic audio, full WAV support
  73(Paul Voegler, Drew Phillips) <http://voegler.eu/pub/audio>
  74- New Flash audio streaming button.  User defined image and size supported
  75- Additional options for customizing captcha (noise_level, send_headers,
  76no_exit, no_session, display_value
  77- Add captcha ID support.  Uses sqlite and unique captcha IDs to track captchas,
  78no session used
  79- Add static methods for creating and validating captcha by ID
  80- Automatic clearing of old codes from SQLite database
  81
  823.0.3Beta
  83- Add improved mixing function to WavFile class (Paul Voegler)
  84- Improve performance and security of captcha audio (Paul Voegler, Drew Phillips)
  85- Add option to use random file as background noise in captcha audio
  86- Add new securimage options for audio files
  87
  883.0.2Beta
  89- Fix issue with session variables when upgrading from 2.0 - 3.0
  90- Improve audio captcha, switch to use WavFile class, make mathematical captcha audio work
  91
  923.0.1
  93- Bugfix: removed use of deprecated variable in addSignature method that would cause errors with display_errors on
  94
  953.0
  96- Rewrite class using PHP5 OOP
  97- Remove support for GD fonts, require FreeType
  98- Remove support for multi-color codes
  99- Add option to make codes case-sensitive
 100- Add namespaces to support multiple captchas on a single page or page specific captchas
 101- Add option to show simple math problems instead of codes
 102- Remove support for mp3 files due to vulnerability in decoding mp3 audio files
 103- Create new flash file to stream wav files instead of mp3
 104- Changed to BSD license
 105
 1062.0.2
 107- Fix pathing to make integration into libraries easier (Nathan Phillip Brink ohnobinki@ohnopublishing.net)
 108
 1092.0.1
 110- Add support for browsers with cookies disabled (requires php5, sqlite) maps users to md5 hashed ip addresses and md5 hashed codes for security
 111- 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)
 112- Check for previous definition of image type constants (Mike Challis)
 113- Fix mime type settings for audio output
 114- Fixed color allocation issues with multiple colors and background images, consolidate allocation to one function
 115- Ability to let codes expire after a given length of time
 116- Allow HTML color codes to be passed to Securimage_Color (suggested by Mike Challis)
 117
 1182.0.0
 119- Add mathematical distortion to characters (using code from HKCaptcha)
 120- Improved session support
 121- Added Securimage_Color class for easier color definitions
 122- Add distortion to audio output to prevent binary comparison attack (proposed by Sven "SavageTiger" Hagemann [insecurity.nl])
 123- Flash button to stream mp3 audio (Douglas Walsh www.douglaswalsh.net)
 124- Audio output is mp3 format by default
 125- Change font to AlteHaasGrotesk by yann le coroller
 126- Some code cleanup
 127
 1281.0.4 (unreleased)
 129- Ability to output audible codes in mp3 format to stream from flash
 130
 1311.0.3.1
 132- Error reading from wordlist in some cases caused words to be cut off 1 letter short
 133
 1341.0.3
 135- Removed shadow_text from code which could cause an undefined property error due to removal from previous version
 136
 1371.0.2
 138- Audible CAPTCHA Code wav files
 139- Create codes from a word list instead of random strings
 140
 1411.0
 142- Added the ability to use a selected character set, rather than a-z0-9 only.
 143- Added the multi-color text option to use different colors for each letter.
 144- Switched to automatic session handling instead of using files for code storage
 145- Added GD Font support if ttf support is not available.  Can use internal GD fonts or load new ones.
 146- Added the ability to set line thickness
 147- Added option for drawing arced lines over letters
 148- Added ability to choose image type for output
 149
 150 */
 151
 152
 153/**
 154 * Securimage CAPTCHA Class.
 155 *
 156 * @version    3.5
 157 * @package    Securimage
 158 * @subpackage classes
 159 * @author     Drew Phillips <drew@drew-phillips.com>
 160 *
 161 */
 162class Securimage
 163{
 164	// All of the public variables below are securimage options
 165	// They can be passed as an array to the Securimage constructor, set below,
 166	// or set from securimage_show.php and securimage_play.php
 167
 168	/**
 169	 * Renders captcha as a JPEG image
 170	 * @var int
 171	 */
 172	const SI_IMAGE_JPEG = 1;
 173	/**
 174	 * Renders captcha as a PNG image (default)
 175	 * @var int
 176	 */
 177	const SI_IMAGE_PNG = 2;
 178	/**
 179	 * Renders captcha as a GIF image
 180	 * @var int
 181	 */
 182	const SI_IMAGE_GIF = 3;
 183
 184	/**
 185	 * Create a normal alphanumeric captcha
 186	 * @var int
 187	 */
 188	const SI_CAPTCHA_STRING = 0;
 189	/**
 190	 * Create a captcha consisting of a simple math problem
 191	 * @var int
 192	 */
 193	const SI_CAPTCHA_MATHEMATIC = 1;
 194	/**
 195	 * Create a word based captcha using 2 words
 196	 * @var int
 197	 */
 198	const SI_CAPTCHA_WORDS = 2;
 199
 200	/**
 201	 * MySQL option identifier for database storage option
 202	 *
 203	 * @var string
 204	 */
 205	const SI_DRIVER_MYSQL = 'mysql';
 206
 207	/**
 208	 * PostgreSQL option identifier for database storage option
 209	 *
 210	 * @var string
 211	 */
 212	const SI_DRIVER_PGSQL = 'pgsql';
 213
 214	/**
 215	 * SQLite option identifier for database storage option
 216	 *
 217	 * @var string
 218	 */
 219	const SI_DRIVER_SQLITE3 = 'sqlite';
 220
 221	/*%*********************************************************************%*/
 222	// Properties
 223
 224	/**
 225	 * The width of the captcha image
 226	 * @var int
 227	 */
 228	public $image_width = 215;
 229	/**
 230	 * The height of the captcha image
 231	 * @var int
 232	 */
 233	public $image_height = 80;
 234	/**
 235	 * The type of the image, default = png
 236	 * @var int
 237	 */
 238	public $image_type = self::SI_IMAGE_PNG;
 239
 240	/**
 241	 * The background color of the captcha
 242	 * @var Securimage_Color
 243	 */
 244	public $image_bg_color = '#ffffff';
 245	/**
 246	 * The color of the captcha text
 247	 * @var Securimage_Color
 248	 */
 249	public $text_color = '#707070';
 250	/**
 251	 * The color of the lines over the captcha
 252	 * @var Securimage_Color
 253	 */
 254	public $line_color = '#707070';
 255	/**
 256	 * The color of the noise that is drawn
 257	 * @var Securimage_Color
 258	 */
 259	public $noise_color = '#707070';
 260
 261	/**
 262	 * How transparent to make the text 0 = completely opaque, 100 = invisible
 263	 * @var int
 264	 */
 265	public $text_transparency_percentage = 20;
 266	/**
 267	 * Whether or not to draw the text transparently, true = use transparency, false = no transparency
 268	 * @var bool
 269	 */
 270	public $use_transparent_text = true;
 271
 272	/**
 273	 * The length of the captcha code
 274	 * @var int
 275	 */
 276	public $code_length = 6;
 277	/**
 278	 * Whether the captcha should be case sensitive (not recommended, use only for maximum protection)
 279	 * @var bool
 280	 */
 281	public $case_sensitive = false;
 282	/**
 283	 * The character set to use for generating the captcha code
 284	 * @var string
 285	 */
 286	public $charset = 'ABCDEFGHKLMNPRSTUVWYZabcdefghklmnprstuvwyz23456789';
 287	/**
 288	 * How long in seconds a captcha remains valid, after this time it will not be accepted
 289	 * @var unknown_type
 290	 */
 291	public $expiry_time = 900;
 292
 293	/**
 294	 * The session name securimage should use, only set this if your application uses a custom session name
 295	 * It is recommended to set this value below so it is used by all securimage scripts
 296	 * @var string
 297	 */
 298	public $session_name = null;
 299
 300	/**
 301	 * true to use the wordlist file, false to generate random captcha codes
 302	 * @var bool
 303	 */
 304	public $use_wordlist = false;
 305
 306	/**
 307	 * The level of distortion, 0.75 = normal, 1.0 = very high distortion
 308	 * @var double
 309	 */
 310	public $perturbation = 0.85;
 311	/**
 312	 * How many lines to draw over the captcha code to increase security
 313	 * @var int
 314	 */
 315	public $num_lines = 5;
 316	/**
 317	 * The level of noise (random dots) to place on the image, 0-10
 318	 * @var int
 319	 */
 320	public $noise_level = 2;
 321
 322	/**
 323	 * The signature text to draw on the bottom corner of the image
 324	 * @var string
 325	 */
 326	public $image_signature = '';
 327	/**
 328	 * The color of the signature text
 329	 * @var Securimage_Color
 330	 */
 331	public $signature_color = '#707070';
 332	/**
 333	 * The path to the ttf font file to use for the signature text, defaults to $ttf_file (AHGBold.ttf)
 334	 * @var string
 335	 */
 336	public $signature_font;
 337
 338	/**
 339	 * DO NOT USE!!!
 340	 * Use an SQLite database to store data (for users that do not support cookies)
 341	 * @var bool
 342	 * @see Securimage::$use_sqlite_db
 343	 * @deprecated 3.2RC4
 344	 */
 345	public $use_sqlite_db = false;
 346
 347	/**
 348	 * Use a database backend for code storage.
 349	 * Provides a fallback to users with cookies disabled.
 350	 * Required when using captcha IDs.
 351	 *
 352	 * @see Securimage::$database_driver
 353	 * @var bool
 354	 */
 355	public $use_database = false;
 356
 357	/**
 358	 * Database driver to use for database support.
 359	 * Allowable values: 'mysql', 'pgsql', 'sqlite'.
 360	 * Default: sqlite
 361	 *
 362	 * @var string
 363	 */
 364	public $database_driver = self::SI_DRIVER_SQLITE3;
 365
 366	/**
 367	 * Database host to connect to when using mysql or postgres
 368	 * On Linux use "localhost" for Unix domain socket, otherwise uses TCP/IP
 369	 * Does not apply to SQLite
 370	 *
 371	 * @var string
 372	 */
 373	public $database_host = 'localhost';
 374
 375	/**
 376	 * Database username for connection (mysql, postgres only)
 377	 * Default is an empty string
 378	 *
 379	 * @var string
 380	 */
 381	public $database_user = '';
 382
 383	/**
 384	 * Database password for connection (mysql, postgres only)
 385	 * Default is empty string
 386	 *
 387	 * @var string
 388	 */
 389	public $database_pass = '';
 390
 391	/**
 392	 * Name of the database to select (mysql, postgres only)
 393	 *
 394	 * @see Securimage::$database_file for SQLite
 395	 * @var string
 396	 */
 397	public $database_name = '';
 398
 399	/**
 400	 * Database table where captcha codes are stored
 401	 * Note: Securimage will attempt to create this table for you if it does
 402	 * not exist.  If the table cannot be created, an E_USER_WARNING is emitted.
 403	 *
 404	 * @var string
 405	 */
 406	public $database_table = 'captcha_codes';
 407
 408	/**
 409	 * Fully qualified path to the database file when using SQLite3.
 410	 * This value is only used when $database_driver == sqlite3 and does
 411	 * not apply when no database is used, or when using MySQL or PostgreSQL.
 412	 *
 413	 * @var string
 414	 */
 415	public $database_file;
 416
 417	/**
 418	 * The type of captcha to create, either alphanumeric, or a math problem<br />
 419	 * Securimage::SI_CAPTCHA_STRING or Securimage::SI_CAPTCHA_MATHEMATIC
 420	 * @var int
 421	 */
 422	public $captcha_type = self::SI_CAPTCHA_STRING; // or self::SI_CAPTCHA_MATHEMATIC;
 423
 424	/**
 425	 * 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
 426	 * @var string
 427	 * <code>
 428	 * <?php
 429	 * // in securimage_show.php (create one show script for each form)
 430	 * $img->namespace = 'contact_form';
 431	 *
 432	 * // in form validator
 433	 * $img->namespace = 'contact_form';
 434	 * if ($img->check($code) == true) {
 435	 *     echo "Valid!";
 436	 *  }
 437	 * </code>
 438	 */
 439	public $namespace;
 440
 441	/**
 442	 * The font file to use to draw the captcha code, leave blank for default font AHGBold.ttf
 443	 * @var string
 444	 */
 445	public $ttf_file;
 446	/**
 447	 * The path to the wordlist file to use, leave blank for default words/words.txt
 448	 * @var string
 449	 */
 450	public $wordlist_file;
 451	/**
 452	 * The directory to scan for background images, if set a random background will be chosen from this folder
 453	 * @var string
 454	 */
 455	public $background_directory;
 456	/**
 457	 * The path to the SQLite database file to use, if $use_sqlite_database = true, should be chmod 666
 458	 * @deprecated 3.2RC4
 459	 * @var string
 460	 */
 461	public $sqlite_database;
 462	/**
 463	 * The path to the securimage audio directory, can be set in securimage_play.php
 464	 * @var string
 465	 * <code>
 466	 * $img->audio_path = '/home/yoursite/public_html/securimage/audio/en/';
 467	 * </code>
 468	 */
 469	public $audio_path;
 470	/**
 471	 * The path to the directory containing audio files that will be selected
 472	 * randomly and mixed with the captcha audio.
 473	 *
 474	 * @var string
 475	 */
 476	public $audio_noise_path;
 477	/**
 478	 * Whether or not to mix background noise files into captcha audio (true = mix, false = no)
 479	 * Mixing random background audio with noise can help improve security of audio captcha.
 480	 * Default: securimage/audio/noise
 481	 *
 482	 * @since 3.0.3
 483	 * @see Securimage::$audio_noise_path
 484	 * @var bool
 485	 */
 486	public $audio_use_noise;
 487	/**
 488	 * The method and threshold (or gain factor) used to normalize the mixing with background noise.
 489	 * See http://www.voegler.eu/pub/audio/ for more information.
 490	 *
 491	 * Valid: <ul>
 492	 *     <li> >= 1 - Normalize by multiplying by the threshold (boost - positive gain). <br />
 493	 *            A value of 1 in effect means no normalization (and results in clipping). </li>
 494	 *     <li> <= -1 - Normalize by dividing by the the absolute value of threshold (attenuate - negative gain). <br />
 495	 *            A factor of 2 (-2) is about 6dB reduction in volume.</li>
 496	 *     <li> [0, 1) - (open inverval - not including 1) - The threshold
 497	 *            above which amplitudes are comressed logarithmically. <br />
 498	 *            e.g. 0.6 to leave amplitudes up to 60% "as is" and compress above. </li>
 499	 *     <li> (-1, 0) - (open inverval - not including -1 and 0) - The threshold
 500	 *            above which amplitudes are comressed linearly. <br />
 501	 *            e.g. -0.6 to leave amplitudes up to 60% "as is" and compress above. </li></ul>
 502	 *
 503	 * Default: 0.6
 504	 *
 505	 * @since 3.0.4
 506	 * @var float
 507	 */
 508	public $audio_mix_normalization = 0.6;
 509	/**
 510	 * Whether or not to degrade audio by introducing random noise (improves security of audio captcha)
 511	 * Default: true
 512	 *
 513	 * @since 3.0.3
 514	 * @var bool
 515	 */
 516	public $degrade_audio;
 517	/**
 518	 * Minimum delay to insert between captcha audio letters in milliseconds
 519	 *
 520	 * @since 3.0.3
 521	 * @var float
 522	 */
 523	public $audio_gap_min = 0;
 524	/**
 525	 * Maximum delay to insert between captcha audio letters in milliseconds
 526	 *
 527	 * @since 3.0.3
 528	 * @var float
 529	 */
 530	public $audio_gap_max = 600;
 531
 532	/**
 533	 * Captcha ID if using static captcha
 534	 * @var string Unique captcha id
 535	 */
 536	protected static $_captchaId = null;
 537
 538	protected $im;
 539	protected $tmpimg;
 540	protected $bgimg;
 541	protected $iscale = 5;
 542
 543	public $securimage_path = null;
 544
 545	/**
 546	 * The captcha challenge value (either the case-sensitive/insensitive word captcha, or the solution to the math captcha)
 547	 *
 548	 * @var string Captcha challenge value
 549	 */
 550	protected $code;
 551
 552	/**
 553	 * The display value of the captcha to draw on the image (the word captcha, or the math equation to present to the user)
 554	 *
 555	 * @var string Captcha display value to draw on the image
 556	 */
 557	protected $code_display;
 558
 559	/**
 560	 * A value that can be passed to the constructor that can be used to generate a captcha image with a given value
 561	 * This value does not get stored in the session or database and is only used when calling Securimage::show().
 562	 * If a display_value was passed to the constructor and the captcha image is generated, the display_value will be used
 563	 * as the string to draw on the captcha image.  Used only if captcha codes are generated and managed by a 3rd party app/library
 564	 *
 565	 * @var string Captcha code value to display on the image
 566	 */
 567	public $display_value;
 568
 569	/**
 570	 * Captcha code supplied by user [set from Securimage::check()]
 571	 *
 572	 * @var string
 573	 */
 574	protected $captcha_code;
 575
 576	/**
 577	 * Flag that can be specified telling securimage not to call exit after generating a captcha image or audio file
 578	 *
 579	 * @var bool If true, script will not terminate; if false script will terminate (default)
 580	 */
 581	protected $no_exit;
 582
 583	/**
 584	 * Flag indicating whether or not a PHP session should be started and used
 585	 *
 586	 * @var bool If true, no session will be started; if false, session will be started and used to store data (default)
 587	 */
 588	protected $no_session;
 589
 590	/**
 591	 * Flag indicating whether or not HTTP headers will be sent when outputting captcha image/audio
 592	 *
 593	 * @var bool If true (default) headers will be sent, if false, no headers are sent
 594	 */
 595	protected $send_headers;
 596
 597	/**
 598	 * PDO connection when a database is used
 599	 *
 600	 * @var resource
 601	 */
 602	protected $pdo_conn;
 603
 604	// gd color resources that are allocated for drawing the image
 605	protected $gdbgcolor;
 606	protected $gdtextcolor;
 607	protected $gdlinecolor;
 608	protected $gdsignaturecolor;
 609
 610	/**
 611	 * Create a new securimage object, pass options to set in the constructor.<br />
 612	 * This can be used to display a captcha, play an audible captcha, or validate an entry
 613	 * @param array $options
 614	 * <code>
 615	 * $options = array(
 616	 *     'text_color' => new Securimage_Color('#013020'),
 617	 *     'code_length' => 5,
 618	 *     'num_lines' => 5,
 619	 *     'noise_level' => 3,
 620	 *     'font_file' => Securimage::getPath() . '/custom.ttf'
 621	 * );
 622	 *
 623	 * $img = new Securimage($options);
 624	 * </code>
 625	 */
 626	public function __construct($options = array())
 627	{
 628		$this->securimage_path = dirname(__FILE__);
 629
 630		if (is_array($options) && sizeof($options) > 0)
 631		{
 632			foreach ($options as $prop => $val)
 633			{
 634				if ($prop == 'captchaId')
 635				{
 636					Securimage::$_captchaId = $val;
 637					$this->use_database = true;
 638				}
 639				else if ($prop == 'use_sqlite_db')
 640				{
 641					trigger_error("The use_sqlite_db option is deprecated, use 'use_database' instead", E_USER_NOTICE);
 642				}
 643				else
 644				{
 645					$this->$prop = $val;
 646				}
 647			}
 648		}
 649
 650		$this->image_bg_color = $this->initColor($this->image_bg_color, '#ffffff');
 651		$this->text_color = $this->initColor($this->text_color, '#616161');
 652		$this->line_color = $this->initColor($this->line_color, '#616161');
 653		$this->noise_color = $this->initColor($this->noise_color, '#616161');
 654		$this->signature_color = $this->initColor($this->signature_color, '#616161');
 655
 656		if (is_null($this->ttf_file))
 657		{
 658			$this->ttf_file = $this->securimage_path . '/AHGBold.ttf';
 659		}
 660
 661		$this->signature_font = $this->ttf_file;
 662
 663		if (is_null($this->wordlist_file))
 664		{
 665			$this->wordlist_file = $this->securimage_path . '/words/words.txt';
 666		}
 667
 668		if (is_null($this->database_file))
 669		{
 670			$this->database_file = $this->securimage_path . '/database/securimage.sq3';
 671		}
 672
 673		if (is_null($this->audio_path))
 674		{
 675			$this->audio_path = $this->securimage_path . '/audio/en/';
 676		}
 677
 678		if (is_null($this->audio_noise_path))
 679		{
 680			$this->audio_noise_path = $this->securimage_path . '/audio/noise/';
 681		}
 682
 683		if (is_null($this->audio_use_noise))
 684		{
 685			$this->audio_use_noise = true;
 686		}
 687
 688		if (is_null($this->degrade_audio))
 689		{
 690			$this->degrade_audio = true;
 691		}
 692
 693		if (is_null($this->code_length) || (int) $this->code_length < 1)
 694		{
 695			$this->code_length = 6;
 696		}
 697
 698		if (is_null($this->perturbation) || !is_numeric($this->perturbation))
 699		{
 700			$this->perturbation = 0.75;
 701		}
 702
 703		if (is_null($this->namespace) || !is_string($this->namespace))
 704		{
 705			$this->namespace = 'default';
 706		}
 707
 708		if (is_null($this->no_exit))
 709		{
 710			$this->no_exit = false;
 711		}
 712
 713		if (is_null($this->no_session))
 714		{
 715			$this->no_session = false;
 716		}
 717
 718		if (is_null($this->send_headers))
 719		{
 720			$this->send_headers = false;
 721		}
 722
 723		if ($this->no_session != true)
 724		{
 725			// Initialize session or attach to existing
 726			if (session_id() == '')
 727			{ // no session has been started yet, which is needed for validation
 728				if (!is_null($this->session_name) && trim($this->session_name) != '')
 729				{
 730					session_name(trim($this->session_name)); // set session name if provided
 731				}
 732				session_start();
 733			}
 734		}
 735	}
 736
 737	/**
 738	 * Return the absolute path to the Securimage directory
 739	 * @return string The path to the securimage base directory
 740	 */
 741	public static function getPath()
 742	{
 743		return dirname(__FILE__);
 744	}
 745
 746	/**
 747	 * Generate a new captcha ID or retrieve the current ID
 748	 *
 749	 * @param $new bool If true, generates a new challenge and returns and ID
 750	 * @param $options array Additional options to be passed to Securimage.
 751	 * Must include database options if not set directly in securimage.php
 752	 *
 753	 * @return null|string Returns null if no captcha id set and new was false, or string captcha ID
 754	 */
 755	public static function getCaptchaId($new = true, array $options = array())
 756	{
 757		if (is_null($new) || (bool) $new == true)
 758		{
 759			$id = sha1(uniqid($_SERVER['REMOTE_ADDR'], true));
 760			$opts = array('no_session' => true, 'use_database' => true);
 761			if (sizeof($options) > 0)
 762				$opts = array_merge($options, $opts);
 763			$si = new self($opts);
 764			Securimage::$_captchaId = $id;
 765			$si->createCode();
 766
 767			return $id;
 768		}
 769		else
 770		{
 771			return Securimage::$_captchaId;
 772		}
 773	}
 774
 775	/**
 776	 * Validate a captcha code input against a captcha ID
 777	 *
 778	 * @param string $id       The captcha ID to check
 779	 * @param string $value    The captcha value supplied by the user
 780	 * @param array  $options  Array of options to construct Securimage with.
 781	 * Options must include database options if they are not set in securimage.php
 782	 *
 783	 * @see Securimage::$database_driver
 784	 * @return bool true if the code was valid for the given captcha ID, false if not or if database failed to open
 785	 */
 786	public static function checkByCaptchaId($id, $value, array $options = array())
 787	{
 788		$opts = array('captchaId' => $id, 'no_session' => true, 'use_database' => true);
 789
 790		if (sizeof($options) > 0)
 791			$opts = array_merge($options, $opts);
 792
 793		$si = new self($opts);
 794
 795		if ($si->openDatabase())
 796		{
 797			$code = $si->getCodeFromDatabase();
 798
 799			if (is_array($code))
 800			{
 801				$si->code = $code['code'];
 802				$si->code_display = $code['code_disp'];
 803			}
 804
 805			if ($si->check($value))
 806			{
 807				$si->clearCodeFromDatabase();
 808
 809				return true;
 810			}
 811			else
 812			{
 813				return false;
 814			}
 815		}
 816		else
 817		{
 818			return false;
 819		}
 820	}
 821
 822
 823	/**
 824	 * Used to serve a captcha image to the browser
 825	 * @param string $background_image The path to the background image to use
 826	 * <code>
 827	 * $img = new Securimage();
 828	 * $img->code_length = 6;
 829	 * $img->num_lines   = 5;
 830	 * $img->noise_level = 5;
 831	 *
 832	 * $img->show(); // sends the image to browser
 833	 * exit;
 834	 * </code>
 835	 */
 836	public function show($background_image = '')
 837	{
 838		set_error_handler(array(&$this, 'errorHandler'));
 839
 840		if ($background_image != '' && is_readable($background_image))
 841		{
 842			$this->bgimg = $background_image;
 843		}
 844
 845		$this->doImage();
 846	}
 847
 848	/**
 849	 * Check a submitted code against the stored value
 850	 * @param string $code  The captcha code to check
 851	 * <code>
 852	 * $code = $_POST['code'];
 853	 * $img  = new Securimage();
 854	 * if ($img->check($code) == true) {
 855	 *     $captcha_valid = true;
 856	 * } else {
 857	 *     $captcha_valid = false;
 858	 * }
 859	 * </code>
 860	 */
 861	public function check($code)
 862	{
 863		$this->code_entered = $code;
 864		$this->validate();
 865		return $this->correct_code;
 866	}
 867
 868	/**
 869	 * Output a wav file of the captcha code to the browser
 870	 *
 871	 * <code>
 872	 * $img = new Securimage();
 873	 * $img->outputAudioFile(); // outputs a wav file to the browser
 874	 * exit;
 875	 * </code>
 876	 */
 877	public function outputAudioFile()
 878	{
 879		set_error_handler(array(&$this, 'errorHandler'));
 880
 881		require_once dirname(__FILE__) . '/WavFile.php';
 882
 883		try
 884		{
 885			$audio = $this->getAudibleCode();
 886		}
 887		catch (Exception $ex)
 888		{
 889			if (($fp = @fopen(dirname(__FILE__) . '/si.error_log', 'a+')) !== false)
 890			{
 891				fwrite($fp, date('Y-m-d H:i:s') . ': Securimage audio error "' . $ex->getMessage() . '"' . "\n");
 892				fclose($fp);
 893			}
 894
 895			$audio = $this->audioError();
 896		}
 897
 898		if ($this->canSendHeaders() || $this->send_headers == false)
 899		{
 900			if ($this->send_headers)
 901			{
 902				$uniq = md5(uniqid(microtime()));
 903				header("Content-Disposition: attachment; filename=\"securimage_audio-{$uniq}.wav\"");
 904				header('Cache-Control: no-store, no-cache, must-revalidate');
 905				header('Expires: Sun, 1 Jan 2000 12:00:00 GMT');
 906				header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
 907				header('Content-type: audio/x-wav');
 908
 909				if (extension_loaded('zlib'))
 910				{
 911					ini_set('zlib.output_compression', true); // compress output if supported by browser
 912				}
 913				else
 914				{
 915					header('Content-Length: ' . strlen($audio));
 916				}
 917			}
 918
 919			echo $audio;
 920		}
 921		else
 922		{
 923			echo '<hr /><strong>' . 'Failed to generate audio file, content has already been ' . 'output.<br />This is most likely due to misconfiguration or ' . 'a PHP error was sent to the browser.</strong>';
 924		}
 925
 926		restore_error_handler();
 927
 928		if (!$this->no_exit)
 929			exit;
 930	}
 931
 932	/**
 933	 * Return the code from the session or sqlite database if used.  If none exists yet, an empty string is returned
 934	 *
 935	 * @param $array bool   True to receive an array containing the code and properties
 936	 * @return array|string Array if $array = true, otherwise a string containing the code
 937	 */
 938	public function getCode($array = false, $returnExisting = false)
 939	{
 940		$code = '';
 941		$time = 0;
 942		$disp = 'error';
 943
 944		if ($returnExisting && strlen($this->code) > 0)
 945		{
 946			if ($array)
 947			{
 948				return array('code' => $this->code, 'display' => $this->code_display, 'code_display' => $this->code_display, 'time' => 0);
 949			}
 950			else
 951			{
 952				return $this->code;
 953			}
 954		}
 955
 956		if ($this->no_session != true)
 957		{
 958			if (isset($_SESSION['securimage_code_value'][$this->namespace]) && trim($_SESSION['securimage_code_value'][$this->namespace]) != '')
 959			{
 960				if ($this->isCodeExpired($_SESSION['securimage_code_ctime'][$this->namespace]) == false)
 961				{
 962					$code = $_SESSION['securimage_code_value'][$this->namespace];
 963					$time = $_SESSION['securimage_code_ctime'][$this->namespace];
 964					$disp = $_SESSION['securimage_code_disp'] [$this->namespace];
 965				}
 966			}
 967		}
 968
 969		if (empty($code) && $this->use_database)
 970		{
 971			// no code in session - may mean user has cookies turned off
 972			$this->openDatabase();
 973			$code = $this->getCodeFromDatabase();
 974		}
 975		else
 976		{ /* no code stored in session or sqlite database, validation will fail */
 977		}
 978
 979		if ($array == true)
 980		{
 981			return array('code' => $code, 'ctime' => $time, 'display' => $disp);
 982		}
 983		else
 984		{
 985			return $code;
 986		}
 987	}
 988
 989	/**
 990	 * The main image drawing routing, responsible for constructing the entire image and serving it
 991	 */
 992	protected function doImage()
 993	{
 994		if (($this->use_transparent_text == true || $this->bgimg != '') && function_exists('imagecreatetruecolor'))
 995		{
 996			$imagecreate = 'imagecreatetruecolor';
 997		}
 998		else
 999		{
1000			$imagecreate = 'imagecreate';
1001		}
1002
1003		$this->im = $imagecreate($this->image_width, $this->image_height);
1004		$this->tmpimg = $imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale);
1005
1006		$this->allocateColors();
1007		imagepalettecopy($this->tmpimg, $this->im);
1008
1009		$this->setBackground();
1010
1011		$code = '';
1012
1013		if ($this->getCaptchaId(false) !== null)
1014		{
1015			// a captcha Id was supplied
1016
1017			// check to see if a display_value for the captcha image was set
1018			if (is_string($this->display_value) && strlen($this->display_value) > 0)
1019			{
1020				$this->code_display = $this->display_value;
1021				$this->code = ($this->case_sensitive)?$this->display_value:strtolower($this->display_value);
1022				$code = $this->code;
1023			}
1024			else if ($this->openDatabase())
1025			{
1026				// no display_value, check the database for existing captchaId
1027				$code = $this->getCodeFromDatabase();
1028
1029				// got back a result from the database with a valid code for captchaId
1030				if (is_array($code))
1031				{
1032					$this->code = $code['code'];
1033					$this->code_display = $code['code_disp'];
1034					$code = $code['code'];
1035				}
1036			}
1037		}
1038
1039		if ($code == '')
1040		{
1041			// if the code was not set using display_value or was not found in
1042			// the database, create a new code
1043			$this->createCode();
1044		}
1045
1046		if ($this->noise_level > 0)
1047		{
1048			$this->drawNoise();
1049		}
1050
1051		$this->drawWord();
1052
1053		if ($this->perturbation > 0 && is_readable($this->ttf_file))
1054		{
1055			$this->distortedCopy();
1056		}
1057
1058		if ($this->num_lines > 0)
1059		{
1060			$this->drawLines();
1061		}
1062
1063		if (trim($this->image_signature) != '')
1064		{
1065			$this->addSignature();
1066		}
1067
1068		$this->output();
1069	}
1070
1071	/**
1072	 * Allocate the colors to be used for the image
1073	 */
1074	protected function allocateColors()
1075	{
1076		// allocate bg color first for imagecreate
1077		$this->gdbgcolor = imagecolorallocate($this->im, $this->image_bg_color->r, $this->image_bg_color->g, $this->image_bg_color->b);
1078
1079		$alpha = intval($this->text_transparency_percentage / 100 * 127);
1080
1081		if ($this->use_transparent_text == true)
1082		{
1083			$this->gdtextcolor = imagecolorallocatealpha($this->im, $this->text_color->r, $this->text_color->g, $this->text_color->b, $alpha);
1084			$this->gdlinecolor = imagecolorallocatealpha($this->im, $this->line_color->r, $this->line_color->g, $this->line_color->b, $alpha);
1085			$this->gdnoisecolor = imagecolorallocatealpha($this->im, $this->noise_color->r, $this->noise_color->g, $this->noise_color->b, $alpha);
1086		}
1087		else
1088		{
1089			$this->gdtextcolor = imagecolorallocate($this->im, $this->text_color->r, $this->text_color->g, $this->text_color->b);
1090			$this->gdlinecolor = imagecolorallocate($this->im, $this->line_color->r, $this->line_color->g, $this->line_color->b);
1091			$this->gdnoisecolor = imagecolorallocate($this->im, $this->noise_color->r, $this->noise_color->g, $this->noise_color->b);
1092		}
1093
1094		$this->gdsignaturecolor = imagecolorallocate($this->im, $this->signature_color->r, $this->signature_color->g, $this->signature_color->b);
1095
1096	}
1097
1098	/**
1099	 * The the background color, or background image to be used
1100	 */
1101	protected function setBackground()
1102	{
1103		// set background color of image by drawing a rectangle since imagecreatetruecolor doesn't set a bg color
1104		imagefilledrectangle($this->im, 0, 0, $this->image_width, $this->image_height, $this->gdbgcolor);
1105		imagefilledrectangle($this->tmpimg, 0, 0, $this->image_width * $this->iscale, $this->image_height * $this->iscale, $this->gdbgcolor);
1106
1107		if ($this->bgimg == '')
1108		{
1109			if ($this->background_directory != null && is_dir($this->background_directory) && is_readable($this->background_directory))
1110			{
1111				$img = $this->getBackgroundFromDirectory();
1112				if ($img != false)
1113				{
1114					$this->bgimg = $img;
1115				}
1116			}
1117		}
1118
1119		if ($this->bgimg == '')
1120		{
1121			return;
1122		}
1123
1124		$dat = @getimagesize($this->bgimg);
1125		if ($dat == false)
1126		{
1127			return;
1128		}
1129
1130		switch ($dat[2])
1131		{
1132			case 1:
1133				$newim = @imagecreatefromgif($this->bgimg);
1134				break;
1135			case 2:
1136				$newim = @imagecreatefromjpeg($this->bgimg);
1137				break;
1138			case 3:
1139				$newim = @imagecreatefrompng($this->bgimg);
1140				break;
1141			default:
1142				return;
1143		}
1144
1145		if (!$newim)
1146			return;
1147
1148		imagecopyresized($this->im, $newim, 0, 0, 0, 0, $this->image_width, $this->image_height, imagesx($newim), imagesy($newim));
1149	}
1150
1151	/**
1152	 * Scan the directory for a background image to use
1153	 */
1154	protected function getBackgroundFromDirectory()
1155	{
1156		$images = array();
1157
1158		if (($dh = opendir($this->background_directory)) !== false)
1159		{
1160			while (($file = readdir($dh)) !== false)
1161			{
1162				if (preg_match('/(jpg|gif|png)$/i', $file))
1163					$images[] = $file;
1164			}
1165
1166			closedir($dh);
1167
1168			if (sizeof($images) > 0)
1169			{
1170				return rtrim($this->background_directory, '/') . '/' . $images[mt_rand(0, sizeof($images) - 1)];
1171			}
1172		}
1173
1174		return false;
1175	}
1176
1177	/**
1178	 * Generates the code or math problem and saves the value to the session
1179	 */
1180	public function createCode()
1181	{
1182		$this->code = false;
1183
1184		switch ($this->captcha_type)
1185		{
1186			case self::SI_CAPTCHA_MATHEMATIC:
1187			{
1188				do
1189				{
1190					$signs = array('+', '-', 'x');
1191					$left = mt_rand(1, 10);
1192					$right = mt_rand(1, 5);
1193					$sign = $signs[mt_rand(0, 2)];
1194
1195					switch ($sign)
1196					{
1197						case 'x':
1198							$c = $left * $right;
1199							break;
1200						case '-':
1201							$c = $left - $right;
1202							break;
1203						default:
1204							$c = $left + $right;
1205							break;
1206					}
1207				} while ($c <= 0); // no negative #'s or 0
1208
1209				$this->code = $c;
1210				$this->code_display = "$left $sign $right";
1211				break;
1212			}
1213
1214			case self::SI_CAPTCHA_WORDS:
1215				$words = $this->readCodeFromFile(2);
1216				$this->code = implode(' ', $words);
1217				$this->code_display = $this->code;
1218				break;
1219
1220			default:
1221				{
1222				if ($this->use_wordlist && is_readable($this->wordlist_file))
1223				{
1224					$this->code = $this->readCodeFromFile();
1225				}
1226
1227				if ($this->code == false)
1228				{
1229					$this->code = $this->generateCode($this->code_length);
1230				}
1231
1232				$this->code_display = $this->code;
1233				$this->code = ($this->case_sensitive)?$this->code:strtolower($this->code);
1234				} // default
1235		}
1236
1237		$this->saveData();
1238	}
1239
1240	/**
1241	 * Draws the captcha code on the image
1242	 */
1243	protected function drawWord()
1244	{
1245		$width2 = $this->image_width * $this->iscale;
1246		$height2 = $this->image_height * $this->iscale;
1247
1248		if (!is_readable($this->ttf_file))
1249		{
1250			imagestring($this->im, 4, 10, ($this->image_height / 2) - 5, 'Failed to load TTF font file!', $this->gdtextcolor);
1251		}
1252		else
1253		{
1254			if ($this->perturbation > 0)
1255			{
1256				$font_size = $height2 * .4;
1257				$bb = imageftbbox($font_size, 0, $this->ttf_file, $this->code_display);
1258				$tx = $bb[4] - $bb[0];
1259				$ty = $bb[5] - $bb[1];
1260				$x = floor($width2 / 2 - $tx / 2 - $bb[0]);
1261				$y = round($height2 / 2 - $ty / 2 - $bb[1]);
1262
1263				imagettftext($this->tmpimg, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
1264			}
1265			else
1266			{
1267				$font_size = $this->image_height * .4;
1268				$bb = imageftbbox($font_size, 0, $this->ttf_file, $this->code_display);
1269				$tx = $bb[4] - $bb[0];
1270				$ty = $bb[5] - $bb[1];
1271				$x = floor($this->image_width / 2 - $tx / 2 - $bb[0]);
1272				$y = round($this->image_height / 2 - $ty / 2 - $bb[1]);
1273
1274				imagettftext($this->im, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
1275			}
1276		}
1277
1278		// DEBUG
1279		//$this->im = $this->tmpimg;
1280		//$this->output();
1281
1282	}
1283
1284	/**
1285	 * Copies the captcha image to the final image with distortion applied
1286	 */
1287	protected function distortedCopy()
1288	{
1289		$numpoles = 3; // distortion factor
1290		// make array of poles AKA attractor points
1291		for ($i = 0; $i < $numpoles; ++$i)
1292		{
1293			$px[$i] = mt_rand($this->image_width * 0.2, $this->image_width * 0.8);
1294			$py[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8);
1295			$rad[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8);
1296			$tmp = ((-$this->frand()) * 0.15) - .15;
1297			$amp[$i] = $this->perturbation * $tmp;
1298		}
1299
1300		$bgCol = imagecolorat($this->tmpimg, 0, 0);
1301		$width2 = $this->iscale * $this->image_width;
1302		$height2 = $this->iscale * $this->image_height;
1303		imagepalettecopy($this->im, $this->tmpimg); // copy palette to final image so text colors come across
1304		// loop over $img pixels, take pixels from $tmpimg with distortion field
1305		for ($ix = 0; $ix < $this->image_width; ++$ix)
1306		{
1307			for ($iy = 0; $iy < $this->image_height; ++$iy)
1308			{
1309				$x = $ix;
1310				$y = $iy;
1311				for ($i = 0; $i < $numpoles; ++$i)
1312				{
1313					$dx = $ix - $px[$i];
1314					$dy = $iy - $py[$i];
1315					if ($dx == 0 && $dy == 0)
1316					{
1317						continue;
1318					}
1319					$r = sqrt($dx * $dx + $dy * $dy);
1320					if ($r > $rad[$i])
1321					{
1322						continue;
1323					}
1324					$rscale = $amp[$i] * sin(3.14 * $r / $rad[$i]);
1325					$x += $dx * $rscale;
1326					$y += $dy * $rscale;
1327				}
1328				$c = $bgCol;
1329				$x *= $this->iscale;
1330				$y *= $this->iscale;
1331				if ($x >= 0 && $x < $width2 && $y >= 0 && $y < $height2)
1332				{
1333					$c = imagecolorat($this->tmpimg, $x, $y);
1334				}
1335				if ($c != $bgCol)
1336				{ // only copy pixels of letters to preserve any background image
1337					imagesetpixel($this->im, $ix, $iy, $c);
1338				}
1339			}
1340		}
1341	}
1342
1343	/**
1344	 * Draws distorted lines on the image
1345	 */
1346	protected function drawLines()
1347	{
1348		for ($line = 0; $line < $this->num_lines; ++$line)
1349		{
1350			$x = $this->image_width * (1 + $line) / ($this->num_lines + 1);
1351			$x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines;
1352			$y = mt_rand($this->image_height * 0.1, $this->image_height * 0.9);
1353
1354			$theta = ($this->frand() - 0.5) * M_PI * 0.7;
1355			$w = $this->image_width;
1356			$len = mt_rand($w * 0.4, $w * 0.7);
1357			$lwid = mt_rand(0, 2);
1358
1359			$k = $this->frand() * 0.6 + 0.2;
1360			$k = $k * $k * 0.5;
1361			$phi = $this->frand() * 6.28;
1362			$step = 0.5;
1363			$dx = $step * cos($theta);
1364			$dy = $step * sin($theta);
1365			$n = $len / $step;
1366			$amp = 1.5 * $this->frand() / ($k + 5.0 / $len);
1367			$x0 = $x - 0.5 * $len * cos($theta);
1368			$y0 = $y - 0.5 * $len * sin($theta);
1369
1370			$ldx = round(-$dy * $lwid);
1371			$ldy = round($dx * $lwid);
1372
1373			for ($i = 0; $i < $n; ++$i)
1374			{
1375				$x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi);
1376				$y = $y0 + $i * $dy - $amp * $dx * sin($k * $i * $step + $phi);
1377				imagefilledrectangle($this->im, $x, $y, $x + $lwid, $y + $lwid, $this->gdlinecolor);
1378			}
1379		}
1380	}
1381
1382	/**
1383	 * Draws random noise on the image
1384	 */
1385	protected function drawNoise()
1386	{
1387		if ($this->noise_level > 10)
1388		{
1389			$noise_level = 10;
1390		}
1391		else
1392		{
1393			$noise_level = $this->noise_level;
1394		}
1395
1396		$t0 = microtime(true);
1397
1398		$noise_level *= 125; // an arbitrary number that works well on a 1-10 scale
1399
1400		$points = $this->image_width * $this->image_height * $this->iscale;
1401		$height = $this->image_height * $this->iscale;
1402		$width = $this->image_width * $this->iscale;
1403		for ($i = 0; $i < $noise_level; ++$i)
1404		{
1405			$x = mt_rand(10, $width);
1406			$y = mt_rand(10, $height);
1407			$size = mt_rand(7, 10);
1408			if ($x - $size <= 0 && $y - $size <= 0)
1409				continue; // dont cover 0,0 since it is used by imagedistortedcopy
1410			imagefilledarc($this->tmpimg, $x, $y, $size, $size, 0, 360, $this->gdnoisecolor, IMG_ARC_PIE);
1411		}
1412
1413		$t1 = microtime(true);
1414
1415		$t = $t1 - $t0;
1416
1417		/*
1418		// DEBUG
1419		imagestring($this->tmpimg, 5, 25, 30, "$t", $this->gdnoisecolor);
1420		header('content-type: image/png');
1421		imagepng($this->tmpimg);
1422		exit;
1423		*/
1424	}
1425
1426	/**
1427	 * Print signature text on image
1428	 */
1429	protected function addSignature()
1430	{
1431		$bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature);
1432		$textlen = $bbox[2] - $bbox[0];
1433		$x = $this->image_width - $textlen - 5;
1434		$y = $this->image_height - 3;
1435
1436		imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature);
1437	}
1438
1439	/**
1440	 * Sends the appropriate image and cache headers and outputs image to the browser
1441	 */
1442	protected function output()
1443	{
1444		if ($this->canSendHeaders() || $this->send_headers == false)
1445		{
1446			if ($this->send_headers)
1447			{
1448				// only send the content-type headers if no headers have been output
1449				// this will ease debugging on misconfigured servers where warnings
1450				// may have been output which break the image and prevent easily viewing
1451				// source to see the error.
1452				header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
1453				header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
1454				header("Cache-Control: no-store, no-cache, must-revalidate");
1455				header("Cache-Control: post-check=0, pre-check=0", false);
1456				header("Pragma: no-cache");
1457			}
1458
1459			switch ($this->image_type)
1460			{
1461				case self::SI_IMAGE_JPEG:
1462					if ($this->send_headers)
1463						header("Content-Type: image/jpeg");
1464					imagejpeg($this->im, null, 90);
1465					break;
1466				case self::SI_IMAGE_GIF:
1467					if ($this->send_headers)
1468						header("Content-Type: image/gif");
1469					imagegif($this->im);
1470					break;
1471				default:
1472					if ($this->send_headers)
1473						header("Content-Type: image/png");
1474					imagepng($this->im);
1475					break;
1476			}
1477		}
1478		else
1479		{
1480			echo '<hr /><strong>' . 'Failed to generate captcha image, content has already been ' . 'output.<br />This is most likely due to misconfiguration or ' . 'a PHP error was sent to the browser.</strong>';
1481		}
1482
1483		imagedestroy($this->im);
1484		restore_error_handler();
1485
1486		//if (!$this->no_exit) exit;
1487	}
1488
1489	/**
1490	 * Gets the code and returns the binary audio file for the stored captcha code
1491	 *
1492	 * @return The audio representation of the captcha in Wav format
1493	 */
1494	protected function getAudibleCode()
1495	{
1496		$letters = array();
1497		$code = $this->getCode(true, true);
1498
1499		if ($code['code'] == '')
1500		{
1501			if (strlen($this->display_value) > 0)
1502			{
1503				$code = array('code' => $this->display_value, 'display' => $this->display_value);
1504			}
1505			else
1506			{
1507				$this->createCode();
1508				$code = $this->getCode(true);
1509			}
1510		}
1511
1512		if (preg_match('/(\d+) (\+|-|x) (\d+)/i', $code['display'], $eq))
1513		{
1514			$math = true;
1515
1516			$left = $eq[1];
1517			$sign = str_replace(array('+', '-', 'x'), array('plus', 'minus', 'times'), $eq[2]);
1518			$right = $eq[3];
1519
1520			$letters = array($left, $sign, $right);
1521		}
1522		else
1523		{
1524			$math = false;
1525
1526			$length = strlen($code['display']);
1527
1528			for ($i = 0; $i < $length; ++$i)
1529			{
1530				$letter = $code['display']{$i};
1531				$letters[] = $letter;
1532			}
1533		}
1534
1535		try
1536		{
1537			return $this->generateWAV($letters);
1538		}
1539		catch (Exception $ex)
1540		{
1541			throw $ex;
1542		}
1543	}
1544
1545	/**
1546	 * Gets a captcha code from a wordlist
1547	 */
1548	protected function readCodeFromFile($numWords = 1)
1549	{
1550		$fp = fopen($this->wordlist_file, 'rb');
1551		if (!$fp)
1552			return false;
1553
1554		$fsize = filesize($this->wordlist_file);
1555		if ($fsize < 128)
1556			return false; // too small of a list to be effective
1557
1558		if ((int) $numWords < 1 || (int) $numWords > 5)
1559			$numWords = 1;
1560
1561		$words = array();
1562		$i = 0;
1563		do
1564		{
1565			fseek($fp, mt_rand(0, $fsize - 64), SEEK_SET); // seek to a random position of file from 0 to filesize-64
1566			$data = fread($fp, 64); // read a chunk from our random position
1567			$data = preg_replace("/\r?\n/", "\n", $data);
1568
1569			$start = @strpos($data, "\n", mt_rand(0, 56)) + 1; // random start position
1570			$end = @strpos($data, "\n", $start); // find end of word
1571
1572			if ($start === false)
1573			{
1574				// picked start position at end of file
1575				continue;
1576			}
1577			else if ($end === false)
1578			{
1579				$end = strlen($data);
1580			}
1581
1582			$word = strtolower(substr($data, $start, $end - $start)); // return a line of the file
1583			$words[] = $word;
1584		} while (++$i < $numWords);
1585
1586		fclose($fp);
1587
1588		if ($numWords < 2)
1589		{
1590			return $words[0];
1591		}
1592		else
1593		{
1594			return $words;
1595		}
1596	}
1597
1598	/**
1599	 * Generates a random captcha code from the set character set
1600	 */
1601	protected function generateCode()
1602	{
1603		$code = '';
1604
1605		if (function_exists('mb_strlen'))
1606		{
1607			for ($i = 1, $cslen = mb_strlen($this->charset); $i <= $this->code_length; ++$i)
1608			{
1609				$code .= mb_substr($this->charset, mt_rand(0, $cslen - 1), 1, 'UTF-8');
1610			}
1611		}
1612		else
1613		{
1614			for ($i = 1, $cslen = strlen($this->charset); $i <= $this->code_length; ++$i)
1615			{
1616				$code .= substr($this->charset, mt_rand(0, $cslen - 1), 1);
1617			}
1618		}
1619
1620		return $code;
1621	}
1622
1623	/**
1624	 * Checks the entered code against the value stored in the session or sqlite database, handles case sensitivity
1625	 * Also clears the stored codes if the code was entered correctly to prevent re-use
1626	 */
1627	protected function validate()
1628	{
1629		if (!is_string($this->code) || strlen($this->code) == 0)
1630		{
1631			$code = $this->getCode();
1632			// returns stored code, or an empty string if no stored code was found
1633			// checks the session and database if enabled
1634		}
1635		else
1636		{
1637			$code = $this->code;
1638		}
1639
1640		if ($this->case_sensitive == false && preg_match('/[A-Z]/', $code))
1641		{
1642			// case sensitive was set from securimage_show.php but not in class
1643			// the code saved in the session has capitals so set case sensitive to true
1644			$this->case_sensitive = true;
1645		}
1646
1647		$code_entered = trim((($this->case_sensitive)?$this->code_entered:strtolower($this->code_entered)));
1648		$this->correct_code = false;
1649
1650		if ($code != '')
1651		{
1652			if (strpos($code, ' ') !== false)
1653			{
1654				// for multi word captchas, remove more than once space from input
1655				$code_entered = preg_replace('/\s+/', ' ', $code_entered);
1656				$code_entered = strtolower($code_entered);
1657			}
1658
1659			if ($code == $code_entered)
1660			{
1661				$this->correct_code = true;
1662				if ($this->no_session != true)
1663				{
1664					$_SESSION['securimage_code_value'][$this->namespace] = '';
1665					$_SESSION['securimage_code_ctime'][$this->namespace] = '';
1666				}
1667				$this->clearCodeFromDatabase();
1668			}
1669		}
1670	}
1671
1672	/**
1673	 * Save data to session namespace and database if used
1674	 */
1675	protected function saveData()
1676	{
1677		if ($this->no_session != true)
1678		{
1679			if (isset($_SESSION['securimage_code_value']) && is_scalar($_SESSION['securimage_code_value']))
1680			{
1681				// fix for migration from v2 - v3
1682				unset($_SESSION['securimage_code_value']);
1683				unset($_SESSION['securimage_code_ctime']);
1684			}
1685
1686			$_SESSION['securimage_code_disp'] [$this->namespace] = $this->code_display;
1687			$_SESSION['securimage_code_value'][$this->namespace] = $this->code;
1688			$_SESSION['securimage_code_ctime'][$this->namespace] = time();
1689		}
1690
1691		if ($this->use_database)
1692		{
1693			$this->saveCodeToDatabase();
1694		}
1695	}
1696
1697	/**
1698	 * Saves the code to the sqlite database
1699	 */
1700	protected function saveCodeToDatabase()
1701	{
1702		$success = false;
1703		$this->openDatabase();
1704
1705		if ($this->use_database && $this->pdo_conn)
1706		{
1707			$id = $this->getCaptchaId(false);
1708			$ip = $_SERVER['REMOTE_ADDR'];
1709
1710			if (empty($id))
1711			{
1712				$id = $ip;
1713			}
1714
1715			$time = time();
1716			$code = $this->code;
1717			$code_disp = $this->code_display;
1718
1719			// This is somewhat expensive in PDO Sqlite3 (when there is something to delete)
1720			$this->clearCodeFromDatabase();
1721
1722			$query = "INSERT INTO {$this->database_table} (" . "id, code, code_display, namespace, created) " . "VALUES(?, ?, ?, ?, ?)";
1723
1724			$stmt = $this->pdo_conn->prepare($query);
1725			$success = $stmt->execute(array($id, $code, $code_disp, $this->namespace, $time));
1726
1727			if (!$success)
1728			{
1729				$err = $stmt->errorInfo();
1730				trigger_error("Failed to insert code into database. {$err[1]}: {$err[2]}", E_USER_WARNING);
1731			}
1732		}
1733
1734		return $success !== false;
1735	}
1736
1737	/**
1738	 * Open sqlite database
1739	 */
1740	protected function openDatabase()
1741	{
1742		$this->pdo_conn = false;
1743
1744		if ($this->use_database)
1745		{
1746			$pdo_extension = 'PDO_' . strtoupper($this->database_driver);
1747
1748			if (!extension_loaded($pdo_extension))
1749			{
1750				trigger_error("Database support is turned on in Securimage, but the chosen extension $pdo_extension is not loaded in PHP.", E_USER_WARNING);
1751				return false;
1752			}
1753		}
1754
1755		if ($this->database_driver == self::SI_DRIVER_SQLITE3)
1756		{
1757			if (!file_exists($this->database_file))
1758			{
1759				$fp = fopen($this->database_file, 'w+');
1760				if (!$fp)
1761				{
1762					$err = error_get_last();
1763					trigger_error("Securimage failed to create SQLite3 database file '{$this->database_file}'. Reason: {$err['message']}", E_USER_WARNING);
1764					return false;
1765				}
1766				fclose($fp);
1767				chmod($this->database_file, 0666);
1768			}
1769			else if (!is_writeable($this->database_file))
1770			{
1771				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);
1772				return false;
1773			}
1774		}
1775
1776		$dsn = $this->getDsn();
1777
1778		try
1779		{
1780			$options = array();
1781			$this->pdo_conn = new PDO($dsn, $this->database_user, $this->database_pass, $options);
1782		}
1783		catch (PDOException $pdoex)
1784		{
1785			trigger_error("Database connection failed: " . $pdoex->getMessage(), E_USER_WARNING);
1786			return false;
1787		}
1788
1789		try
1790		{
1791			if (!$this->checkTablesExist())
1792			{
1793				// create tables...
1794				$this->createDatabaseTables();
1795			}
1796		}
1797		catch (Exception $ex)
1798		{
1799			trigger_error($ex->getMessage(), E_USER_WARNING);
1800			$this->pdo_conn = null;
1801			return false;
1802		}
1803
1804		if (mt_rand(0, 100) / 100.0 == 1.0)
1805		{
1806			$this->purgeOldCodesFromDatabase();
1807		}
1808
1809		return $this->pdo_conn;
1810	}
1811
1812	protected function getDsn()
1813	{
1814		$dsn = sprintf('%s:', $this->database_driver);
1815
1816		switch ($this->database_driver)
1817		{
1818			case self::SI_DRIVER_SQLITE3:
1819				$dsn .= $this->database_file;
1820				break;
1821
1822			case self::SI_DRIVER_MYSQL:
1823			case self::SI_DRIVER_PGSQL:
1824				$dsn .= sprintf('host=%s;dbname=%s', $this->database_host, $this->database_name);
1825				break;
1826
1827		}
1828
1829		return $dsn;
1830	}
1831
1832	protected function checkTablesExist()
1833	{
1834		$table = $this->pdo_conn->quote($this->database_table);
1835
1836		switch ($this->database_driver)
1837		{
1838			case self::SI_DRIVER_SQLITE3:
1839				// query row count for sqlite, PRAGMA queries seem to return no
1840				// rowCount using PDO even if there are rows returned
1841				$query = "SELECT COUNT(id) FROM $table";
1842				break;
1843
1844			case self::SI_DRIVER_MYSQL:
1845				$query = "SHOW TABLES LIKE $table";
1846				break;
1847
1848			ca…

Large files files are truncated, but you can click here to view the full file