all repos — captcha @ 01012728403bd41ed7ecf55aca32d459dada2eb6

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
 20// expValue stores timestamp and id of captchas. It is used in a list inside
 21// storage for indexing generated captchas by timestamp to enable garbage
 22// collection of expired captchas.
 23type expValue struct {
 24	timestamp int64
 25	id        string
 26}
 27
 28// storage is an internal storage for captcha ids and their values.
 29type storage struct {
 30	mu  sync.RWMutex
 31	ids map[string][]byte
 32	exp *list.List
 33	// Number of items stored after last collection
 34	colNum int
 35}
 36
 37func newStore() *storage {
 38	s := new(storage)
 39	s.ids = make(map[string][]byte)
 40	s.exp = list.New()
 41	return s
 42}
 43
 44var store = newStore()
 45
 46func init() {
 47	rand.Seed(time.Seconds())
 48}
 49
 50func randomNumbers() []byte {
 51	n := make([]byte, 6)
 52	if _, err := io.ReadFull(crand.Reader, n); err != nil {
 53		panic(err)
 54	}
 55	for i := range n {
 56		n[i] %= 10
 57	}
 58	return n
 59}
 60
 61// New creates a new captcha, saves it in internal storage, and returns its id.
 62func New() string {
 63	ns := randomNumbers()
 64	id := uniuri.New()
 65	store.mu.Lock()
 66	defer store.mu.Unlock()
 67	store.ids[id] = ns
 68	store.exp.PushBack(expValue{time.Seconds(), id})
 69	store.colNum++
 70	if store.colNum > collectNum {
 71		Collect()
 72		store.colNum = 0
 73	}
 74	return id
 75}
 76
 77// WriteImage writes PNG-encoded captcha image of the given width and height
 78// with the given captcha id into the io.Writer.
 79func WriteImage(w io.Writer, id string, width, height int) os.Error {
 80	store.mu.RLock()
 81	defer store.mu.RUnlock()
 82	ns, ok := store.ids[id]
 83	if !ok {
 84		return os.NewError("captcha id not found")
 85	}
 86	return NewImage(ns, width, height).PNGEncode(w)
 87}
 88
 89// Verify returns true if the given numbers are the numbers that were used in
 90// the given captcha id.
 91func Verify(id string, numbers []byte) bool {
 92	store.mu.Lock()
 93	defer store.mu.Unlock()
 94	realns, ok := store.ids[id]
 95	if !ok {
 96		return false
 97	}
 98	store.ids[id] = nil, false
 99	return bytes.Equal(numbers, realns)
100}
101
102// Collect garbage-collects expired and used captchas from the internal
103// storage. It is called automatically by New function every CollectNum
104// generated captchas, but still exported to enable freeing memory manually if
105// needed.
106func Collect() {
107	now := time.Seconds()
108	store.mu.Lock()
109	defer store.mu.Unlock()
110	for e := store.exp.Front(); e != nil; e = e.Next() {
111		ev, ok := e.Value.(expValue)
112		if !ok {
113			return
114		}
115		if ev.timestamp+expiration < now {
116			store.ids[ev.id] = nil, false
117			store.exp.Remove(e)
118		} else {
119			return
120		}
121	}
122}