all repos — captcha @ 0674e88f74f59174b585f767a122945803bf90f9

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