forked from dchest/captcha
-
Notifications
You must be signed in to change notification settings - Fork 0
/
store.go
155 lines (145 loc) · 4.11 KB
/
store.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Copyright 2011 Dmitry Chestnykh. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package captcha
import (
"container/list"
"sync"
"time"
)
// Store An object implementing Store interface can be registered with SetCustomStore
// function to handle storage and retrieval of captcha ids and solutions for
// them, replacing the default memory store.
//
// It is the responsibility of an object to delete expired and used captchas
// when necessary (for example, the default memory store collects them in Set
// method after the certain amount of captchas has been stored.)
type Store interface {
// Set sets the digits for the captcha id.
Set(id string, digits []byte, maxcheckCnt int, binddata string)
// Get returns stored digits for the captcha id. Clear indicates
// whether the captcha must be deleted from the store.
Get(id string, clear bool) (digits []byte, maxCheckCnt int, binddata string)
GetForID(id string) (digits []byte, maxCheckCnt int, checkCnt int, binddata string)
Clear(id string)
}
// expValue stores timestamp and id of captchas. It is used in the list inside
// memoryStore for indexing generated captchas by timestamp to enable garbage
// collection of expired captchas.
type idByTimeValue struct {
timestamp time.Time
id string
}
type idValue struct {
value []byte
checkCnt int
maxCheckCnt int
binddata string
}
// memoryStore is an internal store for captcha ids and their values.
type memoryStore struct {
sync.RWMutex
digitsByID map[string]*idValue
idByTime *list.List
// Number of items stored since last collection.
numStored int
// Number of saved items that triggers collection.
collectNum int
// Expiration time of captchas.
expiration time.Duration
}
// NewMemoryStore returns a new standard memory store for captchas with the
// given collection threshold and expiration time (duration). The returned
// store must be registered with SetCustomStore to replace the default one.
func NewMemoryStore(collectNum int, expiration time.Duration) Store {
s := new(memoryStore)
s.digitsByID = make(map[string]*idValue)
s.idByTime = list.New()
s.collectNum = collectNum
s.expiration = expiration
return s
}
func (s *memoryStore) Set(id string, digits []byte, maxcheckCnt int, binddata string) {
s.Lock()
s.digitsByID[id] = &idValue{value: digits, maxCheckCnt: maxcheckCnt, binddata: binddata}
s.idByTime.PushBack(idByTimeValue{time.Now(), id})
s.numStored++
if s.numStored <= s.collectNum {
s.Unlock()
return
}
s.Unlock()
go s.collect()
}
func (s *memoryStore) GetForID(id string) (digits []byte, maxCheckCnt int, checkCnt int, binddata string) {
s.RLock()
defer s.RUnlock()
val, ok := s.digitsByID[id]
if !ok {
return
}
return val.value, val.maxCheckCnt, val.checkCnt, val.binddata
}
func (s *memoryStore) Clear(id string) {
s.Lock()
defer s.Unlock()
_, ok := s.digitsByID[id]
if !ok {
return
}
delete(s.digitsByID, id)
}
func (s *memoryStore) Get(id string, clear bool) (digits []byte, maxCheckCnt int, binddata string) {
if !clear {
// When we don't need to clear captcha, acquire read lock.
s.RLock()
defer s.RUnlock()
} else {
s.Lock()
defer s.Unlock()
}
val, ok := s.digitsByID[id]
if !ok {
return
}
maxCheckCnt = val.maxCheckCnt
binddata = val.binddata
if !clear && val.checkCnt < val.maxCheckCnt {
digits = val.value
return
}
val.checkCnt++
if val.checkCnt > val.maxCheckCnt {
// 已经验证到指定次数
return
}
digits = val.value
if val.checkCnt >= val.maxCheckCnt {
delete(s.digitsByID, id)
// XXX(dchest) Index (s.idByTime) will be cleaned when
// collecting expired captchas. Can't clean it here, because
// we don't store reference to expValue in the map.
// Maybe store it?
}
return
}
func (s *memoryStore) collect() {
now := time.Now()
s.Lock()
defer s.Unlock()
s.numStored = 0
for e := s.idByTime.Front(); e != nil; {
ev, ok := e.Value.(idByTimeValue)
if !ok {
return
}
if ev.timestamp.Add(s.expiration).Before(now) {
delete(s.digitsByID, ev.id)
next := e.Next()
s.idByTime.Remove(e)
e = next
} else {
return
}
}
}