all repos — captcha @ b04efdccea61eed956f4e924eb31848a581b790b

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 + 3) * 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) + 8
 59	}
 60	drawCirclesLine(img, color)
 61	return img
 62}
 63
 64func init() {
 65	rand.Seed(time.Seconds())
 66}
 67
 68func randomNumbers() []byte {
 69	n := make([]byte, 6)
 70	if _, err := io.ReadFull(crand.Reader, n); err != nil {
 71		panic(err)
 72	}
 73	for i := range n {
 74		n[i] %= 10
 75	}
 76	return n
 77}
 78
 79func Encode(w io.Writer) (numbers []byte, err os.Error) {
 80	numbers = randomNumbers()
 81	err = png.Encode(w, NewImage(numbers))
 82	return
 83}
 84
 85func New() string {
 86	ns := randomNumbers()
 87	id := uniuri.New()
 88	store.mu.Lock()
 89	defer store.mu.Unlock()
 90	store.ids[id] = ns
 91	store.exp.PushBack(expValue{time.Seconds(), id})
 92	store.colNum++
 93	if store.colNum > collectNum {
 94		Collect()
 95		store.colNum = 0
 96	}
 97	return id
 98}
 99
100func WriteImage(w io.Writer, id string) os.Error {
101	store.mu.RLock()
102	defer store.mu.RUnlock()
103	ns, ok := store.ids[id]
104	if !ok {
105		return os.NewError("captcha id not found")
106	}
107	return png.Encode(w, NewImage(ns))
108}
109
110func Verify(w io.Writer, id string, ns []byte) bool {
111	store.mu.Lock()
112	defer store.mu.Unlock()
113	realns, ok := store.ids[id]
114	if !ok {
115		return false
116	}
117	store.ids[id] = nil, false
118	return bytes.Equal(ns, realns)
119}
120
121func Collect() {
122	now := time.Seconds()
123	store.mu.Lock()
124	defer store.mu.Unlock()
125	for e := store.exp.Front(); e != nil; e = e.Next() {
126		ev, ok := e.Value.(expValue)
127		if !ok {
128			return
129		}
130		if ev.timestamp+expiration < now {
131			store.ids[ev.id] = nil, false
132			store.exp.Remove(e)
133		} else {
134			return
135		}
136	}
137}