captcha.go 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. package captcha
  2. import (
  3. "bytes"
  4. "crypto/rand"
  5. "github.com/dchest/uniuri"
  6. "io"
  7. "os"
  8. )
  9. // Standard number of digits in captcha.
  10. const StdLength = 6
  11. var ErrNotFound = os.NewError("captcha with the given id not found")
  12. var globalStore = newStore()
  13. // RandomDigits returns a byte slice of the given length containing random
  14. // digits in range 0-9.
  15. func RandomDigits(length int) []byte {
  16. d := make([]byte, length)
  17. if _, err := io.ReadFull(rand.Reader, d); err != nil {
  18. panic(err)
  19. }
  20. for i := range d {
  21. d[i] %= 10
  22. }
  23. return d
  24. }
  25. // New creates a new captcha of the given length, saves it in the internal
  26. // storage, and returns its id.
  27. func New(length int) (id string) {
  28. id = uniuri.New()
  29. globalStore.saveCaptcha(id, RandomDigits(length))
  30. return
  31. }
  32. // Reload generates and remembers new digits for the given captcha id. This
  33. // function returns false if there is no captcha with the given id.
  34. //
  35. // After calling this function, the image or audio presented to a user must be
  36. // refreshed to show the new captcha representation (WriteImage and WriteAudio
  37. // will write the new one).
  38. func Reload(id string) bool {
  39. old := globalStore.getDigits(id)
  40. if old == nil {
  41. return false
  42. }
  43. globalStore.saveCaptcha(id, RandomDigits(len(old)))
  44. return true
  45. }
  46. // WriteImage writes PNG-encoded image representation of the captcha with the
  47. // given id. The image will have the given width and height.
  48. func WriteImage(w io.Writer, id string, width, height int) os.Error {
  49. d := globalStore.getDigits(id)
  50. if d == nil {
  51. return ErrNotFound
  52. }
  53. _, err := NewImage(d, width, height).WriteTo(w)
  54. return err
  55. }
  56. // WriteAudio writes WAV-encoded audio representation of the captcha with the
  57. // given id.
  58. func WriteAudio(w io.Writer, id string) os.Error {
  59. d := globalStore.getDigits(id)
  60. if d == nil {
  61. return ErrNotFound
  62. }
  63. _, err := NewAudio(d).WriteTo(w)
  64. return err
  65. }
  66. // Verify returns true if the given digits are the ones that were used to
  67. // create the given captcha id.
  68. //
  69. // The function deletes the captcha with the given id from the internal
  70. // storage, so that the same captcha can't be verified anymore.
  71. func Verify(id string, digits []byte) bool {
  72. reald := globalStore.getDigitsClear(id)
  73. if reald == nil {
  74. return false
  75. }
  76. return bytes.Equal(digits, reald)
  77. }
  78. // Collect deletes expired and used captchas from the internal
  79. // storage. It is called automatically by New function every CollectNum
  80. // generated captchas, but still exported to enable freeing memory manually if
  81. // needed.
  82. //
  83. // Collection is launched in a new goroutine, so this function returns
  84. // immediately.
  85. func Collect() {
  86. go globalStore.collect()
  87. }