1
1
mirror of https://github.com/go-gitea/gitea synced 2025-09-28 03:28:13 +00:00

New UI merge in progress

This commit is contained in:
Unknwon
2014-07-26 00:24:27 -04:00
parent 0a739cf9ac
commit 8dd07c0ddd
199 changed files with 15030 additions and 9325 deletions

201
modules/captcha/captcha.go Normal file
View File

@@ -0,0 +1,201 @@
// Copyright 2014 The Gogs Authors. 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 a middleware that provides captcha service for Macaron.
package captcha
import (
"fmt"
"html/template"
"net/http"
"path"
"strings"
"github.com/Unknwon/macaron"
"github.com/gogits/cache"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
var (
defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
)
const (
// default captcha attributes
challengeNums = 6
expiration = 600
fieldIdName = "captcha_id"
fieldCaptchaName = "captcha"
cachePrefix = "captcha_"
defaultURLPrefix = "/captcha/"
)
// Captcha struct
type Captcha struct {
store cache.Cache
// url prefix for captcha image
URLPrefix string
// specify captcha id input field name
FieldIdName string
// specify captcha result input field name
FieldCaptchaName string
// captcha image width and height
StdWidth int
StdHeight int
// captcha chars nums
ChallengeNums int
// captcha expiration seconds
Expiration int64
// cache key prefix
CachePrefix string
}
// generate key string
func (c *Captcha) key(id string) string {
return c.CachePrefix + id
}
// generate rand chars with default chars
func (c *Captcha) genRandChars() []byte {
return base.RandomCreateBytes(c.ChallengeNums, defaultChars...)
}
// beego filter handler for serve captcha image
func (c *Captcha) Handler(ctx *macaron.Context) {
var chars []byte
id := path.Base(ctx.Req.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.Status(404)
ctx.Write([]byte("captcha not found"))
return
}
// reload captcha
if len(ctx.Query("reload")) > 0 {
chars = c.genRandChars()
if err := c.store.Put(key, chars, c.Expiration); err != nil {
ctx.Status(500)
ctx.Write([]byte("captcha reload error"))
log.Error(4, "Reload Create Captcha Error: %v", err)
return
}
}
img := NewImage(chars, c.StdWidth, c.StdHeight)
if _, err := img.WriteTo(ctx.RW()); err != nil {
log.Error(4, "Write Captcha Image Error: %v", err)
}
}
// tempalte func for output html
func (c *Captcha) CreateCaptchaHtml() template.HTML {
value, err := c.CreateCaptcha()
if err != nil {
log.Error(4, "Create Captcha Error: %v", 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))
}
// create a new captcha id
func (c *Captcha) CreateCaptcha() (string, error) {
// generate captcha id
id := string(base.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
}
// verify from a request
func (c *Captcha) VerifyReq(req *http.Request) bool {
req.ParseForm()
return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName))
}
// direct verify id and challenge string
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
}
// create a new captcha.Captcha
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 = defaultURLPrefix
}
if urlPrefix[len(urlPrefix)-1] != '/' {
urlPrefix += "/"
}
cpt.URLPrefix = urlPrefix
base.TemplateFuncs["CreateCaptcha"] = cpt.CreateCaptchaHtml
return cpt
}

487
modules/captcha/image.go Normal file
View File

@@ -0,0 +1,487 @@
// Copyright 2014 The Gogs Authors. 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
import (
"bytes"
"image"
"image/color"
"image/png"
"io"
"math"
)
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
}
var prng = &siprng{}
// randIntn returns a pseudorandom non-negative int in range [0, n).
func randIntn(n int) int {
return prng.Intn(n)
}
// randInt returns a pseudorandom int in range [from, to].
func randInt(from, to int) int {
return prng.Intn(to+1-from) + from
}
// randFloat returns a pseudorandom float64 in range [from, to].
func randFloat(from, to float64) float64 {
return (to-from)*prng.Float64() + from
}
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
}

View File

@@ -0,0 +1,42 @@
// Copyright 2014 The Gogs Authors. 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
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
}
}

267
modules/captcha/siprng.go Normal file
View File

@@ -0,0 +1,267 @@
// Copyright 2014 The Gogs Authors. 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
import (
"crypto/rand"
"encoding/binary"
"io"
"sync"
)
// siprng is PRNG based on SipHash-2-4.
type siprng struct {
mu sync.Mutex
k0, k1, ctr uint64
}
// siphash implements SipHash-2-4, accepting a uint64 as a message.
func siphash(k0, k1, m uint64) uint64 {
// Initialization.
v0 := k0 ^ 0x736f6d6570736575
v1 := k1 ^ 0x646f72616e646f6d
v2 := k0 ^ 0x6c7967656e657261
v3 := k1 ^ 0x7465646279746573
t := uint64(8) << 56
// Compression.
v3 ^= m
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
v0 ^= m
// Compress last block.
v3 ^= t
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
v0 ^= t
// Finalization.
v2 ^= 0xff
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 3.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 4.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
return v0 ^ v1 ^ v2 ^ v3
}
// rekey sets a new PRNG key, which is read from crypto/rand.
func (p *siprng) rekey() {
var k [16]byte
if _, err := io.ReadFull(rand.Reader, k[:]); err != nil {
panic(err.Error())
}
p.k0 = binary.LittleEndian.Uint64(k[0:8])
p.k1 = binary.LittleEndian.Uint64(k[8:16])
p.ctr = 1
}
// Uint64 returns a new pseudorandom uint64.
// It rekeys PRNG on the first call and every 64 MB of generated data.
func (p *siprng) Uint64() uint64 {
p.mu.Lock()
if p.ctr == 0 || p.ctr > 8*1024*1024 {
p.rekey()
}
v := siphash(p.k0, p.k1, p.ctr)
p.ctr++
p.mu.Unlock()
return v
}
func (p *siprng) Int63() int64 {
return int64(p.Uint64() & 0x7fffffffffffffff)
}
func (p *siprng) Uint32() uint32 {
return uint32(p.Uint64())
}
func (p *siprng) Int31() int32 {
return int32(p.Uint32() & 0x7fffffff)
}
func (p *siprng) Intn(n int) int {
if n <= 0 {
panic("invalid argument to Intn")
}
if n <= 1<<31-1 {
return int(p.Int31n(int32(n)))
}
return int(p.Int63n(int64(n)))
}
func (p *siprng) Int63n(n int64) int64 {
if n <= 0 {
panic("invalid argument to Int63n")
}
max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
v := p.Int63()
for v > max {
v = p.Int63()
}
return v % n
}
func (p *siprng) Int31n(n int32) int32 {
if n <= 0 {
panic("invalid argument to Int31n")
}
max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
v := p.Int31()
for v > max {
v = p.Int31()
}
return v % n
}
func (p *siprng) Float64() float64 { return float64(p.Int63()) / (1 << 63) }

View File

@@ -0,0 +1,23 @@
// Copyright 2014 The Gogs Authors. 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
import "testing"
func TestSiphash(t *testing.T) {
good := uint64(0xe849e8bb6ffe2567)
cur := siphash(0, 0, 0)
if cur != good {
t.Fatalf("siphash: expected %x, got %x", good, cur)
}
}
func BenchmarkSiprng(b *testing.B) {
b.SetBytes(8)
p := &siprng{}
for i := 0; i < b.N; i++ {
p.Uint64()
}
}