Reorganize code. Add storage.
Dmitry Chestnykh dmitry@codingrobots.com
Wed, 20 Apr 2011 01:24:30 +0200
8 files changed,
381 insertions(+),
228 deletions(-)
A
.gitignore
@@ -0,0 +1,28 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe + +# Generated test captchas +*.png + +# Program +cmd/captcha
A
LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Dmitry Chestnykh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.
A
Makefile
@@ -0,0 +1,10 @@
+include $(GOROOT)/src/Make.inc + +TARG=github.com/dchest/captcha +GOFILES=\ + captcha.go\ + draw.go\ + font.go + +include $(GOROOT)/src/Make.pkg +
M
captcha.go
→
captcha.go
@@ -1,268 +1,136 @@
-package main +package captcha import ( + "bytes" "image" "image/png" "os" "rand" "time" crand "crypto/rand" + "github.com/dchest/uniuri" "io" + "container/list" + "sync" ) -var numbers = [][]byte{ - { - 0, 1, 1, 1, 0, - 1, 0, 0, 0, 1, - 1, 0, 0, 0, 1, - 1, 0, 0, 0, 1, - 1, 0, 0, 0, 1, - 1, 0, 0, 0, 1, - 1, 0, 0, 0, 1, - 0, 1, 1, 1, 0, - }, - { - 0, 0, 1, 0, 0, - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 1, 0, 0, - 1, 1, 1, 1, 1, - }, - { - 0, 1, 1, 1, 0, - 1, 0, 0, 0, 1, - 0, 0, 0, 0, 1, - 0, 0, 0, 1, 1, - 0, 1, 1, 0, 0, - 1, 0, 0, 0, 0, - 1, 0, 0, 0, 0, - 1, 1, 1, 1, 1, - }, - { - 1, 1, 1, 1, 1, - 0, 0, 0, 0, 1, - 0, 0, 0, 1, 1, - 0, 1, 1, 0, 0, - 0, 0, 0, 1, 0, - 0, 0, 0, 0, 1, - 0, 0, 0, 0, 1, - 1, 1, 1, 1, 0, - }, - { - 1, 0, 0, 1, 0, - 1, 0, 0, 1, 0, - 1, 0, 0, 1, 0, - 1, 0, 0, 1, 0, - 1, 1, 1, 1, 1, - 0, 0, 0, 1, 0, - 0, 0, 0, 1, 0, - 0, 0, 0, 1, 0, - }, - { - 1, 1, 1, 1, 1, - 1, 0, 0, 0, 0, - 1, 0, 0, 0, 0, - 1, 1, 1, 1, 0, - 0, 0, 0, 1, 1, - 0, 0, 0, 0, 1, - 0, 0, 0, 1, 1, - 1, 1, 1, 1, 0, - }, - { - 0, 0, 1, 1, 1, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 1, 1, 1, 1, 0, - 1, 1, 0, 0, 1, - 1, 0, 0, 0, 1, - 1, 1, 0, 0, 1, - 0, 1, 1, 1, 0, - }, - { - 1, 1, 1, 1, 1, - 0, 0, 0, 0, 1, - 0, 0, 0, 0, 1, - 0, 0, 0, 1, 0, - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 1, 0, 0, 0, - }, - { - 0, 1, 1, 1, 0, - 1, 0, 0, 0, 1, - 1, 1, 0, 1, 1, - 0, 1, 1, 1, 0, - 1, 1, 0, 1, 1, - 1, 0, 0, 0, 1, - 1, 1, 0, 1, 1, - 0, 1, 1, 1, 0, - }, - { - 0, 1, 1, 1, 0, - 1, 0, 0, 1, 1, - 1, 0, 0, 0, 1, - 1, 1, 0, 0, 1, - 0, 1, 1, 1, 1, - 0, 0, 0, 0, 1, - 0, 0, 0, 0, 1, - 1, 1, 1, 1, 0, - }, -} - const ( - NumberWidth = 5 - NumberHeight = 8 - DotSize = 6 - SkewFactor = 3 + dotSize = 6 + maxSkew = 3 + expiration = 2 * 60 // 2 minutes + collectNum = 100 // number of items that triggers collection ) -func drawHorizLine(img *image.NRGBA, color image.Color, fromX, toX, y int) { - for x := fromX; x <= toX; x++ { - img.Set(x, y, color) - } +type expValue struct { + timestamp int64 + id string } -func drawCircle(img *image.NRGBA, color image.Color, x0, y0, radius int) { - f := 1 - radius - ddF_x := 1 - ddF_y := -2 * radius - x := 0 - y := radius +type storage struct { + mu sync.RWMutex + ids map[string][]byte + exp *list.List + // Number of items stored after last collection + colNum int +} - img.Set(x0, y0+radius, color) - img.Set(x0, y0-radius, color) - //img.Set(x0+radius, y0, color) - //img.Set(x0-radius, y0, color) - drawHorizLine(img, color, x0-radius, x0+radius, y0) +func newStore() *storage { + s := new(storage) + s.ids = make(map[string][]byte) + s.exp = list.New() + return s +} - for x < y { - // ddF_x == 2 * x + 1; - // ddF_y == -2 * y; - // f == x*x + y*y - radius*radius + 2*x - y + 1; - if f >= 0 { - y-- - ddF_y += 2 - f += ddF_y - } - x++ - ddF_x += 2 - f += ddF_x - //img.Set(x0+x, y0+y, color) - //img.Set(x0-x, y0+y, color) - drawHorizLine(img, color, x0-x, x0+x, y0+y) - //img.Set(x0+x, y0-y, color) - //img.Set(x0-x, y0-y, color) - drawHorizLine(img, color, x0-x, x0+x, y0-y) - //img.Set(x0+y, y0+x, color) - //img.Set(x0-y, y0+x, color) - drawHorizLine(img, color, x0-y, x0+y, y0+x) - //img.Set(x0+y, y0-x, color) - //img.Set(x0-y, y0-x, color) - drawHorizLine(img, color, x0-y, x0+y, y0-x) +var store = newStore() + +func NewImage(numbers []byte) *image.NRGBA { + w := numberWidth * (dotSize + 2) * len(numbers) + h := numberHeight * (dotSize + 5) + img := image.NewNRGBA(w, h) + color := image.NRGBAColor{uint8(rand.Intn(50)), uint8(rand.Intn(50)), uint8(rand.Intn(128)), 0xFF} + fillWithCircles(img, color, 40, 4) + x := rand.Intn(dotSize) + y := 0 + setRandomBrightness(&color, 180) + for _, n := range numbers { + y = rand.Intn(dotSize * 4) + drawNumber(img, font[n], x, y, color) + x += dotSize*numberWidth + rand.Intn(maxSkew) + 3 } + return img } -func min3(x, y, z uint8) (o uint8) { - o = x - if y < o { - o = y - } - if z < o { - o = z - } - return +func init() { + rand.Seed(time.Seconds()) } -func max3(x, y, z uint8) (o uint8) { - o = x - if y > o { - o = y +func randomNumbers() []byte { + n := make([]byte, 6) + if _, err := io.ReadFull(crand.Reader, n); err != nil { + panic(err) } - if z > o { - o = z + for i := range n { + n[i] %= 10 } - return + return n } -func setRandomBrightness(c *image.NRGBAColor, max uint8) { - minc := min3(c.R, c.G, c.B) - maxc := max3(c.R, c.G, c.B) - if maxc > max { - return - } - n := rand.Intn(int(max-maxc)) - int(minc) - c.R = uint8(int(c.R) + n) - c.G = uint8(int(c.G) + n) - c.B = uint8(int(c.B) + n) +func Encode(w io.Writer) (numbers []byte, err os.Error) { + numbers = randomNumbers() + err = png.Encode(w, NewImage(numbers)) + return } -func fillWithCircles(img *image.NRGBA, n, maxradius int) { - maxx := img.Bounds().Max.X - maxy := img.Bounds().Max.Y - color := image.NRGBAColor{0, 0, 0x80, 0xFF} - for i := 0; i < n; i++ { - setRandomBrightness(&color, 255) - r := rand.Intn(maxradius-1)+1 - drawCircle(img, color, rand.Intn(maxx-r*2)+r, rand.Intn(maxy-r*2)+r, r) +func New() string { + ns := randomNumbers() + id := uniuri.New() + store.mu.Lock() + defer store.mu.Unlock() + store.ids[id] = ns + store.exp.PushBack(expValue{time.Seconds(), id}) + store.colNum++ + if store.colNum > collectNum { + Collect() + store.colNum = 0 } + return id } -func drawNumber(img *image.NRGBA, number []byte, x, y int, color image.NRGBAColor) { - skf := rand.Intn(SkewFactor)-SkewFactor/2 - if skf < 0 { - x -= skf * NumberHeight +func WriteImage(w io.Writer, id string) os.Error { + store.mu.RLock() + defer store.mu.RUnlock() + ns, ok := store.ids[id] + if !ok { + return os.NewError("captcha id not found") } - for y0 := 0; y0 < NumberHeight; y0++ { - for x0 := 0; x0 < NumberWidth; x0++ { - radius := rand.Intn(DotSize/2)+DotSize/2 - addx := rand.Intn(radius/2) - addy := rand.Intn(radius/2) - if number[y0*NumberWidth+x0] == 1 { - drawCircle(img, color, x+x0*DotSize+DotSize+addx, y+y0*DotSize+DotSize+addy, radius) - } - } - x += skf - } + return png.Encode(w, NewImage(ns)) } -func drawNumbersToImage(ns []byte) { - img := image.NewNRGBA(NumberWidth*(DotSize+2)*len(ns)+DotSize, NumberHeight*DotSize+(DotSize*6)) - for y := 0; y < img.Bounds().Max.Y; y++ { - for x := 0; x < img.Bounds().Max.X; x++ { - img.Set(x, y, image.NRGBAColor{0xFF, 0xFF, 0xFF, 0xFF}) - } +func Verify(w io.Writer, id string, ns []byte) bool { + store.mu.Lock() + defer store.mu.Unlock() + realns, ok := store.ids[id] + if !ok { + return false } - fillWithCircles(img, 60, 3) - x := rand.Intn(DotSize) - y := 0 - color := image.NRGBAColor{0, 0, 0x80, 0xFF} - setRandomBrightness(&color, 180) - for _, n := range ns { - y = rand.Intn(DotSize*4) - drawNumber(img, numbers[n], x, y, color) - x += DotSize * NumberWidth + rand.Intn(SkewFactor)+3 - } - f, err := os.Create("captcha.png") - if err != nil { - panic(err) - } - defer f.Close() - png.Encode(f, img) + store.ids[id] = nil, false + return bytes.Equal(ns, realns) } -func main() { - rand.Seed(time.Seconds()) - n := make([]byte, 6) - if _, err := io.ReadFull(crand.Reader, n); err != nil { - panic(err) - } - for i := range n { - n[i] %= 10 +func Collect() { + now := time.Seconds() + store.mu.Lock() + defer store.mu.Unlock() + for e := store.exp.Front(); e != nil; e = e.Next() { + ev, ok := e.Value.(expValue) + if !ok { + return + } + if ev.timestamp+expiration < now { + store.ids[ev.id] = nil, false + store.exp.Remove(e) + } else { + return + } } - drawNumbersToImage(n) } -
A
cmd/Makefile
@@ -0,0 +1,7 @@
+include $(GOROOT)/src/Make.inc + +TARG=captcha +GOFILES=\ + main.go + +include $(GOROOT)/src/Make.cmd
A
cmd/main.go
@@ -0,0 +1,10 @@
+package main + +import ( + "github.com/dchest/captcha" + "os" +) + +func main() { + captcha.Encode(os.Stdout) +}
A
draw.go
@@ -0,0 +1,102 @@
+package captcha + +import ( + "image" + "rand" +) + +func drawHorizLine(img *image.NRGBA, color image.Color, fromX, toX, y int) { + for x := fromX; x <= toX; x++ { + img.Set(x, y, color) + } +} + +func drawCircle(img *image.NRGBA, color image.Color, x0, y0, radius int) { + f := 1 - radius + ddFx := 1 + ddFy := -2 * radius + x := 0 + y := radius + + img.Set(x0, y0+radius, color) + img.Set(x0, y0-radius, color) + drawHorizLine(img, color, x0-radius, x0+radius, y0) + + for x < y { + if f >= 0 { + y-- + ddFy += 2 + f += ddFy + } + x++ + ddFx += 2 + f += ddFx + drawHorizLine(img, color, x0-x, x0+x, y0+y) + drawHorizLine(img, color, x0-x, x0+x, y0-y) + drawHorizLine(img, color, x0-y, x0+y, y0+x) + drawHorizLine(img, color, x0-y, x0+y, y0-x) + } +} + +func min3(x, y, z uint8) (o uint8) { + o = x + if y < o { + o = y + } + if z < o { + o = z + } + return +} + +func max3(x, y, z uint8) (o uint8) { + o = x + if y > o { + o = y + } + if z > o { + o = z + } + return +} + +func setRandomBrightness(c *image.NRGBAColor, max uint8) { + minc := min3(c.R, c.G, c.B) + maxc := max3(c.R, c.G, c.B) + if maxc > max { + return + } + n := rand.Intn(int(max-maxc)) - int(minc) + c.R = uint8(int(c.R) + n) + c.G = uint8(int(c.G) + n) + c.B = uint8(int(c.B) + n) +} + +func fillWithCircles(img *image.NRGBA, color image.NRGBAColor, n, maxradius int) { + maxx := img.Bounds().Max.X + maxy := img.Bounds().Max.Y + for i := 0; i < n; i++ { + setRandomBrightness(&color, 255) + r := rand.Intn(maxradius-1) + 1 + drawCircle(img, color, rand.Intn(maxx-r*2)+r, rand.Intn(maxy-r*2)+r, r) + } +} + +func drawNumber(img *image.NRGBA, number []byte, x, y int, color image.NRGBAColor) { + skf := rand.Intn(maxSkew) - maxSkew/2 + if skf < 0 { + x -= skf * numberHeight + } + for y0 := 0; y0 < numberHeight; y0++ { + for x0 := 0; x0 < numberWidth; x0++ { + radius := rand.Intn(dotSize/2) + dotSize/2 + addx := rand.Intn(radius / 2) + addy := rand.Intn(radius / 2) + if number[y0*numberWidth+x0] == 1 { + drawCircle(img, color, x+x0*dotSize+dotSize+addx, + y+y0*dotSize+dotSize+addy, radius) + } + } + x += skf + } +}
A
font.go
@@ -0,0 +1,109 @@
+package captcha + +const ( + numberWidth = 5 + numberHeight = 8 +) + +var font = [][]byte{ + { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + }, + { + 0, 0, 1, 0, 0, + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 1, 1, 1, 1, 1, + }, + { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 1, + 0, 1, 1, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 1, 1, 1, 1, + }, + { + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 1, + 0, 1, 1, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 1, 1, 1, 1, 0, + }, + { + 1, 0, 0, 1, 0, + 1, 0, 0, 1, 0, + 1, 0, 0, 1, 0, + 1, 0, 0, 1, 0, + 1, 1, 1, 1, 1, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + }, + { + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 1, 1, 1, 0, + 0, 0, 0, 1, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 1, + 1, 1, 1, 1, 0, + }, + { + 0, 0, 1, 1, 1, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 1, 1, 1, 0, + 1, 1, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 1, 0, 0, 1, + 0, 1, 1, 1, 0, + }, + { + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + }, + { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 1, 0, 1, 1, + 0, 1, 1, 1, 0, + 1, 1, 0, 1, 1, + 1, 0, 0, 0, 1, + 1, 1, 0, 1, 1, + 0, 1, 1, 1, 0, + }, + { + 0, 1, 1, 1, 0, + 1, 0, 0, 1, 1, + 1, 0, 0, 0, 1, + 1, 1, 0, 0, 1, + 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 1, 1, 1, 1, 0, + }, +}