captcha.go (view raw)
1package captcha
2
3import (
4 "bytes"
5 "crypto/rand"
6 "github.com/dchest/uniuri"
7 "io"
8 "os"
9)
10
11const (
12 // Standard number of digits in captcha.
13 StdLength = 6
14 // The number of captchas created that triggers garbage collection.
15 StdCollectNum = 100
16 // Expiration time of captchas.
17 StdExpiration = 2 * 60 // 2 minutes
18
19)
20
21var ErrNotFound = os.NewError("captcha with the given id not found")
22
23// globalStore is a shared storage for captchas, generated by New function.
24var globalStore = newStore(StdCollectNum, StdExpiration)
25
26// RandomDigits returns a byte slice of the given length containing random
27// digits in range 0-9.
28func RandomDigits(length int) []byte {
29 d := make([]byte, length)
30 if _, err := io.ReadFull(rand.Reader, d); err != nil {
31 panic("error reading random source: " + err.String())
32 }
33 for i := range d {
34 d[i] %= 10
35 }
36 return d
37}
38
39// New creates a new captcha of the given length, saves it in the internal
40// storage, and returns its id.
41func New(length int) (id string) {
42 id = uniuri.New()
43 globalStore.saveCaptcha(id, RandomDigits(length))
44 return
45}
46
47// Reload generates and remembers new digits for the given captcha id. This
48// function returns false if there is no captcha with the given id.
49//
50// After calling this function, the image or audio presented to a user must be
51// refreshed to show the new captcha representation (WriteImage and WriteAudio
52// will write the new one).
53func Reload(id string) bool {
54 old := globalStore.getDigits(id)
55 if old == nil {
56 return false
57 }
58 globalStore.saveCaptcha(id, RandomDigits(len(old)))
59 return true
60}
61
62// WriteImage writes PNG-encoded image representation of the captcha with the
63// given id. The image will have the given width and height.
64func WriteImage(w io.Writer, id string, width, height int) os.Error {
65 d := globalStore.getDigits(id)
66 if d == nil {
67 return ErrNotFound
68 }
69 _, err := NewImage(d, width, height).WriteTo(w)
70 return err
71}
72
73// WriteAudio writes WAV-encoded audio representation of the captcha with the
74// given id.
75func WriteAudio(w io.Writer, id string) os.Error {
76 d := globalStore.getDigits(id)
77 if d == nil {
78 return ErrNotFound
79 }
80 _, err := NewAudio(d).WriteTo(w)
81 return err
82}
83
84// Verify returns true if the given digits are the ones that were used to
85// create the given captcha id.
86//
87// The function deletes the captcha with the given id from the internal
88// storage, so that the same captcha can't be verified anymore.
89func Verify(id string, digits []byte) bool {
90 reald := globalStore.getDigitsClear(id)
91 if reald == nil {
92 return false
93 }
94 return bytes.Equal(digits, reald)
95}
96
97// Collect deletes expired and used captchas from the internal
98// storage. It is called automatically by New function every CollectNum
99// generated captchas, but still exported to enable freeing memory manually if
100// needed.
101//
102// Collection is launched in a new goroutine.
103func Collect() {
104 go globalStore.collect()
105}