all repos — captcha @ f9f7db1f433c5e6c539629a98e1def6e6be38f79

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