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