Browse Source

Reorganize code. Add storage.

Dmitry Chestnykh 14 years ago
parent
commit
caf79f3bd2
8 changed files with 381 additions and 228 deletions
  1. 28 0
      .gitignore
  2. 19 0
      LICENSE
  3. 10 0
      Makefile
  4. 96 228
      captcha.go
  5. 7 0
      cmd/Makefile
  6. 10 0
      cmd/main.go
  7. 102 0
      draw.go
  8. 109 0
      font.go

+ 28 - 0
.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

+ 19 - 0
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.

+ 10 - 0
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
+

+ 96 - 228
captcha.go

@@ -1,268 +1,136 @@
-package main
+package captcha
 
 
 import (
 import (
+	"bytes"
 	"image"
 	"image"
 	"image/png"
 	"image/png"
 	"os"
 	"os"
 	"rand"
 	"rand"
 	"time"
 	"time"
 	crand "crypto/rand"
 	crand "crypto/rand"
+	"github.com/dchest/uniuri"
 	"io"
 	"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 (
 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
+}
+
+type storage struct {
+	mu  sync.RWMutex
+	ids map[string][]byte
+	exp *list.List
+	// Number of items stored after last collection
+	colNum int
 }
 }
 
 
-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
+func newStore() *storage {
+	s := new(storage)
+	s.ids = make(map[string][]byte)
+	s.exp = list.New()
+	return s
+}
 
 
-	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)
+var store = newStore()
 
 
-	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)
+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
-	}
-	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
+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")
 	}
 	}
+	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})
-		}
-	}
-	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 
+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
 	}
 	}
-	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)
 }
 }
-

+ 7 - 0
cmd/Makefile

@@ -0,0 +1,7 @@
+include $(GOROOT)/src/Make.inc
+
+TARG=captcha
+GOFILES=\
+	main.go
+
+include $(GOROOT)/src/Make.cmd

+ 10 - 0
cmd/main.go

@@ -0,0 +1,10 @@
+package main
+
+import (
+	"github.com/dchest/captcha"
+	"os"
+)
+
+func main() {
+	captcha.Encode(os.Stdout)
+}

+ 102 - 0
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
+	}
+}

+ 109 - 0
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,
+	},
+}