You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
116 lines
2.8 KiB
116 lines
2.8 KiB
// Copyright 2017 Eric Zhou. All Rights Reserved. |
|
// |
|
// Licensed under the Apache License, Version 2.0 (the "License"); |
|
// you may not use this file except in compliance with the License. |
|
// You may obtain a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, |
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
// See the License for the specific language governing permissions and |
|
// limitations under the License. |
|
|
|
package base64Captcha |
|
|
|
import ( |
|
"container/list" |
|
"sync" |
|
"time" |
|
) |
|
|
|
// 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 |
|
} |
|
|
|
// memoryStore is an internal store for captcha ids and their values. |
|
type memoryStore struct { |
|
sync.RWMutex |
|
digitsById map[string]string |
|
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]string) |
|
s.idByTime = list.New() |
|
s.collectNum = collectNum |
|
s.expiration = expiration |
|
return s |
|
} |
|
|
|
func (s *memoryStore) Set(id string, value string) error { |
|
s.Lock() |
|
s.digitsById[id] = value |
|
s.idByTime.PushBack(idByTimeValue{time.Now(), id}) |
|
s.numStored++ |
|
s.Unlock() |
|
if s.numStored > s.collectNum { |
|
go s.collect() |
|
} |
|
return nil |
|
} |
|
|
|
func (s *memoryStore) Verify(id, answer string, clear bool) bool { |
|
v := s.Get(id, clear) |
|
return v == answer |
|
} |
|
|
|
func (s *memoryStore) Get(id string, clear bool) (value 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() |
|
} |
|
value, ok := s.digitsById[id] |
|
if !ok { |
|
return |
|
} |
|
if clear { |
|
delete(s.digitsById, id) |
|
} |
|
return |
|
} |
|
|
|
func (s *memoryStore) collect() { |
|
now := time.Now() |
|
s.Lock() |
|
defer s.Unlock() |
|
for e := s.idByTime.Front(); e != nil; { |
|
e = s.collectOne(e, now) |
|
} |
|
} |
|
|
|
func (s *memoryStore) collectOne(e *list.Element, specifyTime time.Time) *list.Element { |
|
|
|
ev, ok := e.Value.(idByTimeValue) |
|
if !ok { |
|
return nil |
|
} |
|
|
|
if ev.timestamp.Add(s.expiration).Before(specifyTime) { |
|
delete(s.digitsById, ev.id) |
|
next := e.Next() |
|
s.idByTime.Remove(e) |
|
s.numStored-- |
|
return next |
|
} |
|
return nil |
|
}
|
|
|