Ignore:
Timestamp:
Jan 9, 2014, 1:36:32 PM (10 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.