diff options
Diffstat (limited to 'includes/securimage/securimage.php')
-rw-r--r-- | includes/securimage/securimage.php | 1584 |
1 files changed, 1584 insertions, 0 deletions
diff --git a/includes/securimage/securimage.php b/includes/securimage/securimage.php new file mode 100644 index 0000000..ebabab0 --- /dev/null +++ b/includes/securimage/securimage.php | |||
@@ -0,0 +1,1584 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * Project: Securimage: A PHP class for creating and managing form CAPTCHA images<br /> | ||
5 | * File: securimage.php<br /> | ||
6 | * | ||
7 | * This library is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU Lesser General Public | ||
9 | * License as published by the Free Software Foundation; either | ||
10 | * version 2.1 of the License, or any later version.<br /><br /> | ||
11 | * | ||
12 | * This library is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * Lesser General Public License for more details.<br /><br /> | ||
16 | * | ||
17 | * You should have received a copy of the GNU Lesser General Public | ||
18 | * License along with this library; if not, write to the Free Software | ||
19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA<br /><br /> | ||
20 | * | ||
21 | * Any modifications to the library should be indicated clearly in the source code | ||
22 | * to inform users that the changes are not a part of the original software.<br /><br /> | ||
23 | * | ||
24 | * If you found this script useful, please take a quick moment to rate it.<br /> | ||
25 | * http://www.hotscripts.com/rate/49400.html Thanks. | ||
26 | * | ||
27 | * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA | ||
28 | * @link http://www.phpcaptcha.org/latest.zip Download Latest Version | ||
29 | * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation | ||
30 | * @copyright 2009 Drew Phillips | ||
31 | * @author Drew Phillips <drew@drew-phillips.com> | ||
32 | * @version 2.0.1 BETA (December 6th, 2009) | ||
33 | * @package Securimage | ||
34 | * | ||
35 | */ | ||
36 | |||
37 | /** | ||
38 | ChangeLog | ||
39 | |||
40 | 2.0.1 | ||
41 | - Add support for browsers with cookies disabled (requires php5, sqlite) maps users to md5 hashed ip addresses and md5 hashed codes for security | ||
42 | - 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) | ||
43 | - Check for previous definition of image type constants (Mike Challis) | ||
44 | - Fix mime type settings for audio output | ||
45 | - Fixed color allocation issues with multiple colors and background images, consolidate allocation to one function | ||
46 | - Ability to let codes expire after a given length of time | ||
47 | - Allow HTML color codes to be passed to Securimage_Color (suggested by Mike Challis) | ||
48 | |||
49 | 2.0.0 | ||
50 | - Add mathematical distortion to characters (using code from HKCaptcha) | ||
51 | - Improved session support | ||
52 | - Added Securimage_Color class for easier color definitions | ||
53 | - Add distortion to audio output to prevent binary comparison attack (proposed by Sven "SavageTiger" Hagemann [insecurity.nl]) | ||
54 | - Flash button to stream mp3 audio (Douglas Walsh www.douglaswalsh.net) | ||
55 | - Audio output is mp3 format by default | ||
56 | - Change font to AlteHaasGrotesk by yann le coroller | ||
57 | - Some code cleanup | ||
58 | |||
59 | 1.0.4 (unreleased) | ||
60 | - Ability to output audible codes in mp3 format to stream from flash | ||
61 | |||
62 | 1.0.3.1 | ||
63 | - Error reading from wordlist in some cases caused words to be cut off 1 letter short | ||
64 | |||
65 | 1.0.3 | ||
66 | - Removed shadow_text from code which could cause an undefined property error due to removal from previous version | ||
67 | |||
68 | 1.0.2 | ||
69 | - Audible CAPTCHA Code wav files | ||
70 | - Create codes from a word list instead of random strings | ||
71 | |||
72 | 1.0 | ||
73 | - Added the ability to use a selected character set, rather than a-z0-9 only. | ||
74 | - Added the multi-color text option to use different colors for each letter. | ||
75 | - Switched to automatic session handling instead of using files for code storage | ||
76 | - Added GD Font support if ttf support is not available. Can use internal GD fonts or load new ones. | ||
77 | - Added the ability to set line thickness | ||
78 | - Added option for drawing arced lines over letters | ||
79 | - Added ability to choose image type for output | ||
80 | |||
81 | */ | ||
82 | |||
83 | /** | ||
84 | * Output images in JPEG format | ||
85 | */ | ||
86 | if (!defined('SI_IMAGE_JPEG')) | ||
87 | define('SI_IMAGE_JPEG', 1); | ||
88 | /** | ||
89 | * Output images in PNG format | ||
90 | */ | ||
91 | if (!defined('SI_IMAGE_PNG')) | ||
92 | define('SI_IMAGE_PNG', 2); | ||
93 | /** | ||
94 | * Output images in GIF format (not recommended) | ||
95 | * Must have GD >= 2.0.28! | ||
96 | */ | ||
97 | if (!defined('SI_IMAGE_GIF')) | ||
98 | define('SI_IMAGE_GIF', 3); | ||
99 | |||
100 | /** | ||
101 | * Securimage CAPTCHA Class. | ||
102 | * | ||
103 | * @package Securimage | ||
104 | * @subpackage classes | ||
105 | * | ||
106 | */ | ||
107 | class Securimage { | ||
108 | |||
109 | /** | ||
110 | * The desired width of the CAPTCHA image. | ||
111 | * | ||
112 | * @var int | ||
113 | */ | ||
114 | var $image_width; | ||
115 | |||
116 | /** | ||
117 | * The desired width of the CAPTCHA image. | ||
118 | * | ||
119 | * @var int | ||
120 | */ | ||
121 | var $image_height; | ||
122 | |||
123 | /** | ||
124 | * The image format for output.<br /> | ||
125 | * Valid options: SI_IMAGE_PNG, SI_IMAGE_JPG, SI_IMAGE_GIF | ||
126 | * | ||
127 | * @var int | ||
128 | */ | ||
129 | var $image_type; | ||
130 | |||
131 | /** | ||
132 | * The length of the code to generate. | ||
133 | * | ||
134 | * @var int | ||
135 | */ | ||
136 | var $code_length; | ||
137 | |||
138 | /** | ||
139 | * The character set for individual characters in the image.<br /> | ||
140 | * Letters are converted to uppercase.<br /> | ||
141 | * The font must support the letters or there may be problematic substitutions. | ||
142 | * | ||
143 | * @var string | ||
144 | */ | ||
145 | var $charset; | ||
146 | |||
147 | /** | ||
148 | * Create codes using this word list | ||
149 | * | ||
150 | * @var string The path to the word list to use for creating CAPTCHA codes | ||
151 | */ | ||
152 | var $wordlist_file; | ||
153 | |||
154 | /** | ||
155 | * Use wordlist of not | ||
156 | * | ||
157 | * @var bool true to use wordlist file, false to use random code | ||
158 | */ | ||
159 | var $use_wordlist = false; | ||
160 | |||
161 | /** | ||
162 | * Note: Use of GD fonts is not recommended as many distortion features are not available<br /> | ||
163 | * The GD font to use.<br /> | ||
164 | * Internal gd fonts can be loaded by their number.<br /> | ||
165 | * Alternatively, a file path can be given and the font will be loaded from file. | ||
166 | * | ||
167 | * @var mixed | ||
168 | */ | ||
169 | var $gd_font_file; | ||
170 | |||
171 | /** | ||
172 | * The approximate size of the font in pixels.<br /> | ||
173 | * This does not control the size of the font because that is determined by the GD font itself.<br /> | ||
174 | * This is used to aid the calculations of positioning used by this class.<br /> | ||
175 | * | ||
176 | * @var int | ||
177 | */ | ||
178 | var $gd_font_size; | ||
179 | |||
180 | /** | ||
181 | * Use a gd font instead of TTF | ||
182 | * | ||
183 | * @var bool true for gd font, false for TTF | ||
184 | */ | ||
185 | var $use_gd_font; | ||
186 | |||
187 | // Note: These font options below do not apply if you set $use_gd_font to true with the exception of $text_color | ||
188 | |||
189 | /** | ||
190 | * The path to the TTF font file to load. | ||
191 | * | ||
192 | * @var string | ||
193 | */ | ||
194 | var $ttf_file; | ||
195 | |||
196 | /** | ||
197 | * How much to distort image, higher = more distortion.<br /> | ||
198 | * Distortion is only available when using TTF fonts.<br /> | ||
199 | * | ||
200 | * @var float | ||
201 | */ | ||
202 | var $perturbation; | ||
203 | |||
204 | /** | ||
205 | * The minimum angle in degrees, with 0 degrees being left-to-right reading text.<br /> | ||
206 | * Higher values represent a counter-clockwise rotation.<br /> | ||
207 | * For example, a value of 90 would result in bottom-to-top reading text.<br /> | ||
208 | * This value along with maximum angle distance do not need to be very high with perturbation | ||
209 | * | ||
210 | * @var int | ||
211 | */ | ||
212 | var $text_angle_minimum; | ||
213 | |||
214 | /** | ||
215 | * The minimum angle in degrees, with 0 degrees being left-to-right reading text.<br /> | ||
216 | * Higher values represent a counter-clockwise rotation.<br /> | ||
217 | * For example, a value of 90 would result in bottom-to-top reading text. | ||
218 | * | ||
219 | * @var int | ||
220 | */ | ||
221 | var $text_angle_maximum; | ||
222 | |||
223 | /** | ||
224 | * The X-Position on the image where letter drawing will begin.<br /> | ||
225 | * This value is in pixels from the left side of the image. | ||
226 | * | ||
227 | * @var int | ||
228 | * @deprecated 2.0 | ||
229 | */ | ||
230 | var $text_x_start; | ||
231 | |||
232 | /** | ||
233 | * The background color for the image as a Securimage_Color.<br /> | ||
234 | * | ||
235 | * @var Securimage_Color | ||
236 | */ | ||
237 | var $image_bg_color; | ||
238 | |||
239 | /** | ||
240 | * Scan this directory for gif, jpg, and png files to use as background images.<br /> | ||
241 | * A random image file will be picked each time.<br /> | ||
242 | * Change from null to the full path to your directory.<br /> | ||
243 | * i.e. var $background_directory = $_SERVER['DOCUMENT_ROOT'] . '/securimage/backgrounds'; | ||
244 | * Make sure not to pass a background image to the show function, otherwise this directive is ignored. | ||
245 | * | ||
246 | * @var string | ||
247 | */ | ||
248 | var $background_directory = null; //'./backgrounds'; | ||
249 | |||
250 | /** | ||
251 | * The text color to use for drawing characters as a Securimage_Color.<br /> | ||
252 | * This value is ignored if $use_multi_text is set to true.<br /> | ||
253 | * Make sure this contrasts well with the background color or image.<br /> | ||
254 | * | ||
255 | * @see Securimage::$use_multi_text | ||
256 | * @var Securimage_Color | ||
257 | */ | ||
258 | var $text_color; | ||
259 | |||
260 | /** | ||
261 | * Set to true to use multiple colors for each character. | ||
262 | * | ||
263 | * @see Securimage::$multi_text_color | ||
264 | * @var boolean | ||
265 | */ | ||
266 | var $use_multi_text; | ||
267 | |||
268 | /** | ||
269 | * Array of Securimage_Colors which will be randomly selected for each letter.<br /> | ||
270 | * | ||
271 | * @var array | ||
272 | */ | ||
273 | var $multi_text_color; | ||
274 | |||
275 | /** | ||
276 | * Set to true to make the characters appear transparent. | ||
277 | * | ||
278 | * @see Securimage::$text_transparency_percentage | ||
279 | * @var boolean | ||
280 | */ | ||
281 | var $use_transparent_text; | ||
282 | |||
283 | /** | ||
284 | * The percentage of transparency, 0 to 100.<br /> | ||
285 | * A value of 0 is completely opaque, 100 is completely transparent (invisble) | ||
286 | * | ||
287 | * @see Securimage::$use_transparent_text | ||
288 | * @var int | ||
289 | */ | ||
290 | var $text_transparency_percentage; | ||
291 | |||
292 | |||
293 | // Line options | ||
294 | /** | ||
295 | * Draw vertical and horizontal lines on the image. | ||
296 | * | ||
297 | * @see Securimage::$line_color | ||
298 | * @see Securimage::$draw_lines_over_text | ||
299 | * @var boolean | ||
300 | */ | ||
301 | var $num_lines; | ||
302 | |||
303 | /** | ||
304 | * Color of lines drawn over text | ||
305 | * | ||
306 | * @var string | ||
307 | */ | ||
308 | var $line_color; | ||
309 | |||
310 | /** | ||
311 | * Draw the lines over the text.<br /> | ||
312 | * If fales lines will be drawn before putting the text on the image. | ||
313 | * | ||
314 | * @var boolean | ||
315 | */ | ||
316 | var $draw_lines_over_text; | ||
317 | |||
318 | /** | ||
319 | * Text to write at the bottom corner of captcha image | ||
320 | * | ||
321 | * @since 2.0 | ||
322 | * @var string Signature text | ||
323 | */ | ||
324 | var $image_signature; | ||
325 | |||
326 | /** | ||
327 | * Color to use for writing signature text | ||
328 | * | ||
329 | * @since 2.0 | ||
330 | * @var Securimage_Color | ||
331 | */ | ||
332 | var $signature_color; | ||
333 | |||
334 | /** | ||
335 | * Full path to the WAV files to use to make the audio files, include trailing /.<br /> | ||
336 | * Name Files [A-Z0-9].wav | ||
337 | * | ||
338 | * @since 1.0.1 | ||
339 | * @var string | ||
340 | */ | ||
341 | var $audio_path; | ||
342 | |||
343 | /** | ||
344 | * Type of audio file to generate (mp3 or wav) | ||
345 | * | ||
346 | * @var string | ||
347 | */ | ||
348 | var $audio_format; | ||
349 | |||
350 | /** | ||
351 | * The session name to use if not the default. Blank for none | ||
352 | * | ||
353 | * @see http://php.net/session_name | ||
354 | * @since 2.0 | ||
355 | * @var string | ||
356 | */ | ||
357 | var $session_name = ''; | ||
358 | |||
359 | /** | ||
360 | * The amount of time in seconds that a code remains valid.<br /> | ||
361 | * Any code older than this number will be considered invalid even if entered correctly.<br /> | ||
362 | * Any non-numeric or value less than 1 disables this functionality. | ||
363 | * | ||
364 | * @var int | ||
365 | */ | ||
366 | var $expiry_time; | ||
367 | |||
368 | /** | ||
369 | * Path to the file to use for storing codes for users.<br /> | ||
370 | * THIS FILE MUST ABSOLUTELY NOT BE ACCESSIBLE FROM A WEB BROWSER!!<br /> | ||
371 | * Put this file in a directory below the web root or one that is restricted (i.e. an apache .htaccess file with deny from all)<br /> | ||
372 | * If you cannot meet those requirements your forms may not be completely protected.<br /> | ||
373 | * You could obscure the database file name but this is also not recommended. | ||
374 | * | ||
375 | * @var string | ||
376 | */ | ||
377 | var $sqlite_database; | ||
378 | |||
379 | /** | ||
380 | * Use an SQLite database for storing codes as a backup to sessions.<br /> | ||
381 | * Note: Sessions will still be used | ||
382 | */ | ||
383 | var $use_sqlite_db; | ||
384 | |||
385 | |||
386 | //END USER CONFIGURATION | ||
387 | //There should be no need to edit below unless you really know what you are doing. | ||
388 | |||
389 | /** | ||
390 | * The gd image resource. | ||
391 | * | ||
392 | * @access private | ||
393 | * @var resource | ||
394 | */ | ||
395 | var $im; | ||
396 | |||
397 | /** | ||
398 | * Temporary image for rendering | ||
399 | * | ||
400 | * @access private | ||
401 | * @var resource | ||
402 | */ | ||
403 | var $tmpimg; | ||
404 | |||
405 | /** | ||
406 | * Internal scale factor for anti-alias @hkcaptcha | ||
407 | * | ||
408 | * @access private | ||
409 | * @since 2.0 | ||
410 | * @var int | ||
411 | */ | ||
412 | var $iscale; // internal scale factor for anti-alias @hkcaptcha | ||
413 | |||
414 | /** | ||
415 | * The background image resource | ||
416 | * | ||
417 | * @access private | ||
418 | * @var resource | ||
419 | */ | ||
420 | var $bgimg; | ||
421 | |||
422 | /** | ||
423 | * The code generated by the script | ||
424 | * | ||
425 | * @access private | ||
426 | * @var string | ||
427 | */ | ||
428 | var $code; | ||
429 | |||
430 | /** | ||
431 | * The code that was entered by the user | ||
432 | * | ||
433 | * @access private | ||
434 | * @var string | ||
435 | */ | ||
436 | var $code_entered; | ||
437 | |||
438 | /** | ||
439 | * Whether or not the correct code was entered | ||
440 | * | ||
441 | * @access private | ||
442 | * @var boolean | ||
443 | */ | ||
444 | var $correct_code; | ||
445 | |||
446 | /** | ||
447 | * Handle to SQLite database | ||
448 | * | ||
449 | * @access private | ||
450 | * @var resource | ||
451 | */ | ||
452 | var $sqlite_handle; | ||
453 | |||
454 | /** | ||
455 | * Color resource for image line color | ||
456 | * | ||
457 | * @access private | ||
458 | * @var int | ||
459 | */ | ||
460 | var $gdlinecolor; | ||
461 | |||
462 | /** | ||
463 | * Array of colors for multi colored codes | ||
464 | * | ||
465 | * @access private | ||
466 | * @var array | ||
467 | */ | ||
468 | var $gdmulticolor; | ||
469 | |||
470 | /** | ||
471 | * Color resource for image font color | ||
472 | * | ||
473 | * @access private | ||
474 | * @var int | ||
475 | */ | ||
476 | var $gdtextcolor; | ||
477 | |||
478 | /** | ||
479 | * Color resource for image signature color | ||
480 | * | ||
481 | * @access private | ||
482 | * @var int | ||
483 | */ | ||
484 | var $gdsignaturecolor; | ||
485 | |||
486 | /** | ||
487 | * Color resource for image background color | ||
488 | * | ||
489 | * @access private | ||
490 | * @var int | ||
491 | */ | ||
492 | var $gdbgcolor; | ||
493 | |||
494 | |||
495 | /** | ||
496 | * Class constructor.<br /> | ||
497 | * Because the class uses sessions, this will attempt to start a session if there is no previous one.<br /> | ||
498 | * If you do not start a session before calling the class, the constructor must be called before any | ||
499 | * output is sent to the browser. | ||
500 | * | ||
501 | * <code> | ||
502 | * $securimage = new Securimage(); | ||
503 | * </code> | ||
504 | * | ||
505 | */ | ||
506 | function Securimage() | ||
507 | { | ||
508 | // Initialize session or attach to existing | ||
509 | if ( session_id() == '' ) { // no session has been started yet, which is needed for validation | ||
510 | if (trim($this->session_name) != '') { | ||
511 | session_name($this->session_name); // set session name if provided | ||
512 | } | ||
513 | session_start(); | ||
514 | } | ||
515 | |||
516 | // Set Default Values | ||
517 | $this->image_width = 230; | ||
518 | $this->image_height = 80; | ||
519 | $this->image_type = SI_IMAGE_PNG; | ||
520 | |||
521 | $this->code_length = 6; | ||
522 | $this->charset = 'ABCDEFGHKLMNPRSTUVWYZabcdefghklmnprstuvwyz23456789'; | ||
523 | $this->wordlist_file = './words/words.txt'; | ||
524 | $this->use_wordlist = false; | ||
525 | |||
526 | $this->gd_font_file = 'gdfonts/automatic.gdf'; | ||
527 | $this->use_gd_font = false; | ||
528 | $this->gd_font_size = 24; | ||
529 | $this->text_x_start = 15; | ||
530 | |||
531 | $this->ttf_file = './AHGBold.ttf'; | ||
532 | |||
533 | $this->perturbation = 0.75; | ||
534 | $this->iscale = 5; | ||
535 | $this->text_angle_minimum = 0; | ||
536 | $this->text_angle_maximum = 0; | ||
537 | |||
538 | $this->image_bg_color = new Securimage_Color(0xff, 0xff, 0xff); | ||
539 | $this->text_color = new Securimage_Color(0x3d, 0x3d, 0x3d); | ||
540 | $this->multi_text_color = array(new Securimage_Color(0x0, 0x20, 0xCC), | ||
541 | new Securimage_Color(0x0, 0x30, 0xEE), | ||
542 | new Securimage_color(0x0, 0x40, 0xCC), | ||
543 | new Securimage_Color(0x0, 0x50, 0xEE), | ||
544 | new Securimage_Color(0x0, 0x60, 0xCC)); | ||
545 | $this->use_multi_text = false; | ||
546 | |||
547 | $this->use_transparent_text = false; | ||
548 | $this->text_transparency_percentage = 30; | ||
549 | |||
550 | $this->num_lines = 10; | ||
551 | $this->line_color = new Securimage_Color(0x3d, 0x3d, 0x3d); | ||
552 | $this->draw_lines_over_text = true; | ||
553 | |||
554 | $this->image_signature = ''; | ||
555 | $this->signature_color = new Securimage_Color(0x20, 0x50, 0xCC); | ||
556 | $this->signature_font = './AHGBold.ttf'; | ||
557 | |||
558 | $this->audio_path = './audio/'; | ||
559 | $this->audio_format = 'mp3'; | ||
560 | $this->session_name = ''; | ||
561 | $this->expiry_time = 900; | ||
562 | |||
563 | $this->sqlite_database = 'database/securimage.sqlite'; | ||
564 | $this->use_sqlite_db = false; | ||
565 | |||
566 | $this->sqlite_handle = false; | ||
567 | } | ||
568 | |||
569 | /** | ||
570 | * Generate a code and output the image to the browser. | ||
571 | * | ||
572 | * <code> | ||
573 | * <?php | ||
574 | * include 'securimage.php'; | ||
575 | * $securimage = new Securimage(); | ||
576 | * $securimage->show('bg.jpg'); | ||
577 | * ?> | ||
578 | * </code> | ||
579 | * | ||
580 | * @param string $background_image The path to an image to use as the background for the CAPTCHA | ||
581 | */ | ||
582 | function show($background_image = "") | ||
583 | { | ||
584 | if($background_image != "" && is_readable($background_image)) { | ||
585 | $this->bgimg = $background_image; | ||
586 | } | ||
587 | |||
588 | $this->doImage(); | ||
589 | } | ||
590 | |||
591 | /** | ||
592 | * Validate the code entered by the user. | ||
593 | * | ||
594 | * <code> | ||
595 | * $code = $_POST['code']; | ||
596 | * if ($securimage->check($code) == false) { | ||
597 | * die("Sorry, the code entered did not match."); | ||
598 | * } else { | ||
599 | * $valid = true; | ||
600 | * } | ||
601 | * </code> | ||
602 | * @param string $code The code the user entered | ||
603 | * @return boolean true if the code was correct, false if not | ||
604 | */ | ||
605 | function check($code) | ||
606 | { | ||
607 | $this->code_entered = $code; | ||
608 | $this->validate(); | ||
609 | return $this->correct_code; | ||
610 | } | ||
611 | |||
612 | /** | ||
613 | * Output audio file with HTTP headers to browser | ||
614 | * | ||
615 | * <code> | ||
616 | * $sound = new Securimage(); | ||
617 | * $sound->audio_format = 'mp3'; | ||
618 | * $sound->outputAudioFile(); | ||
619 | * </code> | ||
620 | * | ||
621 | * @since 2.0 | ||
622 | */ | ||
623 | function outputAudioFile() | ||
624 | { | ||
625 | if (strtolower($this->audio_format) == 'wav') { | ||
626 | header('Content-type: audio/x-wav'); | ||
627 | $ext = 'wav'; | ||
628 | } else { | ||
629 | header('Content-type: audio/mpeg'); // default to mp3 | ||
630 | $ext = 'mp3'; | ||
631 | } | ||
632 | |||
633 | header("Content-Disposition: attachment; filename=\"securimage_audio.{$ext}\""); | ||
634 | header('Cache-Control: no-store, no-cache, must-revalidate'); | ||
635 | header('Expires: Sun, 1 Jan 2000 12:00:00 GMT'); | ||
636 | header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT'); | ||
637 | |||
638 | $audio = $this->getAudibleCode($ext); | ||
639 | |||
640 | header('Content-Length: ' . strlen($audio)); | ||
641 | |||
642 | echo $audio; | ||
643 | exit; | ||
644 | } | ||
645 | |||
646 | /** | ||
647 | * Generate and output the image | ||
648 | * | ||
649 | * @access private | ||
650 | * | ||
651 | */ | ||
652 | function doImage() | ||
653 | { | ||
654 | if ($this->use_gd_font == true) { | ||
655 | $this->iscale = 1; | ||
656 | } | ||
657 | if($this->use_transparent_text == true || $this->bgimg != "") { | ||
658 | $this->im = imagecreatetruecolor($this->image_width, $this->image_height); | ||
659 | $this->tmpimg = imagecreatetruecolor($this->image_width * $this->iscale, $this->image_height * $this->iscale); | ||
660 | |||
661 | } else { //no transparency | ||
662 | $this->im = imagecreate($this->image_width, $this->image_height); | ||
663 | $this->tmpimg = imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale); | ||
664 | } | ||
665 | |||
666 | $this->allocateColors(); | ||
667 | imagepalettecopy($this->tmpimg, $this->im); | ||
668 | |||
669 | $this->setBackground(); | ||
670 | |||
671 | $this->createCode(); | ||
672 | |||
673 | if (!$this->draw_lines_over_text && $this->num_lines > 0) $this->drawLines(); | ||
674 | |||
675 | $this->drawWord(); | ||
676 | if ($this->use_gd_font == false && is_readable($this->ttf_file)) $this->distortedCopy(); | ||
677 | |||
678 | if ($this->draw_lines_over_text && $this->num_lines > 0) $this->drawLines(); | ||
679 | |||
680 | if (trim($this->image_signature) != '') $this->addSignature(); | ||
681 | |||
682 | $this->output(); | ||
683 | |||
684 | } | ||
685 | |||
686 | /** | ||
687 | * Allocate all colors that will be used in the CAPTCHA image | ||
688 | * | ||
689 | * @since 2.0.1 | ||
690 | * @access private | ||
691 | */ | ||
692 | function allocateColors() | ||
693 | { | ||
694 | // allocate bg color first for imagecreate | ||
695 | $this->gdbgcolor = imagecolorallocate($this->im, $this->image_bg_color->r, $this->image_bg_color->g, $this->image_bg_color->b); | ||
696 | |||
697 | $alpha = intval($this->text_transparency_percentage / 100 * 127); | ||
698 | |||
699 | if ($this->use_transparent_text == true) { | ||
700 | $this->gdtextcolor = imagecolorallocatealpha($this->im, $this->text_color->r, $this->text_color->g, $this->text_color->b, $alpha); | ||
701 | $this->gdlinecolor = imagecolorallocatealpha($this->im, $this->line_color->r, $this->line_color->g, $this->line_color->b, $alpha); | ||
702 | } else { | ||
703 | $this->gdtextcolor = imagecolorallocate($this->im, $this->text_color->r, $this->text_color->g, $this->text_color->b); | ||
704 | $this->gdlinecolor = imagecolorallocate($this->im, $this->line_color->r, $this->line_color->g, $this->line_color->b); | ||
705 | } | ||
706 | |||
707 | $this->gdsignaturecolor = imagecolorallocate($this->im, $this->signature_color->r, $this->signature_color->g, $this->signature_color->b); | ||
708 | |||
709 | if ($this->use_multi_text == true) { | ||
710 | $this->gdmulticolor = array(); | ||
711 | |||
712 | foreach($this->multi_text_color as $color) { | ||
713 | if ($this->use_transparent_text == true) { | ||
714 | $this->gdmulticolor[] = imagecolorallocatealpha($this->im, $color->r, $color->g, $color->b, $alpha); | ||
715 | } else { | ||
716 | $this->gdmulticolor[] = imagecolorallocate($this->im, $color->r, $color->g, $color->b); | ||
717 | } | ||
718 | } | ||
719 | } | ||
720 | } | ||
721 | |||
722 | /** | ||
723 | * Set the background of the CAPTCHA image | ||
724 | * | ||
725 | * @access private | ||
726 | * | ||
727 | */ | ||
728 | function setBackground() | ||
729 | { | ||
730 | imagefilledrectangle($this->im, 0, 0, $this->image_width * $this->iscale, $this->image_height * $this->iscale, $this->gdbgcolor); | ||
731 | imagefilledrectangle($this->tmpimg, 0, 0, $this->image_width * $this->iscale, $this->image_height * $this->iscale, $this->gdbgcolor); | ||
732 | |||
733 | if ($this->bgimg == '') { | ||
734 | if ($this->background_directory != null && is_dir($this->background_directory) && is_readable($this->background_directory)) { | ||
735 | $img = $this->getBackgroundFromDirectory(); | ||
736 | if ($img != false) { | ||
737 | $this->bgimg = $img; | ||
738 | } | ||
739 | } | ||
740 | } | ||
741 | |||
742 | $dat = @getimagesize($this->bgimg); | ||
743 | if($dat == false) { | ||
744 | return; | ||
745 | } | ||
746 | |||
747 | switch($dat[2]) { | ||
748 | case 1: $newim = @imagecreatefromgif($this->bgimg); break; | ||
749 | case 2: $newim = @imagecreatefromjpeg($this->bgimg); break; | ||
750 | case 3: $newim = @imagecreatefrompng($this->bgimg); break; | ||
751 | case 15: $newim = @imagecreatefromwbmp($this->bgimg); break; | ||
752 | case 16: $newim = @imagecreatefromxbm($this->bgimg); break; | ||
753 | default: return; | ||
754 | } | ||
755 | |||
756 | if(!$newim) return; | ||
757 | |||
758 | imagecopyresized($this->im, $newim, 0, 0, 0, 0, $this->image_width, $this->image_height, imagesx($newim), imagesy($newim)); | ||
759 | } | ||
760 | |||
761 | /** | ||
762 | * Return the full path to a random gif, jpg, or png from the background directory. | ||
763 | * | ||
764 | * @access private | ||
765 | * @see Securimage::$background_directory | ||
766 | * @return mixed false if none found, string $path if found | ||
767 | */ | ||
768 | function getBackgroundFromDirectory() | ||
769 | { | ||
770 | $images = array(); | ||
771 | |||
772 | if ($dh = opendir($this->background_directory)) { | ||
773 | while (($file = readdir($dh)) !== false) { | ||
774 | if (preg_match('/(jpg|gif|png)$/i', $file)) $images[] = $file; | ||
775 | } | ||
776 | |||
777 | closedir($dh); | ||
778 | |||
779 | if (sizeof($images) > 0) { | ||
780 | return rtrim($this->background_directory, '/') . '/' . $images[rand(0, sizeof($images)-1)]; | ||
781 | } | ||
782 | } | ||
783 | |||
784 | return false; | ||
785 | } | ||
786 | |||
787 | /** | ||
788 | * Draw random curvy lines over the image<br /> | ||
789 | * Modified code from HKCaptcha | ||
790 | * | ||
791 | * @since 2.0 | ||
792 | * @access private | ||
793 | * | ||
794 | */ | ||
795 | function drawLines() | ||
796 | { | ||
797 | for ($line = 0; $line < $this->num_lines; ++$line) { | ||
798 | $x = $this->image_width * (1 + $line) / ($this->num_lines + 1); | ||
799 | $x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines; | ||
800 | $y = rand($this->image_height * 0.1, $this->image_height * 0.9); | ||
801 | |||
802 | $theta = ($this->frand()-0.5) * M_PI * 0.7; | ||
803 | $w = $this->image_width; | ||
804 | $len = rand($w * 0.4, $w * 0.7); | ||
805 | $lwid = rand(0, 2); | ||
806 | |||
807 | $k = $this->frand() * 0.6 + 0.2; | ||
808 | $k = $k * $k * 0.5; | ||
809 | $phi = $this->frand() * 6.28; | ||
810 | $step = 0.5; | ||
811 | $dx = $step * cos($theta); | ||
812 | $dy = $step * sin($theta); | ||
813 | $n = $len / $step; | ||
814 | $amp = 1.5 * $this->frand() / ($k + 5.0 / $len); | ||
815 | $x0 = $x - 0.5 * $len * cos($theta); | ||
816 | $y0 = $y - 0.5 * $len * sin($theta); | ||
817 | |||
818 | $ldx = round(-$dy * $lwid); | ||
819 | $ldy = round($dx * $lwid); | ||
820 | |||
821 | for ($i = 0; $i < $n; ++$i) { | ||
822 | $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi); | ||
823 | $y = $y0 + $i * $dy - $amp * $dx * sin($k * $i * $step + $phi); | ||
824 | imagefilledrectangle($this->im, $x, $y, $x + $lwid, $y + $lwid, $this->gdlinecolor); | ||
825 | } | ||
826 | } | ||
827 | } | ||
828 | |||
829 | /** | ||
830 | * Draw the CAPTCHA code over the image | ||
831 | * | ||
832 | * @access private | ||
833 | * | ||
834 | */ | ||
835 | function drawWord() | ||
836 | { | ||
837 | $width2 = $this->image_width * $this->iscale; | ||
838 | $height2 = $this->image_height * $this->iscale; | ||
839 | |||
840 | if ($this->use_gd_font == true || !is_readable($this->ttf_file)) { | ||
841 | if (!is_int($this->gd_font_file)) { //is a file name | ||
842 | $font = @imageloadfont($this->gd_font_file); | ||
843 | if ($font == false) { | ||
844 | trigger_error("Failed to load GD Font file {$this->gd_font_file} ", E_USER_WARNING); | ||
845 | return; | ||
846 | } | ||
847 | } else { //gd font identifier | ||
848 | $font = $this->gd_font_file; | ||
849 | } | ||
850 | |||
851 | imagestring($this->im, $font, $this->text_x_start, ($this->image_height / 2) - ($this->gd_font_size / 2), $this->code, $this->gdtextcolor); | ||
852 | } else { //ttf font | ||
853 | $font_size = $height2 * .35; | ||
854 | $bb = imagettfbbox($font_size, 0, $this->ttf_file, $this->code); | ||
855 | $tx = $bb[4] - $bb[0]; | ||
856 | $ty = $bb[5] - $bb[1]; | ||
857 | $x = floor($width2 / 2 - $tx / 2 - $bb[0]); | ||
858 | $y = round($height2 / 2 - $ty / 2 - $bb[1]); | ||
859 | |||
860 | $strlen = strlen($this->code); | ||
861 | if (!is_array($this->multi_text_color)) $this->use_multi_text = false; | ||
862 | |||
863 | |||
864 | if ($this->use_multi_text == false && $this->text_angle_minimum == 0 && $this->text_angle_maximum == 0) { // no angled or multi-color characters | ||
865 | imagettftext($this->tmpimg, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code); | ||
866 | } else { | ||
867 | for($i = 0; $i < $strlen; ++$i) { | ||
868 | $angle = rand($this->text_angle_minimum, $this->text_angle_maximum); | ||
869 | $y = rand($y - 5, $y + 5); | ||
870 | if ($this->use_multi_text == true) { | ||
871 | $font_color = $this->gdmulticolor[rand(0, sizeof($this->gdmulticolor) - 1)]; | ||
872 | } else { | ||
873 | $font_color = $this->gdtextcolor; | ||
874 | } | ||
875 | |||
876 | $ch = $this->code{$i}; | ||
877 | |||
878 | imagettftext($this->tmpimg, $font_size, $angle, $x, $y, $font_color, $this->ttf_file, $ch); | ||
879 | |||
880 | // estimate character widths to increment $x without creating spaces that are too large or too small | ||
881 | // these are best estimates to align text but may vary between fonts | ||
882 | // for optimal character widths, do not use multiple text colors or character angles and the complete string will be written by imagettftext | ||
883 | if (strpos('abcdeghknopqsuvxyz', $ch) !== false) { | ||
884 | $min_x = $font_size - ($this->iscale * 6); | ||
885 | $max_x = $font_size - ($this->iscale * 6); | ||
886 | } else if (strpos('ilI1', $ch) !== false) { | ||
887 | $min_x = $font_size / 5; | ||
888 | $max_x = $font_size / 3; | ||
889 | } else if (strpos('fjrt', $ch) !== false) { | ||
890 | $min_x = $font_size - ($this->iscale * 12); | ||
891 | $max_x = $font_size - ($this->iscale * 12); | ||
892 | } else if ($ch == 'wm') { | ||
893 | $min_x = $font_size; | ||
894 | $max_x = $font_size + ($this->iscale * 3); | ||
895 | } else { // numbers, capitals or unicode | ||
896 | $min_x = $font_size + ($this->iscale * 2); | ||
897 | $max_x = $font_size + ($this->iscale * 5); | ||
898 | } | ||
899 | |||
900 | $x += rand($min_x, $max_x); | ||
901 | } //for loop | ||
902 | } // angled or multi-color | ||
903 | } //else ttf font | ||
904 | //$this->im = $this->tmpimg; | ||
905 | //$this->output(); | ||
906 | } //function | ||
907 | |||
908 | /** | ||
909 | * Warp text from temporary image onto final image.<br /> | ||
910 | * Modified for securimage | ||
911 | * | ||
912 | * @access private | ||
913 | * @since 2.0 | ||
914 | * @author Han-Kwang Nienhuys modified | ||
915 | * @copyright Han-Kwang Neinhuys | ||
916 | * | ||
917 | */ | ||
918 | function distortedCopy() | ||
919 | { | ||
920 | $numpoles = 3; // distortion factor | ||
921 | |||
922 | // make array of poles AKA attractor points | ||
923 | for ($i = 0; $i < $numpoles; ++$i) { | ||
924 | $px[$i] = rand($this->image_width * 0.3, $this->image_width * 0.7); | ||
925 | $py[$i] = rand($this->image_height * 0.3, $this->image_height * 0.7); | ||
926 | $rad[$i] = rand($this->image_width * 0.4, $this->image_width * 0.7); | ||
927 | $tmp = -$this->frand() * 0.15 - 0.15; | ||
928 | $amp[$i] = $this->perturbation * $tmp; | ||
929 | } | ||
930 | |||
931 | $bgCol = imagecolorat($this->tmpimg, 0, 0); | ||
932 | $width2 = $this->iscale * $this->image_width; | ||
933 | $height2 = $this->iscale * $this->image_height; | ||
934 | |||
935 | imagepalettecopy($this->im, $this->tmpimg); // copy palette to final image so text colors come across | ||
936 | |||
937 | // loop over $img pixels, take pixels from $tmpimg with distortion field | ||
938 | for ($ix = 0; $ix < $this->image_width; ++$ix) { | ||
939 | for ($iy = 0; $iy < $this->image_height; ++$iy) { | ||
940 | $x = $ix; | ||
941 | $y = $iy; | ||
942 | |||
943 | for ($i = 0; $i < $numpoles; ++$i) { | ||
944 | $dx = $ix - $px[$i]; | ||
945 | $dy = $iy - $py[$i]; | ||
946 | if ($dx == 0 && $dy == 0) continue; | ||
947 | |||
948 | $r = sqrt($dx * $dx + $dy * $dy); | ||
949 | if ($r > $rad[$i]) continue; | ||
950 | |||
951 | $rscale = $amp[$i] * sin(3.14 * $r / $rad[$i]); | ||
952 | $x += $dx * $rscale; | ||
953 | $y += $dy * $rscale; | ||
954 | } | ||
955 | |||
956 | $c = $bgCol; | ||
957 | $x *= $this->iscale; | ||
958 | $y *= $this->iscale; | ||
959 | |||
960 | if ($x >= 0 && $x < $width2 && $y >= 0 && $y < $height2) { | ||
961 | $c = imagecolorat($this->tmpimg, $x, $y); | ||
962 | } | ||
963 | |||
964 | if ($c != $bgCol) { // only copy pixels of letters to preserve any background image | ||
965 | imagesetpixel($this->im, $ix, $iy, $c); | ||
966 | } | ||
967 | } | ||
968 | } | ||
969 | } | ||
970 | |||
971 | /** | ||
972 | * Create a code and save to the session | ||
973 | * | ||
974 | * @access private | ||
975 | * @since 1.0.1 | ||
976 | * | ||
977 | */ | ||
978 | function createCode() | ||
979 | { | ||
980 | $this->code = false; | ||
981 | |||
982 | if ($this->use_wordlist && is_readable($this->wordlist_file)) { | ||
983 | $this->code = $this->readCodeFromFile(); | ||
984 | } | ||
985 | |||
986 | if ($this->code == false) { | ||
987 | $this->code = $this->generateCode($this->code_length); | ||
988 | } | ||
989 | |||
990 | $this->saveData(); | ||
991 | } | ||
992 | |||
993 | /** | ||
994 | * Generate a code | ||
995 | * | ||
996 | * @access private | ||
997 | * @param int $len The code length | ||
998 | * @return string | ||
999 | */ | ||
1000 | function generateCode($len) | ||
1001 | { | ||
1002 | $code = ''; | ||
1003 | |||
1004 | for($i = 1, $cslen = strlen($this->charset); $i <= $len; ++$i) { | ||
1005 | $code .= $this->charset{rand(0, $cslen - 1)}; | ||
1006 | } | ||
1007 | return $code; | ||
1008 | } | ||
1009 | |||
1010 | /** | ||
1011 | * Reads a word list file to get a code | ||
1012 | * | ||
1013 | * @access private | ||
1014 | * @since 1.0.2 | ||
1015 | * @return mixed false on failure, a word on success | ||
1016 | */ | ||
1017 | function readCodeFromFile() | ||
1018 | { | ||
1019 | $fp = @fopen($this->wordlist_file, 'rb'); | ||
1020 | if (!$fp) return false; | ||
1021 | |||
1022 | $fsize = filesize($this->wordlist_file); | ||
1023 | if ($fsize < 32) return false; // too small of a list to be effective | ||
1024 | |||
1025 | if ($fsize < 128) { | ||
1026 | $max = $fsize; // still pretty small but changes the range of seeking | ||
1027 | } else { | ||
1028 | $max = 128; | ||
1029 | } | ||
1030 | |||
1031 | fseek($fp, rand(0, $fsize - $max), SEEK_SET); | ||
1032 | $data = fread($fp, 128); // read a random 128 bytes from file | ||
1033 | fclose($fp); | ||
1034 | $data = preg_replace("/\r?\n/", "\n", $data); | ||
1035 | |||
1036 | $start = strpos($data, "\n", rand(0, 100)) + 1; // random start position | ||
1037 | $end = strpos($data, "\n", $start); // find end of word | ||
1038 | |||
1039 | return strtolower(substr($data, $start, $end - $start)); // return substring in 128 bytes | ||
1040 | } | ||
1041 | |||
1042 | /** | ||
1043 | * Output image to the browser | ||
1044 | * | ||
1045 | * @access private | ||
1046 | * | ||
1047 | */ | ||
1048 | function output() | ||
1049 | { | ||
1050 | header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); | ||
1051 | header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT"); | ||
1052 | header("Cache-Control: no-store, no-cache, must-revalidate"); | ||
1053 | header("Cache-Control: post-check=0, pre-check=0", false); | ||
1054 | header("Pragma: no-cache"); | ||
1055 | |||
1056 | switch($this->image_type) | ||
1057 | { | ||
1058 | case SI_IMAGE_JPEG: | ||
1059 | header("Content-Type: image/jpeg"); | ||
1060 | imagejpeg($this->im, null, 90); | ||
1061 | break; | ||
1062 | |||
1063 | case SI_IMAGE_GIF: | ||
1064 | header("Content-Type: image/gif"); | ||
1065 | imagegif($this->im); | ||
1066 | break; | ||
1067 | |||
1068 | default: | ||
1069 | header("Content-Type: image/png"); | ||
1070 | imagepng($this->im); | ||
1071 | break; | ||
1072 | } | ||
1073 | |||
1074 | imagedestroy($this->im); | ||
1075 | exit; | ||
1076 | } | ||
1077 | |||
1078 | /** | ||
1079 | * Get WAV or MP3 file data of the spoken code.<br /> | ||
1080 | * This is appropriate for output to the browser as audio/x-wav or audio/mpeg | ||
1081 | * | ||
1082 | * @since 1.0.1 | ||
1083 | * @return string WAV or MP3 data | ||
1084 | * | ||
1085 | */ | ||
1086 | function getAudibleCode($format = 'wav') | ||
1087 | { | ||
1088 | $letters = array(); | ||
1089 | $code = $this->getCode(); | ||
1090 | |||
1091 | if ($code == '') { | ||
1092 | $this->createCode(); | ||
1093 | $code = $this->getCode(); | ||
1094 | } | ||
1095 | |||
1096 | for($i = 0; $i < strlen($code); ++$i) { | ||
1097 | $letters[] = $code{$i}; | ||
1098 | } | ||
1099 | |||
1100 | if ($format == 'mp3') { | ||
1101 | return $this->generateMP3($letters); | ||
1102 | } else { | ||
1103 | return $this->generateWAV($letters); | ||
1104 | } | ||
1105 | } | ||
1106 | |||
1107 | /** | ||
1108 | * Set the path to the audio directory.<br /> | ||
1109 | * | ||
1110 | * @since 1.0.4 | ||
1111 | * @return bool true if the directory exists and is readble, false if not | ||
1112 | */ | ||
1113 | function setAudioPath($audio_directory) | ||
1114 | { | ||
1115 | if (is_dir($audio_directory) && is_readable($audio_directory)) { | ||
1116 | $this->audio_path = $audio_directory; | ||
1117 | return true; | ||
1118 | } else { | ||
1119 | return false; | ||
1120 | } | ||
1121 | } | ||
1122 | |||
1123 | /** | ||
1124 | * Save the code in the session | ||
1125 | * | ||
1126 | * @access private | ||
1127 | * | ||
1128 | */ | ||
1129 | function saveData() | ||
1130 | { | ||
1131 | $_SESSION['securimage_code_value'] = strtolower($this->code); | ||
1132 | $_SESSION['securimage_code_ctime'] = time(); | ||
1133 | |||
1134 | $this->saveCodeToDatabase(); | ||
1135 | } | ||
1136 | |||
1137 | /** | ||
1138 | * Validate the code to the user code | ||
1139 | * | ||
1140 | * @access private | ||
1141 | * | ||
1142 | */ | ||
1143 | function validate() | ||
1144 | { | ||
1145 | // retrieve code from session, if no code exists check sqlite database if supported. | ||
1146 | |||
1147 | if (isset($_SESSION['securimage_code_value']) && trim($_SESSION['securimage_code_value']) != '') { | ||
1148 | if ($this->isCodeExpired($_SESSION['securimage_code_ctime']) == false) { | ||
1149 | $code = $_SESSION['securimage_code_value']; | ||
1150 | } | ||
1151 | } else if ($this->use_sqlite_db == true && function_exists('sqlite_open')) { // no code in session - may mean user has cookies turned off | ||
1152 | $this->openDatabase(); | ||
1153 | $code = $this->getCodeFromDatabase(); | ||
1154 | } else { | ||
1155 | // session code invalid or non-existant and code not found in sqlite db or sqlite is not available | ||
1156 | $code = ''; | ||
1157 | } | ||
1158 | |||
1159 | $code = trim(strtolower($code)); | ||
1160 | $code_entered = trim(strtolower($this->code_entered)); | ||
1161 | $this->correct_code = false; | ||
1162 | |||
1163 | if ($code != '') { | ||
1164 | if ($code == $code_entered) { | ||
1165 | $this->correct_code = true; | ||
1166 | $_SESSION['securimage_code_value'] = ''; | ||
1167 | $_SESSION['securimage_code_ctime'] = ''; | ||
1168 | $this->clearCodeFromDatabase(); | ||
1169 | } | ||
1170 | } | ||
1171 | } | ||
1172 | |||
1173 | /** | ||
1174 | * Get the captcha code | ||
1175 | * | ||
1176 | * @since 1.0.1 | ||
1177 | * @return string | ||
1178 | */ | ||
1179 | function getCode() | ||
1180 | { | ||
1181 | if (isset($_SESSION['securimage_code_value']) && !empty($_SESSION['securimage_code_value'])) { | ||
1182 | return strtolower($_SESSION['securimage_code_value']); | ||
1183 | } else { | ||
1184 | if ($this->sqlite_handle == false) $this->openDatabase(); | ||
1185 | |||
1186 | return $this->getCodeFromDatabase(); // attempt to get from database, returns empty string if sqlite is not available or disabled | ||
1187 | } | ||
1188 | } | ||
1189 | |||
1190 | /** | ||
1191 | * Check if the user entered code was correct | ||
1192 | * | ||
1193 | * @access private | ||
1194 | * @return boolean | ||
1195 | */ | ||
1196 | function checkCode() | ||
1197 | { | ||
1198 | return $this->correct_code; | ||
1199 | } | ||
1200 | |||
1201 | /** | ||
1202 | * Generate a wav file by concatenating individual files | ||
1203 | * | ||
1204 | * @since 1.0.1 | ||
1205 | * @access private | ||
1206 | * @param array $letters Array of letters to build a file from | ||
1207 | * @return string WAV file data | ||
1208 | */ | ||
1209 | function generateWAV($letters) | ||
1210 | { | ||
1211 | $data_len = 0; | ||
1212 | $files = array(); | ||
1213 | $out_data = ''; | ||
1214 | |||
1215 | foreach ($letters as $letter) { | ||
1216 | $filename = $this->audio_path . strtoupper($letter) . '.wav'; | ||
1217 | |||
1218 | $fp = fopen($filename, 'rb'); | ||
1219 | |||
1220 | $file = array(); | ||
1221 | |||
1222 | $data = fread($fp, filesize($filename)); // read file in | ||
1223 | |||
1224 | $header = substr($data, 0, 36); | ||
1225 | $body = substr($data, 44); | ||
1226 | |||
1227 | |||
1228 | $data = unpack('NChunkID/VChunkSize/NFormat/NSubChunk1ID/VSubChunk1Size/vAudioFormat/vNumChannels/VSampleRate/VByteRate/vBlockAlign/vBitsPerSample', $header); | ||
1229 | |||
1230 | $file['sub_chunk1_id'] = $data['SubChunk1ID']; | ||
1231 | $file['bits_per_sample'] = $data['BitsPerSample']; | ||
1232 | $file['channels'] = $data['NumChannels']; | ||
1233 | $file['format'] = $data['AudioFormat']; | ||
1234 | $file['sample_rate'] = $data['SampleRate']; | ||
1235 | $file['size'] = $data['ChunkSize'] + 8; | ||
1236 | $file['data'] = $body; | ||
1237 | |||
1238 | if ( ($p = strpos($file['data'], 'LIST')) !== false) { | ||
1239 | // If the LIST data is not at the end of the file, this will probably break your sound file | ||
1240 | $info = substr($file['data'], $p + 4, 8); | ||
1241 | $data = unpack('Vlength/Vjunk', $info); | ||
1242 | $file['data'] = substr($file['data'], 0, $p); | ||
1243 | $file['size'] = $file['size'] - (strlen($file['data']) - $p); | ||
1244 | } | ||
1245 | |||
1246 | $files[] = $file; | ||
1247 | $data = null; | ||
1248 | $header = null; | ||
1249 | $body = null; | ||
1250 | |||
1251 | $data_len += strlen($file['data']); | ||
1252 | |||
1253 | fclose($fp); | ||
1254 | } | ||
1255 | |||
1256 | $out_data = ''; | ||
1257 | for($i = 0; $i < sizeof($files); ++$i) { | ||
1258 | if ($i == 0) { // output header | ||
1259 | $out_data .= pack('C4VC8', ord('R'), ord('I'), ord('F'), ord('F'), $data_len + 36, ord('W'), ord('A'), ord('V'), ord('E'), ord('f'), ord('m'), ord('t'), ord(' ')); | ||
1260 | |||
1261 | $out_data .= pack('VvvVVvv', | ||
1262 | 16, | ||
1263 | $files[$i]['format'], | ||
1264 | $files[$i]['channels'], | ||
1265 | $files[$i]['sample_rate'], | ||
1266 | $files[$i]['sample_rate'] * (($files[$i]['bits_per_sample'] * $files[$i]['channels']) / 8), | ||
1267 | ($files[$i]['bits_per_sample'] * $files[$i]['channels']) / 8, | ||
1268 | $files[$i]['bits_per_sample'] ); | ||
1269 | |||
1270 | $out_data .= pack('C4', ord('d'), ord('a'), ord('t'), ord('a')); | ||
1271 | |||
1272 | $out_data .= pack('V', $data_len); | ||
1273 | } | ||
1274 | |||
1275 | $out_data .= $files[$i]['data']; | ||
1276 | } | ||
1277 | |||
1278 | $this->scrambleAudioData($out_data, 'wav'); | ||
1279 | return $out_data; | ||
1280 | } | ||
1281 | |||
1282 | /** | ||
1283 | * Randomly modify the audio data to scramble sound and prevent binary recognition.<br /> | ||
1284 | * Take care not to "break" the audio file by leaving the header data intact. | ||
1285 | * | ||
1286 | * @since 2.0 | ||
1287 | * @access private | ||
1288 | * @param $data Sound data in mp3 of wav format | ||
1289 | */ | ||
1290 | function scrambleAudioData(&$data, $format) | ||
1291 | { | ||
1292 | if ($format == 'wav') { | ||
1293 | $start = strpos($data, 'data') + 4; // look for "data" indicator | ||
1294 | if ($start === false) $start = 44; // if not found assume 44 byte header | ||
1295 | } else { // mp3 | ||
1296 | $start = 4; // 4 byte (32 bit) frame header | ||
1297 | } | ||
1298 | |||
1299 | $start += rand(1, 64); // randomize starting offset | ||
1300 | $datalen = strlen($data) - $start - 256; // leave last 256 bytes unchanged | ||
1301 | |||
1302 | for ($i = $start; $i < $datalen; $i += 64) { | ||
1303 | $ch = ord($data{$i}); | ||
1304 | if ($ch < 9 || $ch > 119) continue; | ||
1305 | |||
1306 | $data{$i} = chr($ch + rand(-8, 8)); | ||
1307 | } | ||
1308 | } | ||
1309 | |||
1310 | /** | ||
1311 | * Generate an mp3 file by concatenating individual files | ||
1312 | * @since 1.0.4 | ||
1313 | * @access private | ||
1314 | * @param array $letters Array of letters to build a file from | ||
1315 | * @return string MP3 file data | ||
1316 | */ | ||
1317 | function generateMP3($letters) | ||
1318 | { | ||
1319 | $data_len = 0; | ||
1320 | $files = array(); | ||
1321 | $out_data = ''; | ||
1322 | |||
1323 | foreach ($letters as $letter) { | ||
1324 | $filename = $this->audio_path . strtoupper($letter) . '.mp3'; | ||
1325 | |||
1326 | $fp = fopen($filename, 'rb'); | ||
1327 | $data = fread($fp, filesize($filename)); // read file in | ||
1328 | |||
1329 | $this->scrambleAudioData($data, 'mp3'); | ||
1330 | $out_data .= $data; | ||
1331 | |||
1332 | fclose($fp); | ||
1333 | } | ||
1334 | |||
1335 | |||
1336 | return $out_data; | ||
1337 | } | ||
1338 | |||
1339 | /** | ||
1340 | * Generate random number less than 1 | ||
1341 | * @since 2.0 | ||
1342 | * @access private | ||
1343 | * @return float | ||
1344 | */ | ||
1345 | function frand() | ||
1346 | { | ||
1347 | return 0.0001*rand(0,9999); | ||
1348 | } | ||
1349 | |||
1350 | /** | ||
1351 | * Print signature text on image | ||
1352 | * | ||
1353 | * @since 2.0 | ||
1354 | * @access private | ||
1355 | * | ||
1356 | */ | ||
1357 | function addSignature() | ||
1358 | { | ||
1359 | if ($this->use_gd_font) { | ||
1360 | imagestring($this->im, 5, $this->image_width - (strlen($this->image_signature) * 10), $this->image_height - 20, $this->image_signature, $this->gdsignaturecolor); | ||
1361 | } else { | ||
1362 | |||
1363 | $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature); | ||
1364 | $textlen = $bbox[2] - $bbox[0]; | ||
1365 | $x = $this->image_width - $textlen - 5; | ||
1366 | $y = $this->image_height - 3; | ||
1367 | |||
1368 | imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature); | ||
1369 | } | ||
1370 | } | ||
1371 | |||
1372 | /** | ||
1373 | * Get hashed IP address of remote user | ||
1374 | * | ||
1375 | * @access private | ||
1376 | * @since 2.0.1 | ||
1377 | * @return string | ||
1378 | */ | ||
1379 | function getIPHash() | ||
1380 | { | ||
1381 | return strtolower(md5($_SERVER['REMOTE_ADDR'])); | ||
1382 | } | ||
1383 | |||
1384 | /** | ||
1385 | * Open SQLite database | ||
1386 | * | ||
1387 | * @access private | ||
1388 | * @since 2.0.1 | ||
1389 | * @return bool true if database was opened successfully | ||
1390 | */ | ||
1391 | function openDatabase() | ||
1392 | { | ||
1393 | $this->sqlite_handle = false; | ||
1394 | |||
1395 | if ($this->use_sqlite_db && function_exists('sqlite_open')) { | ||
1396 | $this->sqlite_handle = sqlite_open($this->sqlite_database, 0666, $error); | ||
1397 | |||
1398 | if ($this->sqlite_handle !== false) { | ||
1399 | $res = sqlite_query($this->sqlite_handle, "PRAGMA table_info(codes)"); | ||
1400 | if (sqlite_num_rows($res) == 0) { | ||
1401 | sqlite_query($this->sqlite_handle, "CREATE TABLE codes (iphash VARCHAR(32) PRIMARY KEY, code VARCHAR(32) NOT NULL, created INTEGER)"); | ||
1402 | } | ||
1403 | } | ||
1404 | |||
1405 | return $this->sqlite_handle != false; | ||
1406 | } | ||
1407 | |||
1408 | return $this->sqlite_handle; | ||
1409 | } | ||
1410 | |||
1411 | /** | ||
1412 | * Save captcha code to sqlite database | ||
1413 | * | ||
1414 | * @access private | ||
1415 | * @since 2.0.1 | ||
1416 | * @return bool true if code was saved, false if not | ||
1417 | */ | ||
1418 | function saveCodeToDatabase() | ||
1419 | { | ||
1420 | $success = false; | ||
1421 | |||
1422 | $this->openDatabase(); | ||
1423 | |||
1424 | if ($this->use_sqlite_db && $this->sqlite_handle !== false) { | ||
1425 | $ip = $this->getIPHash(); | ||
1426 | $time = time(); | ||
1427 | $code = $_SESSION['securimage_code_value']; // hash code for security - if cookies are disabled the session still exists at this point | ||
1428 | $success = sqlite_query($this->sqlite_handle, "INSERT OR REPLACE INTO codes(iphash, code, created) VALUES('$ip', '$code', $time)"); | ||
1429 | } | ||
1430 | |||
1431 | return $success !== false; | ||
1432 | } | ||
1433 | |||
1434 | /** | ||
1435 | * Get stored captcha code from sqlite database based on ip address hash | ||
1436 | * | ||
1437 | * @access private | ||
1438 | * @since 2.0.1 | ||
1439 | * @return string captcha code | ||
1440 | */ | ||
1441 | function getCodeFromDatabase() | ||
1442 | { | ||
1443 | $code = ''; | ||
1444 | |||
1445 | if ($this->use_sqlite_db && $this->sqlite_handle !== false) { | ||
1446 | $ip = $this->getIPHash(); | ||
1447 | |||
1448 | $res = sqlite_query($this->sqlite_handle, "SELECT * FROM codes WHERE iphash = '$ip'"); | ||
1449 | if ($res && sqlite_num_rows($res) > 0) { | ||
1450 | $res = sqlite_fetch_array($res); | ||
1451 | |||
1452 | if ($this->isCodeExpired($res['created']) == false) { | ||
1453 | $code = $res['code']; | ||
1454 | } | ||
1455 | } | ||
1456 | } | ||
1457 | |||
1458 | return $code; | ||
1459 | } | ||
1460 | |||
1461 | /** | ||
1462 | * Delete a code from the database by ip address hash | ||
1463 | * | ||
1464 | * @access private | ||
1465 | * @since 2.0.1 | ||
1466 | */ | ||
1467 | function clearCodeFromDatabase() | ||
1468 | { | ||
1469 | if ($this->sqlite_handle !== false) { | ||
1470 | $ip = $this->getIPHash(); | ||
1471 | |||
1472 | sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE iphash = '$ip'"); | ||
1473 | } | ||
1474 | } | ||
1475 | |||
1476 | /** | ||
1477 | * Purge codes over a day old from database | ||
1478 | * | ||
1479 | * @access private | ||
1480 | * @since 2.0.1 | ||
1481 | */ | ||
1482 | function purgeOldCodesFromDatabase() | ||
1483 | { | ||
1484 | if ($this->use_sqlite_db && $this->sqlite_handle !== false) { | ||
1485 | $now = time(); | ||
1486 | $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time; | ||
1487 | |||
1488 | sqlite_query($this->sqlite_handle, "DELETE FROM codes WHERE $now - created > $limit"); | ||
1489 | } | ||
1490 | } | ||
1491 | |||
1492 | /** | ||
1493 | * Check a code to see if it is expired based on creation time | ||
1494 | * | ||
1495 | * @access private | ||
1496 | * @since 2.0.1 | ||
1497 | * @param $creation_time unix timestamp of code creation time | ||
1498 | * @return bool true if code has expired, false if not | ||
1499 | */ | ||
1500 | function isCodeExpired($creation_time) | ||
1501 | { | ||
1502 | $expired = true; | ||
1503 | |||
1504 | if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) { | ||
1505 | $expired = false; | ||
1506 | } else if (time() - $creation_time < $this->expiry_time) { | ||
1507 | $expired = false; | ||
1508 | } | ||
1509 | |||
1510 | return $expired; | ||
1511 | } | ||
1512 | |||
1513 | } /* class Securimage */ | ||
1514 | |||
1515 | |||
1516 | /** | ||
1517 | * Color object for Securimage CAPTCHA | ||
1518 | * | ||
1519 | * @since 2.0 | ||
1520 | * @package Securimage | ||
1521 | * @subpackage classes | ||
1522 | * | ||
1523 | */ | ||
1524 | class Securimage_Color { | ||
1525 | /** | ||
1526 | * Red component: 0-255 | ||
1527 | * | ||
1528 | * @var int | ||
1529 | */ | ||
1530 | var $r; | ||
1531 | /** | ||
1532 | * Green component: 0-255 | ||
1533 | * | ||
1534 | * @var int | ||
1535 | */ | ||
1536 | var $g; | ||
1537 | /** | ||
1538 | * Blue component: 0-255 | ||
1539 | * | ||
1540 | * @var int | ||
1541 | */ | ||
1542 | var $b; | ||
1543 | |||
1544 | /** | ||
1545 | * Create a new Securimage_Color object.<br /> | ||
1546 | * Specify the red, green, and blue components using their HTML hex code equivalent.<br /> | ||
1547 | * Example: The code for the HTML color #4A203C is:<br /> | ||
1548 | * $color = new Securimage_Color(0x4A, 0x20, 0x3C); | ||
1549 | * | ||
1550 | * @param $red Red component 0-255 | ||
1551 | * @param $green Green component 0-255 | ||
1552 | * @param $blue Blue component 0-255 | ||
1553 | */ | ||
1554 | function Securimage_Color($red, $green = null, $blue = null) | ||
1555 | { | ||
1556 | if ($green == null && $blue == null && preg_match('/^#[a-f0-9]{3,6}$/i', $red)) { | ||
1557 | $col = substr($red, 1); | ||
1558 | if (strlen($col) == 3) { | ||
1559 | $red = str_repeat(substr($col, 0, 1), 2); | ||
1560 | $green = str_repeat(substr($col, 1, 1), 2); | ||
1561 | $blue = str_repeat(substr($col, 2, 1), 2); | ||
1562 | } else { | ||
1563 | $red = substr($col, 0, 2); | ||
1564 | $green = substr($col, 2, 2); | ||
1565 | $blue = substr($col, 4, 2); | ||
1566 | } | ||
1567 | |||
1568 | $red = hexdec($red); | ||
1569 | $green = hexdec($green); | ||
1570 | $blue = hexdec($blue); | ||
1571 | } else { | ||
1572 | if ($red < 0) $red = 0; | ||
1573 | if ($red > 255) $red = 255; | ||
1574 | if ($green < 0) $green = 0; | ||
1575 | if ($green > 255) $green = 255; | ||
1576 | if ($blue < 0) $blue = 0; | ||
1577 | if ($blue > 255) $blue = 255; | ||
1578 | } | ||
1579 | |||
1580 | $this->r = $red; | ||
1581 | $this->g = $green; | ||
1582 | $this->b = $blue; | ||
1583 | } | ||
1584 | } | ||