captcha.go 5.5 KB

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