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}