forked from dchest/captcha
-
Notifications
You must be signed in to change notification settings - Fork 0
/
captcha.go
165 lines (153 loc) · 5.71 KB
/
captcha.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// 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)
}