mirror of
https://github.com/astaxie/beego.git
synced 2024-11-25 19:00:55 +00:00
add captcha util
This commit is contained in:
parent
fee3c2b8f9
commit
f419c12427
@ -3,7 +3,6 @@ package beego
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
@ -22,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
"github.com/astaxie/beego/session"
|
"github.com/astaxie/beego/session"
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -455,7 +455,7 @@ func (c *Controller) XsrfToken() string {
|
|||||||
} else {
|
} else {
|
||||||
expire = int64(XSRFExpire)
|
expire = int64(XSRFExpire)
|
||||||
}
|
}
|
||||||
token = getRandomString(15)
|
token = string(utils.RandomCreateBytes(15))
|
||||||
c.SetSecureCookie(XSRFKEY, "_xsrf", token, expire)
|
c.SetSecureCookie(XSRFKEY, "_xsrf", token, expire)
|
||||||
}
|
}
|
||||||
c._xsrf_token = token
|
c._xsrf_token = token
|
||||||
@ -492,14 +492,3 @@ func (c *Controller) XsrfFormHtml() string {
|
|||||||
func (c *Controller) GetControllerAndAction() (controllerName, actionName string) {
|
func (c *Controller) GetControllerAndAction() (controllerName, actionName string) {
|
||||||
return c.controllerName, c.actionName
|
return c.controllerName, c.actionName
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRandomString returns random string.
|
|
||||||
func getRandomString(n int) string {
|
|
||||||
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
||||||
var bytes = make([]byte, n)
|
|
||||||
rand.Read(bytes)
|
|
||||||
for i, b := range bytes {
|
|
||||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
|
||||||
}
|
|
||||||
return string(bytes)
|
|
||||||
}
|
|
||||||
|
184
utils/captcha/captcha.go
Normal file
184
utils/captcha/captcha.go
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package captcha
|
||||||
|
|
||||||
|
// modifiy and integrated to Beego in one file from https://github.com/dchest/captcha
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
challengeNums = 6
|
||||||
|
expiration = 600
|
||||||
|
fieldIdName = "captcha_id"
|
||||||
|
fieldCaptchaName = "captcha"
|
||||||
|
cachePrefix = "captcha_"
|
||||||
|
urlPrefix = "/captcha/"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Captcha struct {
|
||||||
|
store cache.Cache
|
||||||
|
urlPrefix string
|
||||||
|
FieldIdName string
|
||||||
|
FieldCaptchaName string
|
||||||
|
StdWidth int
|
||||||
|
StdHeight int
|
||||||
|
ChallengeNums int
|
||||||
|
Expiration int64
|
||||||
|
CachePrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Captcha) key(id string) string {
|
||||||
|
return c.CachePrefix + id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Captcha) genRandChars() []byte {
|
||||||
|
return utils.RandomCreateBytes(c.ChallengeNums, defaultChars...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Captcha) Handler(ctx *context.Context) {
|
||||||
|
var chars []byte
|
||||||
|
|
||||||
|
id := path.Base(ctx.Request.RequestURI)
|
||||||
|
if i := strings.Index(id, "."); i != -1 {
|
||||||
|
id = id[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
key := c.key(id)
|
||||||
|
|
||||||
|
if v, ok := c.store.Get(key).([]byte); ok {
|
||||||
|
chars = v
|
||||||
|
} else {
|
||||||
|
ctx.Output.SetStatus(404)
|
||||||
|
ctx.WriteString("captcha not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// reload captcha
|
||||||
|
if len(ctx.Input.Query("reload")) > 0 {
|
||||||
|
chars = c.genRandChars()
|
||||||
|
if err := c.store.Put(key, chars, c.Expiration); err != nil {
|
||||||
|
ctx.Output.SetStatus(500)
|
||||||
|
ctx.WriteString("captcha reload error")
|
||||||
|
beego.Error("Reload Create Captcha Error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img := NewImage(chars, c.StdWidth, c.StdHeight)
|
||||||
|
if _, err := img.WriteTo(ctx.ResponseWriter); err != nil {
|
||||||
|
beego.Error("Write Captcha Image Error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Captcha) CreateCaptchaHtml() template.HTML {
|
||||||
|
value, err := c.CreateCaptcha()
|
||||||
|
if err != nil {
|
||||||
|
beego.Error("Create Captcha Error:", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// create html
|
||||||
|
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`+
|
||||||
|
`<a class="captcha" href="javascript:">`+
|
||||||
|
`<img onclick="this.src=('%s%s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%s%s.png">`+
|
||||||
|
`</a>`, c.FieldIdName, value, c.urlPrefix, value, c.urlPrefix, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Captcha) CreateCaptcha() (string, error) {
|
||||||
|
// generate captcha id
|
||||||
|
id := string(utils.RandomCreateBytes(15))
|
||||||
|
|
||||||
|
// get the captcha chars
|
||||||
|
chars := c.genRandChars()
|
||||||
|
|
||||||
|
// save to store
|
||||||
|
if err := c.store.Put(c.key(id), chars, c.Expiration); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Captcha) VerifyReq(req *http.Request) bool {
|
||||||
|
req.ParseForm()
|
||||||
|
return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Captcha) Verify(id string, challenge string) (success bool) {
|
||||||
|
if len(challenge) == 0 || len(id) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var chars []byte
|
||||||
|
|
||||||
|
key := c.key(id)
|
||||||
|
|
||||||
|
if v, ok := c.store.Get(key).([]byte); ok && len(v) == len(challenge) {
|
||||||
|
chars = v
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
// finally remove it
|
||||||
|
c.store.Delete(key)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// verify challenge
|
||||||
|
for i, c := range chars {
|
||||||
|
if c != challenge[i]-48 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha {
|
||||||
|
cpt := &Captcha{}
|
||||||
|
cpt.store = store
|
||||||
|
cpt.FieldIdName = fieldIdName
|
||||||
|
cpt.FieldCaptchaName = fieldCaptchaName
|
||||||
|
cpt.ChallengeNums = challengeNums
|
||||||
|
cpt.Expiration = expiration
|
||||||
|
cpt.CachePrefix = cachePrefix
|
||||||
|
cpt.StdWidth = stdWidth
|
||||||
|
cpt.StdHeight = stdHeight
|
||||||
|
|
||||||
|
if len(urlPrefix) == 0 {
|
||||||
|
urlPrefix = urlPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
if urlPrefix[len(urlPrefix)-1] != '/' {
|
||||||
|
urlPrefix += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
cpt.urlPrefix = urlPrefix
|
||||||
|
|
||||||
|
return cpt
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithFilter(urlPrefix string, store cache.Cache) *Captcha {
|
||||||
|
cpt := NewCaptcha(urlPrefix, store)
|
||||||
|
|
||||||
|
// create filter for serve captcha image
|
||||||
|
beego.AddFilter(urlPrefix+":", "BeforeRouter", cpt.Handler)
|
||||||
|
|
||||||
|
// add to template func map
|
||||||
|
beego.AddFuncMap("create_captcha", cpt.CreateCaptchaHtml)
|
||||||
|
|
||||||
|
return cpt
|
||||||
|
}
|
484
utils/captcha/image.go
Normal file
484
utils/captcha/image.go
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
package captcha
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fontWidth = 11
|
||||||
|
fontHeight = 18
|
||||||
|
blackChar = 1
|
||||||
|
|
||||||
|
// Standard width and height of a captcha image.
|
||||||
|
stdWidth = 240
|
||||||
|
stdHeight = 80
|
||||||
|
// Maximum absolute skew factor of a single digit.
|
||||||
|
maxSkew = 0.7
|
||||||
|
// Number of background circles.
|
||||||
|
circleCount = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
var font = [][]byte{
|
||||||
|
{ // 0
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||||
|
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
},
|
||||||
|
{ // 1
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
},
|
||||||
|
{ // 2
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||||
|
1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
},
|
||||||
|
{ // 3
|
||||||
|
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
|
||||||
|
0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
},
|
||||||
|
{ // 4
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
|
||||||
|
0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
},
|
||||||
|
{ // 5
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||||
|
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
},
|
||||||
|
{ // 6
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||||
|
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||||
|
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||||
|
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
},
|
||||||
|
{ // 7
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||||
|
},
|
||||||
|
{ // 8
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||||
|
0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
|
||||||
|
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
},
|
||||||
|
{ // 9
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||||
|
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
|
||||||
|
0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||||
|
0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
*image.Paletted
|
||||||
|
numWidth int
|
||||||
|
numHeight int
|
||||||
|
dotSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
func getrand() *rand.Rand {
|
||||||
|
return rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func randIntn(max int) int {
|
||||||
|
return getrand().Intn(max)
|
||||||
|
}
|
||||||
|
|
||||||
|
func randInt(min, max int) int {
|
||||||
|
return getrand().Intn(max-min) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
func randFloat(min, max float64) float64 {
|
||||||
|
return (max-min)*getrand().Float64() + min
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomPalette() color.Palette {
|
||||||
|
p := make([]color.Color, circleCount+1)
|
||||||
|
// Transparent color.
|
||||||
|
p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}
|
||||||
|
// Primary color.
|
||||||
|
prim := color.RGBA{
|
||||||
|
uint8(randIntn(129)),
|
||||||
|
uint8(randIntn(129)),
|
||||||
|
uint8(randIntn(129)),
|
||||||
|
0xFF,
|
||||||
|
}
|
||||||
|
p[1] = prim
|
||||||
|
// Circle colors.
|
||||||
|
for i := 2; i <= circleCount; i++ {
|
||||||
|
p[i] = randomBrightness(prim, 255)
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewImage returns a new captcha image of the given width and height with the
|
||||||
|
// given digits, where each digit must be in range 0-9.
|
||||||
|
func NewImage(digits []byte, width, height int) *Image {
|
||||||
|
m := new(Image)
|
||||||
|
m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette())
|
||||||
|
m.calculateSizes(width, height, len(digits))
|
||||||
|
// Randomly position captcha inside the image.
|
||||||
|
maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize
|
||||||
|
maxy := height - m.numHeight - m.dotSize*2
|
||||||
|
var border int
|
||||||
|
if width > height {
|
||||||
|
border = height / 5
|
||||||
|
} else {
|
||||||
|
border = width / 5
|
||||||
|
}
|
||||||
|
x := randInt(border, maxx-border)
|
||||||
|
y := randInt(border, maxy-border)
|
||||||
|
// Draw digits.
|
||||||
|
for _, n := range digits {
|
||||||
|
m.drawDigit(font[n], x, y)
|
||||||
|
x += m.numWidth + m.dotSize
|
||||||
|
}
|
||||||
|
// Draw strike-through line.
|
||||||
|
m.strikeThrough()
|
||||||
|
// Apply wave distortion.
|
||||||
|
m.distort(randFloat(5, 10), randFloat(100, 200))
|
||||||
|
// Fill image with random circles.
|
||||||
|
m.fillWithCircles(circleCount, m.dotSize)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodedPNG encodes an image to PNG and returns
|
||||||
|
// the result as a byte slice.
|
||||||
|
func (m *Image) encodedPNG() []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := png.Encode(&buf, m.Paletted); err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes captcha image in PNG format into the given writer.
|
||||||
|
func (m *Image) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
n, err := w.Write(m.encodedPNG())
|
||||||
|
return int64(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Image) calculateSizes(width, height, ncount int) {
|
||||||
|
// Goal: fit all digits inside the image.
|
||||||
|
var border int
|
||||||
|
if width > height {
|
||||||
|
border = height / 4
|
||||||
|
} else {
|
||||||
|
border = width / 4
|
||||||
|
}
|
||||||
|
// Convert everything to floats for calculations.
|
||||||
|
w := float64(width - border*2)
|
||||||
|
h := float64(height - border*2)
|
||||||
|
// fw takes into account 1-dot spacing between digits.
|
||||||
|
fw := float64(fontWidth + 1)
|
||||||
|
fh := float64(fontHeight)
|
||||||
|
nc := float64(ncount)
|
||||||
|
// Calculate the width of a single digit taking into account only the
|
||||||
|
// width of the image.
|
||||||
|
nw := w / nc
|
||||||
|
// Calculate the height of a digit from this width.
|
||||||
|
nh := nw * fh / fw
|
||||||
|
// Digit too high?
|
||||||
|
if nh > h {
|
||||||
|
// Fit digits based on height.
|
||||||
|
nh = h
|
||||||
|
nw = fw / fh * nh
|
||||||
|
}
|
||||||
|
// Calculate dot size.
|
||||||
|
m.dotSize = int(nh / fh)
|
||||||
|
// Save everything, making the actual width smaller by 1 dot to account
|
||||||
|
// for spacing between digits.
|
||||||
|
m.numWidth = int(nw) - m.dotSize
|
||||||
|
m.numHeight = int(nh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) {
|
||||||
|
for x := fromX; x <= toX; x++ {
|
||||||
|
m.SetColorIndex(x, y, colorIdx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) {
|
||||||
|
f := 1 - radius
|
||||||
|
dfx := 1
|
||||||
|
dfy := -2 * radius
|
||||||
|
xo := 0
|
||||||
|
yo := radius
|
||||||
|
|
||||||
|
m.SetColorIndex(x, y+radius, colorIdx)
|
||||||
|
m.SetColorIndex(x, y-radius, colorIdx)
|
||||||
|
m.drawHorizLine(x-radius, x+radius, y, colorIdx)
|
||||||
|
|
||||||
|
for xo < yo {
|
||||||
|
if f >= 0 {
|
||||||
|
yo--
|
||||||
|
dfy += 2
|
||||||
|
f += dfy
|
||||||
|
}
|
||||||
|
xo++
|
||||||
|
dfx += 2
|
||||||
|
f += dfx
|
||||||
|
m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)
|
||||||
|
m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)
|
||||||
|
m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)
|
||||||
|
m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Image) fillWithCircles(n, maxradius int) {
|
||||||
|
maxx := m.Bounds().Max.X
|
||||||
|
maxy := m.Bounds().Max.Y
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
colorIdx := uint8(randInt(1, circleCount-1))
|
||||||
|
r := randInt(1, maxradius)
|
||||||
|
m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Image) strikeThrough() {
|
||||||
|
maxx := m.Bounds().Max.X
|
||||||
|
maxy := m.Bounds().Max.Y
|
||||||
|
y := randInt(maxy/3, maxy-maxy/3)
|
||||||
|
amplitude := randFloat(5, 20)
|
||||||
|
period := randFloat(80, 180)
|
||||||
|
dx := 2.0 * math.Pi / period
|
||||||
|
for x := 0; x < maxx; x++ {
|
||||||
|
xo := amplitude * math.Cos(float64(y)*dx)
|
||||||
|
yo := amplitude * math.Sin(float64(x)*dx)
|
||||||
|
for yn := 0; yn < m.dotSize; yn++ {
|
||||||
|
r := randInt(0, m.dotSize)
|
||||||
|
m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Image) drawDigit(digit []byte, x, y int) {
|
||||||
|
skf := randFloat(-maxSkew, maxSkew)
|
||||||
|
xs := float64(x)
|
||||||
|
r := m.dotSize / 2
|
||||||
|
y += randInt(-r, r)
|
||||||
|
for yo := 0; yo < fontHeight; yo++ {
|
||||||
|
for xo := 0; xo < fontWidth; xo++ {
|
||||||
|
if digit[yo*fontWidth+xo] != blackChar {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1)
|
||||||
|
}
|
||||||
|
xs += skf
|
||||||
|
x = int(xs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Image) distort(amplude float64, period float64) {
|
||||||
|
w := m.Bounds().Max.X
|
||||||
|
h := m.Bounds().Max.Y
|
||||||
|
|
||||||
|
oldm := m.Paletted
|
||||||
|
newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette)
|
||||||
|
|
||||||
|
dx := 2.0 * math.Pi / period
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
xo := amplude * math.Sin(float64(y)*dx)
|
||||||
|
yo := amplude * math.Cos(float64(x)*dx)
|
||||||
|
newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.Paletted = newm
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomBrightness(c color.RGBA, max uint8) color.RGBA {
|
||||||
|
minc := min3(c.R, c.G, c.B)
|
||||||
|
maxc := max3(c.R, c.G, c.B)
|
||||||
|
if maxc > max {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
n := randIntn(int(max-maxc)) - int(minc)
|
||||||
|
return color.RGBA{
|
||||||
|
uint8(int(c.R) + n),
|
||||||
|
uint8(int(c.G) + n),
|
||||||
|
uint8(int(c.B) + n),
|
||||||
|
uint8(c.A),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func min3(x, y, z uint8) (m uint8) {
|
||||||
|
m = x
|
||||||
|
if y < m {
|
||||||
|
m = y
|
||||||
|
}
|
||||||
|
if z < m {
|
||||||
|
m = z
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func max3(x, y, z uint8) (m uint8) {
|
||||||
|
m = x
|
||||||
|
if y > m {
|
||||||
|
m = y
|
||||||
|
}
|
||||||
|
if z > m {
|
||||||
|
m = z
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
38
utils/captcha/image_test.go
Normal file
38
utils/captcha/image_test.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package captcha
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type byteCounter struct {
|
||||||
|
n int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc *byteCounter) Write(b []byte) (int, error) {
|
||||||
|
bc.n += int64(len(b))
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNewImage(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
d := utils.RandomCreateBytes(challengeNums, defaultChars...)
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
NewImage(d, stdWidth, stdHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkImageWriteTo(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
d := utils.RandomCreateBytes(challengeNums, defaultChars...)
|
||||||
|
b.StartTimer()
|
||||||
|
counter := &byteCounter{}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
img := NewImage(d, stdWidth, stdHeight)
|
||||||
|
img.WriteTo(counter)
|
||||||
|
b.SetBytes(counter.n)
|
||||||
|
counter.n = 0
|
||||||
|
}
|
||||||
|
}
|
20
utils/rand.go
Normal file
20
utils/rand.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandomCreateBytes generate random []byte by specify chars.
|
||||||
|
func RandomCreateBytes(n int, alphabets ...byte) []byte {
|
||||||
|
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
|
var bytes = make([]byte, n)
|
||||||
|
rand.Read(bytes)
|
||||||
|
for i, b := range bytes {
|
||||||
|
if len(alphabets) == 0 {
|
||||||
|
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||||
|
} else {
|
||||||
|
bytes[i] = alphabets[b%byte(len(alphabets))]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user