// 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 }