all repos — captcha @ bd0535eaebba405865e1ef9553be477c52adb83c

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

captcha.go (view raw)

  1package captcha
  2
  3import (
  4	"bytes"
  5	"crypto/rand"
  6	"github.com/dchest/uniuri"
  7	"http"
  8	"io"
  9	"os"
 10	"path"
 11)
 12
 13const (
 14	// Standard number of digits in captcha.
 15	StdLength = 6
 16	// The number of captchas created that triggers garbage collection.
 17	StdCollectNum = 100
 18	// Expiration time of captchas.
 19	StdExpiration = 2 * 60 // 2 minutes
 20
 21)
 22
 23var ErrNotFound = os.NewError("captcha with the given id not found")
 24
 25// globalStore is a shared storage for captchas, generated by New function.
 26var globalStore = newStore(StdCollectNum, StdExpiration)
 27
 28// RandomDigits returns a byte slice of the given length containing random
 29// digits in range 0-9.
 30func RandomDigits(length int) []byte {
 31	d := make([]byte, length)
 32	if _, err := io.ReadFull(rand.Reader, d); err != nil {
 33		panic("error reading random source: " + err.String())
 34	}
 35	for i := range d {
 36		d[i] %= 10
 37	}
 38	return d
 39}
 40
 41// New creates a new captcha of the given length, saves it in the internal
 42// storage, and returns its id.
 43func New(length int) (id string) {
 44	id = uniuri.New()
 45	globalStore.saveCaptcha(id, RandomDigits(length))
 46	return
 47}
 48
 49// Reload generates and remembers new digits for the given captcha id.  This
 50// function returns false if there is no captcha with the given id.
 51//
 52// After calling this function, the image or audio presented to a user must be
 53// refreshed to show the new captcha representation (WriteImage and WriteAudio
 54// will write the new one).
 55func Reload(id string) bool {
 56	old := globalStore.getDigits(id)
 57	if old == nil {
 58		return false
 59	}
 60	globalStore.saveCaptcha(id, RandomDigits(len(old)))
 61	return true
 62}
 63
 64// WriteImage writes PNG-encoded image representation of the captcha with the
 65// given id. The image will have the given width and height.
 66func WriteImage(w io.Writer, id string, width, height int) os.Error {
 67	d := globalStore.getDigits(id)
 68	if d == nil {
 69		return ErrNotFound
 70	}
 71	_, err := NewImage(d, width, height).WriteTo(w)
 72	return err
 73}
 74
 75// WriteAudio writes WAV-encoded audio representation of the captcha with the
 76// given id.
 77func WriteAudio(w io.Writer, id string) os.Error {
 78	d := globalStore.getDigits(id)
 79	if d == nil {
 80		return ErrNotFound
 81	}
 82	_, err := NewAudio(d).WriteTo(w)
 83	return err
 84}
 85
 86// Verify returns true if the given digits are the ones that were used to
 87// create the given captcha id.
 88// 
 89// The function deletes the captcha with the given id from the internal
 90// storage, so that the same captcha can't be verified anymore.
 91func Verify(id string, digits []byte) bool {
 92	reald := globalStore.getDigitsClear(id)
 93	if reald == nil {
 94		return false
 95	}
 96	return bytes.Equal(digits, reald)
 97}
 98
 99// Collect deletes expired or used captchas from the internal storage. It is
100// called automatically by New function every CollectNum generated captchas,
101// but still exported to enable freeing memory manually if needed.
102//
103// Collection is launched in a new goroutine.
104func Collect() {
105	go globalStore.collect()
106}
107
108type captchaHandler struct {
109	imgWidth  int
110	imgHeight int
111}
112
113// CaptchaServer returns a handler that serves HTTP requests with image or
114// audio representations of captchas. Image dimensions are accepted as
115// arguments. The server decides which captcha to serve based on the last URL
116// path component: file name part must contain a captcha id, file extension —
117// its format (PNG or WAV).
118//
119// For example, for file name "B9QTvDV1RXbVJ3Ac.png" it serves an image captcha
120// with id "B9QTvDV1RXbVJ3Ac", and for "B9QTvDV1RXbVJ3Ac.wav" it serves the
121// same captcha in audio format.
122func Server(w, h int) http.Handler { return &captchaHandler{w, h} }
123
124func (h *captchaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
125	_, file := path.Split(r.URL.Path)
126	ext := path.Ext(file)
127	id := file[:len(file)-len(ext)]
128	if ext == "" || id == "" {
129		http.NotFound(w, r)
130		return
131	}
132	var err os.Error
133	switch ext {
134	case ".png", ".PNG":
135		w.Header().Set("Content-Type", "image/png")
136		err = WriteImage(w, id, h.imgWidth, h.imgHeight)
137	case ".wav", ".WAV":
138		w.Header().Set("Content-Type", "audio/x-wav")
139		err = WriteAudio(w, id)
140	default:
141		err = ErrNotFound
142	}
143	if err != nil {
144		if err == ErrNotFound {
145			http.NotFound(w, r)
146			return
147		}
148		http.Error(w, "error serving captcha", http.StatusInternalServerError)
149	}
150}