~lss-team/lilsoftstats/trunk

« back to all changes in this revision

Viewing changes to inc/securimage.php

  • Committer: Nick
  • Date: 2011-11-14 04:10:28 UTC
  • Revision ID: nick@little-apps.org-20111114041028-cvmpwq6z6hx3pkya
first commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
// error_reporting(E_ALL); ini_set('display_errors', 1); // uncomment this line for debugging
 
3
 
 
4
/**
 
5
 * Project:     Securimage: A PHP class for creating and managing form CAPTCHA images<br />
 
6
 * File:        securimage.php<br />
 
7
 *
 
8
 * Copyright (c) 2011, Drew Phillips
 
9
 * All rights reserved.
 
10
 * 
 
11
 * Redistribution and use in source and binary forms, with or without modification,
 
12
 * are permitted provided that the following conditions are met:
 
13
 * 
 
14
 *  - Redistributions of source code must retain the above copyright notice,
 
15
 *    this list of conditions and the following disclaimer.
 
16
 *  - Redistributions in binary form must reproduce the above copyright notice,
 
17
 *    this list of conditions and the following disclaimer in the documentation
 
18
 *    and/or other materials provided with the distribution.
 
19
 * 
 
20
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 
21
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 
22
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 
23
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 
24
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 
25
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 
26
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 
27
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 
28
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 
29
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 
30
 * POSSIBILITY OF SUCH DAMAGE.
 
31
 *
 
32
 * Any modifications to the library should be indicated clearly in the source code
 
33
 * to inform users that the changes are not a part of the original software.<br /><br />
 
34
 *
 
35
 * If you found this script useful, please take a quick moment to rate it.<br />
 
36
 * http://www.hotscripts.com/rate/49400.html  Thanks.
 
37
 *
 
38
 * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
 
39
 * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
 
40
 * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
 
41
 * @copyright 2011 Drew Phillips
 
42
 * @author Drew Phillips <drew@drew-phillips.com>
 
43
 * @version 3.0 (October 2011)
 
44
 * @package Securimage
 
45
 *
 
46
 */
 
47
 
 
48
/**
 
49
 ChangeLog
 
50
 
 
51
 3.0
 
52
 - Rewrite class using PHP5 OOP
 
53
 - Remove support for GD fonts, require FreeType
 
54
 - Remove support for multi-color codes
 
55
 - Add option to make codes case-sensitive
 
56
 - Add namespaces to support multiple captchas on a single page or page specific captchas
 
57
 - Add option to show simple math problems instead of codes
 
58
 - Remove support for mp3 files due to vulnerability in decoding mp3 audio files
 
59
 - Create new flash file to stream wav files instead of mp3
 
60
 - Changed to BSD license
 
61
 
 
62
 2.0.2
 
63
 - Fix pathing to make integration into libraries easier (Nathan Phillip Brink ohnobinki@ohnopublishing.net)
 
64
 
 
65
 2.0.1
 
66
 - Add support for browsers with cookies disabled (requires php5, sqlite) maps users to md5 hashed ip addresses and md5 hashed codes for security
 
67
 - Add fallback to gd fonts if ttf support is not enabled or font file not found (Mike Challis http://www.642weather.com/weather/scripts.php)
 
68
 - Check for previous definition of image type constants (Mike Challis)
 
69
 - Fix mime type settings for audio output
 
70
 - Fixed color allocation issues with multiple colors and background images, consolidate allocation to one function
 
71
 - Ability to let codes expire after a given length of time
 
72
 - Allow HTML color codes to be passed to Securimage_Color (suggested by Mike Challis)
 
73
 
 
74
 2.0.0
 
75
 - Add mathematical distortion to characters (using code from HKCaptcha)
 
76
 - Improved session support
 
77
 - Added Securimage_Color class for easier color definitions
 
78
 - Add distortion to audio output to prevent binary comparison attack (proposed by Sven "SavageTiger" Hagemann [insecurity.nl])
 
79
 - Flash button to stream mp3 audio (Douglas Walsh www.douglaswalsh.net)
 
80
 - Audio output is mp3 format by default
 
81
 - Change font to AlteHaasGrotesk by yann le coroller
 
82
 - Some code cleanup 
 
83
 
 
84
 1.0.4 (unreleased)
 
85
 - Ability to output audible codes in mp3 format to stream from flash
 
86
 
 
87
 1.0.3.1
 
88
 - Error reading from wordlist in some cases caused words to be cut off 1 letter short
 
89
 
 
90
 1.0.3
 
91
 - Removed shadow_text from code which could cause an undefined property error due to removal from previous version
 
92
 
 
93
 1.0.2
 
94
 - Audible CAPTCHA Code wav files
 
95
 - Create codes from a word list instead of random strings
 
96
 
 
97
 1.0
 
98
 - Added the ability to use a selected character set, rather than a-z0-9 only.
 
99
 - Added the multi-color text option to use different colors for each letter.
 
100
 - Switched to automatic session handling instead of using files for code storage
 
101
 - Added GD Font support if ttf support is not available.  Can use internal GD fonts or load new ones.
 
102
 - Added the ability to set line thickness
 
103
 - Added option for drawing arced lines over letters
 
104
 - Added ability to choose image type for output
 
105
 
 
106
 */
 
107
 
 
108
 
 
109
/**
 
110
 * Securimage CAPTCHA Class.
 
111
 *
 
112
 * @version    3.0
 
113
 * @package    Securimage
 
114
 * @subpackage classes
 
115
 * @author     Drew Phillips <drew@drew-phillips.com>
 
116
 *
 
117
 */
 
118
class Securimage
 
119
{
 
120
        // All of the public variables below are securimage options
 
121
        // They can be passed as an array to the Securimage constructor, set below,
 
122
        // or set from securimage_show.php and securimage_play.php
 
123
        
 
124
    /**
 
125
     * Renders captcha as a JPEG image
 
126
     * @var int
 
127
     */
 
128
    const SI_IMAGE_JPEG = 1;
 
129
    /**
 
130
     * Renders captcha as a PNG image (default)
 
131
     * @var int
 
132
     */
 
133
    const SI_IMAGE_PNG  = 2;
 
134
    /**
 
135
     * Renders captcha as a GIF image
 
136
     * @var int
 
137
     */
 
138
    const SI_IMAGE_GIF  = 3;
 
139
    
 
140
    /**
 
141
     * Create a normal alphanumeric captcha
 
142
     * @var int
 
143
     */
 
144
    const SI_CAPTCHA_STRING     = 0;
 
145
    /**
 
146
     * Create a captcha consisting of a simple math problem
 
147
     * @var int
 
148
     */
 
149
    const SI_CAPTCHA_MATHEMATIC = 1;
 
150
    
 
151
    /**
 
152
     * The width of the captcha image
 
153
     * @var int
 
154
     */
 
155
    public $image_width = 300;
 
156
    /**
 
157
     * The height of the captcha image
 
158
     * @var int
 
159
     */
 
160
    public $image_height = 80;
 
161
    /**
 
162
     * The type of the image, default = png
 
163
     * @var int
 
164
     */
 
165
    public $image_type   = self::SI_IMAGE_PNG;
 
166
 
 
167
    /**
 
168
     * The background color of the captcha
 
169
     * @var Securimage_Color
 
170
     */
 
171
    public $image_bg_color = '#ffffff';
 
172
    /**
 
173
     * The color of the captcha text
 
174
     * @var Securimage_Color
 
175
     */
 
176
    public $text_color     = '#707070';
 
177
    /**
 
178
     * The color of the lines over the captcha
 
179
     * @var Securimage_Color
 
180
     */
 
181
    public $line_color     = '#707070';
 
182
    /**
 
183
     * The color of the noise that is drawn
 
184
     * @var Securimage_Color 
 
185
     */
 
186
    public $noise_color    = '#707070';
 
187
    
 
188
    /**
 
189
     * How transparent to make the text 0 = completely opaque, 100 = invisible
 
190
     * @var int
 
191
     */
 
192
    public $text_transparency_percentage = 50;
 
193
    /**
 
194
     * Whether or not to draw the text transparently, true = use transparency, false = no transparency
 
195
     * @var bool
 
196
     */
 
197
    public $use_transparent_text         = false;
 
198
    
 
199
    /**
 
200
     * The length of the captcha code
 
201
     * @var int
 
202
     */
 
203
    public $code_length    = 6;
 
204
    /**
 
205
     * Whether the captcha should be case sensitive (not recommended, use only for maximum protection)
 
206
     * @var bool
 
207
     */
 
208
    public $case_sensitive = false;
 
209
    /**
 
210
     * The character set to use for generating the captcha code
 
211
     * @var string
 
212
     */
 
213
    public $charset        = 'ABCDEFGHKLMNPRSTUVWYZabcdefghklmnprstuvwyz23456789';
 
214
    /**
 
215
     * How long in seconds a captcha remains valid, after this time it will not be accepted
 
216
     * @var unknown_type
 
217
     */
 
218
    public $expiry_time    = 900;
 
219
    
 
220
    /**
 
221
     * The session name securimage should use, only set this if your application uses a custom session name
 
222
     * It is recommended to set this value below so it is used by all securimage scripts
 
223
     * @var string
 
224
     */
 
225
    public $session_name   = null;
 
226
    
 
227
    /**
 
228
     * true to use the wordlist file, false to generate random captcha codes
 
229
     * @var bool
 
230
     */
 
231
    public $use_wordlist   = false;
 
232
 
 
233
    /**
 
234
     * The level of distortion, 0.75 = normal, 1.0 = very high distortion
 
235
     * @var double
 
236
     */
 
237
    public $perturbation = 0.75;
 
238
    /**
 
239
     * How many lines to draw over the captcha code to increase security
 
240
     * @var int
 
241
     */
 
242
    public $num_lines    = 8;
 
243
    /**
 
244
     * The level of noise (random dots) to place on the image, 0-10
 
245
     * @var int
 
246
     */
 
247
    public $noise_level  = 0;
 
248
    
 
249
    /**
 
250
     * The signature text to draw on the bottom corner of the image
 
251
     * @var string
 
252
     */
 
253
    public $image_signature = '';
 
254
    /**
 
255
     * The color of the signature text
 
256
     * @var Securimage_Color
 
257
     */
 
258
    public $signature_color = '#707070';
 
259
    /**
 
260
     * The path to the ttf font file to use for the signature text, defaults to $ttf_file (AHGBold.ttf)
 
261
     * @var string
 
262
     */
 
263
    public $signature_font;
 
264
    
 
265
    /**
 
266
     * Use an SQLite database to store data (for users that do not support cookies)
 
267
     * @var bool
 
268
     */
 
269
    public $use_sqlite_db = false;
 
270
    
 
271
    /**
 
272
     * The type of captcha to create, either alphanumeric, or a math problem<br />
 
273
     * Securimage::SI_CAPTCHA_STRING or Securimage::SI_CAPTCHA_MATHEMATIC
 
274
     * @var int
 
275
     */
 
276
    public $captcha_type  = self::SI_CAPTCHA_STRING;
 
277
    
 
278
    /**
 
279
     * 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
 
280
     * @var string
 
281
     * <code>
 
282
     * <?php
 
283
     * // in securimage_show.php (create one show script for each form)
 
284
     * $img->namespace = 'contact_form';
 
285
     * 
 
286
     * // in form validator
 
287
     * $img->namespace = 'contact_form';
 
288
     * if ($img->check($code) == true) {
 
289
     *     echo "Valid!";
 
290
     *  }
 
291
     * </code>
 
292
     */
 
293
    public $namespace;
 
294
    
 
295
    /**
 
296
     * The font file to use to draw the captcha code, leave blank for default font AHGBold.ttf
 
297
     * @var string
 
298
     */
 
299
    public $ttf_file;
 
300
    /**
 
301
     * The path to the wordlist file to use, leave blank for default words/words.txt
 
302
     * @var string
 
303
     */
 
304
    public $wordlist_file;
 
305
    /**
 
306
     * The directory to scan for background images, if set a random background will be chosen from this folder
 
307
     * @var string
 
308
     */
 
309
    public $background_directory;
 
310
    /**
 
311
     * The path to the SQLite database file to use, if $use_sqlite_database = true, should be chmod 666
 
312
     * @var string
 
313
     */
 
314
    public $sqlite_database;
 
315
    /**
 
316
     * The path to the securimage audio directory, can be set in securimage_play.php
 
317
     * @var string
 
318
     * <code>
 
319
     * $img->audio_path = '/home/yoursite/public_html/securimage/audio/';
 
320
     * </code>
 
321
     */
 
322
    public $audio_path;
 
323
 
 
324
    
 
325
    
 
326
    protected $im;
 
327
    protected $tmpimg;
 
328
    protected $bgimg;
 
329
    protected $iscale = 5;
 
330
    
 
331
    protected $securimage_path = null;
 
332
    
 
333
    protected $code;
 
334
    protected $code_display;
 
335
    
 
336
    protected $captcha_code;
 
337
    protected $sqlite_handle;
 
338
    
 
339
    protected $gdbgcolor;
 
340
    protected $gdtextcolor;
 
341
    protected $gdlinecolor;
 
342
    protected $gdsignaturecolor;
 
343
    
 
344
    /**
 
345
     * Create a new securimage object, pass options to set in the constructor.<br />
 
346
     * This can be used to display a captcha, play an audible captcha, or validate an entry
 
347
     * @param array $options
 
348
     * <code>
 
349
     * $options = array(
 
350
     *     'text_color' => new Securimage_Color('#013020'),
 
351
     *     'code_length' => 5,
 
352
     *     'num_lines' => 5,
 
353
     *     'noise_level' => 3,
 
354
     *     'font_file' => Securimage::getPath() . '/custom.ttf'
 
355
     * );
 
356
     * 
 
357
     * $img = new Securimage($options);
 
358
     * </code>
 
359
     */
 
360
    public function __construct($options = array())
 
361
    {
 
362
        $this->securimage_path = dirname(__FILE__);
 
363
        
 
364
        if (is_array($options) && sizeof($options) > 0) {
 
365
            foreach($options as $prop => $val) {
 
366
                $this->$prop = $val;
 
367
            }
 
368
        }
 
369
 
 
370
        $this->image_bg_color  = $this->initColor($this->image_bg_color,  '#ffffff');
 
371
        $this->text_color      = $this->initColor($this->text_color,      '#616161');
 
372
        $this->line_color      = $this->initColor($this->line_color,      '#616161');
 
373
        $this->noise_color     = $this->initColor($this->noise_color,     '#616161');
 
374
        $this->signature_color = $this->initColor($this->signature_color, '#616161');
 
375
 
 
376
        if ($this->ttf_file == null) {
 
377
            $this->ttf_file = $this->securimage_path . '/AHGBold.ttf';
 
378
        }
 
379
        
 
380
        $this->signature_font = $this->ttf_file;
 
381
        
 
382
        if ($this->wordlist_file == null) {
 
383
            $this->wordlist_file = $this->securimage_path . '/words/words.txt';
 
384
        }
 
385
        
 
386
        if ($this->sqlite_database == null) {
 
387
            $this->sqlite_database = $this->securimage_path . '/database/securimage.sqlite';
 
388
        }
 
389
        
 
390
        if ($this->audio_path == null) {
 
391
            $this->audio_path = $this->securimage_path . '/audio/';
 
392
        }
 
393
        
 
394
        if ($this->code_length == null || $this->code_length < 1) {
 
395
            $this->code_length = 6;
 
396
        }
 
397
        
 
398
        if ($this->perturbation == null || !is_numeric($this->perturbation)) {
 
399
            $this->perturbation = 0.75;
 
400
        }
 
401
        
 
402
        if ($this->namespace == null || !is_string($this->namespace)) {
 
403
            $this->namespace = 'default';
 
404
        }
 
405
 
 
406
        // Initialize session or attach to existing
 
407
        if ( session_id() == '' ) { // no session has been started yet, which is needed for validation
 
408
            if ($this->session_name != null && trim($this->session_name) != '') {
 
409
                session_name(trim($this->session_name)); // set session name if provided
 
410
            }
 
411
            session_start();
 
412
        }
 
413
    }
 
414
    
 
415
    /**
 
416
     * Return the absolute path to the Securimage directory
 
417
     * @return string The path to the securimage base directory
 
418
     */
 
419
    public static function getPath()
 
420
    {
 
421
        return dirname(__FILE__);
 
422
    }
 
423
    
 
424
    /**
 
425
     * Used to serve a captcha image to the browser
 
426
     * @param string $background_image The path to the background image to use
 
427
     * <code> 
 
428
     * $img = new Securimage();
 
429
     * $img->code_length = 6;
 
430
     * $img->num_lines   = 5;
 
431
     * $img->noise_level = 5;
 
432
     * 
 
433
     * $img->show(); // sends the image to browser
 
434
     * exit;
 
435
     * </code>
 
436
     */
 
437
    public function show($background_image = '')
 
438
    {
 
439
        if($background_image != '' && is_readable($background_image)) {
 
440
            $this->bgimg = $background_image;
 
441
        }
 
442
 
 
443
        $this->doImage();
 
444
    }
 
445
    
 
446
    /**
 
447
     * Check a submitted code against the stored value
 
448
     * @param string $code  The captcha code to check
 
449
     * <code>
 
450
     * $code = $_POST['code'];
 
451
     * $img  = new Securimage();
 
452
     * if ($img->check($code) == true) {
 
453
     *     $captcha_valid = true;
 
454
     * } else {
 
455
     *     $captcha_valid = false;
 
456
     * }
 
457
     * </code>
 
458
     */
 
459
    public function check($code)
 
460
    {
 
461
        $this->code_entered = $code;
 
462
        $this->validate();
 
463
        return $this->correct_code;
 
464
    }
 
465
    
 
466
    /**
 
467
     * Output a wav file of the captcha code to the browser
 
468
     * 
 
469
     * <code>
 
470
     * $img = new Securimage();
 
471
     * $img->outputAudioFile(); // outputs a wav file to the browser
 
472
     * exit;
 
473
     * </code>
 
474
     */
 
475
    public function outputAudioFile()
 
476
    {
 
477
        $ext = 'wav'; // force wav - mp3 is insecure
 
478
        
 
479
        header("Content-Disposition: attachment; filename=\"securimage_audio.{$ext}\"");
 
480
        header('Cache-Control: no-store, no-cache, must-revalidate');
 
481
        header('Expires: Sun, 1 Jan 2000 12:00:00 GMT');
 
482
        header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
 
483
        header('Content-type: audio/x-wav');
 
484
        
 
485
        $audio = $this->getAudibleCode($ext);
 
486
 
 
487
        header('Content-Length: ' . strlen($audio));
 
488
 
 
489
        echo $audio;
 
490
        exit;
 
491
    }
 
492
    
 
493
    /**
 
494
     * The main image drawing routing, responsible for constructing the entire image and serving it
 
495
     */
 
496
    protected function doImage()
 
497
    {
 
498
        if( ($this->use_transparent_text == true || $this->bgimg != '') && function_exists('imagecreatetruecolor')) {
 
499
            $imagecreate = 'imagecreatetruecolor';
 
500
        } else {
 
501
            $imagecreate = 'imagecreate';
 
502
        }
 
503
        
 
504
        $this->im     = $imagecreate($this->image_width, $this->image_height);
 
505
        $this->tmpimg = $imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale);
 
506
        
 
507
        $this->allocateColors();
 
508
        imagepalettecopy($this->tmpimg, $this->im);
 
509
 
 
510
        $this->setBackground();
 
511
 
 
512
        $this->createCode();
 
513
 
 
514
        if ($this->noise_level > 0) {
 
515
            $this->drawNoise();
 
516
        }
 
517
        
 
518
        $this->drawWord();
 
519
        
 
520
        if ($this->perturbation > 0 && is_readable($this->ttf_file)) {
 
521
            $this->distortedCopy();
 
522
        }
 
523
 
 
524
        if ($this->num_lines > 0) {
 
525
            $this->drawLines();
 
526
        }
 
527
 
 
528
        if (trim($this->image_signature) != '') {
 
529
            $this->addSignature();
 
530
        }
 
531
 
 
532
        $this->output();
 
533
    }
 
534
    
 
535
    /**
 
536
     * Allocate the colors to be used for the image
 
537
     */
 
538
    protected function allocateColors()
 
539
    {
 
540
        // allocate bg color first for imagecreate
 
541
        $this->gdbgcolor = imagecolorallocate($this->im,
 
542
                                              $this->image_bg_color->r,
 
543
                                              $this->image_bg_color->g,
 
544
                                              $this->image_bg_color->b);
 
545
        
 
546
        $alpha = intval($this->text_transparency_percentage / 100 * 127);
 
547
        
 
548
        if ($this->use_transparent_text == true) {
 
549
            $this->gdtextcolor = imagecolorallocatealpha($this->im,
 
550
                                                         $this->text_color->r,
 
551
                                                         $this->text_color->g,
 
552
                                                         $this->text_color->b,
 
553
                                                         $alpha);
 
554
            $this->gdlinecolor = imagecolorallocatealpha($this->im,
 
555
                                                         $this->line_color->r,
 
556
                                                         $this->line_color->g,
 
557
                                                         $this->line_color->b,
 
558
                                                         $alpha);
 
559
            $this->gdnoisecolor = imagecolorallocatealpha($this->im,
 
560
                                                          $this->noise_color->r,
 
561
                                                          $this->noise_color->g,
 
562
                                                          $this->noise_color->b,
 
563
                                                          $alpha);
 
564
        } else {
 
565
            $this->gdtextcolor = imagecolorallocate($this->im,
 
566
                                                    $this->text_color->r,
 
567
                                                    $this->text_color->g,
 
568
                                                    $this->text_color->b);
 
569
            $this->gdlinecolor = imagecolorallocate($this->im,
 
570
                                                    $this->line_color->r,
 
571
                                                    $this->line_color->g,
 
572
                                                    $this->line_color->b);
 
573
            $this->gdnoisecolor = imagecolorallocate($this->im,
 
574
                                                          $this->noise_color->r,
 
575
                                                          $this->noise_color->g,
 
576
                                                          $this->noise_color->b);
 
577
        }
 
578
    
 
579
        $this->gdsignaturecolor = imagecolorallocate($this->im,
 
580
                                                     $this->signature_color->r,
 
581
                                                     $this->signature_color->g,
 
582
                                                     $this->signature_color->b);
 
583
 
 
584
    }
 
585
    
 
586
    /**
 
587
     * The the background color, or background image to be used
 
588
     */
 
589
    protected function setBackground()
 
590
    {
 
591
        // set background color of image by drawing a rectangle since imagecreatetruecolor doesn't set a bg color
 
592
        imagefilledrectangle($this->im, 0, 0,
 
593
                             $this->image_width, $this->image_height,
 
594
                             $this->gdbgcolor);
 
595
        imagefilledrectangle($this->tmpimg, 0, 0,
 
596
                             $this->image_width * $this->iscale, $this->image_height * $this->iscale,
 
597
                             $this->gdbgcolor);
 
598
    
 
599
        if ($this->bgimg == '') {
 
600
            if ($this->background_directory != null && 
 
601
                is_dir($this->background_directory) &&
 
602
                is_readable($this->background_directory))
 
603
            {
 
604
                $img = $this->getBackgroundFromDirectory();
 
605
                if ($img != false) {
 
606
                    $this->bgimg = $img;
 
607
                }
 
608
            }
 
609
        }
 
610
        
 
611
        if ($this->bgimg == '') {
 
612
            return;
 
613
        }
 
614
 
 
615
        $dat = @getimagesize($this->bgimg);
 
616
        if($dat == false) { 
 
617
            return;
 
618
        }
 
619
 
 
620
        switch($dat[2]) {
 
621
            case 1:  $newim = @imagecreatefromgif($this->bgimg); break;
 
622
            case 2:  $newim = @imagecreatefromjpeg($this->bgimg); break;
 
623
            case 3:  $newim = @imagecreatefrompng($this->bgimg); break;
 
624
            default: return;
 
625
        }
 
626
 
 
627
        if(!$newim) return;
 
628
 
 
629
        imagecopyresized($this->im, $newim, 0, 0, 0, 0,
 
630
                         $this->image_width, $this->image_height,
 
631
                         imagesx($newim), imagesy($newim));
 
632
    }
 
633
    
 
634
    /**
 
635
     * Scan the directory for a background image to use
 
636
     */
 
637
    protected function getBackgroundFromDirectory()
 
638
    {
 
639
        $images = array();
 
640
 
 
641
        if ( ($dh = opendir($this->background_directory)) !== false) {
 
642
            while (($file = readdir($dh)) !== false) {
 
643
                if (preg_match('/(jpg|gif|png)$/i', $file)) $images[] = $file;
 
644
            }
 
645
 
 
646
            closedir($dh);
 
647
 
 
648
            if (sizeof($images) > 0) {
 
649
                return rtrim($this->background_directory, '/') . '/' . $images[rand(0, sizeof($images)-1)];
 
650
            }
 
651
        }
 
652
 
 
653
        return false;
 
654
    }
 
655
    
 
656
    /**
 
657
     * Generates the code or math problem and saves the value to the session
 
658
     */
 
659
    protected function createCode()
 
660
    {
 
661
        $this->code = false;
 
662
 
 
663
        switch($this->captcha_type) {
 
664
            case self::SI_CAPTCHA_MATHEMATIC:
 
665
            {
 
666
                $signs = array('+', '-', 'x');
 
667
                $left  = rand(1, 10);
 
668
                $right = rand(1, 5);
 
669
                $sign  = $signs[rand(0, 2)];
 
670
                
 
671
                switch($sign) {
 
672
                    case 'x': $c = $left * $right; break;
 
673
                    case '-': $c = $left - $right; break;
 
674
                    default:  $c = $left + $right; break;
 
675
                }
 
676
                
 
677
                $this->code         = $c;
 
678
                $this->code_display = "$left $sign $right";
 
679
                break;
 
680
            }
 
681
            
 
682
            default:
 
683
            {
 
684
                if ($this->use_wordlist && is_readable($this->wordlist_file)) {
 
685
                    $this->code = $this->readCodeFromFile();
 
686
                }
 
687
 
 
688
                if ($this->code == false) {
 
689
                    $this->code = $this->generateCode($this->code_length);
 
690
                }
 
691
                
 
692
                $this->code_display = $this->code;
 
693
                $this->code         = ($this->case_sensitive) ? $this->code : strtolower($this->code);
 
694
            } // default
 
695
        }
 
696
        
 
697
        $this->saveData();
 
698
    }
 
699
    
 
700
    /**
 
701
     * Draws the captcha code on the image
 
702
     */
 
703
    protected function drawWord()
 
704
    {
 
705
        $width2  = $this->image_width * $this->iscale;
 
706
        $height2 = $this->image_height * $this->iscale;
 
707
         
 
708
        if (!is_readable($this->ttf_file)) {
 
709
            imagestring($this->im, 4, 10, ($this->image_height / 2) - 5, 'Failed to load TTF font file!', $this->gdtextcolor);
 
710
        } else {
 
711
            if ($this->perturbation > 0) {
 
712
                $font_size = $height2 * .4;
 
713
                $bb = imageftbbox($font_size, 0, $this->ttf_file, $this->code_display);
 
714
                $tx = $bb[4] - $bb[0];
 
715
                $ty = $bb[5] - $bb[1];
 
716
                $x  = floor($width2 / 2 - $tx / 2 - $bb[0]);
 
717
                $y  = round($height2 / 2 - $ty / 2 - $bb[1]);
 
718
 
 
719
                imagettftext($this->tmpimg, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
 
720
            } else {
 
721
                $font_size = $this->image_height * .4;
 
722
                $bb = imageftbbox($font_size, 0, $this->ttf_file, $this->code_display);
 
723
                $tx = $bb[4] - $bb[0];
 
724
                $ty = $bb[5] - $bb[1];
 
725
                $x  = floor($this->image_width / 2 - $tx / 2 - $bb[0]);
 
726
                $y  = round($this->image_height / 2 - $ty / 2 - $bb[1]);
 
727
 
 
728
                imagettftext($this->im, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
 
729
            }
 
730
        }
 
731
        
 
732
        // DEBUG
 
733
        //$this->im = $this->tmpimg;
 
734
        //$this->output();
 
735
        
 
736
    }
 
737
    
 
738
    /**
 
739
     * Copies the captcha image to the final image with distortion applied
 
740
     */
 
741
    protected function distortedCopy()
 
742
    {
 
743
        $numpoles = 3; // distortion factor
 
744
        // make array of poles AKA attractor points
 
745
        for ($i = 0; $i < $numpoles; ++ $i) {
 
746
            $px[$i]  = rand($this->image_width  * 0.2, $this->image_width  * 0.8);
 
747
            $py[$i]  = rand($this->image_height * 0.2, $this->image_height * 0.8);
 
748
            $rad[$i] = rand($this->image_height * 0.2, $this->image_height * 0.8);
 
749
            $tmp     = ((- $this->frand()) * 0.15) - .15;
 
750
            $amp[$i] = $this->perturbation * $tmp;
 
751
        }
 
752
        
 
753
        $bgCol = imagecolorat($this->tmpimg, 0, 0);
 
754
        $width2 = $this->iscale * $this->image_width;
 
755
        $height2 = $this->iscale * $this->image_height;
 
756
        imagepalettecopy($this->im, $this->tmpimg); // copy palette to final image so text colors come across
 
757
        // loop over $img pixels, take pixels from $tmpimg with distortion field
 
758
        for ($ix = 0; $ix < $this->image_width; ++ $ix) {
 
759
            for ($iy = 0; $iy < $this->image_height; ++ $iy) {
 
760
                $x = $ix;
 
761
                $y = $iy;
 
762
                for ($i = 0; $i < $numpoles; ++ $i) {
 
763
                    $dx = $ix - $px[$i];
 
764
                    $dy = $iy - $py[$i];
 
765
                    if ($dx == 0 && $dy == 0) {
 
766
                        continue;
 
767
                    }
 
768
                    $r = sqrt($dx * $dx + $dy * $dy);
 
769
                    if ($r > $rad[$i]) {
 
770
                        continue;
 
771
                    }
 
772
                    $rscale = $amp[$i] * sin(3.14 * $r / $rad[$i]);
 
773
                    $x += $dx * $rscale;
 
774
                    $y += $dy * $rscale;
 
775
                }
 
776
                $c = $bgCol;
 
777
                $x *= $this->iscale;
 
778
                $y *= $this->iscale;
 
779
                if ($x >= 0 && $x < $width2 && $y >= 0 && $y < $height2) {
 
780
                    $c = imagecolorat($this->tmpimg, $x, $y);
 
781
                }
 
782
                if ($c != $bgCol) { // only copy pixels of letters to preserve any background image
 
783
                    imagesetpixel($this->im, $ix, $iy, $c);
 
784
                }
 
785
            }
 
786
        }
 
787
    }
 
788
    
 
789
    /**
 
790
     * Draws distorted lines on the image
 
791
     */
 
792
    protected function drawLines()
 
793
    {
 
794
        for ($line = 0; $line < $this->num_lines; ++ $line) {
 
795
            $x = $this->image_width * (1 + $line) / ($this->num_lines + 1);
 
796
            $x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines;
 
797
            $y = rand($this->image_height * 0.1, $this->image_height * 0.9);
 
798
            
 
799
            $theta = ($this->frand() - 0.5) * M_PI * 0.7;
 
800
            $w = $this->image_width;
 
801
            $len = rand($w * 0.4, $w * 0.7);
 
802
            $lwid = rand(0, 2);
 
803
            
 
804
            $k = $this->frand() * 0.6 + 0.2;
 
805
            $k = $k * $k * 0.5;
 
806
            $phi = $this->frand() * 6.28;
 
807
            $step = 0.5;
 
808
            $dx = $step * cos($theta);
 
809
            $dy = $step * sin($theta);
 
810
            $n = $len / $step;
 
811
            $amp = 1.5 * $this->frand() / ($k + 5.0 / $len);
 
812
            $x0 = $x - 0.5 * $len * cos($theta);
 
813
            $y0 = $y - 0.5 * $len * sin($theta);
 
814
            
 
815
            $ldx = round(- $dy * $lwid);
 
816
            $ldy = round($dx * $lwid);
 
817
            
 
818
            for ($i = 0; $i < $n; ++ $i) {
 
819
                $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi);
 
820
                $y = $y0 + $i * $dy - $amp * $dx * sin($k * $i * $step + $phi);
 
821
                imagefilledrectangle($this->im, $x, $y, $x + $lwid, $y + $lwid, $this->gdlinecolor);
 
822
            }
 
823
        }
 
824
    }
 
825
    
 
826
    /**
 
827
     * Draws random noise on the image
 
828
     */
 
829
    protected function drawNoise()
 
830
    {
 
831
        if ($this->noise_level > 10) {
 
832
            $noise_level = 10;
 
833
        } else {
 
834
            $noise_level = $this->noise_level;
 
835
        }
 
836
 
 
837
        $t0 = microtime(true);
 
838
        
 
839
        $noise_level *= 125; // an arbitrary number that works well on a 1-10 scale
 
840
        
 
841
        $points = $this->image_width * $this->image_height * $this->iscale;
 
842
        $height = $this->image_height * $this->iscale;
 
843
        $width  = $this->image_width * $this->iscale;
 
844
        for ($i = 0; $i < $noise_level; ++$i) {
 
845
            $x = rand(10, $width);
 
846
            $y = rand(10, $height);
 
847
            $size = rand(7, 10);
 
848
            if ($x - $size <= 0 && $y - $size <= 0) continue; // dont cover 0,0 since it is used by imagedistortedcopy
 
849
            imagefilledarc($this->tmpimg, $x, $y, $size, $size, 0, 360, $this->gdnoisecolor, IMG_ARC_PIE);
 
850
        }
 
851
        
 
852
        $t1 = microtime(true);
 
853
        
 
854
        $t = $t1 - $t0;
 
855
        
 
856
        /*
 
857
        // DEBUG
 
858
        imagestring($this->tmpimg, 5, 25, 30, "$t", $this->gdnoisecolor);
 
859
        header('content-type: image/png');
 
860
        imagepng($this->tmpimg);
 
861
        exit;
 
862
        */
 
863
    }
 
864
    
 
865
        /**
 
866
        * Print signature text on image
 
867
        */
 
868
    protected function addSignature()
 
869
    {
 
870
        if ($this->use_gd_font) {
 
871
            imagestring($this->im, 5, $this->image_width - (strlen($this->image_signature) * 10), $this->image_height - 20, $this->image_signature, $this->gdsignaturecolor);
 
872
        } else {
 
873
             
 
874
            $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature);
 
875
            $textlen = $bbox[2] - $bbox[0];
 
876
            $x = $this->image_width - $textlen - 5;
 
877
            $y = $this->image_height - 3;
 
878
             
 
879
            imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature);
 
880
        }
 
881
    }
 
882
    
 
883
    /**
 
884
     * Sends the appropriate image and cache headers and outputs image to the browser
 
885
     */
 
886
    protected function output()
 
887
    {
 
888
        header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
 
889
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
 
890
        header("Cache-Control: no-store, no-cache, must-revalidate");
 
891
        header("Cache-Control: post-check=0, pre-check=0", false);
 
892
        header("Pragma: no-cache");
 
893
        
 
894
        switch ($this->image_type) {
 
895
            case self::SI_IMAGE_JPEG:
 
896
                header("Content-Type: image/jpeg");
 
897
                imagejpeg($this->im, null, 90);
 
898
                break;
 
899
            case self::SI_IMAGE_GIF:
 
900
                header("Content-Type: image/gif");
 
901
                imagegif($this->im);
 
902
                break;
 
903
            default:
 
904
                header("Content-Type: image/png");
 
905
                imagepng($this->im);
 
906
                break;
 
907
        }
 
908
        
 
909
        imagedestroy($this->im);
 
910
        exit();
 
911
    }
 
912
    
 
913
    /**
 
914
     * Gets the code and returns the binary audio file for the stored captcha code
 
915
     * @param string $format WAV only
 
916
     */
 
917
    protected function getAudibleCode($format = 'wav')
 
918
    {
 
919
        // override any format other than wav for now
 
920
        // this is due to security issues with MP3 files
 
921
        $format  = 'wav';
 
922
        
 
923
        $letters = array();
 
924
        $code    = $this->getCode();
 
925
 
 
926
        if ($code == '') {
 
927
            $this->createCode();
 
928
            $code = $this->getCode();
 
929
        }
 
930
 
 
931
        for($i = 0; $i < strlen($code); ++$i) {
 
932
            $letters[] = $code{$i};
 
933
        }
 
934
        
 
935
        if ($format == 'mp3') {
 
936
            return $this->generateMP3($letters);
 
937
        } else {
 
938
            return $this->generateWAV($letters);
 
939
        }
 
940
    }
 
941
 
 
942
    /**
 
943
     * Gets a captcha code from a wordlist
 
944
     */
 
945
    protected function readCodeFromFile()
 
946
    {
 
947
        $fp = @fopen($this->wordlist_file, 'rb');
 
948
        if (!$fp) return false;
 
949
 
 
950
        $fsize = filesize($this->wordlist_file);
 
951
        if ($fsize < 128) return false; // too small of a list to be effective
 
952
 
 
953
        fseek($fp, rand(0, $fsize - 64), SEEK_SET); // seek to a random position of file from 0 to filesize-64
 
954
        $data = fread($fp, 64); // read a chunk from our random position
 
955
        fclose($fp);
 
956
        $data = preg_replace("/\r?\n/", "\n", $data);
 
957
 
 
958
        $start = @strpos($data, "\n", rand(0, 56)) + 1; // random start position
 
959
        $end   = @strpos($data, "\n", $start);          // find end of word
 
960
        
 
961
        if ($start === false) {
 
962
            return false;
 
963
        } else if ($end === false) {
 
964
            $end = strlen($data);
 
965
        }
 
966
 
 
967
        return strtolower(substr($data, $start, $end - $start)); // return a line of the file
 
968
    }
 
969
    
 
970
    /**
 
971
     * Generates a random captcha code from the set character set
 
972
     */
 
973
    protected function generateCode()
 
974
    {
 
975
        $code = '';
 
976
 
 
977
        for($i = 1, $cslen = strlen($this->charset); $i <= $this->code_length; ++$i) {
 
978
            $code .= $this->charset{rand(0, $cslen - 1)};
 
979
        }
 
980
        
 
981
        //return 'testing';  // debug, set the code to given string
 
982
        
 
983
        return $code;
 
984
    }
 
985
    
 
986
    /**
 
987
     * Checks the entered code against the value stored in the session or sqlite database, handles case sensitivity
 
988
     * Also clears the stored codes if the code was entered correctly to prevent re-use
 
989
     */
 
990
    protected function validate()
 
991
    {
 
992
        $code = $this->getCode();
 
993
        // returns stored code, or an empty string if no stored code was found
 
994
        // checks the session and sqlite database if enabled
 
995
        
 
996
        if ($this->case_sensitive == false && preg_match('/[A-Z]/', $code)) {
 
997
            // case sensitive was set from securimage_show.php but not in class
 
998
            // the code saved in the session has capitals so set case sensitive to true
 
999
            $this->case_sensitive = true;
 
1000
        }
 
1001
        
 
1002
        $code_entered = trim( (($this->case_sensitive) ? $this->code_entered
 
1003
                                                       : strtolower($this->code_entered))
 
1004
                        );
 
1005
        $this->correct_code = false;
 
1006
        
 
1007
        if ($code != '') {
 
1008
            if ($code == $code_entered) {
 
1009
                $this->correct_code = true;
 
1010
                $_SESSION['securimage_code_value'][$this->namespace] = '';
 
1011
                $_SESSION['securimage_code_ctime'][$this->namespace] = '';
 
1012
                $this->clearCodeFromDatabase();
 
1013
            }
 
1014
        }
 
1015
    }
 
1016
    
 
1017
    /**
 
1018
     * Return the code from the session or sqlite database if used.  If none exists yet, an empty string is returned
 
1019
     */
 
1020
    protected function getCode()
 
1021
    {
 
1022
        $code = '';
 
1023
        
 
1024
        if (isset($_SESSION['securimage_code_value'][$this->namespace]) &&
 
1025
         trim($_SESSION['securimage_code_value'][$this->namespace]) != '') {
 
1026
            if ($this->isCodeExpired(
 
1027
            $_SESSION['securimage_code_ctime'][$this->namespace]) == false) {
 
1028
                $code = $_SESSION['securimage_code_value'][$this->namespace];
 
1029
            }
 
1030
        } else if ($this->use_sqlite_db == true && function_exists('sqlite_open')) {
 
1031
            // no code in session - may mean user has cookies turned off
 
1032
            $this->openDatabase();
 
1033
            $code = $this->getCodeFromDatabase();
 
1034
        } else { /* no code stored in session or sqlite database, validation will fail */ }
 
1035
        
 
1036
        return $code;
 
1037
    }
 
1038
    
 
1039
    /**
 
1040
     * Save data to session namespace and database if used
 
1041
     */
 
1042
    protected function saveData()
 
1043
    {
 
1044
        $_SESSION['securimage_code_value'][$this->namespace] = $this->code;
 
1045
        $_SESSION['securimage_code_ctime'][$this->namespace] = time();
 
1046
        
 
1047
        $this->saveCodeToDatabase();
 
1048
    }
 
1049
    
 
1050
    /**
 
1051
     * Saves the code to the sqlite database
 
1052
     */
 
1053
    protected function saveCodeToDatabase()
 
1054
    {
 
1055
        $success = false;
 
1056
        
 
1057
        $this->openDatabase();
 
1058
        
 
1059
        if ($this->use_sqlite_db && $this->sqlite_handle !== false) {
 
1060
            $ip      = $_SERVER['REMOTE_ADDR'];
 
1061
            $time    = time();
 
1062
            $code    = $_SESSION['securimage_code_value'][$this->namespace]; // if cookies are disabled the session still exists at this point
 
1063
            $success = sqlite_query($this->sqlite_handle,
 
1064
                                    "INSERT OR REPLACE INTO codes(ip, code, namespace, created)
 
1065
                                    VALUES('$ip', '$code', '{$this->namespace}', $time)");
 
1066
        }
 
1067
        
 
1068
        return $success !== false;
 
1069
    }
 
1070
    
 
1071
    /**
 
1072
     * Open sqlite database
 
1073
     */
 
1074
    protected function openDatabase()
 
1075
    {
 
1076
        $this->sqlite_handle = false;
 
1077
        
 
1078
        if ($this->use_sqlite_db && function_exists('sqlite_open')) {
 
1079
            $this->sqlite_handle = sqlite_open($this->sqlite_database, 0666, $error);
 
1080
            
 
1081
            if ($this->sqlite_handle !== false) {
 
1082
                $res = sqlite_query($this->sqlite_handle, "PRAGMA table_info(codes)");
 
1083
                if (sqlite_num_rows($res) == 0) {
 
1084
                    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)");
 
1085
                }
 
1086
            }
 
1087
            
 
1088
            return $this->sqlite_handle != false;
 
1089
        }
 
1090
        
 
1091
        return $this->sqlite_handle;
 
1092
    }
 
1093
    
 
1094
    /**
 
1095
     * Get a code from the sqlite database for ip address
 
1096
     */
 
1097
    protected function getCodeFromDatabase()
 
1098
    {
 
1099
        $code = '';
 
1100
 
 
1101
        if ($this->use_sqlite_db && $this->sqlite_handle !== false) {
 
1102
            $ip = $_SERVER['REMOTE_ADDR'];
 
1103
            $ns = sqlite_escape_string($this->namespace);
 
1104
 
 
1105
            $res = sqlite_query($this->sqlite_handle, "SELECT * FROM codes WHERE ip = '$ip' AND namespace = '$ns'");
 
1106
            if ($res && sqlite_num_rows($res) > 0) {
 
1107
                $res = sqlite_fetch_array($res);
 
1108
 
 
1109
                if ($this->isCodeExpired($res['created']) == false) {
 
1110
                    $code = $res['code'];
 
1111
                }
 
1112
            }
 
1113
        }
 
1114
        return $code;
 
1115
    }
 
1116
    
 
1117
    /**
 
1118
     * Remove an entered code from the database
 
1119
     */
 
1120
    protected function clearCodeFromDatabase()
 
1121
    {
 
1122
        if (is_resource($this->sqlite_handle)) {
 
1123
            $ip = $_SERVER['REMOTE_ADDR'];
 
1124
            $ns = sqlite_escape_string($this->namespace);
 
1125
            
 
1126
            sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE ip = '$ip' AND namespace = '$ns'");
 
1127
        }
 
1128
    }
 
1129
    
 
1130
    /**
 
1131
     * Deletes old codes from sqlite database
 
1132
     */
 
1133
    protected function purgeOldCodesFromDatabase()
 
1134
    {
 
1135
        if ($this->use_sqlite_db && $this->sqlite_handle !== false) {
 
1136
            $now   = time();
 
1137
            $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time;
 
1138
            
 
1139
            sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE $now - created > $limit");
 
1140
        }
 
1141
    }
 
1142
    
 
1143
    /**
 
1144
     * Checks to see if the captcha code has expired and cannot be used
 
1145
     * @param unknown_type $creation_time
 
1146
     */
 
1147
    protected function isCodeExpired($creation_time)
 
1148
    {
 
1149
        $expired = true;
 
1150
        
 
1151
        if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) {
 
1152
            $expired = false;
 
1153
        } else if (time() - $creation_time < $this->expiry_time) {
 
1154
            $expired = false;
 
1155
        }
 
1156
        
 
1157
        return $expired;
 
1158
    }
 
1159
    
 
1160
    /**
 
1161
     * 
 
1162
     * Generate an MP3 audio file of the captcha image
 
1163
     * 
 
1164
     * @deprecated 3.0
 
1165
     */
 
1166
    protected function generateMP3()
 
1167
    {
 
1168
        return false;
 
1169
    }
 
1170
    
 
1171
    /**
 
1172
     * Generate a wav file given the $letters in the code
 
1173
     * @todo Add ability to merge 2 sound files together to have random background sounds
 
1174
     * @param array $letters
 
1175
     * @return string The binary contents of the wav file
 
1176
     */
 
1177
    protected function generateWAV($letters)
 
1178
    {
 
1179
        $data_len       = 0;
 
1180
        $files          = array();
 
1181
        $out_data       = '';
 
1182
        $out_channels   = 0;
 
1183
        $out_samplert   = 0;
 
1184
        $out_bpersample = 0;
 
1185
        $numSamples     = 0;
 
1186
        $removeChunks   = array('LIST', 'DISP', 'NOTE');
 
1187
 
 
1188
        for ($i = 0; $i < sizeof($letters); ++$i) {
 
1189
            $letter   = $letters[$i];
 
1190
            $filename = $this->audio_path . strtoupper($letter) . '.wav';
 
1191
            $file     = array();
 
1192
            $data     = @file_get_contents($filename);
 
1193
            
 
1194
            if ($data === false) {
 
1195
                // echo "Failed to read $filename";
 
1196
                return $this->audioError();
 
1197
            }
 
1198
 
 
1199
            $header = substr($data, 0, 36);
 
1200
            $info   = unpack('NChunkID/VChunkSize/NFormat/NSubChunk1ID/'
 
1201
                            .'VSubChunk1Size/vAudioFormat/vNumChannels/'
 
1202
                            .'VSampleRate/VByteRate/vBlockAlign/vBitsPerSample',
 
1203
                             $header);
 
1204
            
 
1205
            $dataPos        = strpos($data, 'data');
 
1206
            $out_channels   = $info['NumChannels'];
 
1207
            $out_samplert   = $info['SampleRate'];
 
1208
            $out_bpersample = $info['BitsPerSample'];
 
1209
            
 
1210
            if ($dataPos === false) {
 
1211
                // wav file with no data?
 
1212
                // echo "Failed to find DATA segment in $filename";
 
1213
                return $this->audioError();
 
1214
            }
 
1215
            
 
1216
            if ($info['AudioFormat'] != 1) {
 
1217
                // only work with PCM audio
 
1218
                // echo "$filename was not PCM audio, only PCM is supported";
 
1219
                return $this->audioError();
 
1220
            }
 
1221
            
 
1222
            if ($info['SubChunk1Size'] != 16 && $info['SubChunk1Size'] != 18) {
 
1223
                // probably unsupported extension
 
1224
                // echo "Bad SubChunk1Size in $filename - Size was {$info['SubChunk1Size']}";
 
1225
                return $this->audioError();
 
1226
            }
 
1227
            
 
1228
            if ($info['SubChunk1Size'] > 16) {
 
1229
                $header .= substr($data, 36, $info['SubChunk1Size'] - 16);
 
1230
            }
 
1231
            
 
1232
            if ($i == 0) {
 
1233
                // create the final file's header, size will be adjusted later
 
1234
                $out_data = $header . 'data';
 
1235
            }
 
1236
            
 
1237
            $removed = 0;
 
1238
            
 
1239
            foreach($removeChunks as $chunk) {
 
1240
                $chunkPos = strpos($data, $chunk);
 
1241
                if ($chunkPos !== false) {
 
1242
                    $listSize = unpack('VSize', substr($data, $chunkPos + 4, 4));
 
1243
                    
 
1244
                    $data = substr($data, 0, $chunkPos) .
 
1245
                            substr($data, $chunkPos + 8 + $listSize['Size']);
 
1246
                            
 
1247
                    $removed += $listSize['Size'] + 8;
 
1248
                }
 
1249
            }
 
1250
            
 
1251
            $dataSize    = unpack('VSubchunk2Size', substr($data, $dataPos + 4, 4));
 
1252
            $dataSize['Subchunk2Size'] -= $removed;
 
1253
            $out_data   .= substr($data, $dataPos + 8, $dataSize['Subchunk2Size'] * ($out_bpersample / 8));
 
1254
            $numSamples += $dataSize['Subchunk2Size'];
 
1255
        }
 
1256
 
 
1257
        $filesize  = strlen($out_data);
 
1258
        $chunkSize = $filesize - 8;
 
1259
        $dataCSize = $numSamples;
 
1260
        
 
1261
        $out_data = substr_replace($out_data, pack('V', $chunkSize), 4, 4);
 
1262
        $out_data = substr_replace($out_data, pack('V', $numSamples), 40 + ($info['SubChunk1Size'] - 16), 4);
 
1263
 
 
1264
        $this->scrambleAudioData($out_data, 'wav');
 
1265
        
 
1266
        return $out_data;
 
1267
    }
 
1268
    
 
1269
    /**
 
1270
     * Randomizes the audio data to add noise and prevent binary recognition
 
1271
     * @param string $data  The binary audio file data
 
1272
     * @param string $format The format of the sound file (wav only)
 
1273
     */
 
1274
    protected function scrambleAudioData(&$data, $format)
 
1275
    {
 
1276
        $start = strpos($data, 'data') + 4; // look for "data" indicator
 
1277
        if ($start === false) $start = 44;  // if not found assume 44 byte header
 
1278
         
 
1279
        $start  += rand(1, 4); // randomize starting offset
 
1280
        $datalen = strlen($data) - $start;
 
1281
        $step    = 1;
 
1282
        
 
1283
        for ($i = $start; $i < $datalen; $i += $step) {
 
1284
            $ch = ord($data{$i});
 
1285
            if ($ch == 0 || $ch == 255) continue;
 
1286
            
 
1287
            if ($ch < 16 || $ch > 239) {
 
1288
                $ch += rand(-6, 6);
 
1289
            } else {
 
1290
                $ch += rand(-12, 12);
 
1291
            }
 
1292
            
 
1293
            if ($ch < 0) $ch = 0; else if ($ch > 255) $ch = 255;
 
1294
 
 
1295
            $data{$i} = chr($ch);
 
1296
            
 
1297
            $step = rand(1,4);
 
1298
        }
 
1299
 
 
1300
        return $data;
 
1301
    }
 
1302
    
 
1303
    /**
 
1304
     * Return a wav file saying there was an error generating file
 
1305
     * 
 
1306
     * @return string The binary audio contents
 
1307
     */
 
1308
    protected function audioError()
 
1309
    {
 
1310
        return @file_get_contents(dirname(__FILE__) . '/audio/error.wav');
 
1311
    }
 
1312
    
 
1313
    function frand()
 
1314
    {
 
1315
        return 0.0001 * rand(0,9999);
 
1316
    }
 
1317
    
 
1318
    /**
 
1319
     * Convert an html color code to a Securimage_Color
 
1320
     * @param string $color
 
1321
     * @param Securimage_Color $default The defalt color to use if $color is invalid
 
1322
     */
 
1323
    protected function initColor($color, $default)
 
1324
    {
 
1325
        if ($color == null) {
 
1326
            return new Securimage_Color($default);
 
1327
        } else if (is_string($color)) {
 
1328
            try {
 
1329
                return new Securimage_Color($color);
 
1330
            } catch(Exception $e) {
 
1331
                return new Securimage_Color($default);
 
1332
            }
 
1333
        } else if (is_array($color) && sizeof($color) == 3) {
 
1334
            return new Securimage_Color($color[0], $color[1], $color[2]);
 
1335
        } else {
 
1336
            return new Securimage_Color($default);
 
1337
        }
 
1338
    }
 
1339
}
 
1340
 
 
1341
 
 
1342
/**
 
1343
 * Color object for Securimage CAPTCHA
 
1344
 *
 
1345
 * @version 3.0
 
1346
 * @since 2.0
 
1347
 * @package Securimage
 
1348
 * @subpackage classes
 
1349
 *
 
1350
 */
 
1351
class Securimage_Color
 
1352
{
 
1353
    public $r;
 
1354
    public $g;
 
1355
    public $b;
 
1356
 
 
1357
    /**
 
1358
     * Create a new Securimage_Color object.<br />
 
1359
     * Constructor expects 1 or 3 arguments.<br />
 
1360
     * When passing a single argument, specify the color using HTML hex format,<br />
 
1361
     * when passing 3 arguments, specify each RGB component (from 0-255) individually.<br />
 
1362
     * $color = new Securimage_Color('#0080FF') or <br />
 
1363
     * $color = new Securimage_Color(0, 128, 255)
 
1364
     * 
 
1365
     * @param string $color
 
1366
     * @throws Exception
 
1367
     */
 
1368
    public function __construct($color = '#ffffff')
 
1369
    {
 
1370
        $args = func_get_args();
 
1371
        
 
1372
        if (sizeof($args) == 0) {
 
1373
            $this->r = 255;
 
1374
            $this->g = 255;
 
1375
            $this->b = 255;
 
1376
        } else if (sizeof($args) == 1) {
 
1377
            // set based on html code
 
1378
            if (substr($color, 0, 1) == '#') {
 
1379
                $color = substr($color, 1);
 
1380
            }
 
1381
            
 
1382
            if (strlen($color) != 3 && strlen($color) != 6) {
 
1383
                throw new InvalidArgumentException(
 
1384
                  'Invalid HTML color code passed to Securimage_Color'
 
1385
                );
 
1386
            }
 
1387
            
 
1388
            $this->constructHTML($color);
 
1389
        } else if (sizeof($args) == 3) {
 
1390
            $this->constructRGB($args[0], $args[1], $args[2]);
 
1391
        } else {
 
1392
            throw new InvalidArgumentException(
 
1393
              'Securimage_Color constructor expects 0, 1 or 3 arguments; ' . sizeof($args) . ' given'
 
1394
            );
 
1395
        }
 
1396
    }
 
1397
    
 
1398
    /**
 
1399
     * Construct from an rgb triplet
 
1400
     * @param int $red The red component, 0-255
 
1401
     * @param int $green The green component, 0-255
 
1402
     * @param int $blue The blue component, 0-255
 
1403
     */
 
1404
    protected function constructRGB($red, $green, $blue)
 
1405
    {
 
1406
        if ($red < 0)     $red   = 0;
 
1407
        if ($red > 255)   $red   = 255;
 
1408
        if ($green < 0)   $green = 0;
 
1409
        if ($green > 255) $green = 255;
 
1410
        if ($blue < 0)    $blue  = 0;
 
1411
        if ($blue > 255)  $blue  = 255;
 
1412
        
 
1413
        $this->r = $red;
 
1414
        $this->g = $green;
 
1415
        $this->b = $blue;
 
1416
    }
 
1417
    
 
1418
    /**
 
1419
     * Construct from an html hex color code
 
1420
     * @param string $color
 
1421
     */
 
1422
    protected function constructHTML($color)
 
1423
    {
 
1424
        if (strlen($color) == 3) {
 
1425
            $red   = str_repeat(substr($color, 0, 1), 2);
 
1426
            $green = str_repeat(substr($color, 1, 1), 2);
 
1427
            $blue  = str_repeat(substr($color, 2, 1), 2);
 
1428
        } else {
 
1429
            $red   = substr($color, 0, 2);
 
1430
            $green = substr($color, 2, 2);
 
1431
            $blue  = substr($color, 4, 2); 
 
1432
        }
 
1433
        
 
1434
        $this->r = hexdec($red);
 
1435
        $this->g = hexdec($green);
 
1436
        $this->b = hexdec($blue);
 
1437
    }
 
1438
}