all repos — captcha @ caf79f3bd2e22476ecba6f08fb28db4afaf44c17

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

captcha.go (view raw)

  1package captcha
  2
  3import (
  4	"bytes"
  5	"image"
  6	"image/png"
  7	"os"
  8	"rand"
  9	"time"
 10	crand "crypto/rand"
 11	"github.com/dchest/uniuri"
 12	"io"
 13	"container/list"
 14	"sync"
 15)
 16
 17const (
 18	dotSize    = 6
 19	maxSkew    = 3
 20	expiration = 2 * 60 // 2 minutes
 21	collectNum = 100    // number of items that triggers collection
 22)
 23
 24type expValue struct {
 25	timestamp int64
 26	id        string
 27}
 28
 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 NewImage(numbers []byte) *image.NRGBA {
 47	w := numberWidth * (dotSize + 2) * len(numbers)
 48	h := numberHeight * (dotSize + 5)
 49	img := image.NewNRGBA(w, h)
 50	color := image.NRGBAColor{uint8(rand.Intn(50)), uint8(rand.Intn(50)), uint8(rand.Intn(128)), 0xFF}
 51	fillWithCircles(img, color, 40, 4)
 52	x := rand.Intn(dotSize)
 53	y := 0
 54	setRandomBrightness(&color, 180)
 55	for _, n := range numbers {
 56		y = rand.Intn(dotSize * 4)
 57		drawNumber(img, font[n], x, y, color)
 58		x += dotSize*numberWidth + rand.Intn(maxSkew) + 3
 59	}
 60	return img
 61}
 62
 63func init() {
 64	rand.Seed(time.Seconds())
 65}
 66
 67func randomNumbers() []byte {
 68	n := make([]byte, 6)
 69	if _, err := io.ReadFull(crand.Reader, n); err != nil {
 70		panic(err)
 71	}
 72	for i := range n {
 73		n[i] %= 10
 74	}
 75	return n
 76}
 77
 78func Encode(w io.Writer) (numbers []byte, err os.Error) {
 79	numbers = randomNumbers()
 80	err = png.Encode(w, NewImage(numbers))
 81	return
 82}
 83
 84func New() string {
 85	ns := randomNumbers()
 86	id := uniuri.New()
 87	store.mu.Lock()
 88	defer store.mu.Unlock()
 89	store.ids[id] = ns
 90	store.exp.PushBack(expValue{time.Seconds(), id})
 91	store.colNum++
 92	if store.colNum > collectNum {
 93		Collect()
 94		store.colNum = 0
 95	}
 96	return id
 97}
 98
 99func WriteImage(w io.Writer, id string) os.Error {
100	store.mu.RLock()
101	defer store.mu.RUnlock()
102	ns, ok := store.ids[id]
103	if !ok {
104		return os.NewError("captcha id not found")
105	}
106	return png.Encode(w, NewImage(ns))
107}
108
109func Verify(w io.Writer, id string, ns []byte) bool {
110	store.mu.Lock()
111	defer store.mu.Unlock()
112	realns, ok := store.ids[id]
113	if !ok {
114		return false
115	}
116	store.ids[id] = nil, false
117	return bytes.Equal(ns, realns)
118}
119
120func Collect() {
121	now := time.Seconds()
122	store.mu.Lock()
123	defer store.mu.Unlock()
124	for e := store.exp.Front(); e != nil; e = e.Next() {
125		ev, ok := e.Value.(expValue)
126		if !ok {
127			return
128		}
129		if ev.timestamp+expiration < now {
130			store.ids[ev.id] = nil, false
131			store.exp.Remove(e)
132		} else {
133			return
134		}
135	}
136}