Repository: hanguofeng/gocaptcha
Branch: master
Commit: d395745388d1
Files: 37
Total size: 47.9 KB
Directory structure:
gitextract_al2ckzk5/
├── .github/
│ └── workflows/
│ └── go.yml
├── .gitignore
├── .travis.yml
├── Dockerfile
├── LICENSE
├── README-en.md
├── README.md
├── VERSION
├── captcha.go
├── captcha_test.go
├── captchainfo.go
├── cimage.go
├── confighelper.go
├── configtypes.go
├── cstore.go
├── cstore_test.go
├── data/
│ ├── cn_char
│ ├── cn_phrases
│ ├── en_char
│ └── en_phrases
├── fontmanager.go
├── go.mod
├── go.sum
├── imagefilter.go
├── imagefiltermanager.go
├── imagefiltermanager_test.go
├── imagefilternoiseline.go
├── imagefilternoisepoint.go
├── imagefilterstrike.go
├── mcstore.go
├── mcstore_test.go
├── randhelper.go
├── redisstore.go
├── redisstore_test.go
├── storeinterface.go
├── wordmanager.go
└── wordmanager_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/go.yml
================================================
name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
- name: Install memcache/redis tool
run: |
sudo apt-get update
sudo apt-get install memcached redis-server
memcached -d
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
_test_files
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
================================================
FILE: .travis.yml
================================================
language: go
go:
- 1.3
- 1.4
- tip
services:
- memcached
- redis-server
================================================
FILE: Dockerfile
================================================
FROM centos
RUN yum -y update
RUN yum install -y golang git mercurial memcached
RUN mkdir -p /home/work/gopath
ENV GOPATH /home/work/gopath;
RUN go get github.com/hanguofeng/gocaptcha/samples/gocaptcha-server;
WORKDIR $GOPATH/src/github.com/hanguofeng/gocaptcha/samples/gocaptcha-server
RUN ["go","build"]
EXPOSE 80
ENTRYPOINT ["./gocaptcha-server"]
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2013 hanguofeng
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.
================================================
FILE: README-en.md
================================================
***
View in [[English](README-en.md)][[中文](README.md)]
***
# gocaptcha
Captcha server writen in golang
[](https://travis-ci.org/hanguofeng/gocaptcha) [](https://drone.io/github.com/hanguofeng/gocaptcha/latest) [](https://coveralls.io/r/hanguofeng/gocaptcha)
Feature
-------
* supports captcha char in Chinese
* supports self-define word/char dictionary
* supports filter plugin
* filters:
* noise point
* noise line
* other type of noise
* plugin
* supports extensible store engine
* build-in store engine
* memcache
* redis (from https://github.com/dtynn/gocaptcha)
* implement your own by implement the StoreInterface
Useage
------
**Install**
go get github.com/hanguofeng/gocaptcha
**Quick Start**
See [captcha_test.go](captcha_test.go)
See [samples/gocaptcha-server](samples/gocaptcha-server)
[Demo](http://hanguofeng-gocaptcha.daoapp.io/)
**Document**
[[captcha.go Wiki](https://github.com/hanguofeng/gocaptcha/wiki)]
TODO
----
* ops tools
LICENCE
-------
gocaptcha use [[MIT LICENSE](LICENSE)]
Thanks:
* https://github.com/dchest/captcha
* https://github.com/golang/freetype
* https://github.com/bradfitz/gomemcache
* https://code.google.com/p/zpix/
================================================
FILE: README.md
================================================
***
View in [[English](README-en.md)][[中文](README.md)]
***
# gocaptcha
go语言验证码服务器
Feature
-------
* 支持中文验证码
* 支持自定义词库、字库
* 支持自定义滤镜机制,通过滤镜来增加干扰,加大识别难度
* 当前的滤镜包括:
* 支持干扰点
* 支持干扰线
* 支持其他模式的干扰
* 更多模式,可实现imagefilter接口来扩展
* 支持自定义存储引擎,存储引擎可扩展
* 目前支持的存储引擎包括:
* 内置(buildin)
* memcache
* redis (from https://github.com/dtynn/gocaptcha)
* 如需扩展存储引擎,可实现StoreInterface接口
Useage
------
**安装**
go get github.com/hanguofeng/gocaptcha
**Quick Start**
参考 [captcha_test.go](captcha_test.go)
参考 [samples/gocaptcha-server](samples/gocaptcha-server)
[Demo](http://hanguofeng-gocaptcha.daoapp.io/)
**文档**
[[captcha.go Wiki](https://github.com/hanguofeng/gocaptcha/wiki)]
TODO
----
* 运维管理工具
LICENCE
-------
gocaptcha使用[[MIT许可协议](LICENSE)]
使用的开源软件列表,表示感谢
* https://github.com/dchest/captcha
* https://github.com/golang/freetype
* https://github.com/bradfitz/gomemcache
* https://code.google.com/p/zpix/
================================================
FILE: VERSION
================================================
================================================
FILE: captcha.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"errors"
"image"
"strings"
"time"
)
//Captcha is the core captcha struct
type Captcha struct {
store StoreInterface
wordManager *WordManager
filterManager *ImageFilterManager
captchaConfig *CaptchaConfig
imageConfig *ImageConfig
filterConfig *FilterConfig
storeConfig *StoreConfig
}
//CreateCaptcha is a method to create new Captcha struct
func CreateCaptcha(wordManager *WordManager, captchaConfig *CaptchaConfig, imageConfig *ImageConfig, filterConfig *FilterConfig, storeConfig *StoreConfig) (*Captcha, error) {
var retErr error
captcha := new(Captcha)
store, err := createStore(storeConfig)
if nil == err {
captcha.store = store
} else {
retErr = err
}
if nil != wordManager {
captcha.wordManager = wordManager
} else {
retErr = errors.New("CreateCaptcha fail:invalid wordManager")
}
captcha.captchaConfig = captchaConfig
captcha.imageConfig = imageConfig
captcha.filterConfig = filterConfig
captcha.filterManager = CreateImageFilterManagerByConfig(filterConfig)
return captcha, retErr
}
//CreateCaptchaFromConfigFile is a method to create new Captcha struct
func CreateCaptchaFromConfigFile(configFile string) (*Captcha, error) {
var captcha *Captcha
var retErr error
err, wordDict, captchaConfig, imageConfig, filterConfig, storeConfig := loadConfigFromFile(configFile)
if nil == err {
wordmgr, err := CreateWordManagerFromDataFile(wordDict)
if nil == err {
captcha, retErr = CreateCaptcha(wordmgr, captchaConfig, imageConfig, filterConfig, storeConfig)
} else {
retErr = err
}
} else {
retErr = err
}
return captcha, retErr
}
//GetKey will generate a key with required length
func (captcha *Captcha) GetKey(length int) (string, error) {
var retErr error
var rst string
text, err := captcha.wordManager.Get(length)
if nil != err {
retErr = err
} else {
info := new(CaptchaInfo)
info.Text = text
info.CreateTime = time.Now()
info.ShownTimes = 0
rst = captcha.store.Add(info)
}
return rst, retErr
}
//Verify will verify the user's input and the server stored captcha text
func (captcha *Captcha) Verify(key, textToVerify string) (bool, string) {
info := captcha.store.Get(key)
if nil == info {
return false, "captcha info not found"
}
if info.CreateTime.Add(captcha.captchaConfig.LifeTime).Before(time.Now()) {
return false, "captcha expires"
}
verified := false
if captcha.captchaConfig.CaseSensitive {
verified = info.Text == textToVerify
} else {
verified = strings.ToLower(info.Text) == strings.ToLower(textToVerify)
}
if !verified {
return false, "captcha text not match"
}
captcha.store.Del(key)
return true, ""
}
//GetImage will generate the binary image data
func (captcha *Captcha) GetImage(key string) (image.Image, error) {
info := captcha.store.Get(key)
if nil == info {
return nil, errors.New("captcha info not found")
}
if info.CreateTime.Add(captcha.captchaConfig.LifeTime).Before(time.Now()) {
return nil, errors.New("captcha expires")
}
if captcha.captchaConfig.ChangeTextOnRefresh {
if info.ShownTimes > 0 {
text, err := captcha.wordManager.Get(len(info.Text))
if nil != err {
return nil, err
} else {
info.Text = text
}
}
info.ShownTimes++
captcha.store.Update(key, info)
}
cimg := captcha.genImage(info.Text)
return cimg, nil
}
func createStore(config *StoreConfig) (StoreInterface, error) {
var store StoreInterface
var err error
switch config.Engine {
case STORE_ENGINE_BUILDIN:
store = CreateCStore(config.LifeTime, config.GcProbability, config.GcDivisor)
break
case STORE_ENGINE_MEMCACHE:
store = CreateMCStore(config.LifeTime, config.Servers)
break
default:
creator, has := storeCreators[config.Engine]
if !has {
err = errors.New("Not supported engine:'" + config.Engine + "'")
break
}
store, err = creator(config)
}
return store, err
}
func (captcha *Captcha) genImage(text string) *CImage {
cimg := CreateCImage(captcha.imageConfig)
cimg.drawString(text)
for _, filter := range captcha.filterManager.GetFilters() {
filter.Proc(cimg)
}
return cimg
}
================================================
FILE: captcha_test.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"os"
"testing"
"time"
)
func TestCaptcha(t *testing.T) {
captcha, err := getCaptcha()
if nil != err {
t.Fatalf("getCaptcha Error:%s", err.Error())
}
key, err := captcha.GetKey(4)
if nil != err {
t.Fatalf("GetKey Error:%s", err.Error())
}
captcha.GetImage(key)
captcha.Verify(key, "test")
}
func BenchmarkCaptcha(t *testing.B) {
captcha, err := getCaptcha()
if nil != err {
t.Fatalf("getCaptcha Error:%s", err.Error())
}
for i := 0; i < t.N; i++ {
s, _ := captcha.GetKey(4)
captcha.GetImage(s)
captcha.Verify(s, "ssss")
}
}
func BenchmarkCaptchaInternalAPI(t *testing.B) {
captcha, err := getCaptcha()
if nil != err {
t.Fatalf("getCaptcha Error:%s", err.Error())
}
for i := 0; i < t.N; i++ {
s, _ := captcha.GetKey(4)
captcha.Verify(s, "ssss")
}
}
func BenchmarkCaptchaDrawImage(t *testing.B) {
captcha, err := getCaptcha()
if nil != err {
t.Fatalf("getCaptcha Error:%s", err.Error())
}
for i := 0; i < t.N; i++ {
s, _ := captcha.GetKey(4)
captcha.GetImage(s)
}
}
func getCaptcha() (*Captcha, error) {
wordDict, captchaConfig, imageConfig, filterConfig, storeConfig := loadConfig()
wordmgr, err := CreateWordManagerFromDataFile(wordDict)
captcha, err := CreateCaptcha(wordmgr, captchaConfig, imageConfig, filterConfig, storeConfig)
return captcha, err
}
func loadConfig() (string, *CaptchaConfig, *ImageConfig, *FilterConfig, *StoreConfig) {
pwd, _ := os.Getwd()
data_path := pwd + "/data/"
wordDict := data_path + "en_phrases"
captchaConfig := new(CaptchaConfig)
captchaConfig.LifeTime = 10 * time.Second
imageConfig := new(ImageConfig)
imageConfig.FontFiles = []string{data_path + "zpix.ttf"}
imageConfig.FontSize = 26
imageConfig.Height = 40
imageConfig.Width = 120
filterConfig := new(FilterConfig)
filterConfig.Init()
filterConfig.Filters = []string{"ImageFilterNoiseLine", "ImageFilterNoisePoint", "ImageFilterStrike"}
var filterConfigGroup *FilterConfigGroup
filterConfigGroup = new(FilterConfigGroup)
filterConfigGroup.Init()
filterConfigGroup.SetItem("Num", "5")
filterConfig.SetGroup("ImageFilterNoiseLine", filterConfigGroup)
filterConfigGroup = new(FilterConfigGroup)
filterConfigGroup.Init()
filterConfigGroup.SetItem("Num", "10")
filterConfig.SetGroup("ImageFilterNoisePoint", filterConfigGroup)
filterConfigGroup = new(FilterConfigGroup)
filterConfigGroup.Init()
filterConfigGroup.SetItem("Num", "3")
filterConfig.SetGroup("ImageFilterStrike", filterConfigGroup)
storeConfig := new(StoreConfig)
storeConfig.Engine = STORE_ENGINE_BUILDIN
storeConfig.GcDivisor = 100
storeConfig.GcProbability = 1
return wordDict, captchaConfig, imageConfig, filterConfig, storeConfig
}
================================================
FILE: captchainfo.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"time"
)
// CaptchaInfo is the entity of a captcha
// text:the content text,for the image display and user to recognize
// createTime:the time when the captcha is created
type CaptchaInfo struct {
Text string
CreateTime time.Time
ShownTimes int
}
================================================
FILE: cimage.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"image"
"image/color"
"image/draw"
"github.com/golang/freetype"
)
const (
colorCount = 20
)
//CImage is a image process tool
type CImage struct {
*image.Paletted
config *ImageConfig
}
func (m *CImage) drawHorizLine(fromX, toX, y int, colorIdx uint8) *CImage {
if 0 >= colorIdx || colorIdx > colorCount {
colorIdx = uint8(rnd(0, colorCount))
}
for x := fromX; x <= toX; x++ {
m.SetColorIndex(x, y, colorIdx)
}
return m
}
func (m *CImage) 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 *CImage) drawString(text string) *CImage {
fg, bg := image.Black, &image.Uniform{color.RGBA{255, 255, 255, 255}}
draw.Draw(m, m.Bounds(), bg, image.ZP, draw.Src)
c := freetype.NewContext()
c.SetFontSize(m.config.FontSize)
c.SetClip(m.Bounds())
c.SetDst(m)
c.SetSrc(fg)
i := 0
for _, s := range text {
c.SetFont(m.config.fontManager.GetRandomFont())
charX := (int(c.PointToFixed(m.config.FontSize) >> 8)) * i
charY := int(c.PointToFixed(m.config.FontSize) >> 8)
charPt := freetype.Pt(charX, charY)
c.DrawString(string(s), charPt)
i = i + 1
}
return m
}
//CreateCImage will create a CImage struct with config
func CreateCImage(config *ImageConfig) *CImage {
r := new(CImage)
r.Paletted = image.NewPaletted(image.Rect(0, 0, config.Width, config.Height), randomPalette())
r.config = config
if nil == r.config.fontManager {
fm := CreateFontManager()
for _, fontFile := range config.FontFiles {
fm.AddFont(fontFile)
}
r.config.fontManager = fm
}
return r
}
func randomPalette() color.Palette {
p := make([]color.Color, colorCount+1)
// Transparent color.
p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}
// Primary color.
prim := color.RGBA{
uint8(rnd(0, 255)),
uint8(rnd(0, 255)),
uint8(rnd(0, 255)),
0xFF,
}
p[1] = prim
// Circle colors.
for i := 2; i <= colorCount; i++ {
p[i] = randomBrightness(prim, 255)
}
return p
}
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 := rnd(0, 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
}
================================================
FILE: confighelper.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"errors"
"strings"
"time"
"github.com/hanguofeng/config"
)
const (
DEFAULT_LIFE_TIME = time.Minute * 5
DEFAULT_FONT_SIZE = 26
DEFAULT_WIDTH = 120
DEFAULT_HEIGHT = 40
DEFAULT_GC_PROBABILITY = 1
DEFAULT_GC_DIVISOR = 100
)
func loadConfigFromFile(configFile string) (error, string, *CaptchaConfig, *ImageConfig, *FilterConfig, *StoreConfig) {
var retErr error
c, err := config.ReadDefault(configFile)
//wordDict
wordDict, err := c.String("captcha", "word_dict")
if nil != err {
retErr = errors.New("loadConfigFromFile Fail,Get word_dict options failed:" + err.Error())
}
//captchaConfig
captchaConfig := new(CaptchaConfig)
var lifeTime time.Duration
cfgLifeTime, err := c.String("captcha", "life_time")
if nil == err {
lifeTime, err = time.ParseDuration(cfgLifeTime)
if nil != err {
lifeTime = DEFAULT_LIFE_TIME
}
} else {
lifeTime = DEFAULT_LIFE_TIME
}
captchaConfig.LifeTime = lifeTime
cfgChangeTextOnRefresh, err := c.Bool("captcha", "change_text_on_refresh")
if nil != err {
cfgChangeTextOnRefresh = false
}
captchaConfig.ChangeTextOnRefresh = cfgChangeTextOnRefresh
//imageConfig
imageConfig := new(ImageConfig)
var fontFiles []string
cfgFontFiles, err := c.StringMuti("image", "font_files")
if nil != err {
retErr = errors.New("loadConfigFromFile Fail,font_files options failed:" + err.Error())
} else {
fontFiles = cfgFontFiles
}
imageConfig.FontFiles = fontFiles
var fontSize float64
cfgFontSize, err := c.Int("image", "font_size")
if nil != err {
fontSize = DEFAULT_FONT_SIZE
} else {
fontSize = float64(cfgFontSize)
}
imageConfig.FontSize = fontSize
var width int
cfgWidth, err := c.Int("image", "width")
if nil != err {
width = DEFAULT_WIDTH
} else {
width = int(cfgWidth)
}
imageConfig.Width = width
var height int
cfgHeight, err := c.Int("image", "height")
if nil != err {
height = DEFAULT_HEIGHT
} else {
height = int(cfgHeight)
}
imageConfig.Height = height
//filterConfig
filterConfig := new(FilterConfig)
filterConfig.Init()
cfgOpenFilter, err := c.StringMuti("filter", "open_filter")
if nil == err {
filterConfig.Filters = cfgOpenFilter
} else {
filterConfig.Filters = []string{}
}
for _, section := range c.Sections() {
if strings.HasPrefix(section, "ImageFilter") {
filterConfigGroup := new(FilterConfigGroup)
filterConfigGroup.Init()
options, err := c.Options(section)
if nil == err {
for _, option := range options {
v, err := c.String(section, option)
if nil == err {
filterConfigGroup.SetItem(option, v)
}
}
}
filterConfig.SetGroup(section, filterConfigGroup)
}
}
//storeConfig
storeConfig := new(StoreConfig)
storeConfig.CaptchaConfig = *captchaConfig
engine, err := c.String("store", "engine")
if nil != err {
retErr = errors.New("loadConfigFromFile Fail,engine options failed" + err.Error())
} else {
storeConfig.Engine = engine
}
servers, err := c.StringMuti("store", "servers")
if nil != err {
storeConfig.Servers = []string{}
} else {
storeConfig.Servers = servers
}
gcProbability, err := c.Int("store", "gc_probability")
if nil != err {
storeConfig.GcProbability = gcProbability
} else {
storeConfig.GcProbability = DEFAULT_GC_PROBABILITY
}
gcDivisor, err := c.Int("store", "gc_divisor")
if nil != err {
storeConfig.GcDivisor = gcDivisor
} else {
storeConfig.GcDivisor = DEFAULT_GC_DIVISOR
}
if nil != err && nil == retErr {
retErr = err
}
return retErr, wordDict, captchaConfig, imageConfig, filterConfig, storeConfig
}
================================================
FILE: configtypes.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"time"
)
//CaptchaConfig ,the captcha config
type CaptchaConfig struct {
LifeTime time.Duration
CaseSensitive bool
ChangeTextOnRefresh bool
}
//FilterConfigGroup
type FilterConfigGroup struct {
data map[string]string
}
func (this *FilterConfigGroup) Init() {
this.data = map[string]string{}
}
func (this *FilterConfigGroup) GetItem(key string) (string, bool) {
val, ok := this.data[key]
return val, ok
}
func (this *FilterConfigGroup) SetItem(key string, val string) {
this.data[key] = val
}
type FilterConfig struct {
Filters []string
data map[string]*FilterConfigGroup
}
func (this *FilterConfig) Init() {
this.Filters = []string{}
this.data = map[string]*FilterConfigGroup{}
}
func (this *FilterConfig) GetGroup(key string) (FilterConfigGroup, bool) {
val, ok := this.data[key]
if !ok {
val = new(FilterConfigGroup)
}
return *val, ok
}
func (this *FilterConfig) SetGroup(key string, group *FilterConfigGroup) {
this.data[key] = group
}
//ImageConfig ,the image config
type ImageConfig struct {
Width int
Height int
FontSize float64
FontFiles []string
fontManager *FontManager
}
const STORE_ENGINE_BUILDIN = "buildin"
const STORE_ENGINE_MEMCACHE = "memcache"
//StoreConfig ,the store engine config
type StoreConfig struct {
CaptchaConfig
Engine string
Servers []string
GcProbability int
GcDivisor int
}
================================================
FILE: cstore.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"crypto/md5"
enc "encoding/gob"
"encoding/hex"
"fmt"
"os"
"sync"
"time"
)
//CStore is the Captcha info store service
type CStore struct {
engine string
mu sync.RWMutex
data map[string]*CaptchaInfo
expiresTime time.Duration
gcProbability int
gcDivisor int
}
//CreateCStore will create a new CStore
func CreateCStore(expiresTime time.Duration, gcProbability int, gcDivisor int) *CStore {
store := new(CStore)
store.engine = STORE_ENGINE_BUILDIN
store.data = make(map[string]*CaptchaInfo)
store.expiresTime = expiresTime
store.gcProbability = gcProbability
store.gcDivisor = gcDivisor
return store
}
//Get captcha info by key
func (store *CStore) Get(key string) *CaptchaInfo {
defer store.gcWrapper() //run gc after get
store.mu.Lock()
defer store.mu.Unlock()
ret := store.data[key]
return ret
}
//Add captcha info and get the auto generated key
func (store *CStore) Add(captcha *CaptchaInfo) string {
store.mu.Lock()
defer store.mu.Unlock()
key := fmt.Sprintf("%s%s%x", captcha.Text, randStr(20), time.Now().UnixNano())
key = hex.EncodeToString(md5.New().Sum([]byte(key)))
key = key[:32]
store.data[key] = captcha
return key
}
//Update captcha info
func (store *CStore) Update(key string, captcha *CaptchaInfo) bool {
store.mu.Lock()
defer store.mu.Unlock()
store.data[key] = captcha
return true
}
//Del captcha info by key
func (store *CStore) Del(key string) {
store.mu.Lock()
defer store.mu.Unlock()
delete(store.data, key)
}
//Destroy the whole store
func (store *CStore) Destroy() {
for key := range store.data {
store.Del(key)
}
}
//OnConstruct load data
func (store *CStore) OnConstruct() {
}
//OnDestruct dump data
func (store *CStore) OnDestruct() {
}
//Dump the whole store
func (store *CStore) Dump(file string) error {
store.mu.Lock()
defer store.mu.Unlock()
pwd, err := os.Getwd()
if (nil == err) && ("" != pwd) {
f, err := os.Create(pwd + "/" + file)
if nil == err {
encoder := enc.NewEncoder(f)
err := encoder.Encode(store.data)
f.Close()
if nil == err {
return err
} else {
return nil
}
} else {
return err
}
} else {
return err
}
return nil
}
//LoadDumped file to store
func (store *CStore) LoadDumped(file string) error {
data := &map[string]*CaptchaInfo{}
pwd, err := os.Getwd()
if (nil == err) && ("" != pwd) {
f, err := os.Open(pwd + "/" + file)
if nil == err {
decoder := enc.NewDecoder(f)
err := decoder.Decode(data)
f.Close()
if nil == err {
store.data = *data
return nil
} else {
return err
}
} else {
return err
}
} else {
return err
}
return err
}
func (store *CStore) gcWrapper() {
//run PROBABILITY
if rnd(0, store.gcDivisor) == store.gcProbability {
go store.gc()
}
}
func (store *CStore) gc() {
for key, val := range store.data {
if val.CreateTime.Add(store.expiresTime).Before(time.Now()) {
store.Del(key)
}
}
}
================================================
FILE: cstore_test.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"os"
"testing"
"time"
)
func TestStore(t *testing.T) {
store := CreateCStore(10*time.Second, 1, 100) //10 s
captcha := new(CaptchaInfo)
captcha.Text = "hello"
captcha.CreateTime = time.Now()
//test add and get
key := store.Add(captcha)
retV := store.Get(key)
if retV != captcha {
t.Errorf("not equal")
}
retV.Text = "world"
store.Update(key, retV)
retV = store.Get(key)
if retV.Text != "world" {
t.Errorf("update not equal")
}
//test dump,destroy and loaddumped
store.Dump("data/data.dat")
store.Destroy()
retV = store.Get(key)
if nil != retV {
t.Errorf("Destroy error")
}
store.LoadDumped("data/data.dat")
retV = store.Get(key)
if captcha.Text != retV.Text {
t.Errorf("LoadDumped error")
}
os.Remove("data/data.dat")
//test del
store.Del(key)
retV = store.Get(key)
if nil != retV {
t.Errorf("not del")
}
}
================================================
FILE: data/cn_char
================================================
你
好
啊
哈
哦
浏
览
器
不
要
执
行
与
事
件
关
联
的
默
认
动
作
================================================
FILE: data/cn_phrases
================================================
高枕无忧
各自为政
华而不实
好好先生
狐假虎威
汗流浃背
后来居上
合浦珠还
后起之秀
后生可畏
火树银花
坚壁清野
间不容发
井底之蛙
鸡口牛后
江郎才尽
见猎心喜
鸡鸣狗盗
九牛一毛
胶漆相投
举一反三
开诚布公
空洞无物
沆瀣—气
网油鱼卷
燕窝四字
抓炒鱼片
三鲜瑶柱
芙蓉大虾
龙井竹荪
桂花干贝
金钱吐丝
凤凰展翅
炸鸡葫芦
桃仁鸡丁
鸭丝掐菜
肉末烧饼
龙凤柔情
鸡沾口蘑
咖喱菜花
凤凰趴窝
宫保兔肉
熊猫品竹
御扇豆黄
炝玉龙片
糖醋鱼卷
金鱼鸭掌
琉璃珠玑
金糕
栗子糕
芝麻卷
仿膳饽饽
酥卷佛手
油焖鲜蘑
莲子膳粥
麻辣牛肉
金丝烧麦
凤尾群翅
太极发财燕
雪月羊肉
炸春卷
如意卷
金糕卷
兰花豆干
萝卜桂鱼
凤穿金衣
云河段霄
翠玉豆糕
四喜饺
绣球全鱼
秋菊傲霜
龙衔海棠
滑溜鹌鹑
侉炖羊肉
玉掌献寿
炒榛子酱
发菜黄花
熊猫蟹肉
清炸鹌鹑
双色豆糕
烤羊腿
龙井金鱼
琥珀鸽蛋
二龙戏珠
炒黄瓜酱
雨后春笋
如意竹荪
檀扇鸭掌
鲤跃龙门
炝黄瓜衣
菊花里脊
八宝膳粥
母子相会
豌豆黄
小豆糕
枣泥糕
红烧鱼唇
金鱼角
凤凰鱼肚
宫廷排翅
玉兔白菜
冰花雪莲
松鹤延年
香露苹果
荷花酥
金钱鱼肚
明珠豆腐
芙蓉鱼骨
百子冬瓜
翡翠玉扇
仙鹤鲍鱼
薏米膳继
莲子糕
日月生辉
荷包蟹肉
姜汁扁豆
佛手金卷
翠柳凤丝
燕影金蔬
白银如意
黄袍加身
葵花麻鱼
五丝菜卷
佛手广肚
白梨凤脯
参婆千子
杏仁豆腐
御龙火锅
糖醋荷藕
罗汉大虾
黑米膳粥
金屋藏娇
翡翠银耳
抓炒大虾
喜鹊登枝
油攒大虾
茸鸡待哺
随滑飞龙
鸡油香菇
怪味鸡片
鹦鹉莴笋
芝麻锅炸
芜爆鲜贝
双色马蹄糕
口蘑鹿肉
琵琶大虾
长春羹
干煸牛肉丝
蝴蝶海参
鸳鸯酥盒
芜爆山鸡
燕尾桃花
麦穗虾卷
三鲜鸭舌
金银鸽肉
红烧鹿筋
芙蓉鹿尾
百寿桃
莲花卷
三色糕
玉板翠带
珍珠雪耳
清炒鳝丝
万年长青
金鸡独立
豢蝶大虾
凤戏牡丹
荷塘莲香
一品官燕
芸豆金鱼
豆沙糕
酥页层层
金蟾拜月
奶油菠萝冻
花篮白菜
桂花海蜇
鲍王闹府
凤凰出世
绣球蛋糕
云片鸽蛋
烧瓤菜花
龙凤双锤
鸡丝汤面
红烧鱼骨
凤脯珍珠
干烧冬笋
鸳鸯哺乳
百鸟还巢
卧龙戏珠
如意乌龙
金狮绣球
豆沙凉糕
凤眼秋波
红豆膳粥
棠花吐蕊
群虾戏荷
炒豌豆酱
金镶玉板
梅竹山石
芙蓉鱼角
怀胎桂鱼
雪里藏珍
抓炒里脊
叉烧猪肉
松树猴头
象眼鸽蛋
清蒸鹌鹑
龙凤呈祥
金钱香菇
香爆螺盏
鸳鸯鱼枣
桔子盏
藕丝羹
沙舟踏翠
芥末鸭膀
翡翠鱼丁
桂花鱼条
玉盏龙眼
棒渣膳粥
五丝洋粉
三丝驼峰
晶玉海棠
蜜汁山药
拌银耳
蛋挞
宫保鹌鹑
芙蓉糕
火炼金身
================================================
FILE: data/en_char
================================================
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
================================================
FILE: data/en_phrases
================================================
hello
foo
bar
quick
fox
================================================
FILE: fontmanager.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"github.com/golang/freetype/truetype"
"io/ioutil"
"math/rand"
"time"
)
//FontManager is a Font Manager the manage font list
type FontManager struct {
fontFiles []string
fontObjects map[string]*truetype.Font
randObject *rand.Rand
}
//CreateFontManager will create a new Font Manager
func CreateFontManager() *FontManager {
fm := new(FontManager)
fm.fontFiles = []string{}
fm.fontObjects = make(map[string]*truetype.Font)
fm.randObject = rand.New(rand.NewSource(time.Now().UnixNano()))
return fm
}
//AddFont will add a new font file to the list
func (fm *FontManager) AddFont(pathToFontFile string) error {
fontBytes, err := ioutil.ReadFile(pathToFontFile)
if err != nil {
return err
}
font, err := truetype.Parse(fontBytes)
if err != nil {
return err
}
fm.fontFiles = append(fm.fontFiles, pathToFontFile)
fm.fontObjects[pathToFontFile] = font
return nil
}
//GetFont will return a Font struct by path
func (fm *FontManager) GetFont(pathToFontFile string) *truetype.Font {
return fm.fontObjects[pathToFontFile]
}
//GetRandomFont will return a random Font struct
func (fm *FontManager) GetRandomFont() *truetype.Font {
randomIndex := fm.randObject.Intn(len(fm.fontFiles))
fontFile := fm.fontFiles[randomIndex]
rst := fm.GetFont(fontFile)
return rst
}
================================================
FILE: go.mod
================================================
module github.com/hanguofeng/gocaptcha
go 1.17
require (
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/hanguofeng/config v1.0.0
gopkg.in/redis.v2 v2.3.2
)
require (
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
================================================
FILE: go.sum
================================================
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/hanguofeng/config v1.0.0 h1:AIpXdv7iEzK4v4Qyov7jeZWIwepiwBfELzfpd2r0dH0=
github.com/hanguofeng/config v1.0.0/go.mod h1:YX//r7JfVHJCHW/+HP8WqQ2FVDaUGCo0j5z3ddMnrk4=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU=
gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e/go.mod h1:xsQCaysVCudhrYTfzYWe577fCe7Ceci+6qjO2Rdc0Z4=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/redis.v2 v2.3.2 h1:GPVIIB/JnL1wvfULefy3qXmPu1nfNu2d0yA09FHgwfs=
gopkg.in/redis.v2 v2.3.2/go.mod h1:4wl9PJ/CqzeHk3LVq1hNLHH8krm3+AXEgut4jVc++LU=
================================================
FILE: imagefilter.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import ()
var imageFilterCreators = map[string]func(FilterConfigGroup) ImageFilter{}
func RegisterImageFilter(id string, f func(FilterConfigGroup) ImageFilter) bool {
if _, has := imageFilterCreators[id]; has {
return false
}
imageFilterCreators[id] = f
return true
}
//ImageFilter is the interface of image filter
type ImageFilter interface {
Proc(cimage *CImage)
GetId() string
SetConfig(FilterConfigGroup)
GetConfig() FilterConfigGroup
}
//ImageFilter is the base class of image filter
type ImageFilterBase struct {
config FilterConfigGroup
}
func (filter *ImageFilterBase) Proc(cimage *CImage) {
panic("not impl")
}
func (filter *ImageFilterBase) GetId() string {
panic("not impl")
}
func (filter *ImageFilterBase) SetConfig(config FilterConfigGroup) {
filter.config = config
}
func (filter *ImageFilterBase) GetConfig() FilterConfigGroup {
return filter.config
}
================================================
FILE: imagefiltermanager.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import ()
//ImageFilterManager
type ImageFilterManager struct {
filters []ImageFilter
}
func init() {
RegisterImageFilter(IMAGE_FILTER_NOISE_LINE, imageFilterNoiseLineCreator)
RegisterImageFilter(IMAGE_FILTER_NOISE_POINT, imageFilterNoisePointCreator)
RegisterImageFilter(IMAGE_FILTER_STRIKE, imageFilterStrikeCreator)
}
func CreateImageFilterManager() *ImageFilterManager {
ret := new(ImageFilterManager)
ret.filters = []ImageFilter{}
return ret
}
func (manager *ImageFilterManager) AddFilter(filter ImageFilter) {
manager.filters = append(manager.filters, filter)
}
func (manager *ImageFilterManager) GetFilters() []ImageFilter {
return manager.filters
}
func CreateImageFilterManagerByConfig(config *FilterConfig) *ImageFilterManager {
mgr := new(ImageFilterManager)
mgr.filters = []ImageFilter{}
for _, cfgId := range config.Filters {
creator, has := imageFilterCreators[cfgId]
if !has {
continue
}
cfgGroup, has := config.GetGroup(cfgId)
if !has {
continue
}
mgr.AddFilter(creator(cfgGroup))
}
return mgr
}
================================================
FILE: imagefiltermanager_test.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"testing"
)
func TestImageFilterManager(t *testing.T) {
manager := CreateImageFilterManager()
manager.AddFilter(&ImageFilterBase{})
if 1 != len(manager.GetFilters()) {
t.Error("add or get failed")
}
}
func TestImageFilterManagerConfig(t *testing.T) {
filterConfig := new(FilterConfig)
filterConfig.Init()
filterConfig.Filters = []string{"ImageFilterNoiseLine", "ImageFilterNoisePoint", "ImageFilterStrike"}
var filterConfigGroup *FilterConfigGroup
filterConfigGroup = new(FilterConfigGroup)
filterConfigGroup.Init()
filterConfigGroup.SetItem("Num", "5")
filterConfig.SetGroup("ImageFilterNoiseLine", filterConfigGroup)
filterConfigGroup = new(FilterConfigGroup)
filterConfigGroup.Init()
filterConfigGroup.SetItem("Num", "10")
filterConfig.SetGroup("ImageFilterNoisePoint", filterConfigGroup)
filterConfigGroup = new(FilterConfigGroup)
filterConfigGroup.Init()
filterConfigGroup.SetItem("Num", "3")
filterConfig.SetGroup("ImageFilterStrike", filterConfigGroup)
manager := CreateImageFilterManagerByConfig(filterConfig)
filters := manager.GetFilters()
if 3 != len(filters) {
t.Error("create by config failed")
}
for _, filter := range filters {
config := filter.GetConfig()
var num string
switch filter.GetId() {
case "ImageFilterNoiseLine":
if num, _ = config.GetItem("Num"); "5" != num {
t.Error("ImageFilterNoiseLine ERROR")
}
break
case "ImageFilterNoisePoint":
if num, _ = config.GetItem("Num"); "10" != num {
t.Error("ImageFilterNoisePoint ERROR")
}
break
case "ImageFilterStrike":
if num, _ = config.GetItem("Num"); "3" != num {
t.Error("ImageFilterStrike ERROR")
}
break
}
}
}
================================================
FILE: imagefilternoiseline.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"strconv"
)
//ImageFilter is the interface of image filter
const IMAGE_FILTER_NOISE_LINE = "ImageFilterNoiseLine"
type ImageFilterNoiseLine struct {
ImageFilterBase
}
func imageFilterNoiseLineCreator(config FilterConfigGroup) ImageFilter {
filter := ImageFilterNoiseLine{}
filter.SetConfig(config)
return &filter
}
//Proc the image
func (filter *ImageFilterNoiseLine) Proc(cimage *CImage) {
var num int
var err error
v, ok := filter.config.GetItem("Num")
if ok {
num, err = strconv.Atoi(v)
if nil != err {
num = 3
}
} else {
num = 3
}
for i := 0; i < num; i++ {
x := rnd(0, cimage.Bounds().Max.X)
cimage.drawHorizLine(int(float32(x)/1.5), x, rnd(0, cimage.Bounds().Max.Y), uint8(rnd(1, colorCount)))
}
}
func (filter *ImageFilterNoiseLine) GetId() string {
return IMAGE_FILTER_NOISE_LINE
}
================================================
FILE: imagefilternoisepoint.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"strconv"
)
const IMAGE_FILTER_NOISE_POINT = "ImageFilterNoisePoint"
//ImageFilter is the interface of image filter
type ImageFilterNoisePoint struct {
ImageFilterBase
}
func imageFilterNoisePointCreator(config FilterConfigGroup) ImageFilter {
filter := ImageFilterNoisePoint{}
filter.SetConfig(config)
return &filter
}
//Proc the image
func (filter *ImageFilterNoisePoint) Proc(cimage *CImage) {
var num int
var err error
v, ok := filter.config.GetItem("Num")
if ok {
num, err = strconv.Atoi(v)
if nil != err {
num = 3
}
} else {
num = 3
}
for i := 0; i < num; i++ {
cimage.drawCircle(rnd(0, cimage.Bounds().Max.X), rnd(0, cimage.Bounds().Max.Y), rnd(0, 2), uint8(rnd(1, colorCount)))
}
}
func (filter *ImageFilterNoisePoint) GetId() string {
return IMAGE_FILTER_NOISE_POINT
}
================================================
FILE: imagefilterstrike.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"math"
"strconv"
)
const IMAGE_FILTER_STRIKE = "ImageFilterStrike"
//ImageFilter is the interface of image filter
type ImageFilterStrike struct {
ImageFilterBase
}
func imageFilterStrikeCreator(config FilterConfigGroup) ImageFilter {
filter := ImageFilterStrike{}
filter.SetConfig(config)
return &filter
}
//Proc the image
func (filter *ImageFilterStrike) Proc(cimage *CImage) {
var num int
var err error
v, ok := filter.config.GetItem("Num")
if ok {
num, err = strconv.Atoi(v)
if nil != err {
num = 1
}
} else {
num = 1
}
maxx := cimage.Bounds().Max.X
maxy := cimage.Bounds().Max.Y
y := rnd(maxy/2, maxy-maxy/2)
amplitude := rndf(10, 15)
period := rndf(80, 100)
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 < num; yn++ {
r := rnd(0, 2)
cimage.drawCircle(x+int(xo), y+int(yo)+(yn*(num+1)), r/2, 1)
}
}
}
func (filter *ImageFilterStrike) GetId() string {
return IMAGE_FILTER_STRIKE
}
================================================
FILE: mcstore.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
"log"
"time"
enc "encoding/gob"
"github.com/bradfitz/gomemcache/memcache"
)
const (
MC_KEY_PREFIX = "gocaptcha-"
)
//MCStore is the Captcha info store service
type MCStore struct {
engine string
mc *memcache.Client
}
//CreateCStore will create a new CStore
func CreateMCStore(expiresTime time.Duration, servers []string) *MCStore {
store := new(MCStore)
store.mc = memcache.New(servers...)
store.mc.Timeout = expiresTime
return store
}
//Get captcha info by key
func (store *MCStore) Get(key string) *CaptchaInfo {
item, err := store.mc.Get(MC_KEY_PREFIX + key)
if nil != err {
return nil
}
ret := store.decodeValue(item.Value)
return ret
}
//Add captcha info and get the auto generated key
func (store *MCStore) Add(captcha *CaptchaInfo) string {
key := fmt.Sprintf("%s%s%x", captcha.Text, randStr(20), time.Now().UnixNano())
key = hex.EncodeToString(md5.New().Sum([]byte(key)))
key = key[:32]
item := new(memcache.Item)
item.Key = MC_KEY_PREFIX + key
item.Value = store.encodeValue(captcha)
err := store.mc.Add(item)
if nil != err {
log.Printf("add key in memcache err:%s", err)
}
return key
}
//Update captcha info
func (store *MCStore) Update(key string, captcha *CaptchaInfo) bool {
item := new(memcache.Item)
item.Key = MC_KEY_PREFIX + key
item.Value = store.encodeValue(captcha)
err := store.mc.Set(item)
if nil != err {
log.Printf("update key in memcache err:%s", err)
return false
} else {
return true
}
}
//Del captcha info by key
func (store *MCStore) Del(key string) {
store.mc.Delete(MC_KEY_PREFIX + key)
}
//Destroy the whole store
func (store *MCStore) Destroy() {
}
//OnConstruct load data
func (store *MCStore) OnConstruct() {
}
//OnDestruct dump data
func (store *MCStore) OnDestruct() {
}
func (store *MCStore) encodeValue(val *CaptchaInfo) []byte {
buf := bytes.NewBufferString("")
encoder := enc.NewEncoder(buf)
err := encoder.Encode(val)
if nil != err {
return nil
}
return buf.Bytes()
}
func (store *MCStore) decodeValue(value []byte) *CaptchaInfo {
data := &CaptchaInfo{}
buf := bytes.NewBuffer(value)
decoder := enc.NewDecoder(buf)
decoder.Decode(data)
return data
}
================================================
FILE: mcstore_test.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"testing"
"time"
)
func TestMCStore(t *testing.T) {
memcacheServers := []string{"127.0.0.1:11211"}
store := CreateMCStore(100*time.Second, memcacheServers) //100 s
captcha := new(CaptchaInfo)
captcha.Text = "hello"
captcha.CreateTime = time.Now()
//test add and get
key := store.Add(captcha)
retV := store.Get(key)
if retV.Text != captcha.Text {
t.Errorf("not equal,retV:%#v,captcha:%#v", retV, captcha)
}
retV.Text = "world"
store.Update(key, retV)
retV = store.Get(key)
if retV.Text != "world" {
t.Errorf("update not equal,retV:%#v,captcha:%#v", retV, captcha)
}
t.Logf("TestMCStore:get from mc:%#v", retV)
//test del
store.Del(key)
retV = store.Get(key)
if nil != retV {
t.Errorf("not del")
}
}
================================================
FILE: randhelper.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
// rnd returns a non-crypto pseudorandom int in range [from, to].
func rnd(from, to int) int {
return rand.Intn(to+1-from) + from
}
// rndf returns a non-crypto pseudorandom float64 in range [from, to].
func rndf(from, to float64) float64 {
return (to-from)*rand.Float64() + from
}
func randStr(length int) string {
rst := ""
charset := "abcdefghijklmnopqrstuvwxyz1234567890"
for i := 0; i < length; i++ {
rst = rst + string(charset[rnd(0, len(charset)-1)])
}
return rst
}
================================================
FILE: redisstore.go
================================================
package gocaptcha
import (
"bytes"
"crypto/md5"
"encoding/gob"
"encoding/hex"
"fmt"
"log"
"strconv"
"strings"
"time"
"gopkg.in/redis.v2"
)
func init() {
RegisterStore(storeName, CreateCaptchaRedisStore)
}
const (
captchaKeyFormat = "captcha:text:%s;rand:%s;time:%x;"
storeName = "redis"
)
type CaptchaRedisStore struct {
lifeTime time.Duration
stg *redis.Client
}
func CreateCaptchaRedisStore(config *StoreConfig) (StoreInterface, error) {
lifeTime := config.LifeTime
if config.Servers == nil || len(config.Servers) == 0 {
return nil, fmt.Errorf("servers must not be empty")
}
fullAddr := strings.TrimPrefix(config.Servers[0], "redis://")
pieces := strings.SplitN(fullAddr, "/", 2)
db := 0
addr := pieces[0]
if len(pieces) == 2 {
db, _ = strconv.Atoi(pieces[1])
}
opt := redis.Options{}
opt.Addr = addr
opt.DB = int64(db)
opt.PoolSize = 0
stg := redis.NewTCPClient(&opt)
return &CaptchaRedisStore{lifeTime, stg}, nil
}
func (this *CaptchaRedisStore) Get(key string) *CaptchaInfo {
s, err := this.stg.Get(key).Result()
if err != nil {
log.Printf("get key in redis error:%s", err)
return nil
}
captcha := CaptchaInfo{}
this.decodeCaptachInfo([]byte(s), &captcha)
return &captcha
}
func (this *CaptchaRedisStore) Add(captcha *CaptchaInfo) string {
key := fmt.Sprintf(captchaKeyFormat, captcha.Text, randStr(20), captcha.CreateTime.Unix())
key = hex.EncodeToString(md5.New().Sum([]byte(key)))
key = key[:32]
val, err := this.encodeCaptchaInfo(captcha)
if err == nil {
if seterr := this.stg.SetEx(key, this.lifeTime, string(val)); seterr != nil {
log.Printf("add key in redis error:%s", seterr)
}
}
return key
}
func (this *CaptchaRedisStore) Update(key string, captcha *CaptchaInfo) bool {
val, err := this.encodeCaptchaInfo(captcha)
if err == nil {
if seterr := this.stg.Set(key, string(val)); seterr != nil {
log.Printf("set key in redis error:%s", seterr)
return false
} else {
return true
}
} else {
return false
}
}
func (this *CaptchaRedisStore) Del(key string) {
this.stg.Del(key)
}
func (this *CaptchaRedisStore) Destroy() {
}
func (this *CaptchaRedisStore) OnConstruct() {
}
func (this *CaptchaRedisStore) OnDestruct() {
}
func (this *CaptchaRedisStore) encodeCaptchaInfo(captcha *CaptchaInfo) ([]byte, error) {
buf := new(bytes.Buffer)
encoder := gob.NewEncoder(buf)
err := encoder.Encode(captcha)
if err != nil {
log.Printf("encode captcha info error:%s", err)
return nil, err
}
return buf.Bytes(), nil
}
func (this *CaptchaRedisStore) decodeCaptachInfo(b []byte, ret *CaptchaInfo) {
buf := bytes.NewBuffer(b)
decoder := gob.NewDecoder(buf)
if err := decoder.Decode(ret); err != nil {
log.Printf("decode captcha info error:%s", err)
}
return
}
================================================
FILE: redisstore_test.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"testing"
"time"
)
func TestRedisStore(t *testing.T) {
storeConfig := &StoreConfig{}
storeConfig.CaptchaConfig.LifeTime = time.Second * 100
storeConfig.Engine = "redis"
storeConfig.Servers = []string{"127.0.0.1:6379"}
store, _ := CreateCaptchaRedisStore(storeConfig)
captcha := new(CaptchaInfo)
captcha.Text = "hello"
captcha.CreateTime = time.Now()
//test add and get
key := store.Add(captcha)
retV := store.Get(key)
if retV.Text != captcha.Text {
t.Errorf("not equal,retV:%#v,captcha:%#v", retV, captcha)
}
retV.Text = "world"
store.Update(key, retV)
retV = store.Get(key)
if retV.Text != "world" {
t.Errorf("update not equal,retV:%#v,captcha:%#v", retV, captcha)
}
t.Logf("TestMCStore:get from redis:%#v", retV)
//test del
store.Del(key)
retV = store.Get(key)
if nil != retV {
t.Errorf("not del")
}
}
================================================
FILE: storeinterface.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import ()
var storeCreators = map[string]func(*StoreConfig) (StoreInterface, error){}
//StoreInterface is the interface of store
type StoreInterface interface {
Get(key string) *CaptchaInfo
Add(captcha *CaptchaInfo) string
Update(key string, captcha *CaptchaInfo) bool
Del(key string)
Destroy()
OnConstruct()
OnDestruct()
}
func RegisterStore(name string, f func(*StoreConfig) (StoreInterface, error)) bool {
if _, has := storeCreators[name]; has {
return false
}
storeCreators[name] = f
return true
}
================================================
FILE: wordmanager.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"encoding/csv"
"errors"
"io"
"os"
"strings"
"unicode/utf8"
)
//WordManager is a captcha word manage tool
type WordManager struct {
words []string
isDataSingleChar bool
isValid bool
}
//CreateWordManagerFromDataFile will create a entity from a dictionary file
func CreateWordManagerFromDataFile(filename string) (*WordManager, error) {
mgr := &WordManager{}
mgr.words = []string{}
mgr.isValid = false
f, err := os.Open(filename)
if nil != err {
return mgr, err
}
defer f.Close()
reader := csv.NewReader(f)
mgr.isDataSingleChar = true
for {
record, err := reader.Read()
if err == io.EOF {
break
} else if nil != err {
return mgr, err
}
if 1 < len([]rune(record[0])) {
mgr.isDataSingleChar = false
}
mgr.words = append(mgr.words, strings.TrimSpace(record[0]))
}
mgr.isValid = true
return mgr, nil
}
//Get a specifical length word
func (mgr *WordManager) Get(length int) (string, error) {
var retErr error
rst := ""
if mgr.isValid {
if true == mgr.isDataSingleChar {
if len(mgr.words) < length {
return "", errors.New("dict words count is less than your length")
}
for {
line := mgr.getLine()
if false == strings.ContainsRune(rst, []rune(line)[0]) {
rst = rst + line
}
if utf8.RuneCountInString(rst) >= length {
break
}
}
rstRune := []rune(rst)
rst = string(rstRune[0:length])
} else {
rst = mgr.getLine()
}
} else {
retErr = errors.New("WordManager is invalid")
}
return rst, retErr
}
func (mgr *WordManager) SetWords(words []string) {
mgr.words = words
mgr.isValid = len(words) > 0
mgr.isDataSingleChar = true
for _, s := range words {
if len([]rune(s)) > 1 {
mgr.isDataSingleChar = false
}
}
}
func (mgr *WordManager) getLine() string {
maxIndex := len(mgr.words) - 1
rstIndex := rnd(0, maxIndex)
rst := mgr.words[rstIndex]
return rst
}
================================================
FILE: wordmanager_test.go
================================================
// Copyright 2013 hanguofeng. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package gocaptcha
import (
"os"
"testing"
)
func TestWordManager(t *testing.T) {
pwd, err := os.Getwd()
if (nil != err) || "" == pwd {
return
}
path := pwd + "/data/"
singleCharDict := []string{"cn_char", "en_char"}
phrasesDict := []string{"cn_phrases", "en_phrases"}
length := 6
for _, f := range singleCharDict {
mgr, err := CreateWordManagerFromDataFile(path + f)
s, err := mgr.Get(length)
if nil != err {
t.Errorf(err.Error())
} else if length != len([]rune(s)) {
t.Errorf("get no equals length:" + f)
}
}
for _, f := range phrasesDict {
mgr, err := CreateWordManagerFromDataFile(path + f)
s, err := mgr.Get(length)
if nil != err {
t.Errorf(err.Error())
} else if 0 >= len([]rune(s)) {
t.Errorf("not get:" + f)
}
}
}
gitextract_al2ckzk5/ ├── .github/ │ └── workflows/ │ └── go.yml ├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README-en.md ├── README.md ├── VERSION ├── captcha.go ├── captcha_test.go ├── captchainfo.go ├── cimage.go ├── confighelper.go ├── configtypes.go ├── cstore.go ├── cstore_test.go ├── data/ │ ├── cn_char │ ├── cn_phrases │ ├── en_char │ └── en_phrases ├── fontmanager.go ├── go.mod ├── go.sum ├── imagefilter.go ├── imagefiltermanager.go ├── imagefiltermanager_test.go ├── imagefilternoiseline.go ├── imagefilternoisepoint.go ├── imagefilterstrike.go ├── mcstore.go ├── mcstore_test.go ├── randhelper.go ├── redisstore.go ├── redisstore_test.go ├── storeinterface.go ├── wordmanager.go └── wordmanager_test.go
SYMBOL INDEX (134 symbols across 23 files)
FILE: captcha.go
type Captcha (line 15) | type Captcha struct
method GetKey (line 73) | func (captcha *Captcha) GetKey(length int) (string, error) {
method Verify (line 90) | func (captcha *Captcha) Verify(key, textToVerify string) (bool, string) {
method GetImage (line 116) | func (captcha *Captcha) GetImage(key string) (image.Image, error) {
method genImage (line 167) | func (captcha *Captcha) genImage(text string) *CImage {
function CreateCaptcha (line 27) | func CreateCaptcha(wordManager *WordManager, captchaConfig *CaptchaConfi...
function CreateCaptchaFromConfigFile (line 53) | func CreateCaptchaFromConfigFile(configFile string) (*Captcha, error) {
function createStore (line 146) | func createStore(config *StoreConfig) (StoreInterface, error) {
FILE: captcha_test.go
function TestCaptcha (line 13) | func TestCaptcha(t *testing.T) {
function BenchmarkCaptcha (line 28) | func BenchmarkCaptcha(t *testing.B) {
function BenchmarkCaptchaInternalAPI (line 42) | func BenchmarkCaptchaInternalAPI(t *testing.B) {
function BenchmarkCaptchaDrawImage (line 54) | func BenchmarkCaptchaDrawImage(t *testing.B) {
function getCaptcha (line 66) | func getCaptcha() (*Captcha, error) {
function loadConfig (line 74) | func loadConfig() (string, *CaptchaConfig, *ImageConfig, *FilterConfig, ...
FILE: captchainfo.go
type CaptchaInfo (line 14) | type CaptchaInfo struct
FILE: cimage.go
constant colorCount (line 16) | colorCount = 20
type CImage (line 20) | type CImage struct
method drawHorizLine (line 25) | func (m *CImage) drawHorizLine(fromX, toX, y int, colorIdx uint8) *CIm...
method drawCircle (line 36) | func (m *CImage) drawCircle(x, y, radius int, colorIdx uint8) {
method drawString (line 63) | func (m *CImage) drawString(text string) *CImage {
function CreateCImage (line 86) | func CreateCImage(config *ImageConfig) *CImage {
function randomPalette (line 101) | func randomPalette() color.Palette {
function randomBrightness (line 122) | func randomBrightness(c color.RGBA, max uint8) color.RGBA {
function min3 (line 137) | func min3(x, y, z uint8) (m uint8) {
function max3 (line 148) | func max3(x, y, z uint8) (m uint8) {
FILE: confighelper.go
constant DEFAULT_LIFE_TIME (line 16) | DEFAULT_LIFE_TIME = time.Minute * 5
constant DEFAULT_FONT_SIZE (line 17) | DEFAULT_FONT_SIZE = 26
constant DEFAULT_WIDTH (line 18) | DEFAULT_WIDTH = 120
constant DEFAULT_HEIGHT (line 19) | DEFAULT_HEIGHT = 40
constant DEFAULT_GC_PROBABILITY (line 20) | DEFAULT_GC_PROBABILITY = 1
constant DEFAULT_GC_DIVISOR (line 21) | DEFAULT_GC_DIVISOR = 100
function loadConfigFromFile (line 24) | func loadConfigFromFile(configFile string) (error, string, *CaptchaConfi...
FILE: configtypes.go
type CaptchaConfig (line 12) | type CaptchaConfig struct
type FilterConfigGroup (line 19) | type FilterConfigGroup struct
method Init (line 23) | func (this *FilterConfigGroup) Init() {
method GetItem (line 26) | func (this *FilterConfigGroup) GetItem(key string) (string, bool) {
method SetItem (line 30) | func (this *FilterConfigGroup) SetItem(key string, val string) {
type FilterConfig (line 34) | type FilterConfig struct
method Init (line 39) | func (this *FilterConfig) Init() {
method GetGroup (line 43) | func (this *FilterConfig) GetGroup(key string) (FilterConfigGroup, boo...
method SetGroup (line 51) | func (this *FilterConfig) SetGroup(key string, group *FilterConfigGrou...
type ImageConfig (line 56) | type ImageConfig struct
constant STORE_ENGINE_BUILDIN (line 64) | STORE_ENGINE_BUILDIN = "buildin"
constant STORE_ENGINE_MEMCACHE (line 65) | STORE_ENGINE_MEMCACHE = "memcache"
type StoreConfig (line 68) | type StoreConfig struct
FILE: cstore.go
type CStore (line 18) | type CStore struct
method Get (line 40) | func (store *CStore) Get(key string) *CaptchaInfo {
method Add (line 50) | func (store *CStore) Add(captcha *CaptchaInfo) string {
method Update (line 61) | func (store *CStore) Update(key string, captcha *CaptchaInfo) bool {
method Del (line 69) | func (store *CStore) Del(key string) {
method Destroy (line 76) | func (store *CStore) Destroy() {
method OnConstruct (line 84) | func (store *CStore) OnConstruct() {
method OnDestruct (line 89) | func (store *CStore) OnDestruct() {
method Dump (line 94) | func (store *CStore) Dump(file string) error {
method LoadDumped (line 123) | func (store *CStore) LoadDumped(file string) error {
method gcWrapper (line 151) | func (store *CStore) gcWrapper() {
method gc (line 158) | func (store *CStore) gc() {
function CreateCStore (line 28) | func CreateCStore(expiresTime time.Duration, gcProbability int, gcDiviso...
FILE: cstore_test.go
function TestStore (line 13) | func TestStore(t *testing.T) {
FILE: fontmanager.go
type FontManager (line 15) | type FontManager struct
method AddFont (line 32) | func (fm *FontManager) AddFont(pathToFontFile string) error {
method GetFont (line 50) | func (fm *FontManager) GetFont(pathToFontFile string) *truetype.Font {
method GetRandomFont (line 55) | func (fm *FontManager) GetRandomFont() *truetype.Font {
function CreateFontManager (line 22) | func CreateFontManager() *FontManager {
FILE: imagefilter.go
function RegisterImageFilter (line 11) | func RegisterImageFilter(id string, f func(FilterConfigGroup) ImageFilte...
type ImageFilter (line 20) | type ImageFilter interface
type ImageFilterBase (line 28) | type ImageFilterBase struct
method Proc (line 32) | func (filter *ImageFilterBase) Proc(cimage *CImage) {
method GetId (line 36) | func (filter *ImageFilterBase) GetId() string {
method SetConfig (line 40) | func (filter *ImageFilterBase) SetConfig(config FilterConfigGroup) {
method GetConfig (line 44) | func (filter *ImageFilterBase) GetConfig() FilterConfigGroup {
FILE: imagefiltermanager.go
type ImageFilterManager (line 10) | type ImageFilterManager struct
method AddFilter (line 26) | func (manager *ImageFilterManager) AddFilter(filter ImageFilter) {
method GetFilters (line 30) | func (manager *ImageFilterManager) GetFilters() []ImageFilter {
function init (line 14) | func init() {
function CreateImageFilterManager (line 20) | func CreateImageFilterManager() *ImageFilterManager {
function CreateImageFilterManagerByConfig (line 34) | func CreateImageFilterManagerByConfig(config *FilterConfig) *ImageFilter...
FILE: imagefiltermanager_test.go
function TestImageFilterManager (line 11) | func TestImageFilterManager(t *testing.T) {
function TestImageFilterManagerConfig (line 20) | func TestImageFilterManagerConfig(t *testing.T) {
FILE: imagefilternoiseline.go
constant IMAGE_FILTER_NOISE_LINE (line 12) | IMAGE_FILTER_NOISE_LINE = "ImageFilterNoiseLine"
type ImageFilterNoiseLine (line 14) | type ImageFilterNoiseLine struct
method Proc (line 25) | func (filter *ImageFilterNoiseLine) Proc(cimage *CImage) {
method GetId (line 45) | func (filter *ImageFilterNoiseLine) GetId() string {
function imageFilterNoiseLineCreator (line 18) | func imageFilterNoiseLineCreator(config FilterConfigGroup) ImageFilter {
FILE: imagefilternoisepoint.go
constant IMAGE_FILTER_NOISE_POINT (line 11) | IMAGE_FILTER_NOISE_POINT = "ImageFilterNoisePoint"
type ImageFilterNoisePoint (line 14) | type ImageFilterNoisePoint struct
method Proc (line 25) | func (filter *ImageFilterNoisePoint) Proc(cimage *CImage) {
method GetId (line 43) | func (filter *ImageFilterNoisePoint) GetId() string {
function imageFilterNoisePointCreator (line 18) | func imageFilterNoisePointCreator(config FilterConfigGroup) ImageFilter {
FILE: imagefilterstrike.go
constant IMAGE_FILTER_STRIKE (line 12) | IMAGE_FILTER_STRIKE = "ImageFilterStrike"
type ImageFilterStrike (line 15) | type ImageFilterStrike struct
method Proc (line 26) | func (filter *ImageFilterStrike) Proc(cimage *CImage) {
method GetId (line 56) | func (filter *ImageFilterStrike) GetId() string {
function imageFilterStrikeCreator (line 19) | func imageFilterStrikeCreator(config FilterConfigGroup) ImageFilter {
FILE: mcstore.go
constant MC_KEY_PREFIX (line 21) | MC_KEY_PREFIX = "gocaptcha-"
type MCStore (line 25) | type MCStore struct
method Get (line 41) | func (store *MCStore) Get(key string) *CaptchaInfo {
method Add (line 51) | func (store *MCStore) Add(captcha *CaptchaInfo) string {
method Update (line 71) | func (store *MCStore) Update(key string, captcha *CaptchaInfo) bool {
method Del (line 87) | func (store *MCStore) Del(key string) {
method Destroy (line 92) | func (store *MCStore) Destroy() {
method OnConstruct (line 97) | func (store *MCStore) OnConstruct() {
method OnDestruct (line 102) | func (store *MCStore) OnDestruct() {
method encodeValue (line 106) | func (store *MCStore) encodeValue(val *CaptchaInfo) []byte {
method decodeValue (line 117) | func (store *MCStore) decodeValue(value []byte) *CaptchaInfo {
function CreateMCStore (line 31) | func CreateMCStore(expiresTime time.Duration, servers []string) *MCStore {
FILE: mcstore_test.go
function TestMCStore (line 12) | func TestMCStore(t *testing.T) {
FILE: randhelper.go
function init (line 12) | func init() {
function rnd (line 17) | func rnd(from, to int) int {
function rndf (line 22) | func rndf(from, to float64) float64 {
function randStr (line 26) | func randStr(length int) string {
FILE: redisstore.go
function init (line 17) | func init() {
constant captchaKeyFormat (line 22) | captchaKeyFormat = "captcha:text:%s;rand:%s;time:%x;"
constant storeName (line 23) | storeName = "redis"
type CaptchaRedisStore (line 26) | type CaptchaRedisStore struct
method Get (line 55) | func (this *CaptchaRedisStore) Get(key string) *CaptchaInfo {
method Add (line 67) | func (this *CaptchaRedisStore) Add(captcha *CaptchaInfo) string {
method Update (line 81) | func (this *CaptchaRedisStore) Update(key string, captcha *CaptchaInfo...
method Del (line 96) | func (this *CaptchaRedisStore) Del(key string) {
method Destroy (line 100) | func (this *CaptchaRedisStore) Destroy() {
method OnConstruct (line 104) | func (this *CaptchaRedisStore) OnConstruct() {
method OnDestruct (line 108) | func (this *CaptchaRedisStore) OnDestruct() {
method encodeCaptchaInfo (line 112) | func (this *CaptchaRedisStore) encodeCaptchaInfo(captcha *CaptchaInfo)...
method decodeCaptachInfo (line 123) | func (this *CaptchaRedisStore) decodeCaptachInfo(b []byte, ret *Captch...
function CreateCaptchaRedisStore (line 31) | func CreateCaptchaRedisStore(config *StoreConfig) (StoreInterface, error) {
FILE: redisstore_test.go
function TestRedisStore (line 12) | func TestRedisStore(t *testing.T) {
FILE: storeinterface.go
type StoreInterface (line 12) | type StoreInterface interface
function RegisterStore (line 22) | func RegisterStore(name string, f func(*StoreConfig) (StoreInterface, er...
FILE: wordmanager.go
type WordManager (line 17) | type WordManager struct
method Get (line 56) | func (mgr *WordManager) Get(length int) (string, error) {
method SetWords (line 87) | func (mgr *WordManager) SetWords(words []string) {
method getLine (line 98) | func (mgr *WordManager) getLine() string {
function CreateWordManagerFromDataFile (line 24) | func CreateWordManagerFromDataFile(filename string) (*WordManager, error) {
FILE: wordmanager_test.go
function TestWordManager (line 12) | func TestWordManager(t *testing.T) {
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (57K chars).
[
{
"path": ".github/workflows/go.yml",
"chars": 549,
"preview": "name: Go\n\non:\n push:\n branches: [ master ]\n pull_request:\n branches: [ master ]\n workflow_dispatch:\n\njobs:\n\n b"
},
{
"path": ".gitignore",
"chars": 264,
"preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n_test_files\n\n# Arch"
},
{
"path": ".travis.yml",
"chars": 82,
"preview": "language: go\ngo:\n - 1.3\n - 1.4\n - tip\nservices:\n - memcached\n - redis-server\n"
},
{
"path": "Dockerfile",
"chars": 350,
"preview": "FROM centos\nRUN yum -y update\nRUN yum install -y golang git mercurial memcached\nRUN mkdir -p /home/work/gopath\nENV GOPAT"
},
{
"path": "LICENSE",
"chars": 1077,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2013 hanguofeng\n\nPermission is hereby granted, free of charge, to any person obtain"
},
{
"path": "README-en.md",
"chars": 1414,
"preview": "***\nView in [[English](README-en.md)][[中文](README.md)]\n***\n# gocaptcha\nCaptcha server writen in golang\n\n[![Build Status]"
},
{
"path": "README.md",
"chars": 900,
"preview": "***\nView in [[English](README-en.md)][[中文](README.md)]\n***\n# gocaptcha\ngo语言验证码服务器\n\nFeature\n-------\n* 支持中文验证码\n* 支持自定义词库、字"
},
{
"path": "VERSION",
"chars": 0,
"preview": ""
},
{
"path": "captcha.go",
"chars": 4283,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "captcha_test.go",
"chars": 2872,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "captchainfo.go",
"chars": 442,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "cimage.go",
"chars": 3062,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "confighelper.go",
"chars": 3773,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "configtypes.go",
"chars": 1581,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "cstore.go",
"chars": 3138,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "cstore_test.go",
"chars": 1043,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "data/cn_char",
"chars": 43,
"preview": "你\n好\n啊\n哈\n哦\n浏\n览\n器\n不\n要\n执\n行\n与\n事\n件\n关\n联\n的\n默\n认\n动\n作"
},
{
"path": "data/cn_phrases",
"chars": 1125,
"preview": "高枕无忧\n各自为政\n华而不实\n好好先生 \n狐假虎威\n汗流浃背\n后来居上\n合浦珠还 \n后起之秀\n后生可畏\n火树银花\n坚壁清野 \n间不容发\n井底之蛙\n鸡口牛后\n江郎才尽\n见猎心喜\n鸡鸣狗盗\n九牛一毛\n胶漆相投\n举一反三\n开诚布公\n空洞无物\n沆瀣"
},
{
"path": "data/en_char",
"chars": 51,
"preview": "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz"
},
{
"path": "data/en_phrases",
"chars": 23,
"preview": "hello\nfoo\nbar\nquick\nfox"
},
{
"path": "fontmanager.go",
"chars": 1474,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "go.mod",
"chars": 466,
"preview": "module github.com/hanguofeng/gocaptcha\n\ngo 1.17\n\nrequire (\n\tgithub.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca8"
},
{
"path": "go.sum",
"chars": 2002,
"preview": "github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=\ngithub"
},
{
"path": "imagefilter.go",
"chars": 1066,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "imagefiltermanager.go",
"chars": 1229,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "imagefiltermanager_test.go",
"chars": 1861,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "imagefilternoiseline.go",
"chars": 1010,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "imagefilternoisepoint.go",
"chars": 995,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "imagefilterstrike.go",
"chars": 1232,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "mcstore.go",
"chars": 2411,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "mcstore_test.go",
"chars": 920,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "randhelper.go",
"chars": 743,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "redisstore.go",
"chars": 2785,
"preview": "package gocaptcha\n\nimport (\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"encoding/gob\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"log\"\n\t\"strconv\"\n\t\"strings\"\n\t"
},
{
"path": "redisstore_test.go",
"chars": 1028,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "storeinterface.go",
"chars": 693,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "wordmanager.go",
"chars": 2085,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
},
{
"path": "wordmanager_test.go",
"chars": 928,
"preview": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that"
}
]
About this extraction
This page contains the full source code of the hanguofeng/gocaptcha GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (47.9 KB), approximately 16.1k tokens, and a symbol index with 134 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.