add captcha util
Showing
5 changed files
with
728 additions
and
13 deletions
| ... | @@ -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 | } | ... | ... |
utils/captcha/captcha.go
0 → 100644
| 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 | } |
utils/captcha/image.go
0 → 100644
| 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 | } |
utils/captcha/image_test.go
0 → 100644
| 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 | } |
utils/rand.go
0 → 100644
| 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 | } |
-
Please register or sign in to post a comment