/projects/WebControls/Captcha/CaptchaControl.vb

http://pigeoncms.googlecode.com/ · Visual Basic · 573 lines · 468 code · 63 blank · 42 comment · 3 complexity · 6902a42233563ec1ec8025ddf3ba5f77 MD5 · raw file

  1. Imports System.ComponentModel
  2. Imports System.Web
  3. Imports System.Web.UI
  4. Imports System.Web.UI.WebControls
  5. Imports System.Collections
  6. Imports System.Collections.Specialized
  7. ''' <summary>
  8. ''' CAPTCHA ASP.NET 2.0 user control
  9. ''' </summary>
  10. ''' <remarks>
  11. ''' add a reference to this DLL and add the CaptchaControl to your toolbox;
  12. ''' then just drag and drop the control on a web form and set properties on it.
  13. '''
  14. ''' Jeff Atwood
  15. ''' http://www.codinghorror.com/
  16. ''' </remarks>
  17. <DefaultProperty("Text")> _
  18. Public Class CaptchaControl
  19. Inherits System.Web.UI.WebControls.WebControl
  20. Implements INamingContainer
  21. Implements IPostBackDataHandler
  22. Implements IValidator
  23. Public Enum Layout
  24. Horizontal
  25. Vertical
  26. End Enum
  27. Public Enum CacheType
  28. HttpRuntime
  29. Session
  30. End Enum
  31. Private _timeoutSecondsMax As Integer = 90
  32. Private _timeoutSecondsMin As Integer = 3
  33. Private _userValidated As Boolean = True
  34. Private _text As String = "Enter the code shown:"
  35. Private _font As String = ""
  36. Private _captcha As CaptchaImage = New CaptchaImage
  37. Private _layoutStyle As Layout = Layout.Horizontal
  38. Private _prevguid As String
  39. Private _errorMessage As String = ""
  40. Private _cacheStrategy As CacheType = CacheType.HttpRuntime
  41. #Region " Public Properties"
  42. <Browsable(False), _
  43. Bindable(True), _
  44. Category("Appearance"), _
  45. DefaultValue("The text you typed does not match the text in the image."), _
  46. Description("Message to display in a Validation Summary when the CAPTCHA fails to validate.")> _
  47. Public Property ErrorMessage() As String Implements System.Web.UI.IValidator.ErrorMessage
  48. Get
  49. If Not _userValidated Then
  50. Return _errorMessage
  51. Else
  52. Return ""
  53. End If
  54. End Get
  55. Set(ByVal value As String)
  56. _errorMessage = value
  57. End Set
  58. End Property
  59. <Browsable(False), _
  60. Category("Behavior"), _
  61. DefaultValue(True), _
  62. Description("Is Valid"), _
  63. DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
  64. Public Property IsValid() As Boolean Implements System.Web.UI.IValidator.IsValid
  65. Get
  66. Return _userValidated
  67. End Get
  68. Set(ByVal value As Boolean)
  69. End Set
  70. End Property
  71. Public Overrides Property Enabled() As Boolean
  72. Get
  73. Return MyBase.Enabled
  74. End Get
  75. Set(ByVal value As Boolean)
  76. MyBase.Enabled = value
  77. ' When a validator is disabled, generally, the intent is not to
  78. ' make the page invalid for that round trip.
  79. If Not value Then
  80. _userValidated = True
  81. End If
  82. End Set
  83. End Property
  84. <DefaultValue("Enter the code shown above:"), _
  85. Description("Instructional text displayed next to CAPTCHA image."), _
  86. Category("Appearance")> _
  87. Public Property [Text]() As String
  88. Get
  89. Return _text
  90. End Get
  91. Set(ByVal Value As String)
  92. _text = Value
  93. End Set
  94. End Property
  95. <DefaultValue(GetType(CaptchaControl.Layout), "Horizontal"), _
  96. Description("Determines if image and input area are displayed horizontally, or vertically."), _
  97. Category("Captcha")> _
  98. Public Property LayoutStyle() As Layout
  99. Get
  100. Return _layoutStyle
  101. End Get
  102. Set(ByVal Value As Layout)
  103. _layoutStyle = Value
  104. End Set
  105. End Property
  106. <DefaultValue(GetType(CaptchaControl.CacheType), "HttpRuntime"), _
  107. Description("Determines if CAPTCHA codes are stored in HttpRuntime (fast, but local to current server) or Session (more portable across web farms)."), _
  108. Category("Captcha")> _
  109. Public Property CacheStrategy() As CacheType
  110. Get
  111. Return _cacheStrategy
  112. End Get
  113. Set(ByVal value As CacheType)
  114. _cacheStrategy = value
  115. End Set
  116. End Property
  117. <Description("Returns True if the user was CAPTCHA validated after a postback."), _
  118. Category("Captcha")> _
  119. Public ReadOnly Property UserValidated() As Boolean
  120. Get
  121. Return _userValidated
  122. End Get
  123. End Property
  124. <DefaultValue(""), _
  125. Description("Font used to render CAPTCHA text. If font name is blank, a random font will be chosen."), _
  126. Category("Captcha")> _
  127. Public Property CaptchaFont() As String
  128. Get
  129. Return _font
  130. End Get
  131. Set(ByVal Value As String)
  132. _font = Value
  133. _captcha.Font = _font
  134. End Set
  135. End Property
  136. <DefaultValue(""), _
  137. Description("Characters used to render CAPTCHA text. A character will be picked randomly from the string."), _
  138. Category("Captcha")> _
  139. Public Property CaptchaChars() As String
  140. Get
  141. Return _captcha.TextChars
  142. End Get
  143. Set(ByVal Value As String)
  144. _captcha.TextChars = Value
  145. End Set
  146. End Property
  147. <DefaultValue(5), _
  148. Description("Number of CaptchaChars used in the CAPTCHA text"), _
  149. Category("Captcha")> _
  150. Public Property CaptchaLength() As Integer
  151. Get
  152. Return _captcha.TextLength
  153. End Get
  154. Set(ByVal Value As Integer)
  155. _captcha.TextLength = Value
  156. End Set
  157. End Property
  158. <DefaultValue(2), _
  159. Description("Minimum number of seconds CAPTCHA must be displayed before it is valid. If you're too fast, you must be a robot. Set to zero to disable."), _
  160. Category("Captcha")> _
  161. Public Property CaptchaMinTimeout() As Integer
  162. Get
  163. Return _timeoutSecondsMin
  164. End Get
  165. Set(ByVal Value As Integer)
  166. If Value > 15 Then
  167. Throw New ArgumentOutOfRangeException("CaptchaTimeout", "Timeout must be less than 15 seconds. Humans aren't that slow!")
  168. End If
  169. _timeoutSecondsMin = Value
  170. End Set
  171. End Property
  172. <DefaultValue(90), _
  173. Description("Maximum number of seconds CAPTCHA will be cached and valid. If you're too slow, you may be a CAPTCHA hack attempt. Set to zero to disable."), _
  174. Category("Captcha")> _
  175. Public Property CaptchaMaxTimeout() As Integer
  176. Get
  177. Return _timeoutSecondsMax
  178. End Get
  179. Set(ByVal Value As Integer)
  180. If Value < 15 And Value <> 0 Then
  181. Throw New ArgumentOutOfRangeException("CaptchaTimeout", "Timeout must be greater than 15 seconds. Humans can't type that fast!")
  182. End If
  183. _timeoutSecondsMax = Value
  184. End Set
  185. End Property
  186. <DefaultValue(50), _
  187. Description("Height of generated CAPTCHA image."), _
  188. Category("Captcha")> _
  189. Public Property CaptchaHeight() As Integer
  190. Get
  191. Return _captcha.Height
  192. End Get
  193. Set(ByVal Value As Integer)
  194. _captcha.Height = Value
  195. End Set
  196. End Property
  197. <DefaultValue(180), _
  198. Description("Width of generated CAPTCHA image."), _
  199. Category("Captcha")> _
  200. Public Property CaptchaWidth() As Integer
  201. Get
  202. Return _captcha.Width
  203. End Get
  204. Set(ByVal Value As Integer)
  205. _captcha.Width = Value
  206. End Set
  207. End Property
  208. <DefaultValue(GetType(CaptchaImage.FontWarpFactor), "Low"), _
  209. Description("Amount of random font warping used on the CAPTCHA text"), _
  210. Category("Captcha")> _
  211. Public Property CaptchaFontWarping() As CaptchaImage.FontWarpFactor
  212. Get
  213. Return _captcha.FontWarp
  214. End Get
  215. Set(ByVal Value As CaptchaImage.FontWarpFactor)
  216. _captcha.FontWarp = Value
  217. End Set
  218. End Property
  219. <DefaultValue(GetType(CaptchaImage.BackgroundNoiseLevel), "Low"), _
  220. Description("Amount of background noise to generate in the CAPTCHA image"), _
  221. Category("Captcha")> _
  222. Public Property CaptchaBackgroundNoise() As CaptchaImage.BackgroundNoiseLevel
  223. Get
  224. Return _captcha.BackgroundNoise
  225. End Get
  226. Set(ByVal Value As CaptchaImage.BackgroundNoiseLevel)
  227. _captcha.BackgroundNoise = Value
  228. End Set
  229. End Property
  230. <DefaultValue(GetType(CaptchaImage.LineNoiseLevel), "None"), _
  231. Description("Add line noise to the CAPTCHA image"), _
  232. Category("Captcha")> _
  233. Public Property CaptchaLineNoise() As CaptchaImage.LineNoiseLevel
  234. Get
  235. Return _captcha.LineNoise
  236. End Get
  237. Set(ByVal Value As CaptchaImage.LineNoiseLevel)
  238. _captcha.LineNoise = Value
  239. End Set
  240. End Property
  241. #End Region
  242. Public Sub Validate() Implements System.Web.UI.IValidator.Validate
  243. '-- a no-op, since we validate in LoadPostData
  244. End Sub
  245. Private Function GetCachedCaptcha(ByVal guid As String) As CaptchaImage
  246. If _cacheStrategy = CacheType.HttpRuntime Then
  247. Return CType(HttpRuntime.Cache.Get(guid), CaptchaImage)
  248. Else
  249. Return CType(HttpContext.Current.Session.Item(guid), CaptchaImage)
  250. End If
  251. End Function
  252. Private Sub RemoveCachedCaptcha(ByVal guid As String)
  253. If _cacheStrategy = CacheType.HttpRuntime Then
  254. HttpRuntime.Cache.Remove(guid)
  255. Else
  256. HttpContext.Current.Session.Remove(guid)
  257. End If
  258. End Sub
  259. ''' <summary>
  260. ''' are we in design mode?
  261. ''' </summary>
  262. Private ReadOnly Property IsDesignMode() As Boolean
  263. Get
  264. Return HttpContext.Current Is Nothing
  265. End Get
  266. End Property
  267. ''' <summary>
  268. ''' Validate the user's text against the CAPTCHA text
  269. ''' </summary>
  270. Private Sub ValidateCaptcha(ByVal userEntry As String)
  271. If Not Visible Or Not Enabled Then
  272. _userValidated = True
  273. Return
  274. End If
  275. '-- retrieve the previous captcha from the cache to inspect its properties
  276. Dim ci As CaptchaImage = GetCachedCaptcha(_prevguid)
  277. If ci Is Nothing Then
  278. Me.ErrorMessage = "The code you typed has expired after " & Me.CaptchaMaxTimeout & " seconds."
  279. _userValidated = False
  280. Return
  281. End If
  282. '-- was it entered too quickly?
  283. If Me.CaptchaMinTimeout > 0 Then
  284. If (ci.RenderedAt.AddSeconds(Me.CaptchaMinTimeout) > Now) Then
  285. _userValidated = False
  286. Me.ErrorMessage = "Code was typed too quickly. Wait at least " & Me.CaptchaMinTimeout & " seconds."
  287. RemoveCachedCaptcha(_prevguid)
  288. Return
  289. End If
  290. End If
  291. If String.Compare(userEntry, ci.Text, True) <> 0 Then
  292. Me.ErrorMessage = "The code you typed does not match the code in the image."
  293. _userValidated = False
  294. RemoveCachedCaptcha(_prevguid)
  295. Return
  296. End If
  297. _userValidated = True
  298. RemoveCachedCaptcha(_prevguid)
  299. End Sub
  300. ''' <summary>
  301. ''' returns HTML-ized color strings
  302. ''' </summary>
  303. Private Function HtmlColor(ByVal color As Drawing.Color) As String
  304. If color.IsEmpty Then Return ""
  305. If color.IsNamedColor Then
  306. Return color.ToKnownColor.ToString
  307. End If
  308. If color.IsSystemColor Then
  309. Return color.ToString
  310. End If
  311. Return "#" & color.ToArgb.ToString("x").Substring(2)
  312. End Function
  313. ''' <summary>
  314. ''' returns css "style=" tag for this control
  315. ''' based on standard control visual properties
  316. ''' </summary>
  317. Private Function CssStyle() As String
  318. Dim sb As New System.Text.StringBuilder
  319. Dim strColor As String
  320. With sb
  321. .Append(" style='")
  322. If BorderWidth.ToString.Length > 0 Then
  323. .Append("border-width:")
  324. .Append(BorderWidth.ToString)
  325. .Append(";")
  326. End If
  327. If BorderStyle <> WebControls.BorderStyle.NotSet Then
  328. .Append("border-style:")
  329. .Append(BorderStyle.ToString)
  330. .Append(";")
  331. End If
  332. strColor = HtmlColor(BorderColor)
  333. If strColor.Length > 0 Then
  334. .Append("border-color:")
  335. .Append(strColor)
  336. .Append(";")
  337. End If
  338. strColor = HtmlColor(BackColor)
  339. If strColor.Length > 0 Then
  340. .Append("background-color:" & strColor & ";")
  341. End If
  342. strColor = HtmlColor(ForeColor)
  343. If strColor.Length > 0 Then
  344. .Append("color:" & strColor & ";")
  345. End If
  346. If Font.Bold Then
  347. .Append("font-weight:bold;")
  348. End If
  349. If Font.Italic Then
  350. .Append("font-style:italic;")
  351. End If
  352. If Font.Underline Then
  353. .Append("text-decoration:underline;")
  354. End If
  355. If Font.Strikeout Then
  356. .Append("text-decoration:line-through;")
  357. End If
  358. If Font.Overline Then
  359. .Append("text-decoration:overline;")
  360. End If
  361. If Font.Size.ToString.Length > 0 Then
  362. .Append("font-size:" & Font.Size.ToString & ";")
  363. End If
  364. If Font.Names.Length > 0 Then
  365. Dim strFontFamily As String
  366. .Append("font-family:")
  367. For Each strFontFamily In Font.Names
  368. .Append(strFontFamily)
  369. .Append(",")
  370. Next
  371. .Length = .Length - 1
  372. .Append(";")
  373. End If
  374. If Height.ToString <> "" Then
  375. .Append("height:" & Height.ToString & ";")
  376. End If
  377. If Width.ToString <> "" Then
  378. .Append("width:" & Width.ToString & ";")
  379. End If
  380. .Append("'")
  381. End With
  382. If sb.ToString = " style=''" Then
  383. Return ""
  384. Else
  385. Return sb.ToString
  386. End If
  387. End Function
  388. ''' <summary>
  389. ''' render raw control HTML to the page
  390. ''' </summary>
  391. Protected Overrides Sub Render(ByVal Output As HtmlTextWriter)
  392. With Output
  393. '-- master DIV
  394. .Write("<div")
  395. If CssClass <> "" Then
  396. .Write(" class='" & CssClass & "'")
  397. End If
  398. .Write(CssStyle)
  399. .Write(">")
  400. '-- image DIV/SPAN
  401. If Me.LayoutStyle = Layout.Vertical Then
  402. .Write("<div style='text-align:center;margin:5px;'>")
  403. Else
  404. .Write("<span style='margin:5px;float:left;'>")
  405. End If
  406. '-- this is the URL that triggers the CaptchaImageHandler
  407. If Not IsDesignMode Then
  408. .Write("<img src='" & VirtualPathUtility.ToAbsolute("~/") & "Handlers/CaptchaImage.aspx")
  409. .Write("?guid=" & Convert.ToString(_captcha.UniqueId))
  410. Else
  411. .Write("<img src='CaptchaControl.bmp?1=1")
  412. End If
  413. If Me.CacheStrategy = CacheType.Session Then
  414. .Write("&s=1")
  415. End If
  416. .Write("' border='0'")
  417. If ToolTip.Length > 0 Then
  418. .Write(" alt='" & ToolTip & "'")
  419. End If
  420. .Write(" width=" & _captcha.Width)
  421. .Write(" height=" & _captcha.Height)
  422. .Write(">")
  423. If Me.LayoutStyle = Layout.Vertical Then
  424. .Write("</div>")
  425. Else
  426. .Write("</span>")
  427. End If
  428. '-- text input and submit button DIV/SPAN
  429. If Me.LayoutStyle = Layout.Vertical Then
  430. .Write("<div style='text-align:center;margin:5px;'>")
  431. Else
  432. .Write("<span style='margin:5px;float:left;'>")
  433. End If
  434. If _text.Length > 0 Then
  435. .Write(_text)
  436. .Write("<br />")
  437. End If
  438. .Write("<input name=" & UniqueID & " type='text' size=")
  439. .Write(_captcha.TextLength.ToString)
  440. .Write(" maxlength=")
  441. .Write(_captcha.TextLength.ToString)
  442. If AccessKey.Length > 0 Then
  443. .Write(" accesskey=" & AccessKey)
  444. End If
  445. If Not Enabled Then
  446. .Write(" disabled=""disabled""")
  447. End If
  448. If TabIndex > 0 Then
  449. .Write(" tabindex=" & TabIndex.ToString)
  450. End If
  451. .Write(" value=''>")
  452. If Me.LayoutStyle = Layout.Vertical Then
  453. .Write("</div>")
  454. Else
  455. .Write("</span>")
  456. .Write("<br clear='all'>")
  457. End If
  458. '-- closing tag for master DIV
  459. .Write("</div>")
  460. End With
  461. End Sub
  462. ''' <summary>
  463. ''' generate a new captcha and store it in the ASP.NET Cache by unique GUID
  464. ''' </summary>
  465. Private Sub GenerateNewCaptcha()
  466. If Not IsDesignMode Then
  467. If _cacheStrategy = CacheType.HttpRuntime Then
  468. HttpRuntime.Cache.Add(_captcha.UniqueId, _captcha, Nothing, _
  469. DateTime.Now.AddSeconds(Convert.ToDouble(IIf(Me.CaptchaMaxTimeout = 0, 90, Me.CaptchaMaxTimeout))), _
  470. TimeSpan.Zero, Caching.CacheItemPriority.NotRemovable, Nothing)
  471. Else
  472. HttpContext.Current.Session.Add(_captcha.UniqueId, _captcha)
  473. End If
  474. End If
  475. End Sub
  476. ''' <summary>
  477. ''' Retrieve the user's CAPTCHA input from the posted data
  478. ''' </summary>
  479. Public Function LoadPostData(ByVal PostDataKey As String, ByVal Values As NameValueCollection) As Boolean Implements IPostBackDataHandler.LoadPostData
  480. ValidateCaptcha(Convert.ToString(Values(Me.UniqueID)))
  481. Return False
  482. End Function
  483. Public Sub RaisePostDataChangedEvent() Implements IPostBackDataHandler.RaisePostDataChangedEvent
  484. End Sub
  485. Protected Overrides Function SaveControlState() As Object
  486. Return CType(_captcha.UniqueId, Object)
  487. End Function
  488. Protected Overrides Sub LoadControlState(ByVal state As Object)
  489. If state IsNot Nothing Then
  490. _prevguid = CType(state, String)
  491. End If
  492. End Sub
  493. Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
  494. MyBase.OnInit(e)
  495. Page.RegisterRequiresControlState(Me)
  496. Page.Validators.Add(Me)
  497. End Sub
  498. Protected Overrides Sub OnUnload(ByVal e As System.EventArgs)
  499. If Not (Page Is Nothing) Then
  500. Page.Validators.Remove(Me)
  501. End If
  502. MyBase.OnUnload(e)
  503. End Sub
  504. Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)
  505. If Me.Visible Then
  506. GenerateNewCaptcha()
  507. End If
  508. MyBase.OnPreRender(e)
  509. End Sub
  510. End Class