securimage.php 101 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968
  1. <?php
  2. // error_reporting(E_ALL); ini_set('display_errors', 1); // uncomment this line for debugging
  3. /**
  4. * Project: Securimage: A PHP class dealing with CAPTCHA images, audio, and validation
  5. * File: securimage.php
  6. *
  7. * Copyright (c) 2014, Drew Phillips
  8. * All rights reserved.
  9. *
  10. * Redistribution and use in source and binary forms, with or without modification,
  11. * are permitted provided that the following conditions are met:
  12. *
  13. * - Redistributions of source code must retain the above copyright notice,
  14. * this list of conditions and the following disclaimer.
  15. * - Redistributions in binary form must reproduce the above copyright notice,
  16. * this list of conditions and the following disclaimer in the documentation
  17. * and/or other materials provided with the distribution.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  20. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  21. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  22. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  23. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  24. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  25. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  26. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  27. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  28. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  29. * POSSIBILITY OF SUCH DAMAGE.
  30. *
  31. * Any modifications to the library should be indicated clearly in the source code
  32. * to inform users that the changes are not a part of the original software.
  33. *
  34. * If you found this script useful, please take a quick moment to rate it.
  35. * http://www.hotscripts.com/rate/49400.html Thanks.
  36. *
  37. * @link http://www.phpcaptcha.org Securimage PHP CAPTCHA
  38. * @link http://www.phpcaptcha.org/latest.zip Download Latest Version
  39. * @link http://www.phpcaptcha.org/Securimage_Docs/ Online Documentation
  40. * @copyright 2014 Drew Phillips
  41. * @author Drew Phillips <drew@drew-phillips.com>
  42. * @version 3.5.3 (Mar 17, 2014)
  43. * @package Securimage
  44. *
  45. */
  46. /**
  47. TODO:
  48. - Implement HTML5 playback of audio using Javascript, DOM, and HTML5 <audio> with Flash fallback
  49. ChangeLog
  50. 3.5.3
  51. - Add options for audio button to getCaptchaHtml(), fix urlencoding of flash parameters that was breaking button
  52. 3.5.2
  53. - Add Securimage::getCaptchaHtml() for getting automatically generated captcha html code
  54. - Option for using SoX to add effects to captcha audio to make identification by neural networks more difficult
  55. - Add setNamespace() method
  56. - Add getTimeToSolve() method
  57. - Add session_status() check so session still starts if one had previously been opened and closed
  58. - Add .htaccess file to audio directory to deny access; update audio files
  59. - Option to skip checking of database tables during connection
  60. - Add composer.json to package, submit to packagist
  61. - Add font_ratio variable to determine size of font (github.com/wilkor)
  62. - Add hint if sqlite3 database is not writeable. Improve database error handling, add example database options to securimage_play.php
  63. - Fixed issue regarding database storage and math captcha breaking audio output (github.com/SoftwareAndOutsourcing)
  64. 3.5.1
  65. - Fix XSS vulnerability in example_form.php (discovered by Gjoko Krstic - <gjoko@zeroscience.mk>)
  66. 3.5
  67. - Release new version
  68. - MB string support for charlist
  69. - Modify audio file path to use language directories
  70. - Changed default captcha appearance
  71. 3.2RC4
  72. - Add MySQL, PostgreSQL, and SQLite3 support for database storage
  73. - Deprecate "use_sqlite_db" option and remove SQLite2/sqlite_* functions
  74. - Add new captcha type that displays 2 dictionary words on one image
  75. - Update examples
  76. 3.2RC3
  77. - Fix canSendHeaders() check which was breaking if a PHP startup error was issued
  78. 3.2RC2
  79. - Add error handler (https://github.com/dapphp/securimage/issues/15)
  80. - Fix flash examples to use the correct value name for audio parameter
  81. 3.2RC1
  82. - New audio captcha code. Faster, fully dynamic audio, full WAV support
  83. (Paul Voegler, Drew Phillips) <http://voegler.eu/pub/audio>
  84. - New Flash audio streaming button. User defined image and size supported
  85. - Additional options for customizing captcha (noise_level, send_headers,
  86. no_exit, no_session, display_value
  87. - Add captcha ID support. Uses sqlite and unique captcha IDs to track captchas,
  88. no session used
  89. - Add static methods for creating and validating captcha by ID
  90. - Automatic clearing of old codes from SQLite database
  91. 3.0.3Beta
  92. - Add improved mixing function to WavFile class (Paul Voegler)
  93. - Improve performance and security of captcha audio (Paul Voegler, Drew Phillips)
  94. - Add option to use random file as background noise in captcha audio
  95. - Add new securimage options for audio files
  96. 3.0.2Beta
  97. - Fix issue with session variables when upgrading from 2.0 - 3.0
  98. - Improve audio captcha, switch to use WavFile class, make mathematical captcha audio work
  99. 3.0.1
  100. - Bugfix: removed use of deprecated variable in addSignature method that would cause errors with display_errors on
  101. 3.0
  102. - Rewrite class using PHP5 OOP
  103. - Remove support for GD fonts, require FreeType
  104. - Remove support for multi-color codes
  105. - Add option to make codes case-sensitive
  106. - Add namespaces to support multiple captchas on a single page or page specific captchas
  107. - Add option to show simple math problems instead of codes
  108. - Remove support for mp3 files due to vulnerability in decoding mp3 audio files
  109. - Create new flash file to stream wav files instead of mp3
  110. - Changed to BSD license
  111. 2.0.2
  112. - Fix pathing to make integration into libraries easier (Nathan Phillip Brink ohnobinki@ohnopublishing.net)
  113. 2.0.1
  114. - Add support for browsers with cookies disabled (requires php5, sqlite) maps users to md5 hashed ip addresses and md5 hashed codes for security
  115. - 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)
  116. - Check for previous definition of image type constants (Mike Challis)
  117. - Fix mime type settings for audio output
  118. - Fixed color allocation issues with multiple colors and background images, consolidate allocation to one function
  119. - Ability to let codes expire after a given length of time
  120. - Allow HTML color codes to be passed to Securimage_Color (suggested by Mike Challis)
  121. 2.0.0
  122. - Add mathematical distortion to characters (using code from HKCaptcha)
  123. - Improved session support
  124. - Added Securimage_Color class for easier color definitions
  125. - Add distortion to audio output to prevent binary comparison attack (proposed by Sven "SavageTiger" Hagemann [insecurity.nl])
  126. - Flash button to stream mp3 audio (Douglas Walsh www.douglaswalsh.net)
  127. - Audio output is mp3 format by default
  128. - Change font to AlteHaasGrotesk by yann le coroller
  129. - Some code cleanup
  130. 1.0.4 (unreleased)
  131. - Ability to output audible codes in mp3 format to stream from flash
  132. 1.0.3.1
  133. - Error reading from wordlist in some cases caused words to be cut off 1 letter short
  134. 1.0.3
  135. - Removed shadow_text from code which could cause an undefined property error due to removal from previous version
  136. 1.0.2
  137. - Audible CAPTCHA Code wav files
  138. - Create codes from a word list instead of random strings
  139. 1.0
  140. - Added the ability to use a selected character set, rather than a-z0-9 only.
  141. - Added the multi-color text option to use different colors for each letter.
  142. - Switched to automatic session handling instead of using files for code storage
  143. - Added GD Font support if ttf support is not available. Can use internal GD fonts or load new ones.
  144. - Added the ability to set line thickness
  145. - Added option for drawing arced lines over letters
  146. - Added ability to choose image type for output
  147. */
  148. /**
  149. * Securimage CAPTCHA Class.
  150. *
  151. * A class for creating and validating secure CAPTCHA images and audio.
  152. *
  153. * The class contains many options regarding appearance, security, storage of
  154. * captcha data and image/audio generation options.
  155. *
  156. * @version 3.5.2
  157. * @package Securimage
  158. * @subpackage classes
  159. * @author Drew Phillips <drew@drew-phillips.com>
  160. *
  161. */
  162. class Securimage
  163. {
  164. // All of the public variables below are securimage options
  165. // They can be passed as an array to the Securimage constructor, set below,
  166. // or set from securimage_show.php and securimage_play.php
  167. /**
  168. * Constant for rendering captcha as a JPEG image
  169. * @var int
  170. */
  171. const SI_IMAGE_JPEG = 1;
  172. /**
  173. * Constant for rendering captcha as a PNG image (default)
  174. * @var int
  175. */
  176. const SI_IMAGE_PNG = 2;
  177. /**
  178. * Constant for rendering captcha as a GIF image
  179. * @var int
  180. */
  181. const SI_IMAGE_GIF = 3;
  182. /**
  183. * Constant for generating a normal alphanumeric captcha based on the
  184. * character set
  185. *
  186. * @see Securimage::$charset charset property
  187. * @var int
  188. */
  189. const SI_CAPTCHA_STRING = 0;
  190. /**
  191. * Constant for generating a captcha consisting of a simple math problem
  192. *
  193. * @var int
  194. */
  195. const SI_CAPTCHA_MATHEMATIC = 1;
  196. /**
  197. * Constant for generating a word based captcha using 2 words from a list
  198. *
  199. * @var int
  200. */
  201. const SI_CAPTCHA_WORDS = 2;
  202. /**
  203. * MySQL option identifier for database storage option
  204. *
  205. * @var string
  206. */
  207. const SI_DRIVER_MYSQL = 'mysql';
  208. /**
  209. * PostgreSQL option identifier for database storage option
  210. *
  211. * @var string
  212. */
  213. const SI_DRIVER_PGSQL = 'pgsql';
  214. /**
  215. * SQLite option identifier for database storage option
  216. *
  217. * @var string
  218. */
  219. const SI_DRIVER_SQLITE3 = 'sqlite';
  220. /*%*********************************************************************%*/
  221. // Properties
  222. /**
  223. * The width of the captcha image
  224. * @var int
  225. */
  226. public $image_width = 215;
  227. /**
  228. * The height of the captcha image
  229. * @var int
  230. */
  231. public $image_height = 80;
  232. /**
  233. * Font size is calculated by image height and this ratio. Leave blank for
  234. * default ratio of 0.4.
  235. *
  236. * Valid range: 0.1 - 0.99.
  237. *
  238. * Depending on image_width, values > 0.6 are probably too large and
  239. * values < 0.3 are too small.
  240. *
  241. * @var float
  242. */
  243. public $font_ratio;
  244. /**
  245. * The type of the image, default = png
  246. *
  247. * @see Securimage::SI_IMAGE_PNG SI_IMAGE_PNG
  248. * @see Securimage::SI_IMAGE_JPEG SI_IMAGE_JPEG
  249. * @see Securimage::SI_IMAGE_GIF SI_IMAGE_GIF
  250. * @var int
  251. */
  252. public $image_type = self::SI_IMAGE_PNG;
  253. /**
  254. * The background color of the captcha
  255. * @var Securimage_Color
  256. */
  257. public $image_bg_color = '#ffffff';
  258. /**
  259. * The color of the captcha text
  260. * @var Securimage_Color
  261. */
  262. public $text_color = '#707070';
  263. /**
  264. * The color of the lines over the captcha
  265. * @var Securimage_Color
  266. */
  267. public $line_color = '#707070';
  268. /**
  269. * The color of the noise that is drawn
  270. * @var Securimage_Color
  271. */
  272. public $noise_color = '#707070';
  273. /**
  274. * How transparent to make the text.
  275. *
  276. * 0 = completely opaque, 100 = invisible
  277. *
  278. * @var int
  279. */
  280. public $text_transparency_percentage = 20;
  281. /**
  282. * Whether or not to draw the text transparently.
  283. *
  284. * true = use transparency, false = no transparency
  285. *
  286. * @var bool
  287. */
  288. public $use_transparent_text = true;
  289. /**
  290. * The length of the captcha code
  291. * @var int
  292. */
  293. public $code_length = 6;
  294. /**
  295. * Whether the captcha should be case sensitive or not.
  296. *
  297. * Not recommended, use only for maximum protection.
  298. *
  299. * @var bool
  300. */
  301. public $case_sensitive = false;
  302. /**
  303. * The character set to use for generating the captcha code
  304. * @var string
  305. */
  306. public $charset = 'ABCDEFGHKLMNPRSTUVWYZabcdefghklmnprstuvwyz23456789';
  307. /**
  308. * How long in seconds a captcha remains valid, after this time it will be
  309. * considered incorrect.
  310. *
  311. * @var int
  312. */
  313. public $expiry_time = 900;
  314. /**
  315. * The session name securimage should use.
  316. *
  317. * Only use if your application uses a custom session name (e.g. Joomla).
  318. * It is recommended to set this value here so it is used by all securimage
  319. * scripts (i.e. securimage_show.php)
  320. *
  321. * @var string
  322. */
  323. public $session_name = null;
  324. /**
  325. * true to use the wordlist file, false to generate random captcha codes
  326. * @var bool
  327. */
  328. public $use_wordlist = false;
  329. /**
  330. * The level of distortion.
  331. *
  332. * 0.75 = normal, 1.0 = very high distortion
  333. *
  334. * @var double
  335. */
  336. public $perturbation = 0.85;
  337. /**
  338. * How many lines to draw over the captcha code to increase security
  339. * @var int
  340. */
  341. public $num_lines = 5;
  342. /**
  343. * The level of noise (random dots) to place on the image, 0-10
  344. * @var int
  345. */
  346. public $noise_level = 2;
  347. /**
  348. * The signature text to draw on the bottom corner of the image
  349. * @var string
  350. */
  351. public $image_signature = '';
  352. /**
  353. * The color of the signature text
  354. * @var Securimage_Color
  355. */
  356. public $signature_color = '#707070';
  357. /**
  358. * The path to the ttf font file to use for the signature text.
  359. * Defaults to $ttf_file (AHGBold.ttf)
  360. *
  361. * @see Securimage::$ttf_file
  362. * @var string
  363. */
  364. public $signature_font;
  365. /**
  366. * No longer used.
  367. *
  368. * Use an SQLite database to store data (for users that do not support cookies)
  369. *
  370. * @var bool
  371. * @see Securimage::$database_driver database_driver property
  372. * @deprecated 3.2RC4
  373. */
  374. public $use_sqlite_db = false;
  375. /**
  376. * Use a database backend for code storage.
  377. * Provides a fallback to users with cookies disabled.
  378. * Required when using captcha IDs.
  379. *
  380. * @see Securimage::$database_driver
  381. * @var bool
  382. */
  383. public $use_database = false;
  384. /**
  385. * Whether or not to skip checking if Securimage tables exist when using a
  386. * database.
  387. *
  388. * Turn this to true once database functionality is working to improve
  389. * performance.
  390. *
  391. * @var bool true to not check if captcha_codes tables are set up, false
  392. * to check (and create if necessary)
  393. */
  394. public $skip_table_check = false;
  395. /**
  396. * Database driver to use for database support.
  397. * Allowable values: *mysql*, *pgsql*, *sqlite*.
  398. * Default: sqlite
  399. *
  400. * @var string
  401. */
  402. public $database_driver = self::SI_DRIVER_SQLITE3;
  403. /**
  404. * Database host to connect to when using mysql or postgres
  405. *
  406. * On Linux use "localhost" for Unix domain socket, otherwise uses TCP/IP
  407. *
  408. * Does not apply to SQLite
  409. *
  410. * @var string
  411. */
  412. public $database_host = 'localhost';
  413. /**
  414. * Database username for connection (mysql, postgres only)
  415. * Default is an empty string
  416. *
  417. * @var string
  418. */
  419. public $database_user = '';
  420. /**
  421. * Database password for connection (mysql, postgres only)
  422. * Default is empty string
  423. *
  424. * @var string
  425. */
  426. public $database_pass = '';
  427. /**
  428. * Name of the database to select (mysql, postgres only)
  429. *
  430. * @see Securimage::$database_file for SQLite
  431. * @var string
  432. */
  433. public $database_name = '';
  434. /**
  435. * Database table where captcha codes are stored
  436. *
  437. * Note: Securimage will attempt to create this table for you if it does
  438. * not exist. If the table cannot be created, an E_USER_WARNING is emitted
  439. *
  440. * @var string
  441. */
  442. public $database_table = 'captcha_codes';
  443. /**
  444. * Fully qualified path to the database file when using SQLite3.
  445. *
  446. * This value is only used when $database_driver == sqlite and does
  447. * not apply when no database is used, or when using MySQL or PostgreSQL.
  448. *
  449. * On *nix, file must have permissions of 0666.
  450. *
  451. * **Make sure the directory containing this file is NOT web accessible**
  452. *
  453. * @var string
  454. */
  455. public $database_file;
  456. /**
  457. * The type of captcha to create.
  458. *
  459. * Either alphanumeric based on *charset*, a simple math problem, or an
  460. * image consisting of 2 words from the word list.
  461. *
  462. * @see Securimage::SI_CAPTCHA_STRING SI_CAPTCHA_STRING
  463. * @see Securimage::SI_CAPTCHA_MATHEMATIC SI_CAPTCHA_MATHEMATIC
  464. * @see Securimage::SI_CAPTCHA_WORDS SI_CAPTCHA_WORDS
  465. * @see Securimage::$charset charset property
  466. * @see Securimage::$wordlist_file wordlist_file property
  467. * @var int
  468. */
  469. public $captcha_type = self::SI_CAPTCHA_STRING; // or self::SI_CAPTCHA_MATHEMATIC, or self::SI_CAPTCHA_WORDS;
  470. /**
  471. * The captcha namespace used for having multiple captchas on a page or
  472. * to separate captchas from differen forms on your site.
  473. * Example:
  474. *
  475. * <?php
  476. * // use <img src="securimage_show.php?namespace=contact_form">
  477. * // or manually in securimage_show.php
  478. * $img->setNamespace('contact_form');
  479. *
  480. * // in form validator
  481. * $img->setNamespace('contact_form');
  482. * if ($img->check($code) == true) {
  483. * echo "Valid!";
  484. * }
  485. *
  486. * @var string
  487. */
  488. public $namespace;
  489. /**
  490. * The TTF font file to use to draw the captcha code.
  491. *
  492. * Leave blank for default font AHGBold.ttf
  493. *
  494. * @var string
  495. */
  496. public $ttf_file;
  497. /**
  498. * The path to the wordlist file to use.
  499. *
  500. * Leave blank for default words/words.txt
  501. *
  502. * @var string
  503. */
  504. public $wordlist_file;
  505. /**
  506. * The directory to scan for background images, if set a random background
  507. * will be chosen from this folder
  508. *
  509. * @var string
  510. */
  511. public $background_directory;
  512. /**
  513. * No longer used
  514. *
  515. * The path to the SQLite database file to use
  516. *
  517. * @deprecated 3.2RC4
  518. * @see Securimage::$database_file database_file property
  519. * @var string
  520. */
  521. public $sqlite_database;
  522. /**
  523. * The path to the audio files to be used for audio captchas.
  524. *
  525. * Can also be set in securimage_play.php
  526. *
  527. * Example:
  528. *
  529. * $img->audio_path = '/home/yoursite/public_html/securimage/audio/en/';
  530. *
  531. * @var string
  532. */
  533. public $audio_path;
  534. /**
  535. * Use SoX (The Swiss Army knife of audio manipulation) for audio effects
  536. * and processing.
  537. *
  538. * Using SoX should make it more difficult for bots to solve audio captchas
  539. *
  540. * @see Securimage::$sox_binary_path sox_binary_path property
  541. * @var bool true to use SoX, false to use PHP
  542. */
  543. public $audio_use_sox = false;
  544. /**
  545. * The path to the SoX binary on your system
  546. *
  547. * @var string
  548. */
  549. public $sox_binary_path = '/usr/bin/sox';
  550. /**
  551. * The path to the directory containing audio files that will be selected
  552. * randomly and mixed with the captcha audio.
  553. *
  554. * @var string
  555. */
  556. public $audio_noise_path;
  557. /**
  558. * Whether or not to mix background noise files into captcha audio
  559. *
  560. * Mixing random background audio with noise can help improve security of
  561. * audio captcha.
  562. *
  563. * Default: securimage/audio/noise
  564. *
  565. * @since 3.0.3
  566. * @see Securimage::$audio_noise_path audio_noise_path property
  567. * @var bool true = mix, false = no
  568. */
  569. public $audio_use_noise;
  570. /**
  571. * The method and threshold (or gain factor) used to normalize the mixing
  572. * with background noise.
  573. *
  574. * See http://www.voegler.eu/pub/audio/ for more information.
  575. *
  576. * Default: 0.6
  577. *
  578. * Valid:
  579. * >= 1
  580. * Normalize by multiplying by the threshold (boost - positive gain).
  581. * A value of 1 in effect means no normalization (and results in clipping).
  582. *
  583. * <= -1
  584. * Normalize by dividing by the the absolute value of threshold (attenuate - negative gain).
  585. * A factor of 2 (-2) is about 6dB reduction in volume.
  586. *
  587. * [0, 1) (open inverval - not including 1)
  588. * The threshold above which amplitudes are comressed logarithmically.
  589. * e.g. 0.6 to leave amplitudes up to 60% "as is" and compressabove.
  590. *
  591. * (-1, 0) (open inverval - not including -1 and 0)
  592. * The threshold above which amplitudes are comressed linearly.
  593. * e.g. -0.6 to leave amplitudes up to 60% "as is" and compress above.
  594. *
  595. * @since 3.0.4
  596. * @var float
  597. */
  598. public $audio_mix_normalization = 0.8;
  599. /**
  600. * Whether or not to degrade audio by introducing random noise.
  601. *
  602. * Current research shows this may not increase the security of audible
  603. * captchas.
  604. *
  605. * Default: true
  606. *
  607. * @since 3.0.3
  608. * @var bool
  609. */
  610. public $degrade_audio;
  611. /**
  612. * Minimum delay to insert between captcha audio letters in milliseconds
  613. *
  614. * @since 3.0.3
  615. * @var float
  616. */
  617. public $audio_gap_min = 0;
  618. /**
  619. * Maximum delay to insert between captcha audio letters in milliseconds
  620. *
  621. * @since 3.0.3
  622. * @var float
  623. */
  624. public $audio_gap_max = 3000;
  625. /**
  626. * Captcha ID if using static captcha
  627. * @var string Unique captcha id
  628. */
  629. protected static $_captchaId = null;
  630. /**
  631. * The GD image resource of the captcha image
  632. *
  633. * @var resource
  634. */
  635. protected $im;
  636. /**
  637. * A temporary GD image resource of the captcha image for distortion
  638. *
  639. * @var resource
  640. */
  641. protected $tmpimg;
  642. /**
  643. * The background image GD resource
  644. * @var resource
  645. */
  646. protected $bgimg;
  647. /**
  648. * Scale factor for magnification of distorted captcha image
  649. *
  650. * @var int
  651. */
  652. protected $iscale = 5;
  653. /**
  654. * Absolute path to securimage directory.
  655. *
  656. * This is calculated at runtime
  657. *
  658. * @var string
  659. */
  660. public $securimage_path = null;
  661. /**
  662. * The captcha challenge value.
  663. *
  664. * Either the case-sensitive/insensitive word captcha, or the solution to
  665. * the math captcha.
  666. *
  667. * @var string Captcha challenge value
  668. */
  669. protected $code;
  670. /**
  671. * The display value of the captcha to draw on the image
  672. *
  673. * Either the word captcha or the math equation to present to the user
  674. *
  675. * @var string Captcha display value to draw on the image
  676. */
  677. protected $code_display;
  678. /**
  679. * Alternate text to draw as the captcha image text
  680. *
  681. * A value that can be passed to the constructor that can be used to
  682. * generate a captcha image with a given value.
  683. *
  684. * This value does not get stored in the session or database and is only
  685. * used when calling Securimage::show().
  686. *
  687. * If a display_value was passed to the constructor and the captcha image
  688. * is generated, the display_value will be used as the string to draw on
  689. * the captcha image.
  690. *
  691. * Used only if captcha codes are generated and managed by a 3rd party
  692. * app/library
  693. *
  694. * @var string Captcha code value to display on the image
  695. */
  696. public $display_value;
  697. /**
  698. * Captcha code supplied by user [set from Securimage::check()]
  699. *
  700. * @var string
  701. */
  702. protected $captcha_code;
  703. /**
  704. * Time (in seconds) that the captcha was solved in (correctly or incorrectly).
  705. *
  706. * This is from the time of code creation, to when validation was attempted.
  707. *
  708. * @var int
  709. */
  710. protected $_timeToSolve = 0;
  711. /**
  712. * Flag that can be specified telling securimage not to call exit after
  713. * generating a captcha image or audio file
  714. *
  715. * @var bool If true, script will not terminate; if false script will terminate (default)
  716. */
  717. protected $no_exit;
  718. /**
  719. * Flag indicating whether or not a PHP session should be started and used
  720. *
  721. * @var bool If true, no session will be started; if false, session will be started and used to store data (default)
  722. */
  723. protected $no_session;
  724. /**
  725. * Flag indicating whether or not HTTP headers will be sent when outputting
  726. * captcha image/audio
  727. *
  728. * @var bool If true (default) headers will be sent, if false, no headers are sent
  729. */
  730. protected $send_headers;
  731. /**
  732. * PDO connection when a database is used
  733. *
  734. * @var resource
  735. */
  736. protected $pdo_conn;
  737. /**
  738. * The GD color resource for the background color
  739. *
  740. * @var resource
  741. */
  742. protected $gdbgcolor;
  743. /**
  744. * The GD color resource for the text color
  745. *
  746. * @var resource
  747. */
  748. protected $gdtextcolor;
  749. /**
  750. * The GD color resource for the line color
  751. *
  752. * @var resource
  753. */
  754. protected $gdlinecolor;
  755. /**
  756. * The GD color resource for the signature text color
  757. *
  758. * @var resource
  759. */
  760. protected $gdsignaturecolor;
  761. /**
  762. * Create a new securimage object, pass options to set in the constructor.
  763. *
  764. * The object can then be used to display a captcha, play an audible captcha, or validate a submission.
  765. *
  766. * @param array $options Options to initialize the class. May be any class property.
  767. *
  768. * $options = array(
  769. * 'text_color' => new Securimage_Color('#013020'),
  770. * 'code_length' => 5,
  771. * 'num_lines' => 5,
  772. * 'noise_level' => 3,
  773. * 'font_file' => Securimage::getPath() . '/custom.ttf'
  774. * );
  775. *
  776. * $img = new Securimage($options);
  777. *
  778. */
  779. public function __construct($options = array())
  780. {
  781. $this->securimage_path = dirname(__FILE__);
  782. if (is_array($options) && sizeof($options) > 0) {
  783. foreach($options as $prop => $val) {
  784. if ($prop == 'captchaId') {
  785. Securimage::$_captchaId = $val;
  786. $this->use_database = true;
  787. } else if ($prop == 'use_sqlite_db') {
  788. trigger_error("The use_sqlite_db option is deprecated, use 'use_database' instead", E_USER_NOTICE);
  789. } else {
  790. $this->$prop = $val;
  791. }
  792. }
  793. }
  794. $this->image_bg_color = $this->initColor($this->image_bg_color, '#ffffff');
  795. $this->text_color = $this->initColor($this->text_color, '#616161');
  796. $this->line_color = $this->initColor($this->line_color, '#616161');
  797. $this->noise_color = $this->initColor($this->noise_color, '#616161');
  798. $this->signature_color = $this->initColor($this->signature_color, '#616161');
  799. if (is_null($this->ttf_file)) {
  800. $this->ttf_file = $this->securimage_path . '/AHGBold.ttf';
  801. }
  802. $this->signature_font = $this->ttf_file;
  803. if (is_null($this->wordlist_file)) {
  804. $this->wordlist_file = $this->securimage_path . '/words/words.txt';
  805. }
  806. if (is_null($this->database_file)) {
  807. $this->database_file = $this->securimage_path . '/database/securimage.sq3';
  808. }
  809. if (is_null($this->audio_path)) {
  810. $this->audio_path = $this->securimage_path . '/audio/en/';
  811. }
  812. if (is_null($this->audio_noise_path)) {
  813. $this->audio_noise_path = $this->securimage_path . '/audio/noise/';
  814. }
  815. if (is_null($this->audio_use_noise)) {
  816. $this->audio_use_noise = true;
  817. }
  818. if (is_null($this->degrade_audio)) {
  819. $this->degrade_audio = true;
  820. }
  821. if (is_null($this->code_length) || (int)$this->code_length < 1) {
  822. $this->code_length = 6;
  823. }
  824. if (is_null($this->perturbation) || !is_numeric($this->perturbation)) {
  825. $this->perturbation = 0.75;
  826. }
  827. if (is_null($this->namespace) || !is_string($this->namespace)) {
  828. $this->namespace = 'default';
  829. }
  830. if (is_null($this->no_exit)) {
  831. $this->no_exit = false;
  832. }
  833. if (is_null($this->no_session)) {
  834. $this->no_session = false;
  835. }
  836. if (is_null($this->send_headers)) {
  837. $this->send_headers = true;
  838. }
  839. if ($this->no_session != true) {
  840. // Initialize session or attach to existing
  841. if ( session_id() == '' || (function_exists('session_status') && PHP_SESSION_NONE == session_status()) ) { // no session has been started yet (or it was previousy closed), which is needed for validation
  842. if (!is_null($this->session_name) && trim($this->session_name) != '') {
  843. session_name(trim($this->session_name)); // set session name if provided
  844. }
  845. session_start();
  846. }
  847. }
  848. }
  849. /**
  850. * Return the absolute path to the Securimage directory.
  851. *
  852. * @return string The path to the securimage base directory
  853. */
  854. public static function getPath()
  855. {
  856. return dirname(__FILE__);
  857. }
  858. /**
  859. * Generate a new captcha ID or retrieve the current ID (if exists).
  860. *
  861. * @param bool $new If true, generates a new challenge and returns and ID. If false, the existing captcha ID is returned, or null if none exists.
  862. * @param array $options Additional options to be passed to Securimage.
  863. * $options must include database settings if they are not set directly in securimage.php
  864. *
  865. * @return null|string Returns null if no captcha id set and new was false, or the captcha ID
  866. */
  867. public static function getCaptchaId($new = true, array $options = array())
  868. {
  869. if (is_null($new) || (bool)$new == true) {
  870. $id = sha1(uniqid($_SERVER['REMOTE_ADDR'], true));
  871. $opts = array('no_session' => true,
  872. 'use_database' => true);
  873. if (sizeof($options) > 0) $opts = array_merge($options, $opts);
  874. $si = new self($opts);
  875. Securimage::$_captchaId = $id;
  876. $si->createCode();
  877. return $id;
  878. } else {
  879. return Securimage::$_captchaId;
  880. }
  881. }
  882. /**
  883. * Validate a captcha code input against a captcha ID
  884. *
  885. * @param string $id The captcha ID to check
  886. * @param string $value The captcha value supplied by the user
  887. * @param array $options Array of options to construct Securimage with.
  888. * Options must include database options if they are not set in securimage.php
  889. *
  890. * @see Securimage::$database_driver
  891. * @return bool true if the code was valid for the given captcha ID, false if not or if database failed to open
  892. */
  893. public static function checkByCaptchaId($id, $value, array $options = array())
  894. {
  895. $opts = array('captchaId' => $id,
  896. 'no_session' => true,
  897. 'use_database' => true);
  898. if (sizeof($options) > 0) $opts = array_merge($options, $opts);
  899. $si = new self($opts);
  900. if ($si->openDatabase()) {
  901. $code = $si->getCodeFromDatabase();
  902. if (is_array($code)) {
  903. $si->code = $code['code'];
  904. $si->code_display = $code['code_disp'];
  905. }
  906. if ($si->check($value)) {
  907. $si->clearCodeFromDatabase();
  908. return true;
  909. } else {
  910. return false;
  911. }
  912. } else {
  913. return false;
  914. }
  915. }
  916. /**
  917. * Generates a new challenge and serves a captcha image.
  918. *
  919. * Appropriate headers will be sent to the browser unless the *send_headers* option is false.
  920. *
  921. * @param string $background_image The absolute or relative path to the background image to use as the background of the captcha image.
  922. *
  923. * $img = new Securimage();
  924. * $img->code_length = 6;
  925. * $img->num_lines = 5;
  926. * $img->noise_level = 5;
  927. *
  928. * $img->show(); // sends the image and appropriate headers to browser
  929. * exit;
  930. */
  931. public function show($background_image = '')
  932. {
  933. set_error_handler(array(&$this, 'errorHandler'));
  934. if($background_image != '' && is_readable($background_image)) {
  935. $this->bgimg = $background_image;
  936. }
  937. $this->doImage();
  938. }
  939. /**
  940. * Checks a given code against the correct value from the session and/or database.
  941. *
  942. * @param string $code The captcha code to check
  943. *
  944. * $code = $_POST['code'];
  945. * $img = new Securimage();
  946. * if ($img->check($code) == true) {
  947. * $captcha_valid = true;
  948. * } else {
  949. * $captcha_valid = false;
  950. * }
  951. *
  952. * @return bool true if the given code was correct, false if not.
  953. */
  954. public function check($code)
  955. {
  956. $this->code_entered = $code;
  957. $this->validate();
  958. return $this->correct_code;
  959. }
  960. /**
  961. * Returns HTML code for displaying the captcha image, audio button, and form text input.
  962. *
  963. * Options can be specified to modify the output of the HTML. Accepted options:
  964. *
  965. * 'securimage_path':
  966. * Optional: The URI to where securimage is installed (e.g. /securimage)
  967. * 'image_id':
  968. * A string that sets the "id" attribute of the captcha image (default: captcha_image)
  969. * 'image_alt_text':
  970. * The alt text of the captcha image (default: CAPTCHA Image)
  971. * 'show_audio_button':
  972. * true/false Whether or not to show the audio button (default: true)
  973. * 'show_refresh_button':
  974. * true/false Whether or not to show a button to refresh the image (default: true)
  975. * 'show_text_input':
  976. * true/false Whether or not to show the text input for the captcha (default: true)
  977. * 'refresh_alt_text':
  978. * Alt text for the refresh image (default: Refresh Image)
  979. * 'refresh_title_text':
  980. * Title text for the refresh image link (default: Refresh Image)
  981. * 'input_id':
  982. * A string that sets the "id" attribute of the captcha text input (default: captcha_code)
  983. * 'input_name':
  984. * A string that sets the "name" attribute of the captcha text input (default: same as input_id)
  985. * 'input_text':
  986. * A string that sets the text of the label for the captcha text input (default: Type the text:)
  987. * 'input_attributes':
  988. * An array of additional HTML tag attributes to pass to the text input tag (default: empty)
  989. * 'image_attributes':
  990. * An array of additional HTML tag attributes to pass to the captcha image tag (default: empty)
  991. * 'error_html':
  992. * Optional HTML markup to be shown above the text input field
  993. * 'namespace':
  994. * The optional captcha namespace to use for showing the image and playing back the audio. Namespaces are for using multiple captchas on the same page.
  995. *
  996. * @param array $options Array of options for modifying the HTML code.
  997. *
  998. * @return string The generated HTML code for displaying the captcha
  999. */
  1000. public static function getCaptchaHtml($options = array())
  1001. {
  1002. if (!isset($options['securimage_path'])) {
  1003. $docroot = (isset($_SERVER['DOCUMENT_ROOT'])) ? $_SERVER['DOCUMENT_ROOT'] : substr($_SERVER['SCRIPT_FILENAME'], 0, -strlen($_SERVER['SCRIPT_NAME']));
  1004. $docroot = realpath($docroot);
  1005. $sipath = dirname(__FILE__);
  1006. $securimage_path = str_replace($docroot, '', $sipath);
  1007. } else {
  1008. $securimage_path = $options['securimage_path'];
  1009. }
  1010. $image_id = (isset($options['image_id'])) ? $options['image_id'] : 'captcha_image';
  1011. $image_alt = (isset($options['image_alt_text'])) ? $options['image_alt_text'] : 'CAPTCHA Image';
  1012. $show_audio_btn = (isset($options['show_audio_button'])) ? (bool)$options['show_audio_button'] : true;
  1013. $show_refresh_btn = (isset($options['show_refresh_button'])) ? (bool)$options['show_refresh_button'] : true;
  1014. $audio_but_bg_col = (isset($options['audio_button_bgcol'])) ? $options['audio_button_bgcol'] : '#ffffff';
  1015. $audio_icon_url = (isset($options['audio_icon_url'])) ? $options['audio_icon_url'] : null;
  1016. $audio_play_url = (isset($options['audio_play_url'])) ? $options['audio_play_url'] : null;
  1017. $audio_swf_url = (isset($options['audio_swf_url'])) ? $options['audio_swf_url'] : null;
  1018. $show_input = (isset($options['show_text_input'])) ? (bool)$options['show_text_input'] : true;
  1019. $refresh_alt = (isset($options['refresh_alt_text'])) ? $options['refresh_alt_text'] : 'Refresh Image';
  1020. $refresh_title = (isset($options['refresh_title_text'])) ? $options['refresh_title_text'] : 'Refresh Image';
  1021. $input_text = (isset($options['input_text'])) ? $options['input_text'] : 'Type the text:';
  1022. $input_id = (isset($options['input_id'])) ? $options['input_id'] : 'captcha_code';
  1023. $input_name = (isset($options['input_name'])) ? $options['input_name'] : $input_id;
  1024. $input_attrs = (isset($options['input_attributes'])) ? $options['input_attributes'] : array();
  1025. $image_attrs = (isset($options['image_attributes'])) ? $options['image_attributes'] : array();
  1026. $error_html = (isset($options['error_html'])) ? $options['error_html'] : null;
  1027. $namespace = (isset($options['namespace'])) ? $options['namespace'] : '';
  1028. $rand = md5(uniqid($_SERVER['REMOTE_PORT'], true));
  1029. $securimage_path = rtrim($securimage_path, '/\\');
  1030. $image_attr = '';
  1031. if (!is_array($image_attrs)) $image_attrs = array();
  1032. if (!isset($image_attrs['align'])) $image_attrs['align'] = 'left';
  1033. $image_attrs['id'] = $image_id;
  1034. $show_path = $securimage_path . '/securimage_show.php?';
  1035. if (!empty($namespace)) {
  1036. $show_path .= sprintf('namespace=%s&', $namespace);
  1037. }
  1038. $image_attrs['src'] = $show_path . $rand;
  1039. $image_attrs['alt'] = $image_alt;
  1040. foreach($image_attrs as $name => $val) {
  1041. $image_attr .= sprintf('%s="%s" ', $name, htmlspecialchars($val));
  1042. }
  1043. $html = sprintf('<img %s/>', $image_attr);
  1044. if ($show_audio_btn) {
  1045. $swf_path = $securimage_path . '/securimage_play.swf';
  1046. $play_path = $securimage_path . '/securimage_play.php';
  1047. $icon_path = $securimage_path . '/images/audio_icon.png';
  1048. if (!empty($audio_icon_url)) {
  1049. $icon_path = $audio_icon_url;
  1050. }
  1051. if (!empty($audio_play_url)) {
  1052. $play_path = $audio_play_url;
  1053. }
  1054. if (!empty($audio_swf_url)) {
  1055. $swf_path = $audio_swf_url;
  1056. }
  1057. $html .= sprintf('<object type="application/x-shockwave-flash" data="%s?bgcol=%s&amp;icon_file=%s&amp;audio_file=%s" height="32" width="32">',
  1058. htmlspecialchars($swf_path),
  1059. urlencode($audio_but_bg_col),
  1060. urlencode($icon_path),
  1061. urlencode($play_path)
  1062. );
  1063. $html .= sprintf('<param name="movie" value="%s?bgcol=%s&amp;icon_file=%s&amp;audio_file=%s" />',
  1064. htmlspecialchars($swf_path),
  1065. urlencode($audio_but_bg_col),
  1066. urlencode($icon_path),
  1067. urlencode($play_path)
  1068. );
  1069. $html .= '</object><br />';
  1070. }
  1071. if ($show_refresh_btn) {
  1072. $icon_path = $securimage_path . '/images/refresh.png';
  1073. $img_tag = sprintf('<img height="32" width="32" src="%s" alt="%s" onclick="this.blur()" align="bottom" border="0" />',
  1074. htmlspecialchars($icon_path), htmlspecialchars($refresh_alt));
  1075. $html .= sprintf('<a tabindex="-1" style="border: 0" href="#" title="%s" onclick="document.getElementById(\'%s\').src = \'%s\' + Math.random(); this.blur(); return false">%s</a><br />',
  1076. htmlspecialchars($refresh_title),
  1077. $image_id,
  1078. $show_path,
  1079. $img_tag
  1080. );
  1081. }
  1082. $html .= '<div style="clear: both"></div>';
  1083. $html .= sprintf('<label for="%s">%s</label> ',
  1084. htmlspecialchars($input_id),
  1085. htmlspecialchars($input_text));
  1086. if (!empty($error_html)) {
  1087. $html .= $error_html;
  1088. }
  1089. $input_attr = '';
  1090. if (!is_array($input_attrs)) $input_attrs = array();
  1091. $input_attrs['type'] = 'text';
  1092. $input_attrs['name'] = $input_name;
  1093. $input_attrs['id'] = $input_id;
  1094. foreach($input_attrs as $name => $val) {
  1095. $input_attr .= sprintf('%s="%s" ', $name, htmlspecialchars($val));
  1096. }
  1097. $html .= sprintf('<input %s/>', $input_attr);
  1098. return $html;
  1099. }
  1100. /**
  1101. * Get the time in seconds that it took to solve the captcha.
  1102. *
  1103. * @return int The time in seconds from when the code was created, to when it was solved
  1104. */
  1105. public function getTimeToSolve()
  1106. {
  1107. return $this->_timeToSolve;
  1108. }
  1109. /**
  1110. * Set the namespace for the captcha being stored in the session or database.
  1111. *
  1112. * Namespaces are useful when multiple captchas need to be displayed on a single page.
  1113. *
  1114. * @param string $namespace Namespace value, String consisting of characters "a-zA-Z0-9_-"
  1115. */
  1116. public function setNamespace($namespace)
  1117. {
  1118. $namespace = preg_replace('/[^a-z0-9-_]/i', '', $namespace);
  1119. $namespace = substr($namespace, 0, 64);
  1120. if (!empty($namespace)) {
  1121. $this->namespace = $namespace;
  1122. } else {
  1123. $this->namespace = 'default';
  1124. }
  1125. }
  1126. /**
  1127. * Generate an audible captcha in WAV format and send it to the browser with appropriate headers.
  1128. * Example:
  1129. *
  1130. * $img = new Securimage();
  1131. * $img->outputAudioFile(); // outputs a wav file to the browser
  1132. * exit;
  1133. *
  1134. */
  1135. public function outputAudioFile()
  1136. {
  1137. set_error_handler(array(&$this, 'errorHandler'));
  1138. require_once dirname(__FILE__) . '/WavFile.php';
  1139. try {
  1140. $audio = $this->getAudibleCode();
  1141. } catch (Exception $ex) {
  1142. if (($fp = @fopen(dirname(__FILE__) . '/si.error_log', 'a+')) !== false) {
  1143. fwrite($fp, date('Y-m-d H:i:s') . ': Securimage audio error "' . $ex->getMessage() . '"' . "\n");
  1144. fclose($fp);
  1145. }
  1146. $audio = $this->audioError();
  1147. }
  1148. if ($this->canSendHeaders() || $this->send_headers == false) {
  1149. if ($this->send_headers) {
  1150. $uniq = md5(uniqid(microtime()));
  1151. header("Content-Disposition: attachment; filename=\"securimage_audio-{$uniq}.wav\"");
  1152. header('Cache-Control: no-store, no-cache, must-revalidate');
  1153. header('Expires: Sun, 1 Jan 2000 12:00:00 GMT');
  1154. header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
  1155. header('Content-type: audio/x-wav');
  1156. if (extension_loaded('zlib')) {
  1157. ini_set('zlib.output_compression', true); // compress output if supported by browser
  1158. } else {
  1159. header('Content-Length: ' . strlen($audio));
  1160. }
  1161. }
  1162. echo $audio;
  1163. } else {
  1164. echo '<hr /><strong>'
  1165. .'Failed to generate audio file, content has already been '
  1166. .'output.<br />This is most likely due to misconfiguration or '
  1167. .'a PHP error was sent to the browser.</strong>';
  1168. }
  1169. restore_error_handler();
  1170. if (!$this->no_exit) exit;
  1171. }
  1172. /**
  1173. * Return the code from the session or database (if configured). If none exists or was found, an empty string is returned.
  1174. *
  1175. * @param bool $array true to receive an array containing the code and properties, false to receive just the code.
  1176. * @param bool $returnExisting If true, and the class property *code* is set, it will be returned instead of getting the code from the session or database.
  1177. * @return array|string Return is an array if $array = true, otherwise a string containing the code
  1178. */
  1179. public function getCode($array = false, $returnExisting = false)
  1180. {
  1181. $code = array();
  1182. $time = 0;
  1183. $disp = 'error';
  1184. if ($returnExisting && strlen($this->code) > 0) {
  1185. if ($array) {
  1186. return array(
  1187. 'code' => $this->code,
  1188. 'display' => $this->code_display,
  1189. 'code_display' => $this->code_display,
  1190. 'time' => 0);
  1191. } else {
  1192. return $this->code;
  1193. }
  1194. }
  1195. if ($this->no_session != true) {
  1196. if (isset($_SESSION['securimage_code_value'][$this->namespace]) &&
  1197. trim($_SESSION['securimage_code_value'][$this->namespace]) != '') {
  1198. if ($this->isCodeExpired(
  1199. $_SESSION['securimage_code_ctime'][$this->namespace]) == false) {
  1200. $code['code'] = $_SESSION['securimage_code_value'][$this->namespace];
  1201. $code['time'] = $_SESSION['securimage_code_ctime'][$this->namespace];
  1202. $code['display'] = $_SESSION['securimage_code_disp'] [$this->namespace];
  1203. }
  1204. }
  1205. }
  1206. if (empty($code) && $this->use_database) {
  1207. // no code in session - may mean user has cookies turned off
  1208. $this->openDatabase();
  1209. $code = $this->getCodeFromDatabase();
  1210. if (!empty($code)) {
  1211. $code['display'] = $code['code_disp'];
  1212. unset($code['code_disp']);
  1213. }
  1214. } else { /* no code stored in session or sqlite database, validation will fail */ }
  1215. if ($array == true) {
  1216. return $code;
  1217. } else {
  1218. return $code['code'];
  1219. }
  1220. }
  1221. /**
  1222. * The main image drawing routing, responsible for constructing the entire image and serving it
  1223. */
  1224. protected function doImage()
  1225. {
  1226. if( ($this->use_transparent_text == true || $this->bgimg != '') && function_exists('imagecreatetruecolor')) {
  1227. $imagecreate = 'imagecreatetruecolor';
  1228. } else {
  1229. $imagecreate = 'imagecreate';
  1230. }
  1231. $this->im = $imagecreate($this->image_width, $this->image_height);
  1232. $this->tmpimg = $imagecreate($this->image_width * $this->iscale, $this->image_height * $this->iscale);
  1233. $this->allocateColors();
  1234. imagepalettecopy($this->tmpimg, $this->im);
  1235. $this->setBackground();
  1236. $code = '';
  1237. if ($this->getCaptchaId(false) !== null) {
  1238. // a captcha Id was supplied
  1239. // check to see if a display_value for the captcha image was set
  1240. if (is_string($this->display_value) && strlen($this->display_value) > 0) {
  1241. $this->code_display = $this->display_value;
  1242. $this->code = ($this->case_sensitive) ?
  1243. $this->display_value :
  1244. strtolower($this->display_value);
  1245. $code = $this->code;
  1246. } else if ($this->openDatabase()) {
  1247. // no display_value, check the database for existing captchaId
  1248. $code = $this->getCodeFromDatabase();
  1249. // got back a result from the database with a valid code for captchaId
  1250. if (is_array($code)) {
  1251. $this->code = $code['code'];
  1252. $this->code_display = $code['code_disp'];
  1253. $code = $code['code'];
  1254. }
  1255. }
  1256. }
  1257. if ($code == '') {
  1258. // if the code was not set using display_value or was not found in
  1259. // the database, create a new code
  1260. $this->createCode();
  1261. }
  1262. if ($this->noise_level > 0) {
  1263. $this->drawNoise();
  1264. }
  1265. $this->drawWord();
  1266. if ($this->perturbation > 0 && is_readable($this->ttf_file)) {
  1267. $this->distortedCopy();
  1268. }
  1269. if ($this->num_lines > 0) {
  1270. $this->drawLines();
  1271. }
  1272. if (trim($this->image_signature) != '') {
  1273. $this->addSignature();
  1274. }
  1275. $this->output();
  1276. }
  1277. /**
  1278. * Allocate the colors to be used for the image
  1279. */
  1280. protected function allocateColors()
  1281. {
  1282. // allocate bg color first for imagecreate
  1283. $this->gdbgcolor = imagecolorallocate($this->im,
  1284. $this->image_bg_color->r,
  1285. $this->image_bg_color->g,
  1286. $this->image_bg_color->b);
  1287. $alpha = intval($this->text_transparency_percentage / 100 * 127);
  1288. if ($this->use_transparent_text == true) {
  1289. $this->gdtextcolor = imagecolorallocatealpha($this->im,
  1290. $this->text_color->r,
  1291. $this->text_color->g,
  1292. $this->text_color->b,
  1293. $alpha);
  1294. $this->gdlinecolor = imagecolorallocatealpha($this->im,
  1295. $this->line_color->r,
  1296. $this->line_color->g,
  1297. $this->line_color->b,
  1298. $alpha);
  1299. $this->gdnoisecolor = imagecolorallocatealpha($this->im,
  1300. $this->noise_color->r,
  1301. $this->noise_color->g,
  1302. $this->noise_color->b,
  1303. $alpha);
  1304. } else {
  1305. $this->gdtextcolor = imagecolorallocate($this->im,
  1306. $this->text_color->r,
  1307. $this->text_color->g,
  1308. $this->text_color->b);
  1309. $this->gdlinecolor = imagecolorallocate($this->im,
  1310. $this->line_color->r,
  1311. $this->line_color->g,
  1312. $this->line_color->b);
  1313. $this->gdnoisecolor = imagecolorallocate($this->im,
  1314. $this->noise_color->r,
  1315. $this->noise_color->g,
  1316. $this->noise_color->b);
  1317. }
  1318. $this->gdsignaturecolor = imagecolorallocate($this->im,
  1319. $this->signature_color->r,
  1320. $this->signature_color->g,
  1321. $this->signature_color->b);
  1322. }
  1323. /**
  1324. * The the background color, or background image to be used
  1325. */
  1326. protected function setBackground()
  1327. {
  1328. // set background color of image by drawing a rectangle since imagecreatetruecolor doesn't set a bg color
  1329. imagefilledrectangle($this->im, 0, 0,
  1330. $this->image_width, $this->image_height,
  1331. $this->gdbgcolor);
  1332. imagefilledrectangle($this->tmpimg, 0, 0,
  1333. $this->image_width * $this->iscale, $this->image_height * $this->iscale,
  1334. $this->gdbgcolor);
  1335. if ($this->bgimg == '') {
  1336. if ($this->background_directory != null &&
  1337. is_dir($this->background_directory) &&
  1338. is_readable($this->background_directory))
  1339. {
  1340. $img = $this->getBackgroundFromDirectory();
  1341. if ($img != false) {
  1342. $this->bgimg = $img;
  1343. }
  1344. }
  1345. }
  1346. if ($this->bgimg == '') {
  1347. return;
  1348. }
  1349. $dat = @getimagesize($this->bgimg);
  1350. if($dat == false) {
  1351. return;
  1352. }
  1353. switch($dat[2]) {
  1354. case 1: $newim = @imagecreatefromgif($this->bgimg); break;
  1355. case 2: $newim = @imagecreatefromjpeg($this->bgimg); break;
  1356. case 3: $newim = @imagecreatefrompng($this->bgimg); break;
  1357. default: return;
  1358. }
  1359. if(!$newim) return;
  1360. imagecopyresized($this->im, $newim, 0, 0, 0, 0,
  1361. $this->image_width, $this->image_height,
  1362. imagesx($newim), imagesy($newim));
  1363. }
  1364. /**
  1365. * Scan the directory for a background image to use
  1366. */
  1367. protected function getBackgroundFromDirectory()
  1368. {
  1369. $images = array();
  1370. if ( ($dh = opendir($this->background_directory)) !== false) {
  1371. while (($file = readdir($dh)) !== false) {
  1372. if (preg_match('/(jpg|gif|png)$/i', $file)) $images[] = $file;
  1373. }
  1374. closedir($dh);
  1375. if (sizeof($images) > 0) {
  1376. return rtrim($this->background_directory, '/') . '/' . $images[mt_rand(0, sizeof($images)-1)];
  1377. }
  1378. }
  1379. return false;
  1380. }
  1381. /**
  1382. * This method generates a new captcha code.
  1383. *
  1384. * Generates a random captcha code based on *charset*, math problem, or captcha from the wordlist and saves the value to the session and/or database.
  1385. */
  1386. public function createCode()
  1387. {
  1388. $this->code = false;
  1389. switch($this->captcha_type) {
  1390. case self::SI_CAPTCHA_MATHEMATIC:
  1391. {
  1392. do {
  1393. $signs = array('+', '-', 'x');
  1394. $left = mt_rand(1, 10);
  1395. $right = mt_rand(1, 5);
  1396. $sign = $signs[mt_rand(0, 2)];
  1397. switch($sign) {
  1398. case 'x': $c = $left * $right; break;
  1399. case '-': $c = $left - $right; break;
  1400. default: $c = $left + $right; break;
  1401. }
  1402. } while ($c <= 0); // no negative #'s or 0
  1403. $this->code = $c;
  1404. $this->code_display = "$left $sign $right";
  1405. break;
  1406. }
  1407. case self::SI_CAPTCHA_WORDS:
  1408. $words = $this->readCodeFromFile(2);
  1409. $this->code = implode(' ', $words);
  1410. $this->code_display = $this->code;
  1411. break;
  1412. default:
  1413. {
  1414. if ($this->use_wordlist && is_readable($this->wordlist_file)) {
  1415. $this->code = $this->readCodeFromFile();
  1416. }
  1417. if ($this->code == false) {
  1418. $this->code = $this->generateCode($this->code_length);
  1419. }
  1420. $this->code_display = $this->code;
  1421. $this->code = ($this->case_sensitive) ? $this->code : strtolower($this->code);
  1422. } // default
  1423. }
  1424. $this->saveData();
  1425. }
  1426. /**
  1427. * Draws the captcha code on the image
  1428. */
  1429. protected function drawWord()
  1430. {
  1431. $width2 = $this->image_width * $this->iscale;
  1432. $height2 = $this->image_height * $this->iscale;
  1433. $ratio = ($this->font_ratio) ? $this->font_ratio : 0.4;
  1434. if ((float)$ratio < 0.1 || (float)$ratio >= 1) {
  1435. $ratio = 0.4;
  1436. }
  1437. if (!is_readable($this->ttf_file)) {
  1438. imagestring($this->im, 4, 10, ($this->image_height / 2) - 5, 'Failed to load TTF font file!', $this->gdtextcolor);
  1439. } else {
  1440. if ($this->perturbation > 0) {
  1441. $font_size = $height2 * $ratio;
  1442. $bb = imageftbbox($font_size, 0, $this->ttf_file, $this->code_display);
  1443. $tx = $bb[4] - $bb[0];
  1444. $ty = $bb[5] - $bb[1];
  1445. $x = floor($width2 / 2 - $tx / 2 - $bb[0]);
  1446. $y = round($height2 / 2 - $ty / 2 - $bb[1]);
  1447. imagettftext($this->tmpimg, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
  1448. } else {
  1449. $font_size = $this->image_height * $ratio;
  1450. $bb = imageftbbox($font_size, 0, $this->ttf_file, $this->code_display);
  1451. $tx = $bb[4] - $bb[0];
  1452. $ty = $bb[5] - $bb[1];
  1453. $x = floor($this->image_width / 2 - $tx / 2 - $bb[0]);
  1454. $y = round($this->image_height / 2 - $ty / 2 - $bb[1]);
  1455. imagettftext($this->im, $font_size, 0, $x, $y, $this->gdtextcolor, $this->ttf_file, $this->code_display);
  1456. }
  1457. }
  1458. // DEBUG
  1459. //$this->im = $this->tmpimg;
  1460. //$this->output();
  1461. }
  1462. /**
  1463. * Copies the captcha image to the final image with distortion applied
  1464. */
  1465. protected function distortedCopy()
  1466. {
  1467. $numpoles = 3; // distortion factor
  1468. // make array of poles AKA attractor points
  1469. for ($i = 0; $i < $numpoles; ++ $i) {
  1470. $px[$i] = mt_rand($this->image_width * 0.2, $this->image_width * 0.8);
  1471. $py[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8);
  1472. $rad[$i] = mt_rand($this->image_height * 0.2, $this->image_height * 0.8);
  1473. $tmp = ((- $this->frand()) * 0.15) - .15;
  1474. $amp[$i] = $this->perturbation * $tmp;
  1475. }
  1476. $bgCol = imagecolorat($this->tmpimg, 0, 0);
  1477. $width2 = $this->iscale * $this->image_width;
  1478. $height2 = $this->iscale * $this->image_height;
  1479. imagepalettecopy($this->im, $this->tmpimg); // copy palette to final image so text colors come across
  1480. // loop over $img pixels, take pixels from $tmpimg with distortion field
  1481. for ($ix = 0; $ix < $this->image_width; ++ $ix) {
  1482. for ($iy = 0; $iy < $this->image_height; ++ $iy) {
  1483. $x = $ix;
  1484. $y = $iy;
  1485. for ($i = 0; $i < $numpoles; ++ $i) {
  1486. $dx = $ix - $px[$i];
  1487. $dy = $iy - $py[$i];
  1488. if ($dx == 0 && $dy == 0) {
  1489. continue;
  1490. }
  1491. $r = sqrt($dx * $dx + $dy * $dy);
  1492. if ($r > $rad[$i]) {
  1493. continue;
  1494. }
  1495. $rscale = $amp[$i] * sin(3.14 * $r / $rad[$i]);
  1496. $x += $dx * $rscale;
  1497. $y += $dy * $rscale;
  1498. }
  1499. $c = $bgCol;
  1500. $x *= $this->iscale;
  1501. $y *= $this->iscale;
  1502. if ($x >= 0 && $x < $width2 && $y >= 0 && $y < $height2) {
  1503. $c = imagecolorat($this->tmpimg, $x, $y);
  1504. }
  1505. if ($c != $bgCol) { // only copy pixels of letters to preserve any background image
  1506. imagesetpixel($this->im, $ix, $iy, $c);
  1507. }
  1508. }
  1509. }
  1510. }
  1511. /**
  1512. * Draws distorted lines on the image
  1513. */
  1514. protected function drawLines()
  1515. {
  1516. for ($line = 0; $line < $this->num_lines; ++ $line) {
  1517. $x = $this->image_width * (1 + $line) / ($this->num_lines + 1);
  1518. $x += (0.5 - $this->frand()) * $this->image_width / $this->num_lines;
  1519. $y = mt_rand($this->image_height * 0.1, $this->image_height * 0.9);
  1520. $theta = ($this->frand() - 0.5) * M_PI * 0.7;
  1521. $w = $this->image_width;
  1522. $len = mt_rand($w * 0.4, $w * 0.7);
  1523. $lwid = mt_rand(0, 2);
  1524. $k = $this->frand() * 0.6 + 0.2;
  1525. $k = $k * $k * 0.5;
  1526. $phi = $this->frand() * 6.28;
  1527. $step = 0.5;
  1528. $dx = $step * cos($theta);
  1529. $dy = $step * sin($theta);
  1530. $n = $len / $step;
  1531. $amp = 1.5 * $this->frand() / ($k + 5.0 / $len);
  1532. $x0 = $x - 0.5 * $len * cos($theta);
  1533. $y0 = $y - 0.5 * $len * sin($theta);
  1534. $ldx = round(- $dy * $lwid);
  1535. $ldy = round($dx * $lwid);
  1536. for ($i = 0; $i < $n; ++ $i) {
  1537. $x = $x0 + $i * $dx + $amp * $dy * sin($k * $i * $step + $phi);
  1538. $y = $y0 + $i * $dy - $amp * $dx * sin($k * $i * $step + $phi);
  1539. imagefilledrectangle($this->im, $x, $y, $x + $lwid, $y + $lwid, $this->gdlinecolor);
  1540. }
  1541. }
  1542. }
  1543. /**
  1544. * Draws random noise on the image
  1545. */
  1546. protected function drawNoise()
  1547. {
  1548. if ($this->noise_level > 10) {
  1549. $noise_level = 10;
  1550. } else {
  1551. $noise_level = $this->noise_level;
  1552. }
  1553. $t0 = microtime(true);
  1554. $noise_level *= 125; // an arbitrary number that works well on a 1-10 scale
  1555. $points = $this->image_width * $this->image_height * $this->iscale;
  1556. $height = $this->image_height * $this->iscale;
  1557. $width = $this->image_width * $this->iscale;
  1558. for ($i = 0; $i < $noise_level; ++$i) {
  1559. $x = mt_rand(10, $width);
  1560. $y = mt_rand(10, $height);
  1561. $size = mt_rand(7, 10);
  1562. if ($x - $size <= 0 && $y - $size <= 0) continue; // dont cover 0,0 since it is used by imagedistortedcopy
  1563. imagefilledarc($this->tmpimg, $x, $y, $size, $size, 0, 360, $this->gdnoisecolor, IMG_ARC_PIE);
  1564. }
  1565. $t1 = microtime(true);
  1566. $t = $t1 - $t0;
  1567. /*
  1568. // DEBUG
  1569. imagestring($this->tmpimg, 5, 25, 30, "$t", $this->gdnoisecolor);
  1570. header('content-type: image/png');
  1571. imagepng($this->tmpimg);
  1572. exit;
  1573. */
  1574. }
  1575. /**
  1576. * Print signature text on image
  1577. */
  1578. protected function addSignature()
  1579. {
  1580. $bbox = imagettfbbox(10, 0, $this->signature_font, $this->image_signature);
  1581. $textlen = $bbox[2] - $bbox[0];
  1582. $x = $this->image_width - $textlen - 5;
  1583. $y = $this->image_height - 3;
  1584. imagettftext($this->im, 10, 0, $x, $y, $this->gdsignaturecolor, $this->signature_font, $this->image_signature);
  1585. }
  1586. /**
  1587. * Sends the appropriate image and cache headers and outputs image to the browser
  1588. */
  1589. protected function output()
  1590. {
  1591. if ($this->canSendHeaders() || $this->send_headers == false) {
  1592. if ($this->send_headers) {
  1593. // only send the content-type headers if no headers have been output
  1594. // this will ease debugging on misconfigured servers where warnings
  1595. // may have been output which break the image and prevent easily viewing
  1596. // source to see the error.
  1597. header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
  1598. header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
  1599. header("Cache-Control: no-store, no-cache, must-revalidate");
  1600. header("Cache-Control: post-check=0, pre-check=0", false);
  1601. header("Pragma: no-cache");
  1602. }
  1603. switch ($this->image_type) {
  1604. case self::SI_IMAGE_JPEG:
  1605. if ($this->send_headers) header("Content-Type: image/jpeg");
  1606. imagejpeg($this->im, null, 90);
  1607. break;
  1608. case self::SI_IMAGE_GIF:
  1609. if ($this->send_headers) header("Content-Type: image/gif");
  1610. imagegif($this->im);
  1611. break;
  1612. default:
  1613. if ($this->send_headers) header("Content-Type: image/png");
  1614. imagepng($this->im);
  1615. break;
  1616. }
  1617. } else {
  1618. echo '<hr /><strong>'
  1619. .'Failed to generate captcha image, content has already been '
  1620. .'output.<br />This is most likely due to misconfiguration or '
  1621. .'a PHP error was sent to the browser.</strong>';
  1622. }
  1623. imagedestroy($this->im);
  1624. restore_error_handler();
  1625. if (!$this->no_exit) exit;
  1626. }
  1627. /**
  1628. * Generates an audio captcha in WAV format
  1629. *
  1630. * @return string The audio representation of the captcha in Wav format
  1631. */
  1632. protected function getAudibleCode()
  1633. {
  1634. $letters = array();
  1635. $code = $this->getCode(true, true);
  1636. if (empty($code) || $code['code'] == '') {
  1637. if (strlen($this->display_value) > 0) {
  1638. $code = array('code' => $this->display_value, 'display' => $this->display_value);
  1639. } else {
  1640. $this->createCode();
  1641. $code = $this->getCode(true);
  1642. }
  1643. }
  1644. if (empty($code)) {
  1645. $error = 'Failed to get audible code (are database settings correct?). Check the error log for details';
  1646. trigger_error($error, E_USER_WARNING);
  1647. throw new Exception($error);
  1648. }
  1649. if (preg_match('/(\d+) (\+|-|x) (\d+)/i', $code['display'], $eq)) {
  1650. $math = true;
  1651. $left = $eq[1];
  1652. $sign = str_replace(array('+', '-', 'x'), array('plus', 'minus', 'times'), $eq[2]);
  1653. $right = $eq[3];
  1654. $letters = array($left, $sign, $right);
  1655. } else {
  1656. $math = false;
  1657. $length = strlen($code['display']);
  1658. for($i = 0; $i < $length; ++$i) {
  1659. $letter = $code['display']{$i};
  1660. $letters[] = $letter;
  1661. }
  1662. }
  1663. try {
  1664. return $this->generateWAV($letters);
  1665. } catch(Exception $ex) {
  1666. throw $ex;
  1667. }
  1668. }
  1669. /**
  1670. * Gets a captcha code from a file containing a list of words.
  1671. *
  1672. * Seek to a random offset in the file and reads a block of data and returns a line from the file.
  1673. *
  1674. * @param int $numWords Number of words (lines) to read from the file
  1675. * @return string|array Returns a string if only one word is to be read, or an array of words
  1676. */
  1677. protected function readCodeFromFile($numWords = 1)
  1678. {
  1679. $fp = fopen($this->wordlist_file, 'rb');
  1680. if (!$fp) return false;
  1681. $fsize = filesize($this->wordlist_file);
  1682. if ($fsize < 128) return false; // too small of a list to be effective
  1683. if ((int)$numWords < 1 || (int)$numWords > 5) $numWords = 1;
  1684. $words = array();
  1685. $i = 0;
  1686. do {
  1687. fseek($fp, mt_rand(0, $fsize - 64), SEEK_SET); // seek to a random position of file from 0 to filesize-64
  1688. $data = fread($fp, 64); // read a chunk from our random position
  1689. $data = preg_replace("/\r?\n/", "\n", $data);
  1690. $start = @strpos($data, "\n", mt_rand(0, 56)) + 1; // random start position
  1691. $end = @strpos($data, "\n", $start); // find end of word
  1692. if ($start === false) {
  1693. // picked start position at end of file
  1694. continue;
  1695. } else if ($end === false) {
  1696. $end = strlen($data);
  1697. }
  1698. $word = strtolower(substr($data, $start, $end - $start)); // return a line of the file
  1699. $words[] = $word;
  1700. } while (++$i < $numWords);
  1701. fclose($fp);
  1702. if ($numWords < 2) {
  1703. return $words[0];
  1704. } else {
  1705. return $words;
  1706. }
  1707. }
  1708. /**
  1709. * Generates a random captcha code from the set character set
  1710. *
  1711. * @see Securimage::$charset Charset option
  1712. * @return string A randomly generated CAPTCHA code
  1713. */
  1714. protected function generateCode()
  1715. {
  1716. $code = '';
  1717. if (function_exists('mb_strlen')) {
  1718. for($i = 1, $cslen = mb_strlen($this->charset); $i <= $this->code_length; ++$i) {
  1719. $code .= mb_substr($this->charset, mt_rand(0, $cslen - 1), 1, 'UTF-8');
  1720. }
  1721. } else {
  1722. for($i = 1, $cslen = strlen($this->charset); $i <= $this->code_length; ++$i) {
  1723. $code .= substr($this->charset, mt_rand(0, $cslen - 1), 1);
  1724. }
  1725. }
  1726. return $code;
  1727. }
  1728. /**
  1729. * Validate a code supplied by the user
  1730. *
  1731. * Checks the entered code against the value stored in the session and/or database (if configured). Handles case sensitivity.
  1732. * Also removes the code from session/database if the code was entered correctly to prevent re-use attack.
  1733. *
  1734. * This function does not return a value.
  1735. *
  1736. * @see Securimage::$correct_code 'correct_code' property
  1737. */
  1738. protected function validate()
  1739. {
  1740. if (!is_string($this->code) || strlen($this->code) == 0) {
  1741. $code = $this->getCode(true);
  1742. // returns stored code, or an empty string if no stored code was found
  1743. // checks the session and database if enabled
  1744. } else {
  1745. $code = $this->code;
  1746. }
  1747. if (is_array($code)) {
  1748. if (!empty($code)) {
  1749. $ctime = $code['time'];
  1750. $code = $code['code'];
  1751. $this->_timeToSolve = time() - $ctime;
  1752. } else {
  1753. $code = '';
  1754. }
  1755. }
  1756. if ($this->case_sensitive == false && preg_match('/[A-Z]/', $code)) {
  1757. // case sensitive was set from securimage_show.php but not in class
  1758. // the code saved in the session has capitals so set case sensitive to true
  1759. $this->case_sensitive = true;
  1760. }
  1761. $code_entered = trim( (($this->case_sensitive) ? $this->code_entered
  1762. : strtolower($this->code_entered))
  1763. );
  1764. $this->correct_code = false;
  1765. if ($code != '') {
  1766. if (strpos($code, ' ') !== false) {
  1767. // for multi word captchas, remove more than once space from input
  1768. $code_entered = preg_replace('/\s+/', ' ', $code_entered);
  1769. $code_entered = strtolower($code_entered);
  1770. }
  1771. if ($code == $code_entered) {
  1772. $this->correct_code = true;
  1773. if ($this->no_session != true) {
  1774. $_SESSION['securimage_code_disp'] [$this->namespace] = '';
  1775. $_SESSION['securimage_code_value'][$this->namespace] = '';
  1776. $_SESSION['securimage_code_ctime'][$this->namespace] = '';
  1777. }
  1778. $this->clearCodeFromDatabase();
  1779. }
  1780. }
  1781. }
  1782. /**
  1783. * Save CAPTCHA data to session and database (if configured)
  1784. */
  1785. protected function saveData()
  1786. {
  1787. if ($this->no_session != true) {
  1788. if (isset($_SESSION['securimage_code_value']) && is_scalar($_SESSION['securimage_code_value'])) {
  1789. // fix for migration from v2 - v3
  1790. unset($_SESSION['securimage_code_value']);
  1791. unset($_SESSION['securimage_code_ctime']);
  1792. }
  1793. $_SESSION['securimage_code_disp'] [$this->namespace] = $this->code_display;
  1794. $_SESSION['securimage_code_value'][$this->namespace] = $this->code;
  1795. $_SESSION['securimage_code_ctime'][$this->namespace] = time();
  1796. }
  1797. if ($this->use_database) {
  1798. $this->saveCodeToDatabase();
  1799. }
  1800. }
  1801. /**
  1802. * Saves the CAPTCHA data to the configured database.
  1803. */
  1804. protected function saveCodeToDatabase()
  1805. {
  1806. $success = false;
  1807. $this->openDatabase();
  1808. if ($this->use_database && $this->pdo_conn) {
  1809. $id = $this->getCaptchaId(false);
  1810. $ip = $_SERVER['REMOTE_ADDR'];
  1811. if (empty($id)) {
  1812. $id = $ip;
  1813. }
  1814. $time = time();
  1815. $code = $this->code;
  1816. $code_disp = $this->code_display;
  1817. // This is somewhat expensive in PDO Sqlite3 (when there is something to delete)
  1818. $this->clearCodeFromDatabase();
  1819. $query = "INSERT INTO {$this->database_table} ("
  1820. ."id, code, code_display, namespace, created) "
  1821. ."VALUES(?, ?, ?, ?, ?)";
  1822. $stmt = $this->pdo_conn->prepare($query);
  1823. $success = $stmt->execute(array($id, $code, $code_disp, $this->namespace, $time));
  1824. if (!$success) {
  1825. $err = $stmt->errorInfo();
  1826. $error = "Failed to insert code into database. {$err[1]}: {$err[2]}.";
  1827. if ($this->database_driver == self::SI_DRIVER_SQLITE3) {
  1828. $err14 = ($err[1] == 14);
  1829. if ($err14) $error .= sprintf(" Ensure database directory and file are writeable by user '%s' (%d).",
  1830. get_current_user(), getmyuid());
  1831. }
  1832. trigger_error($error, E_USER_WARNING);
  1833. }
  1834. }
  1835. return $success !== false;
  1836. }
  1837. /**
  1838. * Opens a connection to the configured database.
  1839. *
  1840. * @see Securimage::$use_database Use database
  1841. * @see Securimage::$database_driver Database driver
  1842. * @see Securimage::$pdo_conn pdo_conn
  1843. * @return bool true if the database connection was successful, false if not
  1844. */
  1845. protected function openDatabase()
  1846. {
  1847. $this->pdo_conn = false;
  1848. if ($this->use_database) {
  1849. $pdo_extension = 'PDO_' . strtoupper($this->database_driver);
  1850. if (!extension_loaded($pdo_extension)) {
  1851. trigger_error("Database support is turned on in Securimage, but the chosen extension $pdo_extension is not loaded in PHP.", E_USER_WARNING);
  1852. return false;
  1853. }
  1854. }
  1855. if ($this->database_driver == self::SI_DRIVER_SQLITE3) {
  1856. if (!file_exists($this->database_file)) {
  1857. $fp = fopen($this->database_file, 'w+');
  1858. if (!$fp) {
  1859. $err = error_get_last();
  1860. trigger_error("Securimage failed to create SQLite3 database file '{$this->database_file}'. Reason: {$err['message']}", E_USER_WARNING);
  1861. return false;
  1862. }
  1863. fclose($fp);
  1864. chmod($this->database_file, 0666);
  1865. } else if (!is_writeable($this->database_file)) {
  1866. trigger_error("Securimage does not have read/write access to database file '{$this->database_file}. Make sure permissions are 0666 and writeable by user '" . get_current_user() . "'", E_USER_WARNING);
  1867. return false;
  1868. }
  1869. }
  1870. try {
  1871. $dsn = $this->getDsn();
  1872. $options = array();
  1873. $this->pdo_conn = new PDO($dsn, $this->database_user, $this->database_pass, $options);
  1874. } catch (PDOException $pdoex) {
  1875. trigger_error("Database connection failed: " . $pdoex->getMessage(), E_USER_WARNING);
  1876. return false;
  1877. } catch (Exception $ex) {
  1878. trigger_error($ex->getMessage(), E_USER_WARNING);
  1879. return false;
  1880. }
  1881. try {
  1882. if (!$this->skip_table_check && !$this->checkTablesExist()) {
  1883. // create tables...
  1884. $this->createDatabaseTables();
  1885. }
  1886. } catch (Exception $ex) {
  1887. trigger_error($ex->getMessage(), E_USER_WARNING);
  1888. $this->pdo_conn = null;
  1889. return false;
  1890. }
  1891. if (mt_rand(0, 100) / 100.0 == 1.0) {
  1892. $this->purgeOldCodesFromDatabase();
  1893. }
  1894. return $this->pdo_conn;
  1895. }
  1896. /**
  1897. * Get the PDO DSN string for connecting to the database
  1898. *
  1899. * @see Securimage::$database_driver Database driver
  1900. * @throws Exception If database specific options are not configured
  1901. * @return string The DSN for connecting to the database
  1902. */
  1903. protected function getDsn()
  1904. {
  1905. $dsn = sprintf('%s:', $this->database_driver);
  1906. switch($this->database_driver) {
  1907. case self::SI_DRIVER_SQLITE3:
  1908. $dsn .= $this->database_file;
  1909. break;
  1910. case self::SI_DRIVER_MYSQL:
  1911. case self::SI_DRIVER_PGSQL:
  1912. if (empty($this->database_host)) {
  1913. throw new Exception('Securimage::database_host is not set');
  1914. } else if (empty($this->database_name)) {
  1915. throw new Exception('Securimage::database_name is not set');
  1916. }
  1917. $dsn .= sprintf('host=%s;dbname=%s',
  1918. $this->database_host,
  1919. $this->database_name);
  1920. break;
  1921. }
  1922. return $dsn;
  1923. }
  1924. /**
  1925. * Checks if the necessary database tables for storing captcha codes exist
  1926. *
  1927. * @throws Exception If the table check failed for some reason
  1928. * @return boolean true if the database do exist, false if not
  1929. */
  1930. protected function checkTablesExist()
  1931. {
  1932. $table = $this->pdo_conn->quote($this->database_table);
  1933. switch($this->database_driver) {
  1934. case self::SI_DRIVER_SQLITE3:
  1935. // query row count for sqlite, PRAGMA queries seem to return no
  1936. // rowCount using PDO even if there are rows returned
  1937. $query = "SELECT COUNT(id) FROM $table";
  1938. break;
  1939. case self::SI_DRIVER_MYSQL:
  1940. $query = "SHOW TABLES LIKE $table";
  1941. break;
  1942. case self::SI_DRIVER_PGSQL:
  1943. $query = "SELECT * FROM information_schema.columns WHERE table_name = $table;";
  1944. break;
  1945. }
  1946. $result = $this->pdo_conn->query($query);
  1947. if (!$result) {
  1948. $err = $this->pdo_conn->errorInfo();
  1949. if ($this->database_driver == self::SI_DRIVER_SQLITE3 &&
  1950. $err[1] === 1 && strpos($err[2], 'no such table') !== false)
  1951. {
  1952. return false;
  1953. }
  1954. throw new Exception("Failed to check tables: {$err[0]} - {$err[1]}: {$err[2]}");
  1955. } else if ($this->database_driver == self::SI_DRIVER_SQLITE3) {
  1956. // successful here regardless of row count for sqlite
  1957. return true;
  1958. } else if ($result->rowCount() == 0) {
  1959. return false;
  1960. } else {
  1961. return true;
  1962. }
  1963. }
  1964. /**
  1965. * Create the necessary databaes table for storing captcha codes.
  1966. *
  1967. * Based on the database adapter used, the tables will created in the existing connection.
  1968. *
  1969. * @see Securimage::$database_driver Database driver
  1970. * @return boolean true if the tables were created, false if not
  1971. */
  1972. protected function createDatabaseTables()
  1973. {
  1974. $queries = array();
  1975. switch($this->database_driver) {
  1976. case self::SI_DRIVER_SQLITE3:
  1977. $queries[] = "CREATE TABLE \"{$this->database_table}\" (
  1978. id VARCHAR(40),
  1979. namespace VARCHAR(32) NOT NULL,
  1980. code VARCHAR(32) NOT NULL,
  1981. code_display VARCHAR(32) NOT NULL,
  1982. created INTEGER NOT NULL,
  1983. PRIMARY KEY(id, namespace)
  1984. )";
  1985. $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created)";
  1986. break;
  1987. case self::SI_DRIVER_MYSQL:
  1988. $queries[] = "CREATE TABLE `{$this->database_table}` (
  1989. `id` VARCHAR(40) NOT NULL,
  1990. `namespace` VARCHAR(32) NOT NULL,
  1991. `code` VARCHAR(32) NOT NULL,
  1992. `code_display` VARCHAR(32) NOT NULL,
  1993. `created` INT NOT NULL,
  1994. PRIMARY KEY(id, namespace),
  1995. INDEX(created)
  1996. )";
  1997. break;
  1998. case self::SI_DRIVER_PGSQL:
  1999. $queries[] = "CREATE TABLE {$this->database_table} (
  2000. id character varying(40) NOT NULL,
  2001. namespace character varying(32) NOT NULL,
  2002. code character varying(32) NOT NULL,
  2003. code_display character varying(32) NOT NULL,
  2004. created integer NOT NULL,
  2005. CONSTRAINT pkey_id_namespace PRIMARY KEY (id, namespace)
  2006. )";
  2007. $queries[] = "CREATE INDEX ndx_created ON {$this->database_table} (created);";
  2008. break;
  2009. }
  2010. $this->pdo_conn->beginTransaction();
  2011. foreach($queries as $query) {
  2012. $result = $this->pdo_conn->query($query);
  2013. if (!$result) {
  2014. $err = $this->pdo_conn->errorInfo();
  2015. trigger_error("Failed to create table. {$err[1]}: {$err[2]}", E_USER_WARNING);
  2016. $this->pdo_conn->rollBack();
  2017. $this->pdo_conn = false;
  2018. return false;
  2019. }
  2020. }
  2021. $this->pdo_conn->commit();
  2022. return true;
  2023. }
  2024. /**
  2025. * Retrieves a stored code from the database for based on the captchaId or
  2026. * IP address if captcha ID not used.
  2027. *
  2028. * @return string|array Empty string if no code was found or has expired,
  2029. * otherwise returns array of code information.
  2030. */
  2031. protected function getCodeFromDatabase()
  2032. {
  2033. $code = '';
  2034. if ($this->use_database == true && $this->pdo_conn) {
  2035. if (Securimage::$_captchaId !== null) {
  2036. $query = "SELECT * FROM {$this->database_table} WHERE id = ?";
  2037. $stmt = $this->pdo_conn->prepare($query);
  2038. $result = $stmt->execute(array(Securimage::$_captchaId));
  2039. } else {
  2040. $ip = $_SERVER['REMOTE_ADDR'];
  2041. $ns = $this->namespace;
  2042. // ip is stored in id column when no captchaId
  2043. $query = "SELECT * FROM {$this->database_table} WHERE id = ? AND namespace = ?";
  2044. $stmt = $this->pdo_conn->prepare($query);
  2045. $result = $stmt->execute(array($ip, $ns));
  2046. }
  2047. if (!$result) {
  2048. $err = $this->pdo_conn->errorInfo();
  2049. trigger_error("Failed to select code from database. {$err[0]}: {$err[1]}", E_USER_WARNING);
  2050. } else {
  2051. if ( ($row = $stmt->fetch()) !== false ) {
  2052. if (false == $this->isCodeExpired($row['created'])) {
  2053. $code = array(
  2054. 'code' => $row['code'],
  2055. 'code_disp' => $row['code_display'],
  2056. 'time' => $row['created'],
  2057. );
  2058. }
  2059. }
  2060. }
  2061. }
  2062. return $code;
  2063. }
  2064. /**
  2065. * Remove a stored code from the database based on captchaId or IP address.
  2066. */
  2067. protected function clearCodeFromDatabase()
  2068. {
  2069. if ($this->pdo_conn) {
  2070. $ip = $_SERVER['REMOTE_ADDR'];
  2071. $ns = $this->pdo_conn->quote($this->namespace);
  2072. $id = Securimage::$_captchaId;
  2073. if (empty($id)) {
  2074. $id = $ip; // if no captchaId set, IP address is captchaId.
  2075. }
  2076. $id = $this->pdo_conn->quote($id);
  2077. $query = sprintf("DELETE FROM %s WHERE id = %s AND namespace = %s",
  2078. $this->database_table, $id, $ns);
  2079. $result = $this->pdo_conn->query($query);
  2080. if (!$result) {
  2081. trigger_error("Failed to delete code from database.", E_USER_WARNING);
  2082. }
  2083. }
  2084. }
  2085. /**
  2086. * Deletes old (expired) codes from the database
  2087. */
  2088. protected function purgeOldCodesFromDatabase()
  2089. {
  2090. if ($this->use_database && $this->pdo_conn) {
  2091. $now = time();
  2092. $limit = (!is_numeric($this->expiry_time) || $this->expiry_time < 1) ? 86400 : $this->expiry_time;
  2093. $query = sprintf("DELETE FROM %s WHERE %s - created > %s",
  2094. $this->database_table,
  2095. $this->pdo_conn->quote($now, PDO::PARAM_INT),
  2096. $this->pdo_conn->quote($limit, PDO::PARAM_INT));
  2097. $result = $this->pdo_conn->query($query);
  2098. }
  2099. }
  2100. /**
  2101. * Checks to see if the captcha code has expired and can no longer be used.
  2102. *
  2103. * @see Securimage::$expiry_time expiry_time
  2104. * @param int $creation_time The Unix timestamp of when the captcha code was created
  2105. * @return bool true if the code is expired, false if it is still valid
  2106. */
  2107. protected function isCodeExpired($creation_time)
  2108. {
  2109. $expired = true;
  2110. if (!is_numeric($this->expiry_time) || $this->expiry_time < 1) {
  2111. $expired = false;
  2112. } else if (time() - $creation_time < $this->expiry_time) {
  2113. $expired = false;
  2114. }
  2115. return $expired;
  2116. }
  2117. /**
  2118. * Generate a wav file given the $letters in the code
  2119. *
  2120. * @param array $letters The letters making up the captcha
  2121. * @return string The audio content in WAV format
  2122. */
  2123. protected function generateWAV($letters)
  2124. {
  2125. $wavCaptcha = new WavFile();
  2126. $first = true; // reading first wav file
  2127. if ($this->audio_use_sox && !is_executable($this->sox_binary_path)) {
  2128. throw new Exception("Path to SoX binary is incorrect or not executable");
  2129. }
  2130. foreach ($letters as $letter) {
  2131. $letter = strtoupper($letter);
  2132. try {
  2133. $letter_file = realpath($this->audio_path) . DIRECTORY_SEPARATOR . $letter . '.wav';
  2134. if ($this->audio_use_sox) {
  2135. $sox_cmd = sprintf("%s %s -t wav - %s",
  2136. $this->sox_binary_path,
  2137. $letter_file,
  2138. $this->getSoxEffectChain());
  2139. $data = `$sox_cmd`;
  2140. $l = new WavFile();
  2141. $l->setIgnoreChunkSizes(true);
  2142. $l->setWavData($data);
  2143. } else {
  2144. $l = new WavFile($letter_file);
  2145. }
  2146. if ($first) {
  2147. // set sample rate, bits/sample, and # of channels for file based on first letter
  2148. $wavCaptcha->setSampleRate($l->getSampleRate())
  2149. ->setBitsPerSample($l->getBitsPerSample())
  2150. ->setNumChannels($l->getNumChannels());
  2151. $first = false;
  2152. }
  2153. // append letter to the captcha audio
  2154. $wavCaptcha->appendWav($l);
  2155. // random length of silence between $audio_gap_min and $audio_gap_max
  2156. if ($this->audio_gap_max > 0 && $this->audio_gap_max > $this->audio_gap_min) {
  2157. $wavCaptcha->insertSilence( mt_rand($this->audio_gap_min, $this->audio_gap_max) / 1000.0 );
  2158. }
  2159. } catch (Exception $ex) {
  2160. // failed to open file, or the wav file is broken or not supported
  2161. // 2 wav files were not compatible, different # channels, bits/sample, or sample rate
  2162. throw new Exception("Error generating audio captcha on letter '$letter': " . $ex->getMessage());
  2163. }
  2164. }
  2165. /********* Set up audio filters *****************************/
  2166. $filters = array();
  2167. if ($this->audio_use_noise == true) {
  2168. // use background audio - find random file
  2169. $wavNoise = false;
  2170. $randOffset = 0;
  2171. /*
  2172. // uncomment to try experimental SoX noise generation.
  2173. // warning: sounds may be considered annoying
  2174. if ($this->audio_use_sox) {
  2175. $duration = $wavCaptcha->getDataSize() / ($wavCaptcha->getBitsPerSample() / 8) /
  2176. $wavCaptcha->getNumChannels() / $wavCaptcha->getSampleRate();
  2177. $duration = round($duration, 2);
  2178. $wavNoise = new WavFile();
  2179. $wavNoise->setIgnoreChunkSizes(true);
  2180. $noiseData = $this->getSoxNoiseData($duration,
  2181. $wavCaptcha->getNumChannels(),
  2182. $wavCaptcha->getSampleRate(),
  2183. $wavCaptcha->getBitsPerSample());
  2184. $wavNoise->setWavData($noiseData, true);
  2185. } else
  2186. */
  2187. if ( ($noiseFile = $this->getRandomNoiseFile()) !== false) {
  2188. try {
  2189. $wavNoise = new WavFile($noiseFile, false);
  2190. } catch(Exception $ex) {
  2191. throw $ex;
  2192. }
  2193. // start at a random offset from the beginning of the wavfile
  2194. // in order to add more randomness
  2195. $randOffset = 0;
  2196. if ($wavNoise->getNumBlocks() > 2 * $wavCaptcha->getNumBlocks()) {
  2197. $randBlock = mt_rand(0, $wavNoise->getNumBlocks() - $wavCaptcha->getNumBlocks());
  2198. $wavNoise->readWavData($randBlock * $wavNoise->getBlockAlign(), $wavCaptcha->getNumBlocks() * $wavNoise->getBlockAlign());
  2199. } else {
  2200. $wavNoise->readWavData();
  2201. $randOffset = mt_rand(0, $wavNoise->getNumBlocks() - 1);
  2202. }
  2203. }
  2204. if ($wavNoise !== false) {
  2205. $mixOpts = array('wav' => $wavNoise,
  2206. 'loop' => true,
  2207. 'blockOffset' => $randOffset);
  2208. $filters[WavFile::FILTER_MIX] = $mixOpts;
  2209. $filters[WavFile::FILTER_NORMALIZE] = $this->audio_mix_normalization;
  2210. }
  2211. }
  2212. if ($this->degrade_audio == true) {
  2213. // add random noise.
  2214. // any noise level below 95% is intensely distorted and not pleasant to the ear
  2215. $filters[WavFile::FILTER_DEGRADE] = mt_rand(95, 98) / 100.0;
  2216. }
  2217. if (!empty($filters)) {
  2218. $wavCaptcha->filter($filters); // apply filters to captcha audio
  2219. }
  2220. return $wavCaptcha->__toString();
  2221. }
  2222. /**
  2223. * Gets and returns the path to a random noise file from the audio noise directory.
  2224. *
  2225. * @return bool|string false if a file could not be found, or a string containing the path to the file.
  2226. */
  2227. public function getRandomNoiseFile()
  2228. {
  2229. $return = false;
  2230. if ( ($dh = opendir($this->audio_noise_path)) !== false ) {
  2231. $list = array();
  2232. while ( ($file = readdir($dh)) !== false ) {
  2233. if ($file == '.' || $file == '..') continue;
  2234. if (strtolower(substr($file, -4)) != '.wav') continue;
  2235. $list[] = $file;
  2236. }
  2237. closedir($dh);
  2238. if (sizeof($list) > 0) {
  2239. $file = $list[array_rand($list, 1)];
  2240. $return = $this->audio_noise_path . DIRECTORY_SEPARATOR . $file;
  2241. if (!is_readable($return)) $return = false;
  2242. }
  2243. }
  2244. return $return;
  2245. }
  2246. /**
  2247. * Get a random effect or chain of effects to apply to a segment of the
  2248. * audio file.
  2249. *
  2250. * These effects should increase the randomness of the audio for
  2251. * a particular letter/number by modulating the signal. The SoX effects
  2252. * used are *bend*, *chorus*, *overdrive*, *pitch*, *reverb*, *tempo*, and
  2253. * *tremolo*.
  2254. *
  2255. * For each effect selected, random parameters are supplied to the effect.
  2256. *
  2257. * @param int $numEffects How many effects to chain together
  2258. * @return string A string of valid SoX effects and their respective options.
  2259. */
  2260. protected function getSoxEffectChain($numEffects = 2)
  2261. {
  2262. $effectsList = array('bend', 'chorus', 'overdrive', 'pitch', 'reverb', 'tempo', 'tremolo');
  2263. $effects = array_rand($effectsList, $numEffects);
  2264. $outEffects = array();
  2265. if (!is_array($effects)) $effects = array($effects);
  2266. foreach($effects as $effect) {
  2267. $effect = $effectsList[$effect];
  2268. switch($effect)
  2269. {
  2270. case 'bend':
  2271. $delay = mt_rand(0, 15) / 100.0;
  2272. $cents = mt_rand(-120, 120);
  2273. $dur = mt_rand(75, 400) / 100.0;
  2274. $outEffects[] = "$effect $delay,$cents,$dur";
  2275. break;
  2276. case 'chorus':
  2277. $gainIn = mt_rand(75, 90) / 100.0;
  2278. $gainOut = mt_rand(70, 95) / 100.0;
  2279. $chorStr = "$effect $gainIn $gainOut";
  2280. for ($i = 0; $i < mt_rand(2, 3); ++$i) {
  2281. $delay = mt_rand(20, 100);
  2282. $decay = mt_rand(10, 100) / 100.0;
  2283. $speed = mt_rand(20, 50) / 100.0;
  2284. $depth = mt_rand(150, 250) / 100.0;
  2285. $chorStr .= " $delay $decay $speed $depth -s";
  2286. }
  2287. $outEffects[] = $chorStr;
  2288. break;
  2289. case 'overdrive':
  2290. $gain = mt_rand(5, 25);
  2291. $color = mt_rand(20, 70);
  2292. $outEffects[] = "$effect $gain $color";
  2293. break;
  2294. case 'pitch':
  2295. $cents = mt_rand(-300, 300);
  2296. $outEffects[] = "$effect $cents";
  2297. break;
  2298. case 'reverb':
  2299. $reverberance = mt_rand(20, 80);
  2300. $damping = mt_rand(10, 80);
  2301. $scale = mt_rand(85, 100);
  2302. $depth = mt_rand(90, 100);
  2303. $predelay = mt_rand(0, 5);
  2304. $outEffects[] = "$effect $reverberance $damping $scale $depth $predelay";
  2305. break;
  2306. case 'tempo':
  2307. $factor = mt_rand(65, 135) / 100.0;
  2308. $outEffects[] = "$effect -s $factor";
  2309. break;
  2310. case 'tremolo':
  2311. $hz = mt_rand(10, 30);
  2312. $depth = mt_rand(40, 85);
  2313. $outEffects[] = "$effect $hz $depth";
  2314. break;
  2315. }
  2316. }
  2317. return implode(' ', $outEffects);
  2318. }
  2319. /**
  2320. * This function is not yet used.
  2321. *
  2322. * Generate random background noise from sweeping oscillators
  2323. *
  2324. * @param float $duration How long in seconds the generated sound will be
  2325. * @param int $numChannels Number of channels in output wav
  2326. * @param int $sampleRate Sample rate of output wav
  2327. * @param int $bitRate Bits per sample (8, 16, 24)
  2328. * @return string Audio data in wav format
  2329. */
  2330. protected function getSoxNoiseData($duration, $numChannels, $sampleRate, $bitRate)
  2331. {
  2332. $shapes = array('sine', 'square', 'triangle', 'sawtooth', 'trapezium');
  2333. $steps = array(':', '+', '/', '-');
  2334. $selShapes = array_rand($shapes, 2);
  2335. $selSteps = array_rand($steps, 2);
  2336. $sweep0 = array();
  2337. $sweep0[0] = mt_rand(100, 700);
  2338. $sweep0[1] = mt_rand(1500, 2500);
  2339. $sweep1 = array();
  2340. $sweep1[0] = mt_rand(500, 1000);
  2341. $sweep1[1] = mt_rand(1200, 2000);
  2342. if (mt_rand(0, 10) % 2 == 0)
  2343. $sweep0 = array_reverse($sweep0);
  2344. if (mt_rand(0, 10) % 2 == 0)
  2345. $sweep1 = array_reverse($sweep1);
  2346. $cmd = sprintf("%s -c %d -r %d -b %d -n -t wav - synth noise create vol 0.3 synth %.2f %s mix %d%s%d vol 0.3 synth %.2f %s fmod %d%s%d vol 0.3",
  2347. $this->sox_binary_path,
  2348. $numChannels,
  2349. $sampleRate,
  2350. $bitRate,
  2351. $duration,
  2352. $shapes[$selShapes[0]],
  2353. $sweep0[0],
  2354. $steps[$selSteps[0]],
  2355. $sweep0[1],
  2356. $duration,
  2357. $shapes[$selShapes[1]],
  2358. $sweep1[0],
  2359. $steps[$selSteps[1]],
  2360. $sweep1[1]
  2361. );
  2362. $data = `$cmd`;
  2363. return $data;
  2364. }
  2365. /**
  2366. * Return a wav file saying there was an error generating file
  2367. *
  2368. * @return string The binary audio contents
  2369. */
  2370. protected function audioError()
  2371. {
  2372. return @file_get_contents(dirname(__FILE__) . '/audio/en/error.wav');
  2373. }
  2374. /**
  2375. * Checks to see if headers can be sent and if any error has been output
  2376. * to the browser
  2377. *
  2378. * @return bool true if it is safe to send headers, false if not
  2379. */
  2380. protected function canSendHeaders()
  2381. {
  2382. if (headers_sent()) {
  2383. // output has been flushed and headers have already been sent
  2384. return false;
  2385. } else if (strlen((string)ob_get_contents()) > 0) {
  2386. // headers haven't been sent, but there is data in the buffer that will break image and audio data
  2387. return false;
  2388. }
  2389. return true;
  2390. }
  2391. /**
  2392. * Return a random float between 0 and 0.9999
  2393. *
  2394. * @return float Random float between 0 and 0.9999
  2395. */
  2396. function frand()
  2397. {
  2398. return 0.0001 * mt_rand(0,9999);
  2399. }
  2400. /**
  2401. * Convert an html color code to a Securimage_Color
  2402. * @param string $color
  2403. * @param Securimage_Color $default The defalt color to use if $color is invalid
  2404. */
  2405. protected function initColor($color, $default)
  2406. {
  2407. if ($color == null) {
  2408. return new Securimage_Color($default);
  2409. } else if (is_string($color)) {
  2410. try {
  2411. return new Securimage_Color($color);
  2412. } catch(Exception $e) {
  2413. return new Securimage_Color($default);
  2414. }
  2415. } else if (is_array($color) && sizeof($color) == 3) {
  2416. return new Securimage_Color($color[0], $color[1], $color[2]);
  2417. } else {
  2418. return new Securimage_Color($default);
  2419. }
  2420. }
  2421. /**
  2422. * The error handling function used when outputting captcha image or audio.
  2423. *
  2424. * This error handler helps determine if any errors raised would
  2425. * prevent captcha image or audio from displaying. If they have
  2426. * no effect on the output buffer or headers, true is returned so
  2427. * the script can continue processing.
  2428. *
  2429. * See https://github.com/dapphp/securimage/issues/15
  2430. *
  2431. * @param int $errno PHP error number
  2432. * @param string $errstr String description of the error
  2433. * @param string $errfile File error occurred in
  2434. * @param int $errline Line the error occurred on in file
  2435. * @param array $errcontext Additional context information
  2436. * @return boolean true if the error was handled, false if PHP should handle the error
  2437. */
  2438. public function errorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array())
  2439. {
  2440. // get the current error reporting level
  2441. $level = error_reporting();
  2442. // if error was supressed or $errno not set in current error level
  2443. if ($level == 0 || ($level & $errno) == 0) {
  2444. return true;
  2445. }
  2446. return false;
  2447. }
  2448. }
  2449. /**
  2450. * Color object for Securimage CAPTCHA
  2451. *
  2452. * @version 3.0
  2453. * @since 2.0
  2454. * @package Securimage
  2455. * @subpackage classes
  2456. *
  2457. */
  2458. class Securimage_Color
  2459. {
  2460. /**
  2461. * Red value (0-255)
  2462. * @var int
  2463. */
  2464. public $r;
  2465. /**
  2466. * Gree value (0-255)
  2467. * @var int
  2468. */
  2469. public $g;
  2470. /**
  2471. * Blue value (0-255)
  2472. * @var int
  2473. */
  2474. public $b;
  2475. /**
  2476. * Create a new Securimage_Color object.
  2477. *
  2478. * Constructor expects 1 or 3 arguments.
  2479. *
  2480. * When passing a single argument, specify the color using HTML hex format.
  2481. *
  2482. * When passing 3 arguments, specify each RGB component (from 0-255)
  2483. * individually.
  2484. *
  2485. * Examples:
  2486. *
  2487. * $color = new Securimage_Color('#0080FF');
  2488. * $color = new Securimage_Color(0, 128, 255);
  2489. *
  2490. * @param string $color The html color code to use
  2491. * @throws Exception If any color value is not valid
  2492. */
  2493. public function __construct($color = '#ffffff')
  2494. {
  2495. $args = func_get_args();
  2496. if (sizeof($args) == 0) {
  2497. $this->r = 255;
  2498. $this->g = 255;
  2499. $this->b = 255;
  2500. } else if (sizeof($args) == 1) {
  2501. // set based on html code
  2502. if (substr($color, 0, 1) == '#') {
  2503. $color = substr($color, 1);
  2504. }
  2505. if (strlen($color) != 3 && strlen($color) != 6) {
  2506. throw new InvalidArgumentException(
  2507. 'Invalid HTML color code passed to Securimage_Color'
  2508. );
  2509. }
  2510. $this->constructHTML($color);
  2511. } else if (sizeof($args) == 3) {
  2512. $this->constructRGB($args[0], $args[1], $args[2]);
  2513. } else {
  2514. throw new InvalidArgumentException(
  2515. 'Securimage_Color constructor expects 0, 1 or 3 arguments; ' . sizeof($args) . ' given'
  2516. );
  2517. }
  2518. }
  2519. /**
  2520. * Construct from an rgb triplet
  2521. *
  2522. * @param int $red The red component, 0-255
  2523. * @param int $green The green component, 0-255
  2524. * @param int $blue The blue component, 0-255
  2525. */
  2526. protected function constructRGB($red, $green, $blue)
  2527. {
  2528. if ($red < 0) $red = 0;
  2529. if ($red > 255) $red = 255;
  2530. if ($green < 0) $green = 0;
  2531. if ($green > 255) $green = 255;
  2532. if ($blue < 0) $blue = 0;
  2533. if ($blue > 255) $blue = 255;
  2534. $this->r = $red;
  2535. $this->g = $green;
  2536. $this->b = $blue;
  2537. }
  2538. /**
  2539. * Construct from an html hex color code
  2540. *
  2541. * @param string $color
  2542. */
  2543. protected function constructHTML($color)
  2544. {
  2545. if (strlen($color) == 3) {
  2546. $red = str_repeat(substr($color, 0, 1), 2);
  2547. $green = str_repeat(substr($color, 1, 1), 2);
  2548. $blue = str_repeat(substr($color, 2, 1), 2);
  2549. } else {
  2550. $red = substr($color, 0, 2);
  2551. $green = substr($color, 2, 2);
  2552. $blue = substr($color, 4, 2);
  2553. }
  2554. $this->r = hexdec($red);
  2555. $this->g = hexdec($green);
  2556. $this->b = hexdec($blue);
  2557. }
  2558. }