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}