store.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package captcha
  2. import (
  3. "container/list"
  4. "sync"
  5. "time"
  6. )
  7. // An object implementing Store interface can be registered with SetCustomStore
  8. // function to handle storage and retrieval of captcha ids and solutions for
  9. // them, replacing the default memory store.
  10. type Store interface {
  11. // Set sets the digits for the captcha id.
  12. Set(id string, digits []byte)
  13. // Get returns stored digits for the captcha id. Clear indicates
  14. // whether the captcha must be deleted from the store.
  15. Get(id string, clear bool) (digits []byte)
  16. // Collect deletes expired captchas from the store. For custom stores
  17. // this method is not called automatically, it is only wired to the
  18. // package's Collect function. Custom stores must implement their own
  19. // procedure for calling Collect, for example, in Set method.
  20. Collect()
  21. }
  22. // expValue stores timestamp and id of captchas. It is used in the list inside
  23. // memoryStore for indexing generated captchas by timestamp to enable garbage
  24. // collection of expired captchas.
  25. type expValue struct {
  26. timestamp int64
  27. id string
  28. }
  29. // memoryStore is an internal store for captcha ids and their values.
  30. type memoryStore struct {
  31. mu sync.RWMutex
  32. ids map[string][]byte
  33. exp *list.List
  34. // Number of items stored after last collection.
  35. numStored int
  36. // Number of saved items that triggers collection.
  37. collectNum int
  38. // Expiration time of captchas.
  39. expiration int64
  40. }
  41. // NewMemoryStore returns a new standard memory store for captchas with the
  42. // given collection threshold and expiration time in seconds. The returned
  43. // store must be registered with SetCustomStore to replace the default one.
  44. func NewMemoryStore(collectNum int, expiration int64) Store {
  45. s := new(memoryStore)
  46. s.ids = make(map[string][]byte)
  47. s.exp = list.New()
  48. s.collectNum = collectNum
  49. s.expiration = expiration
  50. return s
  51. }
  52. func (s *memoryStore) Set(id string, digits []byte) {
  53. s.mu.Lock()
  54. s.ids[id] = digits
  55. s.exp.PushBack(expValue{time.Seconds(), id})
  56. s.numStored++
  57. s.mu.Unlock()
  58. if s.numStored > s.collectNum {
  59. go s.Collect()
  60. }
  61. }
  62. func (s *memoryStore) Get(id string, clear bool) (digits []byte) {
  63. if !clear {
  64. // When we don't need to clear captcha, acquire read lock.
  65. s.mu.RLock()
  66. defer s.mu.RUnlock()
  67. } else {
  68. s.mu.Lock()
  69. defer s.mu.Unlock()
  70. }
  71. digits, ok := s.ids[id]
  72. if !ok {
  73. return
  74. }
  75. if clear {
  76. s.ids[id] = nil, false
  77. // XXX(dchest) Index (s.exp) will be cleaned when collecting expired
  78. // captchas. Can't clean it here, because we don't store reference to
  79. // expValue in the map. Maybe store it?
  80. }
  81. return
  82. }
  83. func (s *memoryStore) Collect() {
  84. now := time.Seconds()
  85. s.mu.Lock()
  86. defer s.mu.Unlock()
  87. s.numStored = 0
  88. for e := s.exp.Front(); e != nil; {
  89. ev, ok := e.Value.(expValue)
  90. if !ok {
  91. return
  92. }
  93. if ev.timestamp+s.expiration < now {
  94. s.ids[ev.id] = nil, false
  95. next := e.Next()
  96. s.exp.Remove(e)
  97. e = next
  98. } else {
  99. return
  100. }
  101. }
  102. }