captcha.go (view raw)
1// Package captcha implements generation and verification of image and audio
2// CAPTCHAs.
3//
4// A captcha solution is the sequence of digits 0-9 with the defined length.
5// There are two captcha representations: image and audio.
6//
7// An image representation is a PNG-encoded image with the solution printed on
8// it in such a way that makes it hard for computers to solve it using OCR.
9//
10// An audio representation is a WAVE-encoded (8 kHz unsigned 8-bit) sound
11// with the spoken solution (currently in English). To make it hard for
12// computers to solve audio captcha, the voice that pronounces numbers has
13// random speed and pitch, and there is a randomly generated background noise
14// mixed into the sound.
15//
16// This package doesn't require external files or libraries to generate captcha
17// representations; it is self-contained.
18//
19// To make captchas one-time, the package includes a memory storage that stores
20// captcha ids, their solutions, and expiration time. Used captchas are removed
21// from the store immediately after calling Verify or VerifyString, while
22// unused captchas (user loaded a page with captcha, but didn't submit the
23// form) are collected automatically after the predefined expiration time.
24// Developers can also provide custom store (for example, which saves captcha
25// ids and solutions in database) by implementing Store interface and
26// registering the object with SetCustomStore.
27//
28// Captchas are created by calling New, which returns the captcha id. Their
29// representations, though, are created on-the-fly by calling WriteImage or
30// WriteAudio functions. Created representations are not stored anywhere, so
31// subsequent calls to these functions with the same id will write the same
32// captcha solution, but with a different random representation. Reload
33// function will create a new different solution for the provided captcha,
34// allowing users to "reload" captcha if they can't solve the displayed one
35// without reloading the whole page. Verify and VerifyString are used to
36// verify that the given solution is the right one for the given captcha id.
37//
38// Server provides an http.Handler which can serve image and audio
39// representations of captchas automatically from the URL. It can also be used
40// to reload captchas. Refer to Server function documentation for details, or
41// take a look at the example in "example" subdirectory.
42package captcha
43
44import (
45 "bytes"
46 "crypto/rand"
47 "github.com/dchest/uniuri"
48 "io"
49 "os"
50)
51
52const (
53 // Standard number of digits in captcha.
54 StdLength = 6
55 // The number of captchas created that triggers garbage collection used
56 // by default store.
57 CollectNum = 100
58 // Expiration time of captchas used by default store.
59 Expiration = 10 * 60 // 10 minutes
60
61)
62
63var ErrNotFound = os.NewError("captcha with the given id not found")
64
65// globalStore is a shared storage for captchas, generated by New function.
66var globalStore = NewMemoryStore(CollectNum, Expiration)
67
68// SetCustomStore sets custom storage for captchas, replacing the default
69// memory store. This function must be called before generating any captchas.
70func SetCustomStore(s Store) {
71 globalStore = s
72}
73
74// RandomDigits returns a byte slice of the given length containing random
75// digits in range 0-9.
76func RandomDigits(length int) []byte {
77 d := make([]byte, length)
78 if _, err := io.ReadFull(rand.Reader, d); err != nil {
79 panic("error reading random source: " + err.String())
80 }
81 for i := range d {
82 d[i] %= 10
83 }
84 return d
85}
86
87// New creates a new captcha of the given length, saves it in the internal
88// storage, and returns its id.
89func New(length int) (id string) {
90 id = uniuri.New()
91 globalStore.Set(id, RandomDigits(length))
92 return
93}
94
95// Reload generates and remembers new digits for the given captcha id. This
96// function returns false if there is no captcha with the given id.
97//
98// After calling this function, the image or audio presented to a user must be
99// refreshed to show the new captcha representation (WriteImage and WriteAudio
100// will write the new one).
101func Reload(id string) bool {
102 old := globalStore.Get(id, false)
103 if old == nil {
104 return false
105 }
106 globalStore.Set(id, RandomDigits(len(old)))
107 return true
108}
109
110// WriteImage writes PNG-encoded image representation of the captcha with the
111// given id. The image will have the given width and height.
112func WriteImage(w io.Writer, id string, width, height int) os.Error {
113 d := globalStore.Get(id, false)
114 if d == nil {
115 return ErrNotFound
116 }
117 _, err := NewImage(d, width, height).WriteTo(w)
118 return err
119}
120
121// WriteAudio writes WAV-encoded audio representation of the captcha with the
122// given id.
123func WriteAudio(w io.Writer, id string) os.Error {
124 d := globalStore.Get(id, false)
125 if d == nil {
126 return ErrNotFound
127 }
128 _, err := NewAudio(d).WriteTo(w)
129 return err
130}
131
132// Verify returns true if the given digits are the ones that were used to
133// create the given captcha id.
134//
135// The function deletes the captcha with the given id from the internal
136// storage, so that the same captcha can't be verified anymore.
137func Verify(id string, digits []byte) bool {
138 if digits == nil || len(digits) == 0 {
139 return false
140 }
141 reald := globalStore.Get(id, true)
142 if reald == nil {
143 return false
144 }
145 return bytes.Equal(digits, reald)
146}
147
148// VerifyString is like Verify, but accepts a string of digits. It removes
149// spaces and commas from the string, but any other characters, apart from
150// digits and listed above, will cause the function to return false.
151func VerifyString(id string, digits string) bool {
152 if digits == "" {
153 return false
154 }
155 ns := make([]byte, len(digits))
156 for i := range ns {
157 d := digits[i]
158 switch {
159 case '0' <= d && d <= '9':
160 ns[i] = d - '0'
161 case d == ' ' || d == ',':
162 // ignore
163 default:
164 return false
165 }
166 }
167 return Verify(id, ns)
168}