captcha.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // Copyright 2011 Dmitry Chestnykh. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. // Package captcha implements generation and verification of image and audio
  5. // CAPTCHAs.
  6. //
  7. // A captcha solution is the sequence of digits 0-9 with the defined length.
  8. // There are two captcha representations: image and audio.
  9. //
  10. // An image representation is a PNG-encoded image with the solution printed on
  11. // it in such a way that makes it hard for computers to solve it using OCR.
  12. //
  13. // An audio representation is a WAVE-encoded (8 kHz unsigned 8-bit) sound with
  14. // the spoken solution (currently in English, Russian, Chinese, and Japanese).
  15. // To make it hard for computers to solve audio captcha, the voice that
  16. // pronounces numbers has random speed and pitch, and there is a randomly
  17. // generated background noise mixed into the sound.
  18. //
  19. // This package doesn't require external files or libraries to generate captcha
  20. // representations; it is self-contained.
  21. //
  22. // To make captchas one-time, the package includes a memory storage that stores
  23. // captcha ids, their solutions, and expiration time. Used captchas are removed
  24. // from the store immediately after calling Verify or VerifyString, while
  25. // unused captchas (user loaded a page with captcha, but didn't submit the
  26. // form) are collected automatically after the predefined expiration time.
  27. // Developers can also provide custom store (for example, which saves captcha
  28. // ids and solutions in database) by implementing Store interface and
  29. // registering the object with SetCustomStore.
  30. //
  31. // Captchas are created by calling New, which returns the captcha id. Their
  32. // representations, though, are created on-the-fly by calling WriteImage or
  33. // WriteAudio functions. Created representations are not stored anywhere, but
  34. // subsequent calls to these functions with the same id will write the same
  35. // captcha solution. Reload function will create a new different solution for
  36. // the provided captcha, allowing users to "reload" captcha if they can't solve
  37. // the displayed one without reloading the whole page. Verify and VerifyString
  38. // are used to verify that the given solution is the right one for the given
  39. // captcha id.
  40. //
  41. // Server provides an http.Handler which can serve image and audio
  42. // representations of captchas automatically from the URL. It can also be used
  43. // to reload captchas. Refer to Server function documentation for details, or
  44. // take a look at the example in "capexample" subdirectory.
  45. package captcha
  46. import (
  47. "bytes"
  48. "errors"
  49. "io"
  50. "time"
  51. )
  52. const (
  53. // Default number of digits in captcha solution.
  54. DefaultLen = 6
  55. // The number of captchas created that triggers garbage collection used
  56. // by default store.
  57. CollectNum = 100
  58. // Expiration time of captchas used by default store.
  59. Expiration = 10 * time.Minute
  60. )
  61. var (
  62. ErrNotFound = errors.New("captcha: id not found")
  63. // globalStore is a shared storage for captchas, generated by New function.
  64. globalStore = NewMemoryStore(CollectNum, Expiration)
  65. )
  66. // SetCustomStore sets custom storage for captchas, replacing the default
  67. // memory store. This function must be called before generating any captchas.
  68. func SetCustomStore(s Store) {
  69. globalStore = s
  70. }
  71. // New creates a new captcha with the standard length, saves it in the internal
  72. // storage and returns its id.
  73. func New() string {
  74. return NewLen(DefaultLen)
  75. }
  76. // NewLen is just like New, but accepts length of a captcha solution as the
  77. // argument.
  78. func NewLen(length int) (id string) {
  79. id = randomId()
  80. globalStore.Set(id, RandomDigits(length))
  81. return
  82. }
  83. // Reload generates and remembers new digits for the given captcha id. This
  84. // function returns false if there is no captcha with the given id.
  85. //
  86. // After calling this function, the image or audio presented to a user must be
  87. // refreshed to show the new captcha representation (WriteImage and WriteAudio
  88. // will write the new one).
  89. func Reload(id string) bool {
  90. old := globalStore.Get(id, false)
  91. if old == nil {
  92. return false
  93. }
  94. globalStore.Set(id, RandomDigits(len(old)))
  95. return true
  96. }
  97. // WriteImage writes PNG-encoded image representation of the captcha with the
  98. // given id. The image will have the given width and height.
  99. func WriteImage(w io.Writer, id string, width, height int) error {
  100. d := globalStore.Get(id, false)
  101. if d == nil {
  102. return ErrNotFound
  103. }
  104. _, err := NewImage(id, d, width, height).WriteTo(w)
  105. return err
  106. }
  107. func WriteImage2(w io.Writer, id string, width, height int) error {
  108. d := globalStore.Get(id, false)
  109. if d == nil {
  110. return ErrNotFound
  111. }
  112. _, err := NewImage2(id, d, width, height).WriteTo(w)
  113. return err
  114. }
  115. // WriteAudio writes WAV-encoded audio representation of the captcha with the
  116. // given id and the given language. If there are no sounds for the given
  117. // language, English is used.
  118. func WriteAudio(w io.Writer, id string, lang string) error {
  119. d := globalStore.Get(id, false)
  120. if d == nil {
  121. return ErrNotFound
  122. }
  123. _, err := NewAudio(id, d, lang).WriteTo(w)
  124. return err
  125. }
  126. // Verify returns true if the given digits are the ones that were used to
  127. // create the given captcha id.
  128. //
  129. // The function deletes the captcha with the given id from the internal
  130. // storage, so that the same captcha can't be verified anymore.
  131. func Verify(id string, digits []byte) bool {
  132. if digits == nil || len(digits) == 0 {
  133. return false
  134. }
  135. reald := globalStore.Get(id, true)
  136. if reald == nil {
  137. return false
  138. }
  139. return bytes.Equal(digits, reald)
  140. }
  141. // VerifyString is like Verify, but accepts a string of digits. It removes
  142. // spaces and commas from the string, but any other characters, apart from
  143. // digits and listed above, will cause the function to return false.
  144. func VerifyString(id string, digits string) bool {
  145. if digits == "" {
  146. return false
  147. }
  148. ns := make([]byte, len(digits))
  149. for i := range ns {
  150. d := digits[i]
  151. switch {
  152. case '0' <= d && d <= '9':
  153. ns[i] = d - '0'
  154. case d == ' ' || d == ',':
  155. // ignore
  156. default:
  157. return false
  158. }
  159. }
  160. return Verify(id, ns)
  161. }
  162. func VerifyNew(id string, digits []byte) bool {
  163. if digits == nil || len(digits) == 0 {
  164. return false
  165. }
  166. reald := globalStore.Get(id, false)
  167. if reald == nil {
  168. return false
  169. }
  170. ret := bytes.Equal(digits, reald)
  171. if ret {
  172. globalStore.Get(id, true)
  173. }
  174. return ret
  175. }
  176. func VerifyStringNew(id string, digits string) bool {
  177. if digits == "" {
  178. return false
  179. }
  180. ns := make([]byte, len(digits))
  181. for i := range ns {
  182. d := digits[i]
  183. switch {
  184. case '0' <= d && d <= '9':
  185. ns[i] = d - '0'
  186. case d == ' ' || d == ',':
  187. // ignore
  188. default:
  189. return false
  190. }
  191. }
  192. return VerifyNew(id, ns)
  193. }