captcha.go 2.8 KB

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