123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- // Copyright 2011 Dmitry Chestnykh. All rights reserved.
- // Use of this source code is governed by a MIT-style
- // license that can be found in the LICENSE file.
- // Package captcha implements generation and verification of image and audio
- // CAPTCHAs.
- //
- // A captcha solution is the sequence of digits 0-9 with the defined length.
- // There are two captcha representations: image and audio.
- //
- // An image representation is a PNG-encoded image with the solution printed on
- // it in such a way that makes it hard for computers to solve it using OCR.
- //
- // An audio representation is a WAVE-encoded (8 kHz unsigned 8-bit) sound with
- // the spoken solution (currently in English, Russian, Chinese, and Japanese).
- // To make it hard for computers to solve audio captcha, the voice that
- // pronounces numbers has random speed and pitch, and there is a randomly
- // generated background noise mixed into the sound.
- //
- // This package doesn't require external files or libraries to generate captcha
- // representations; it is self-contained.
- //
- // To make captchas one-time, the package includes a memory storage that stores
- // captcha ids, their solutions, and expiration time. Used captchas are removed
- // from the store immediately after calling Verify or VerifyString, while
- // unused captchas (user loaded a page with captcha, but didn't submit the
- // form) are collected automatically after the predefined expiration time.
- // Developers can also provide custom store (for example, which saves captcha
- // ids and solutions in database) by implementing Store interface and
- // registering the object with SetCustomStore.
- //
- // Captchas are created by calling New, which returns the captcha id. Their
- // representations, though, are created on-the-fly by calling WriteImage or
- // WriteAudio functions. Created representations are not stored anywhere, but
- // subsequent calls to these functions with the same id will write the same
- // captcha solution. Reload function will create a new different solution for
- // the provided captcha, allowing users to "reload" captcha if they can't solve
- // the displayed one without reloading the whole page. Verify and VerifyString
- // are used to verify that the given solution is the right one for the given
- // captcha id.
- //
- // Server provides an http.Handler which can serve image and audio
- // representations of captchas automatically from the URL. It can also be used
- // to reload captchas. Refer to Server function documentation for details, or
- // take a look at the example in "capexample" subdirectory.
- package captcha
- import (
- "bytes"
- "errors"
- "io"
- "time"
- )
- const (
- // Default number of digits in captcha solution.
- DefaultLen = 6
- // The number of captchas created that triggers garbage collection used
- // by default store.
- CollectNum = 100
- // Expiration time of captchas used by default store.
- Expiration = 10 * time.Minute
- )
- var (
- ErrNotFound = errors.New("captcha: id not found")
- // globalStore is a shared storage for captchas, generated by New function.
- globalStore = NewMemoryStore(CollectNum, Expiration)
- )
- // SetCustomStore sets custom storage for captchas, replacing the default
- // memory store. This function must be called before generating any captchas.
- func SetCustomStore(s Store) {
- globalStore = s
- }
- // New creates a new captcha with the standard length, saves it in the internal
- // storage and returns its id.
- func New() string {
- return NewLen(DefaultLen)
- }
- // NewLen is just like New, but accepts length of a captcha solution as the
- // argument.
- func NewLen(length int) (id string) {
- id = randomId()
- globalStore.Set(id, RandomDigits(length))
- return
- }
- // Reload generates and remembers new digits for the given captcha id. This
- // function returns false if there is no captcha with the given id.
- //
- // After calling this function, the image or audio presented to a user must be
- // refreshed to show the new captcha representation (WriteImage and WriteAudio
- // will write the new one).
- func Reload(id string) bool {
- old := globalStore.Get(id, false)
- if old == nil {
- return false
- }
- globalStore.Set(id, RandomDigits(len(old)))
- return true
- }
- // WriteImage writes PNG-encoded image representation of the captcha with the
- // given id. The image will have the given width and height.
- func WriteImage(w io.Writer, id string, width, height int) error {
- d := globalStore.Get(id, false)
- if d == nil {
- return ErrNotFound
- }
- _, err := NewImage(id, d, width, height).WriteTo(w)
- return err
- }
- // WriteAudio writes WAV-encoded audio representation of the captcha with the
- // given id and the given language. If there are no sounds for the given
- // language, English is used.
- func WriteAudio(w io.Writer, id string, lang string) error {
- d := globalStore.Get(id, false)
- if d == nil {
- return ErrNotFound
- }
- _, err := NewAudio(id, d, lang).WriteTo(w)
- return err
- }
- // Verify returns true if the given digits are the ones that were used to
- // create the given captcha id.
- //
- // The function deletes the captcha with the given id from the internal
- // storage, so that the same captcha can't be verified anymore.
- func Verify(id string, digits []byte) bool {
- if digits == nil || len(digits) == 0 {
- return false
- }
- reald := globalStore.Get(id, true)
- if reald == nil {
- return false
- }
- return bytes.Equal(digits, reald)
- }
- // VerifyString is like Verify, but accepts a string of digits. It removes
- // spaces and commas from the string, but any other characters, apart from
- // digits and listed above, will cause the function to return false.
- func VerifyString(id string, digits string) bool {
- if digits == "" {
- return false
- }
- ns := make([]byte, len(digits))
- for i := range ns {
- d := digits[i]
- switch {
- case '0' <= d && d <= '9':
- ns[i] = d - '0'
- case d == ' ' || d == ',':
- // ignore
- default:
- return false
- }
- }
- return Verify(id, ns)
- }
|