all repos — captcha @ 4559a6b5a4bba143b8de700b66be599f47b4679a

Go package captcha implements generation and verification of image and audio CAPTCHAs.

store.go (view raw)

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