captcha.go (view raw)
1package captcha
2
3import (
4 "bytes"
5 "os"
6 "rand"
7 "time"
8 crand "crypto/rand"
9 "github.com/dchest/uniuri"
10 "io"
11 "container/list"
12 "sync"
13)
14
15const (
16 expiration = 2 * 60 // 2 minutes
17 collectNum = 100 // number of items that triggers collection
18)
19
20type expValue struct {
21 timestamp int64
22 id string
23}
24
25type storage struct {
26 mu sync.RWMutex
27 ids map[string][]byte
28 exp *list.List
29 // Number of items stored after last collection
30 colNum int
31}
32
33func newStore() *storage {
34 s := new(storage)
35 s.ids = make(map[string][]byte)
36 s.exp = list.New()
37 return s
38}
39
40var store = newStore()
41
42func init() {
43 rand.Seed(time.Seconds())
44}
45
46func randomNumbers() []byte {
47 n := make([]byte, 3)
48 if _, err := io.ReadFull(crand.Reader, n); err != nil {
49 panic(err)
50 }
51 for i := range n {
52 n[i] %= 10
53 }
54 return n
55}
56
57func New() string {
58 ns := randomNumbers()
59 id := uniuri.New()
60 store.mu.Lock()
61 defer store.mu.Unlock()
62 store.ids[id] = ns
63 store.exp.PushBack(expValue{time.Seconds(), id})
64 store.colNum++
65 if store.colNum > collectNum {
66 Collect()
67 store.colNum = 0
68 }
69 return id
70}
71
72func WriteImage(w io.Writer, id string, width, height int) os.Error {
73 store.mu.RLock()
74 defer store.mu.RUnlock()
75 ns, ok := store.ids[id]
76 if !ok {
77 return os.NewError("captcha id not found")
78 }
79 return NewImage(ns, width, height).PNGEncode(w)
80}
81
82func Verify(w io.Writer, id string, ns []byte) bool {
83 store.mu.Lock()
84 defer store.mu.Unlock()
85 realns, ok := store.ids[id]
86 if !ok {
87 return false
88 }
89 store.ids[id] = nil, false
90 return bytes.Equal(ns, realns)
91}
92
93func Collect() {
94 now := time.Seconds()
95 store.mu.Lock()
96 defer store.mu.Unlock()
97 for e := store.exp.Front(); e != nil; e = e.Next() {
98 ev, ok := e.Value.(expValue)
99 if !ok {
100 return
101 }
102 if ev.timestamp+expiration < now {
103 store.ids[ev.id] = nil, false
104 store.exp.Remove(e)
105 } else {
106 return
107 }
108 }
109}