all repos — captcha @ d88ce81917aa0fc9629bb41df9646ff90edd2250

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

captcha.go (view raw)

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