captcha.go 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package captcha
  2. import (
  3. "bytes"
  4. "image/png"
  5. "os"
  6. "rand"
  7. "time"
  8. crand "crypto/rand"
  9. "github.com/dchest/uniuri"
  10. "io"
  11. "container/list"
  12. "sync"
  13. )
  14. const (
  15. dotSize = 6
  16. maxSkew = 3
  17. expiration = 2 * 60 // 2 minutes
  18. collectNum = 100 // number of items that triggers collection
  19. )
  20. type expValue struct {
  21. timestamp int64
  22. id string
  23. }
  24. type storage struct {
  25. mu sync.RWMutex
  26. ids map[string][]byte
  27. exp *list.List
  28. // Number of items stored after last collection
  29. colNum int
  30. }
  31. func newStore() *storage {
  32. s := new(storage)
  33. s.ids = make(map[string][]byte)
  34. s.exp = list.New()
  35. return s
  36. }
  37. var store = newStore()
  38. func init() {
  39. rand.Seed(time.Seconds())
  40. }
  41. func randomNumbers() []byte {
  42. n := make([]byte, 6)
  43. if _, err := io.ReadFull(crand.Reader, n); err != nil {
  44. panic(err)
  45. }
  46. for i := range n {
  47. n[i] %= 10
  48. }
  49. return n
  50. }
  51. func New() string {
  52. ns := randomNumbers()
  53. id := uniuri.New()
  54. store.mu.Lock()
  55. defer store.mu.Unlock()
  56. store.ids[id] = ns
  57. store.exp.PushBack(expValue{time.Seconds(), id})
  58. store.colNum++
  59. if store.colNum > collectNum {
  60. Collect()
  61. store.colNum = 0
  62. }
  63. return id
  64. }
  65. func WriteImage(w io.Writer, id string) os.Error {
  66. store.mu.RLock()
  67. defer store.mu.RUnlock()
  68. ns, ok := store.ids[id]
  69. if !ok {
  70. return os.NewError("captcha id not found")
  71. }
  72. return png.Encode(w, NewImage(ns))
  73. }
  74. func Verify(w io.Writer, id string, ns []byte) bool {
  75. store.mu.Lock()
  76. defer store.mu.Unlock()
  77. realns, ok := store.ids[id]
  78. if !ok {
  79. return false
  80. }
  81. store.ids[id] = nil, false
  82. return bytes.Equal(ns, realns)
  83. }
  84. func Collect() {
  85. now := time.Seconds()
  86. store.mu.Lock()
  87. defer store.mu.Unlock()
  88. for e := store.exp.Front(); e != nil; e = e.Next() {
  89. ev, ok := e.Value.(expValue)
  90. if !ok {
  91. return
  92. }
  93. if ev.timestamp+expiration < now {
  94. store.ids[ev.id] = nil, false
  95. store.exp.Remove(e)
  96. } else {
  97. return
  98. }
  99. }
  100. }