f419c124 by slene

add captcha util

1 parent fee3c2b8
...@@ -3,7 +3,6 @@ package beego ...@@ -3,7 +3,6 @@ package beego
3 import ( 3 import (
4 "bytes" 4 "bytes"
5 "crypto/hmac" 5 "crypto/hmac"
6 "crypto/rand"
7 "crypto/sha1" 6 "crypto/sha1"
8 "encoding/base64" 7 "encoding/base64"
9 "errors" 8 "errors"
...@@ -22,6 +21,7 @@ import ( ...@@ -22,6 +21,7 @@ import (
22 21
23 "github.com/astaxie/beego/context" 22 "github.com/astaxie/beego/context"
24 "github.com/astaxie/beego/session" 23 "github.com/astaxie/beego/session"
24 "github.com/astaxie/beego/utils"
25 ) 25 )
26 26
27 var ( 27 var (
...@@ -455,7 +455,7 @@ func (c *Controller) XsrfToken() string { ...@@ -455,7 +455,7 @@ func (c *Controller) XsrfToken() string {
455 } else { 455 } else {
456 expire = int64(XSRFExpire) 456 expire = int64(XSRFExpire)
457 } 457 }
458 token = getRandomString(15) 458 token = string(utils.RandomCreateBytes(15))
459 c.SetSecureCookie(XSRFKEY, "_xsrf", token, expire) 459 c.SetSecureCookie(XSRFKEY, "_xsrf", token, expire)
460 } 460 }
461 c._xsrf_token = token 461 c._xsrf_token = token
...@@ -492,14 +492,3 @@ func (c *Controller) XsrfFormHtml() string { ...@@ -492,14 +492,3 @@ func (c *Controller) XsrfFormHtml() string {
492 func (c *Controller) GetControllerAndAction() (controllerName, actionName string) { 492 func (c *Controller) GetControllerAndAction() (controllerName, actionName string) {
493 return c.controllerName, c.actionName 493 return c.controllerName, c.actionName
494 } 494 }
495
496 // getRandomString returns random string.
497 func getRandomString(n int) string {
498 const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
499 var bytes = make([]byte, n)
500 rand.Read(bytes)
501 for i, b := range bytes {
502 bytes[i] = alphanum[b%byte(len(alphanum))]
503 }
504 return string(bytes)
505 }
......
1 package captcha
2
3 // modifiy and integrated to Beego in one file from https://github.com/dchest/captcha
4
5 import (
6 "fmt"
7 "html/template"
8 "net/http"
9 "path"
10 "strings"
11
12 "github.com/astaxie/beego"
13 "github.com/astaxie/beego/cache"
14 "github.com/astaxie/beego/context"
15 "github.com/astaxie/beego/utils"
16 )
17
18 var (
19 defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
20 )
21
22 const (
23 challengeNums = 6
24 expiration = 600
25 fieldIdName = "captcha_id"
26 fieldCaptchaName = "captcha"
27 cachePrefix = "captcha_"
28 urlPrefix = "/captcha/"
29 )
30
31 type Captcha struct {
32 store cache.Cache
33 urlPrefix string
34 FieldIdName string
35 FieldCaptchaName string
36 StdWidth int
37 StdHeight int
38 ChallengeNums int
39 Expiration int64
40 CachePrefix string
41 }
42
43 func (c *Captcha) key(id string) string {
44 return c.CachePrefix + id
45 }
46
47 func (c *Captcha) genRandChars() []byte {
48 return utils.RandomCreateBytes(c.ChallengeNums, defaultChars...)
49 }
50
51 func (c *Captcha) Handler(ctx *context.Context) {
52 var chars []byte
53
54 id := path.Base(ctx.Request.RequestURI)
55 if i := strings.Index(id, "."); i != -1 {
56 id = id[:i]
57 }
58
59 key := c.key(id)
60
61 if v, ok := c.store.Get(key).([]byte); ok {
62 chars = v
63 } else {
64 ctx.Output.SetStatus(404)
65 ctx.WriteString("captcha not found")
66 return
67 }
68
69 // reload captcha
70 if len(ctx.Input.Query("reload")) > 0 {
71 chars = c.genRandChars()
72 if err := c.store.Put(key, chars, c.Expiration); err != nil {
73 ctx.Output.SetStatus(500)
74 ctx.WriteString("captcha reload error")
75 beego.Error("Reload Create Captcha Error:", err)
76 return
77 }
78 }
79
80 img := NewImage(chars, c.StdWidth, c.StdHeight)
81 if _, err := img.WriteTo(ctx.ResponseWriter); err != nil {
82 beego.Error("Write Captcha Image Error:", err)
83 }
84 }
85
86 func (c *Captcha) CreateCaptchaHtml() template.HTML {
87 value, err := c.CreateCaptcha()
88 if err != nil {
89 beego.Error("Create Captcha Error:", err)
90 return ""
91 }
92
93 // create html
94 return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`+
95 `<a class="captcha" href="javascript:">`+
96 `<img onclick="this.src=('%s%s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%s%s.png">`+
97 `</a>`, c.FieldIdName, value, c.urlPrefix, value, c.urlPrefix, value))
98 }
99
100 func (c *Captcha) CreateCaptcha() (string, error) {
101 // generate captcha id
102 id := string(utils.RandomCreateBytes(15))
103
104 // get the captcha chars
105 chars := c.genRandChars()
106
107 // save to store
108 if err := c.store.Put(c.key(id), chars, c.Expiration); err != nil {
109 return "", err
110 }
111
112 return id, nil
113 }
114
115 func (c *Captcha) VerifyReq(req *http.Request) bool {
116 req.ParseForm()
117 return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName))
118 }
119
120 func (c *Captcha) Verify(id string, challenge string) (success bool) {
121 if len(challenge) == 0 || len(id) == 0 {
122 return
123 }
124
125 var chars []byte
126
127 key := c.key(id)
128
129 if v, ok := c.store.Get(key).([]byte); ok && len(v) == len(challenge) {
130 chars = v
131 } else {
132 return
133 }
134
135 defer func() {
136 // finally remove it
137 c.store.Delete(key)
138 }()
139
140 // verify challenge
141 for i, c := range chars {
142 if c != challenge[i]-48 {
143 return
144 }
145 }
146
147 return true
148 }
149
150 func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha {
151 cpt := &Captcha{}
152 cpt.store = store
153 cpt.FieldIdName = fieldIdName
154 cpt.FieldCaptchaName = fieldCaptchaName
155 cpt.ChallengeNums = challengeNums
156 cpt.Expiration = expiration
157 cpt.CachePrefix = cachePrefix
158 cpt.StdWidth = stdWidth
159 cpt.StdHeight = stdHeight
160
161 if len(urlPrefix) == 0 {
162 urlPrefix = urlPrefix
163 }
164
165 if urlPrefix[len(urlPrefix)-1] != '/' {
166 urlPrefix += "/"
167 }
168
169 cpt.urlPrefix = urlPrefix
170
171 return cpt
172 }
173
174 func NewWithFilter(urlPrefix string, store cache.Cache) *Captcha {
175 cpt := NewCaptcha(urlPrefix, store)
176
177 // create filter for serve captcha image
178 beego.AddFilter(urlPrefix+":", "BeforeRouter", cpt.Handler)
179
180 // add to template func map
181 beego.AddFuncMap("create_captcha", cpt.CreateCaptchaHtml)
182
183 return cpt
184 }
1 package captcha
2
3 import (
4 "bytes"
5 "image"
6 "image/color"
7 "image/png"
8 "io"
9 "math"
10 "math/rand"
11 "time"
12 )
13
14 const (
15 fontWidth = 11
16 fontHeight = 18
17 blackChar = 1
18
19 // Standard width and height of a captcha image.
20 stdWidth = 240
21 stdHeight = 80
22 // Maximum absolute skew factor of a single digit.
23 maxSkew = 0.7
24 // Number of background circles.
25 circleCount = 20
26 )
27
28 var font = [][]byte{
29 { // 0
30 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
31 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
32 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
33 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
34 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
35 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
36 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
37 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
38 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
39 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
40 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
41 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
42 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
43 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
44 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
45 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
46 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
47 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
48 },
49 { // 1
50 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
51 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
52 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
53 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
54 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0,
55 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
56 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
57 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
58 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
59 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
60 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
61 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
62 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
63 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
64 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
65 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
66 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
67 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
68 },
69 { // 2
70 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
71 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
72 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
73 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
74 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
75 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
76 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
77 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
78 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
79 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
80 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
81 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
82 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
83 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
84 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
85 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
86 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
87 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
88 },
89 { // 3
90 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
91 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
92 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
93 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
94 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
95 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
96 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
97 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
98 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
99 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
100 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
101 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
102 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
103 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
104 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
105 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
106 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
107 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
108 },
109 { // 4
110 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
111 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
112 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
113 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
114 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
115 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
116 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
117 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
118 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
119 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
120 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
121 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
122 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
123 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
124 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
125 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
126 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
127 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
128 },
129 { // 5
130 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
131 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
132 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
133 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
134 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
135 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
136 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
137 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
138 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
139 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
140 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
141 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
142 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
143 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
144 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
145 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
146 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
147 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
148 },
149 { // 6
150 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
151 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
152 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
153 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
154 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
155 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
156 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
157 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
158 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0,
159 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
160 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
161 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
162 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
163 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
164 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
165 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
166 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
167 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
168 },
169 { // 7
170 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
171 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
172 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
173 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
174 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
175 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
176 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
177 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
178 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
179 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
180 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
181 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
182 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
183 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
184 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
185 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
186 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
187 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
188 },
189 { // 8
190 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
191 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
192 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
193 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
194 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
195 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
196 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
197 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
198 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
199 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
200 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
201 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
202 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
203 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
204 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
205 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
206 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
207 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
208 },
209 { // 9
210 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
211 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
212 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
213 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
214 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
215 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
216 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
217 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
218 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
219 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
220 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
221 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
222 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
223 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
224 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
225 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
226 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
227 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
228 },
229 }
230
231 type Image struct {
232 *image.Paletted
233 numWidth int
234 numHeight int
235 dotSize int
236 }
237
238 func getrand() *rand.Rand {
239 return rand.New(rand.NewSource(time.Now().UnixNano()))
240 }
241
242 func randIntn(max int) int {
243 return getrand().Intn(max)
244 }
245
246 func randInt(min, max int) int {
247 return getrand().Intn(max-min) + min
248 }
249
250 func randFloat(min, max float64) float64 {
251 return (max-min)*getrand().Float64() + min
252 }
253
254 func randomPalette() color.Palette {
255 p := make([]color.Color, circleCount+1)
256 // Transparent color.
257 p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}
258 // Primary color.
259 prim := color.RGBA{
260 uint8(randIntn(129)),
261 uint8(randIntn(129)),
262 uint8(randIntn(129)),
263 0xFF,
264 }
265 p[1] = prim
266 // Circle colors.
267 for i := 2; i <= circleCount; i++ {
268 p[i] = randomBrightness(prim, 255)
269 }
270 return p
271 }
272
273 // NewImage returns a new captcha image of the given width and height with the
274 // given digits, where each digit must be in range 0-9.
275 func NewImage(digits []byte, width, height int) *Image {
276 m := new(Image)
277 m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette())
278 m.calculateSizes(width, height, len(digits))
279 // Randomly position captcha inside the image.
280 maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize
281 maxy := height - m.numHeight - m.dotSize*2
282 var border int
283 if width > height {
284 border = height / 5
285 } else {
286 border = width / 5
287 }
288 x := randInt(border, maxx-border)
289 y := randInt(border, maxy-border)
290 // Draw digits.
291 for _, n := range digits {
292 m.drawDigit(font[n], x, y)
293 x += m.numWidth + m.dotSize
294 }
295 // Draw strike-through line.
296 m.strikeThrough()
297 // Apply wave distortion.
298 m.distort(randFloat(5, 10), randFloat(100, 200))
299 // Fill image with random circles.
300 m.fillWithCircles(circleCount, m.dotSize)
301 return m
302 }
303
304 // encodedPNG encodes an image to PNG and returns
305 // the result as a byte slice.
306 func (m *Image) encodedPNG() []byte {
307 var buf bytes.Buffer
308 if err := png.Encode(&buf, m.Paletted); err != nil {
309 panic(err.Error())
310 }
311 return buf.Bytes()
312 }
313
314 // WriteTo writes captcha image in PNG format into the given writer.
315 func (m *Image) WriteTo(w io.Writer) (int64, error) {
316 n, err := w.Write(m.encodedPNG())
317 return int64(n), err
318 }
319
320 func (m *Image) calculateSizes(width, height, ncount int) {
321 // Goal: fit all digits inside the image.
322 var border int
323 if width > height {
324 border = height / 4
325 } else {
326 border = width / 4
327 }
328 // Convert everything to floats for calculations.
329 w := float64(width - border*2)
330 h := float64(height - border*2)
331 // fw takes into account 1-dot spacing between digits.
332 fw := float64(fontWidth + 1)
333 fh := float64(fontHeight)
334 nc := float64(ncount)
335 // Calculate the width of a single digit taking into account only the
336 // width of the image.
337 nw := w / nc
338 // Calculate the height of a digit from this width.
339 nh := nw * fh / fw
340 // Digit too high?
341 if nh > h {
342 // Fit digits based on height.
343 nh = h
344 nw = fw / fh * nh
345 }
346 // Calculate dot size.
347 m.dotSize = int(nh / fh)
348 // Save everything, making the actual width smaller by 1 dot to account
349 // for spacing between digits.
350 m.numWidth = int(nw) - m.dotSize
351 m.numHeight = int(nh)
352 }
353
354 func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) {
355 for x := fromX; x <= toX; x++ {
356 m.SetColorIndex(x, y, colorIdx)
357 }
358 }
359
360 func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) {
361 f := 1 - radius
362 dfx := 1
363 dfy := -2 * radius
364 xo := 0
365 yo := radius
366
367 m.SetColorIndex(x, y+radius, colorIdx)
368 m.SetColorIndex(x, y-radius, colorIdx)
369 m.drawHorizLine(x-radius, x+radius, y, colorIdx)
370
371 for xo < yo {
372 if f >= 0 {
373 yo--
374 dfy += 2
375 f += dfy
376 }
377 xo++
378 dfx += 2
379 f += dfx
380 m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)
381 m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)
382 m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)
383 m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)
384 }
385 }
386
387 func (m *Image) fillWithCircles(n, maxradius int) {
388 maxx := m.Bounds().Max.X
389 maxy := m.Bounds().Max.Y
390 for i := 0; i < n; i++ {
391 colorIdx := uint8(randInt(1, circleCount-1))
392 r := randInt(1, maxradius)
393 m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx)
394 }
395 }
396
397 func (m *Image) strikeThrough() {
398 maxx := m.Bounds().Max.X
399 maxy := m.Bounds().Max.Y
400 y := randInt(maxy/3, maxy-maxy/3)
401 amplitude := randFloat(5, 20)
402 period := randFloat(80, 180)
403 dx := 2.0 * math.Pi / period
404 for x := 0; x < maxx; x++ {
405 xo := amplitude * math.Cos(float64(y)*dx)
406 yo := amplitude * math.Sin(float64(x)*dx)
407 for yn := 0; yn < m.dotSize; yn++ {
408 r := randInt(0, m.dotSize)
409 m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1)
410 }
411 }
412 }
413
414 func (m *Image) drawDigit(digit []byte, x, y int) {
415 skf := randFloat(-maxSkew, maxSkew)
416 xs := float64(x)
417 r := m.dotSize / 2
418 y += randInt(-r, r)
419 for yo := 0; yo < fontHeight; yo++ {
420 for xo := 0; xo < fontWidth; xo++ {
421 if digit[yo*fontWidth+xo] != blackChar {
422 continue
423 }
424 m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1)
425 }
426 xs += skf
427 x = int(xs)
428 }
429 }
430
431 func (m *Image) distort(amplude float64, period float64) {
432 w := m.Bounds().Max.X
433 h := m.Bounds().Max.Y
434
435 oldm := m.Paletted
436 newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette)
437
438 dx := 2.0 * math.Pi / period
439 for x := 0; x < w; x++ {
440 for y := 0; y < h; y++ {
441 xo := amplude * math.Sin(float64(y)*dx)
442 yo := amplude * math.Cos(float64(x)*dx)
443 newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo)))
444 }
445 }
446 m.Paletted = newm
447 }
448
449 func randomBrightness(c color.RGBA, max uint8) color.RGBA {
450 minc := min3(c.R, c.G, c.B)
451 maxc := max3(c.R, c.G, c.B)
452 if maxc > max {
453 return c
454 }
455 n := randIntn(int(max-maxc)) - int(minc)
456 return color.RGBA{
457 uint8(int(c.R) + n),
458 uint8(int(c.G) + n),
459 uint8(int(c.B) + n),
460 uint8(c.A),
461 }
462 }
463
464 func min3(x, y, z uint8) (m uint8) {
465 m = x
466 if y < m {
467 m = y
468 }
469 if z < m {
470 m = z
471 }
472 return
473 }
474
475 func max3(x, y, z uint8) (m uint8) {
476 m = x
477 if y > m {
478 m = y
479 }
480 if z > m {
481 m = z
482 }
483 return
484 }
1 package captcha
2
3 import (
4 "testing"
5
6 "github.com/astaxie/beego/utils"
7 )
8
9 type byteCounter struct {
10 n int64
11 }
12
13 func (bc *byteCounter) Write(b []byte) (int, error) {
14 bc.n += int64(len(b))
15 return len(b), nil
16 }
17
18 func BenchmarkNewImage(b *testing.B) {
19 b.StopTimer()
20 d := utils.RandomCreateBytes(challengeNums, defaultChars...)
21 b.StartTimer()
22 for i := 0; i < b.N; i++ {
23 NewImage(d, stdWidth, stdHeight)
24 }
25 }
26
27 func BenchmarkImageWriteTo(b *testing.B) {
28 b.StopTimer()
29 d := utils.RandomCreateBytes(challengeNums, defaultChars...)
30 b.StartTimer()
31 counter := &byteCounter{}
32 for i := 0; i < b.N; i++ {
33 img := NewImage(d, stdWidth, stdHeight)
34 img.WriteTo(counter)
35 b.SetBytes(counter.n)
36 counter.n = 0
37 }
38 }
1 package utils
2
3 import (
4 "crypto/rand"
5 )
6
7 // RandomCreateBytes generate random []byte by specify chars.
8 func RandomCreateBytes(n int, alphabets ...byte) []byte {
9 const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
10 var bytes = make([]byte, n)
11 rand.Read(bytes)
12 for i, b := range bytes {
13 if len(alphabets) == 0 {
14 bytes[i] = alphanum[b%byte(len(alphanum))]
15 } else {
16 bytes[i] = alphabets[b%byte(len(alphabets))]
17 }
18 }
19 return bytes
20 }
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!