all repos — captcha @ 364b4304db2a30e1940ea73035bf1b8c4f4f68bf

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

captcha.go (view raw)

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