Ignore:
Timestamp:
01/09/14 13:36:32 (6 years ago)
Author:
mistic100
Message:

update Securimage to 3.5.1, new font, new preset, allow to use backgrounds

File:
1 edited

Legend:

Unmodified
Added
Removed
  • extensions/CryptograPHP/securimage/securimage.php

    r12617 r26554  
    11<?php 
     2 
     3// error_reporting(E_ALL); ini_set('display_errors', 1); // uncomment this line for debugging 
     4 
    25/** 
    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 /> 
    58 * 
    6  * Copyright (c) 2011, Drew Phillips 
     9 * Copyright (c) 2013, Drew Phillips 
    710 * All rights reserved. 
    8  *  
     11 * 
    912 * Redistribution and use in source and binary forms, with or without modification, 
    1013 * are permitted provided that the following conditions are met: 
    11  *  
     14 * 
    1215 *  - Redistributions of source code must retain the above copyright notice, 
    1316 *    this list of conditions and the following disclaimer. 
     
    1518 *    this list of conditions and the following disclaimer in the documentation 
    1619 *    and/or other materials provided with the distribution. 
    17  *  
     20 * 
    1821 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
    1922 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
     
    2932 * 
    3033 * 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 /> 
    3235 * 
    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 /> 
    3437 * http://www.hotscripts.com/rate/49400.html  Thanks. 
    3538 * 
     
    3740 * @link http://www.phpcaptcha.org/latest.zip Download Latest Version 
    3841 * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation 
    39  * @copyright 2011 Drew Phillips 
     42 * @copyright 2013 Drew Phillips 
    4043 * @author Drew Phillips <drew@drew-phillips.com> 
    41  * @version 3.0 (October 2011) 
     44 * @version 3.5.1 (June 21, 2013) 
    4245 * @package Securimage 
    4346 * 
     
    4649/** 
    4750 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 
    4998 3.0 
    5099 - Rewrite class using PHP5 OOP 
     
    78127 - Audio output is mp3 format by default 
    79128 - Change font to AlteHaasGrotesk by yann le coroller 
    80  - Some code cleanup  
     129 - Some code cleanup 
    81130 
    82131 1.0.4 (unreleased) 
     
    108157 * Securimage CAPTCHA Class. 
    109158 * 
    110  * @version    3.0 
     159 * @version    3.5 
    111160 * @package    Securimage 
    112161 * @subpackage classes 
     
    116165class Securimage 
    117166{ 
    118         // All of the public variables below are securimage options 
    119         // They can be passed as an array to the Securimage constructor, set below, 
    120         // or set from securimage_show.php and securimage_play.php 
    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 
    122171    /** 
    123172     * Renders captcha as a JPEG image 
     
    135184     */ 
    136185    const SI_IMAGE_GIF  = 3; 
    137      
     186 
    138187    /** 
    139188     * Create a normal alphanumeric captcha 
     
    146195     */ 
    147196    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 
    149227    /** 
    150228     * The width of the captcha image 
     
    180258    /** 
    181259     * The color of the noise that is drawn 
    182      * @var Securimage_Color  
     260     * @var Securimage_Color 
    183261     */ 
    184262    public $noise_color    = '#707070'; 
    185      
     263 
    186264    /** 
    187265     * How transparent to make the text 0 = completely opaque, 100 = invisible 
    188266     * @var int 
    189267     */ 
    190     public $text_transparency_percentage = 50; 
     268    public $text_transparency_percentage = 20; 
    191269    /** 
    192270     * Whether or not to draw the text transparently, true = use transparency, false = no transparency 
    193271     * @var bool 
    194272     */ 
    195     public $use_transparent_text         = false; 
    196      
     273    public $use_transparent_text         = true; 
     274 
    197275    /** 
    198276     * The length of the captcha code 
     
    215293     */ 
    216294    public $expiry_time    = 900; 
    217      
     295 
    218296    /** 
    219297     * The session name securimage should use, only set this if your application uses a custom session name 
     
    222300     */ 
    223301    public $session_name   = null; 
    224      
     302 
    225303    /** 
    226304     * true to use the wordlist file, false to generate random captcha codes 
     
    233311     * @var double 
    234312     */ 
    235     public $perturbation = 0.75; 
     313    public $perturbation = 0.85; 
    236314    /** 
    237315     * How many lines to draw over the captcha code to increase security 
    238316     * @var int 
    239317     */ 
    240     public $num_lines    = 8; 
     318    public $num_lines    = 5; 
    241319    /** 
    242320     * The level of noise (random dots) to place on the image, 0-10 
    243321     * @var int 
    244322     */ 
    245     public $noise_level  = 0; 
    246      
     323    public $noise_level  = 2; 
     324 
    247325    /** 
    248326     * The signature text to draw on the bottom corner of the image 
     
    260338     */ 
    261339    public $signature_font; 
    262      
    263     /** 
     340 
     341    /** 
     342     * DO NOT USE!!! 
    264343     * Use an SQLite database to store data (for users that do not support cookies) 
    265344     * @var bool 
     345     * @see Securimage::$use_sqlite_db 
     346     * @deprecated 3.2RC4 
    266347     */ 
    267348    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 
    269420    /** 
    270421     * The type of captcha to create, either alphanumeric, or a math problem<br /> 
     
    272423     * @var int 
    273424     */ 
    274     public $captcha_type  = self::SI_CAPTCHA_STRING; 
    275      
     425    public $captcha_type  = self::SI_CAPTCHA_STRING; // or self::SI_CAPTCHA_MATHEMATIC; 
     426 
    276427    /** 
    277428     * 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 
     
    281432     * // in securimage_show.php (create one show script for each form) 
    282433     * $img->namespace = 'contact_form'; 
    283      *  
     434     * 
    284435     * // in form validator 
    285436     * $img->namespace = 'contact_form'; 
     
    290441     */ 
    291442    public $namespace; 
    292      
     443 
    293444    /** 
    294445     * The font file to use to draw the captcha code, leave blank for default font AHGBold.ttf 
     
    308459    /** 
    309460     * The path to the SQLite database file to use, if $use_sqlite_database = true, should be chmod 666 
     461     * @deprecated 3.2RC4 
    310462     * @var string 
    311463     */ 
     
    315467     * @var string 
    316468     * <code> 
    317      * $img->audio_path = '/home/yoursite/public_html/securimage/audio/'; 
     469     * $img->audio_path = '/home/yoursite/public_html/securimage/audio/en/'; 
    318470     * </code> 
    319471     */ 
    320472    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 
    324541    protected $im; 
    325542    protected $tmpimg; 
    326543    protected $bgimg; 
    327544    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     */ 
    331553    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     */ 
    332560    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     */ 
    334577    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 
    337608    protected $gdbgcolor; 
    338609    protected $gdtextcolor; 
    339610    protected $gdlinecolor; 
    340611    protected $gdsignaturecolor; 
    341      
     612 
    342613    /** 
    343614     * Create a new securimage object, pass options to set in the constructor.<br /> 
     
    352623     *     'font_file' => Securimage::getPath() . '/custom.ttf' 
    353624     * ); 
    354      *  
     625     * 
    355626     * $img = new Securimage($options); 
    356627     * </code> 
     
    359630    { 
    360631        $this->securimage_path = dirname(__FILE__); 
    361          
     632 
    362633        if (is_array($options) && sizeof($options) > 0) { 
    363634            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                } 
    365643            } 
    366644        } 
     
    372650        $this->signature_color = $this->initColor($this->signature_color, '#616161'); 
    373651 
    374         if ($this->ttf_file == null) { 
     652        if (is_null($this->ttf_file)) { 
    375653            $this->ttf_file = $this->securimage_path . '/AHGBold.ttf'; 
    376654        } 
    377          
     655 
    378656        $this->signature_font = $this->ttf_file; 
    379          
    380         if ($this->wordlist_file == null) { 
     657 
     658        if (is_null($this->wordlist_file)) { 
    381659            $this->wordlist_file = $this->securimage_path . '/words/words.txt'; 
    382660        } 
    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) { 
    393683            $this->code_length = 6; 
    394684        } 
    395          
    396         if ($this->perturbation == null || !is_numeric($this->perturbation)) { 
     685 
     686        if (is_null($this->perturbation) || !is_numeric($this->perturbation)) { 
    397687            $this->perturbation = 0.75; 
    398688        } 
    399          
    400         if ($this->namespace == null || !is_string($this->namespace)) { 
     689 
     690        if (is_null($this->namespace) || !is_string($this->namespace)) { 
    401691            $this->namespace = 'default'; 
    402692        } 
    403693 
    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 
    413717    /** 
    414718     * Return the absolute path to the Securimage directory 
     
    419723        return dirname(__FILE__); 
    420724    } 
    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 
    422794    /** 
    423795     * Used to serve a captcha image to the browser 
    424796     * @param string $background_image The path to the background image to use 
    425      * <code>  
     797     * <code> 
    426798     * $img = new Securimage(); 
    427799     * $img->code_length = 6; 
    428800     * $img->num_lines   = 5; 
    429801     * $img->noise_level = 5; 
    430      *  
     802     * 
    431803     * $img->show(); // sends the image to browser 
    432804     * exit; 
     
    435807    public function show($background_image = '') 
    436808    { 
     809        set_error_handler(array(&$this, 'errorHandler')); 
     810 
    437811        if($background_image != '' && is_readable($background_image)) { 
    438812            $this->bgimg = $background_image; 
     
    441815        $this->doImage(); 
    442816    } 
    443      
     817 
    444818    /** 
    445819     * Check a submitted code against the stored value 
     
    461835        return $this->correct_code; 
    462836    } 
    463      
     837 
    464838    /** 
    465839     * Output a wav file of the captcha code to the browser 
    466      *  
     840     * 
    467841     * <code> 
    468842     * $img = new Securimage(); 
     
    473847    public function outputAudioFile() 
    474848    { 
    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 
    491941    /** 
    492942     * The main image drawing routing, responsible for constructing the entire image and serving it 
     
    499949            $imagecreate = 'imagecreate'; 
    500950        } 
    501          
     951 
    502952        $this->im     = $imagecreate($this->image_width, $this->image_height); 
    503953        $this->tmpimg = $imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale); 
    504          
     954 
    505955        $this->allocateColors(); 
    506956        imagepalettecopy($this->tmpimg, $this->im); 
     
    508958        $this->setBackground(); 
    509959 
    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        } 
    513990 
    514991        if ($this->noise_level > 0) { 
    515992            $this->drawNoise(); 
    516993        } 
    517          
     994 
     995        $this->drawWord(); 
     996 
    518997        if ($this->perturbation > 0 && is_readable($this->ttf_file)) { 
    519998            $this->distortedCopy(); 
     
    5301009        $this->output(); 
    5311010    } 
    532      
     1011 
    5331012    /** 
    5341013     * Allocate the colors to be used for the image 
     
    5411020                                              $this->image_bg_color->g, 
    5421021                                              $this->image_bg_color->b); 
    543          
     1022 
    5441023        $alpha = intval($this->text_transparency_percentage / 100 * 127); 
    545          
     1024 
    5461025        if ($this->use_transparent_text == true) { 
    5471026            $this->gdtextcolor = imagecolorallocatealpha($this->im, 
     
    5741053                                                          $this->noise_color->b); 
    5751054        } 
    576      
     1055 
    5771056        $this->gdsignaturecolor = imagecolorallocate($this->im, 
    5781057                                                     $this->signature_color->r, 
     
    5811060 
    5821061    } 
    583      
     1062 
    5841063    /** 
    5851064     * The the background color, or background image to be used 
     
    5941073                             $this->image_width * $this->iscale, $this->image_height * $this->iscale, 
    5951074                             $this->gdbgcolor); 
    596      
     1075 
    5971076        if ($this->bgimg == '') { 
    598             if ($this->background_directory != null &&  
     1077            if ($this->background_directory != null && 
    5991078                is_dir($this->background_directory) && 
    6001079                is_readable($this->background_directory)) 
     
    6061085            } 
    6071086        } 
    608          
     1087 
    6091088        if ($this->bgimg == '') { 
    6101089            return; 
     
    6121091 
    6131092        $dat = @getimagesize($this->bgimg); 
    614         if($dat == false) {  
     1093        if($dat == false) { 
    6151094            return; 
    6161095        } 
     
    6291108                         imagesx($newim), imagesy($newim)); 
    6301109    } 
    631      
     1110 
    6321111    /** 
    6331112     * Scan the directory for a background image to use 
     
    6451124 
    6461125            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)]; 
    6481127            } 
    6491128        } 
     
    6511130        return false; 
    6521131    } 
    653      
     1132 
    6541133    /** 
    6551134     * Generates the code or math problem and saves the value to the session 
    6561135     */ 
    657     protected function createCode() 
     1136    public function createCode() 
    6581137    { 
    6591138        $this->code = false; 
     
    6621141            case self::SI_CAPTCHA_MATHEMATIC: 
    6631142            { 
    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 
    6751156                $this->code         = $c; 
    6761157                $this->code_display = "$left $sign $right"; 
    6771158                break; 
    6781159            } 
    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 
    6801167            default: 
    6811168            { 
     
    6871174                    $this->code = $this->generateCode($this->code_length); 
    6881175                } 
    689                  
     1176 
    6901177                $this->code_display = $this->code; 
    6911178                $this->code         = ($this->case_sensitive) ? $this->code : strtolower($this->code); 
    6921179            } // default 
    6931180        } 
    694          
     1181 
    6951182        $this->saveData(); 
    6961183    } 
    697      
     1184 
    6981185    /** 
    6991186     * Draws the captcha code on the image 
     
    7031190        $width2  = $this->image_width * $this->iscale; 
    7041191        $height2 = $this->image_height * $this->iscale; 
    705           
     1192 
    7061193        if (!is_readable($this->ttf_file)) { 
    7071194            imagestring($this->im, 4, 10, ($this->image_height / 2) - 5, 'Failed to load TTF font file!', $this->gdtextcolor); 
     
    7271214            } 
    7281215        } 
    729          
     1216 
    7301217        // DEBUG 
    7311218        //$this->im = $this->tmpimg; 
    7321219        //$this->output(); 
    733          
    734     } 
    735      
     1220 
     1221    } 
     1222 
    7361223    /** 
    7371224     * Copies the captcha image to the final image with distortion applied 
     
    7421229        // make array of poles AKA attractor points 
    7431230        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); 
    7471234            $tmp     = ((- $this->frand()) * 0.15) - .15; 
    7481235            $amp[$i] = $this->perturbation * $tmp; 
    7491236        } 
    750          
     1237 
    7511238        $bgCol = imagecolorat($this->tmpimg, 0, 0); 
    7521239        $width2 = $this->iscale * $this->image_width; 
     
    7841271        } 
    7851272    } 
    786      
     1273 
    7871274    /** 
    7881275     * Draws distorted lines on the image 
     
    7931280            $x = $this->image_width * (1 + $line) / ($this->num_lines + 1); 
    7941281            $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 
    7971284            $theta = ($this->frand() - 0.5) * M_PI * 0.7; 
    7981285            $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 
    8021289            $k = $this->frand() * 0.6 + 0.2; 
    8031290            $k = $k * $k * 0.5; 
     
    8101297            $x0 = $x - 0.5 * $len * cos($theta); 
    8111298            $y0 = $y - 0.5 * $len * sin($theta); 
    812              
     1299 
    8131300            $ldx = round(- $dy * $lwid); 
    8141301            $ldy = round($dx * $lwid); 
    815              
     1302 
    8161303            for ($i = 0; $i < $n; ++ $i) { 
    8171304                $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi); 
     
    8211308        } 
    8221309    } 
    823      
     1310 
    8241311    /** 
    8251312     * Draws random noise on the image 
     
    8341321 
    8351322        $t0 = microtime(true); 
    836          
     1323 
    8371324        $noise_level *= 125; // an arbitrary number that works well on a 1-10 scale 
    838          
     1325 
    8391326        $points = $this->image_width * $this->image_height * $this->iscale; 
    8401327        $height = $this->image_height * $this->iscale; 
    8411328        $width  = $this->image_width * $this->iscale; 
    8421329        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); 
    8461333            if ($x - $size <= 0 && $y - $size <= 0) continue; // dont cover 0,0 since it is used by imagedistortedcopy 
    8471334            imagefilledarc($this->tmpimg, $x, $y, $size, $size, 0, 360, $this->gdnoisecolor, IMG_ARC_PIE); 
    8481335        } 
    849          
     1336 
    8501337        $t1 = microtime(true); 
    851          
     1338 
    8521339        $t = $t1 - $t0; 
    853          
     1340 
    8541341        /* 
    8551342        // DEBUG 
     
    8601347        */ 
    8611348    } 
    862      
    863         /** 
    864         * Print signature text on image 
    865         */ 
     1349 
     1350    /** 
     1351    * Print signature text on image 
     1352    */ 
    8661353    protected function addSignature() 
    8671354    { 
    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            } 
    8701395        } 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 
    9071402        imagedestroy($this->im); 
    908         exit(); 
    909     } 
    910      
     1403        restore_error_handler(); 
     1404 
     1405        if (!$this->no_exit) exit; 
     1406    } 
     1407 
    9111408    /** 
    9121409     * 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    { 
    9211415        $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); 
    9351435        } 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 { 
    9361447            return $this->generateWAV($letters); 
     1448        } catch(Exception $ex) { 
     1449            throw $ex; 
    9371450        } 
    9381451    } 
     
    9411454     * Gets a captcha code from a wordlist 
    9421455     */ 
    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'); 
    9461459        if (!$fp) return false; 
    9471460 
     
    9491462        if ($fsize < 128) return false; // too small of a list to be effective 
    9501463 
    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 
    9531487        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 
    9681496    /** 
    9691497     * Generates a random captcha code from the set character set 
     
    9731501        $code = ''; 
    9741502 
    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 
    9811513        return $code; 
    9821514    } 
    983      
     1515 
    9841516    /** 
    9851517     * Checks the entered code against the value stored in the session or sqlite database, handles case sensitivity 
     
    9881520    protected function validate() 
    9891521    { 
    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 
    9941530        if ($this->case_sensitive == false && preg_match('/[A-Z]/', $code)) { 
    9951531            // case sensitive was set from securimage_show.php but not in class 
     
    9971533            $this->case_sensitive = true; 
    9981534        } 
    999          
     1535 
    10001536        $code_entered = trim( (($this->case_sensitive) ? $this->code_entered 
    10011537                                                       : strtolower($this->code_entered)) 
    10021538                        ); 
    10031539        $this->correct_code = false; 
    1004          
     1540 
    10051541        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 
    10061548            if ($code == $code_entered) { 
    10071549                $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                } 
    10101554                $this->clearCodeFromDatabase(); 
    10111555            } 
    10121556        } 
    10131557    } 
    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() 
    10191814    { 
    10201815        $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 
    10341850        return $code; 
    10351851    } 
    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) { 
    11001859            $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 
    11281879    /** 
    11291880     * Deletes old codes from sqlite database 
     
    11311882    protected function purgeOldCodesFromDatabase() 
    11321883    { 
    1133         if ($this->use_sqlite_db && $this->sqlite_handle !== false) { 
     1884        if ($this->use_database && $this->pdo_conn) { 
    11341885            $now   = time(); 
    11351886            $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 
    11411897    /** 
    11421898     * Checks to see if the captcha code has expired and cannot be used 
     
    11461902    { 
    11471903        $expired = true; 
    1148          
     1904 
    11491905        if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) { 
    11501906            $expired = false; 
     
    11521908            $expired = false; 
    11531909        } 
    1154          
     1910 
    11551911        return $expired; 
    11561912    } 
    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 
    11691914    /** 
    11701915     * Generate a wav file given the $letters in the code 
     
    11751920    protected function generateWAV($letters) 
    11761921    { 
    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; 
    12461937                } 
    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 
    13012026    /** 
    13022027     * Return a wav file saying there was an error generating file 
    1303      *  
     2028     * 
    13042029     * @return string The binary audio contents 
    13052030     */ 
    13062031    protected function audioError() 
    13072032    { 
    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     */ 
    13112059    function frand() 
    13122060    { 
    1313         return 0.0001 * rand(0,9999); 
    1314     } 
    1315      
     2061        return 0.0001 * mt_rand(0,9999); 
     2062    } 
     2063 
    13162064    /** 
    13172065     * Convert an html color code to a Securimage_Color 
     
    13342082            return new Securimage_Color($default); 
    13352083        } 
     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; 
    13362112    } 
    13372113} 
     
    13602136     * $color = new Securimage_Color('#0080FF') or <br /> 
    13612137     * $color = new Securimage_Color(0, 128, 255) 
    1362      *  
     2138     * 
    13632139     * @param string $color 
    13642140     * @throws Exception 
     
    13672143    { 
    13682144        $args = func_get_args(); 
    1369          
     2145 
    13702146        if (sizeof($args) == 0) { 
    13712147            $this->r = 255; 
     
    13772153                $color = substr($color, 1); 
    13782154            } 
    1379              
     2155 
    13802156            if (strlen($color) != 3 && strlen($color) != 6) { 
    13812157                throw new InvalidArgumentException( 
     
    13832159                ); 
    13842160            } 
    1385              
     2161 
    13862162            $this->constructHTML($color); 
    13872163        } else if (sizeof($args) == 3) { 
     
    13932169        } 
    13942170    } 
    1395      
     2171 
    13962172    /** 
    13972173     * Construct from an rgb triplet 
     
    14082184        if ($blue < 0)    $blue  = 0; 
    14092185        if ($blue > 255)  $blue  = 255; 
    1410          
     2186 
    14112187        $this->r = $red; 
    14122188        $this->g = $green; 
    14132189        $this->b = $blue; 
    14142190    } 
    1415      
     2191 
    14162192    /** 
    14172193     * Construct from an html hex color code 
     
    14272203            $red   = substr($color, 0, 2); 
    14282204            $green = substr($color, 2, 2); 
    1429             $blue  = substr($color, 4, 2);  
    1430         } 
    1431          
     2205            $blue  = substr($color, 4, 2); 
     2206        } 
     2207 
    14322208        $this->r = hexdec($red); 
    14332209        $this->g = hexdec($green); 
     
    14352211    } 
    14362212} 
    1437 ?> 
Note: See TracChangeset for help on using the changeset viewer.