all repos — captcha @ master

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

store.go (view raw)

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