package inmemdb import ( "image" "sync" "time" "git.arav.su/Arav/justcaptcha/pkg/captcha" ) // InMemoryCaptchaDB implementation that lives in a memory (map). type InMemoryCaptchaDB struct { sync.Mutex db map[captcha.ID]captcha.Captcha expiry time.Duration expiryScanInterval time.Duration } // NewInMemoryCaptchaDB returns an initialised instance of an InMemoryCaptchaDB. // An expiry is a scan interval for expired CAPTCHAs (if passed a longer one, // resets to a default (captcha.DefaultExpiredScanInterval)). func NewInMemoryCaptchaDB(expiry time.Duration) *InMemoryCaptchaDB { db := &InMemoryCaptchaDB{ db: make(map[captcha.ID]captcha.Captcha), expiry: expiry} if expiry < captcha.DefaultExpiredScanInterval { db.expiryScanInterval = expiry } else { db.expiryScanInterval = captcha.DefaultExpiredScanInterval } go db.cleanExpired() return db } // New accepts a CAPTCHA instance, generates an ID and store it in a database. // A data string is an additional random data used to generate an ID, // e.g. an IP-address. func (imcdb *InMemoryCaptchaDB) New(data string, cptcha captcha.Captcha) (captcha.Captcha, captcha.ID) { id := captcha.NewID(data, cptcha.Answer()) imcdb.Lock() imcdb.db[id] = cptcha imcdb.Unlock() return cptcha, id } // GetExpiry returns an expiry for a CAPTCHA. func (imcdb *InMemoryCaptchaDB) GetExpiry() time.Duration { return imcdb.expiry } // SetExpiry changes an expiry for a CAPTCHA and a scan interval. Scan interval // cannot be longer than a default, so if it is, then resets to a default. func (imcdb *InMemoryCaptchaDB) SetExpiry(expiry time.Duration) { imcdb.expiry = expiry if expiry < captcha.DefaultExpiredScanInterval { imcdb.expiryScanInterval = expiry } else { imcdb.expiryScanInterval = captcha.DefaultExpiredScanInterval } } // Image returns a freshly generated image for a CAPTCHA with style if // applicable. func (imcdb *InMemoryCaptchaDB) Image(id captcha.ID, style string) *image.Image { imcdb.Lock() defer imcdb.Unlock() if c, ok := imcdb.db[id]; ok { return c.Image(style) } return nil } // Solve compares given answer with a stored one and if failed // deletes a CAPTCHA from database. func (imcdb *InMemoryCaptchaDB) Solve(id captcha.ID, answer captcha.Answer) bool { imcdb.Lock() defer imcdb.Unlock() if c, ok := imcdb.db[id]; ok { ok = c.Solve(answer) if !ok { delete(imcdb.db, id) } return ok } return false } // IsSolved checks if CAPTCHA was solved and removes it // from a database. func (imcdb *InMemoryCaptchaDB) IsSolved(id captcha.ID) bool { imcdb.Lock() defer imcdb.Unlock() if c, ok := imcdb.db[id]; ok { delete(imcdb.db, id) return c.IsSolved() } return false } // Remove a CAPTCHA from a database. func (imcdb *InMemoryCaptchaDB) Remove(id captcha.ID) { imcdb.Lock() defer imcdb.Unlock() delete(imcdb.db, id) } // cleanExpired removes expired CAPTCHAs in a loop. func (imcdb *InMemoryCaptchaDB) cleanExpired() { for { sleepFor := imcdb.expiryScanInterval - (time.Duration(time.Now().Second()) % imcdb.expiryScanInterval) time.Sleep(sleepFor) imcdb.Lock() for id, captcha := range imcdb.db { if time.Since(captcha.Expiry()) >= imcdb.expiry { delete(imcdb.db, id) } } imcdb.Unlock() } } // An instance of InMemoryCaptchaDB var imcdb = NewInMemoryCaptchaDB(captcha.DefaultExpiredScanInterval) func GetExpiry() time.Duration { return imcdb.GetExpiry() } func SetExpiry(expiry time.Duration) { imcdb.SetExpiry(expiry) } func New(data string, captcha captcha.Captcha) (captcha.Captcha, captcha.ID) { return imcdb.New(data, captcha) } func Image(id captcha.ID, style string) *image.Image { return imcdb.Image(id, style) } func Solve(id captcha.ID, answer captcha.Answer) bool { return imcdb.Solve(id, answer) } func IsSolved(id captcha.ID) bool { return imcdb.IsSolved(id) } func Remove(id captcha.ID) { imcdb.Remove(id) }