all repos — captcha @ 1b34f643a5185c2992a13744eaf5a319e089f6a7

Go package captcha implements generation and verification of image and audio CAPTCHAs.

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}
169
170// Collect deletes expired or used captchas from the internal storage. It is
171// called automatically by New function every CollectNum generated captchas,
172// but still exported to enable freeing memory manually if needed.
173//
174// Collection is launched in a new goroutine.
175func Collect() {
176	go globalStore.Collect()
177}