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}