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)
12
13// Standard number of numbers in captcha
14const StdLength = 6
15
16var globalStore = newStore()
17
18func init() {
19 rand.Seed(time.Seconds())
20}
21
22// randomNumbers return a byte slice of the given length containing random
23// numbers in range 0-9.
24func randomNumbers(length int) []byte {
25 n := make([]byte, length)
26 if _, err := io.ReadFull(crand.Reader, n); err != nil {
27 panic(err)
28 }
29 for i := range n {
30 n[i] %= 10
31 }
32 return n
33}
34
35// New creates a new captcha of the given length, saves it in the internal
36// storage, and returns its id.
37func New(length int) (id string) {
38 id = uniuri.New()
39 globalStore.saveCaptcha(id, randomNumbers(length))
40 return
41}
42
43// Reload generates new numbers for the given captcha id. This function does
44// nothing if there is no captcha with the given id.
45//
46// After calling this function, the image presented to a user must be refreshed
47// to show the new captcha (WriteImage will write the new one).
48func Reload(id string) {
49 oldns := globalStore.getNumbers(id)
50 if oldns == nil {
51 return
52 }
53 globalStore.saveCaptcha(id, randomNumbers(len(oldns)))
54}
55
56// WriteImage writes PNG-encoded captcha image of the given width and height
57// with the given captcha id into the io.Writer.
58func WriteImage(w io.Writer, id string, width, height int) os.Error {
59 ns := globalStore.getNumbers(id)
60 if ns == nil {
61 return os.NewError("captcha id not found")
62 }
63 return NewImage(ns, width, height).PNGEncode(w)
64}
65
66// Verify returns true if the given numbers are the numbers that were used to
67// create the given captcha id.
68//
69// The function deletes the captcha with the given id from the internal
70// storage, so that the same captcha can't be used anymore.
71func Verify(id string, numbers []byte) bool {
72 realns := globalStore.getNumbersClear(id)
73 if realns == nil {
74 return false
75 }
76 return bytes.Equal(numbers, realns)
77}
78
79// Collect deletes expired and used captchas from the internal
80// storage. It is called automatically by New function every CollectNum
81// generated captchas, but still exported to enable freeing memory manually if
82// needed.
83//
84// Collection is launched in a new goroutine, so this function returns
85// immediately.
86func Collect() {
87 go globalStore.collect()
88}