[
  {
    "path": ".github/workflows/go.yml",
    "content": "name: Go\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n  workflow_dispatch:\n\njobs:\n\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n\n    - name: Set up Go\n      uses: actions/setup-go@v2\n      with:\n        go-version: 1.17\n        \n    - name: Install memcache/redis tool\n      run: |\n        sudo apt-get update\n        sudo apt-get install memcached redis-server\n        memcached -d  \n  \n    - name: Build\n      run: go build -v ./...\n\n    - name: Test\n      run: go test -v ./...\n"
  },
  {
    "path": ".gitignore",
    "content": "# 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# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: go\ngo:\n  - 1.3\n  - 1.4\n  - tip\nservices:\n  - memcached\n  - redis-server\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM centos\nRUN yum -y update\nRUN yum install -y golang git mercurial memcached\nRUN mkdir -p /home/work/gopath\nENV GOPATH /home/work/gopath;\nRUN go get github.com/hanguofeng/gocaptcha/samples/gocaptcha-server;\nWORKDIR $GOPATH/src/github.com/hanguofeng/gocaptcha/samples/gocaptcha-server\nRUN [\"go\",\"build\"]\nEXPOSE 80\nENTRYPOINT [\"./gocaptcha-server\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2013 hanguofeng\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README-en.md",
    "content": "***\nView in [[English](README-en.md)][[中文](README.md)]\n***\n# gocaptcha\nCaptcha server writen in golang\n\n[![Build Status](https://travis-ci.org/hanguofeng/gocaptcha.png?branch=master)](https://travis-ci.org/hanguofeng/gocaptcha)  [![Build Status](https://drone.io/github.com/hanguofeng/gocaptcha/status.png)](https://drone.io/github.com/hanguofeng/gocaptcha/latest)   [![Coverage Status](https://coveralls.io/repos/hanguofeng/gocaptcha/badge.png)](https://coveralls.io/r/hanguofeng/gocaptcha)  \n\nFeature\n-------\n* supports captcha char in Chinese\n* supports self-define word/char dictionary\n* supports filter plugin\n* filters：\n\t* noise point\n\t* noise line\n\t* other type of noise\n\t* plugin\n* supports extensible store engine\n\t* build-in store engine\n\t* memcache\n\t* redis (from https://github.com/dtynn/gocaptcha)\n\t* implement your own by implement the StoreInterface\n\nUseage\n------\n**Install**\n\n\tgo get github.com/hanguofeng/gocaptcha\n\n**Quick Start**\n\nSee [captcha_test.go](captcha_test.go)\n\nSee [samples/gocaptcha-server](samples/gocaptcha-server)\n\n[Demo](http://hanguofeng-gocaptcha.daoapp.io/)\n\n**Document**\n\n[[captcha.go Wiki](https://github.com/hanguofeng/gocaptcha/wiki)]\n\nTODO\n----\n* ops tools\n\nLICENCE\n-------\ngocaptcha use [[MIT LICENSE](LICENSE)]\n\nThanks:\n\n* https://github.com/dchest/captcha\n* https://github.com/golang/freetype\n* https://github.com/bradfitz/gomemcache\n* https://code.google.com/p/zpix/\n"
  },
  {
    "path": "README.md",
    "content": "***\nView in [[English](README-en.md)][[中文](README.md)]\n***\n# gocaptcha\ngo语言验证码服务器\n\nFeature\n-------\n* 支持中文验证码\n* 支持自定义词库、字库\n* 支持自定义滤镜机制，通过滤镜来增加干扰，加大识别难度\n* 当前的滤镜包括：\n\t* 支持干扰点\n\t* 支持干扰线\n\t* 支持其他模式的干扰\n\t* 更多模式，可实现imagefilter接口来扩展\n* 支持自定义存储引擎，存储引擎可扩展\n* 目前支持的存储引擎包括:\n\t* 内置(buildin)\n\t* memcache\n\t* redis (from https://github.com/dtynn/gocaptcha)\n\t* 如需扩展存储引擎，可实现StoreInterface接口\n\nUseage\n------\n**安装**\n\n\tgo get github.com/hanguofeng/gocaptcha\n\n**Quick Start**\n\n参考 [captcha_test.go](captcha_test.go)\n\n参考 [samples/gocaptcha-server](samples/gocaptcha-server)\n\n[Demo](http://hanguofeng-gocaptcha.daoapp.io/)\n\n**文档**\n\n[[captcha.go Wiki](https://github.com/hanguofeng/gocaptcha/wiki)]\n\nTODO\n----\n* 运维管理工具\n\nLICENCE\n-------\ngocaptcha使用[[MIT许可协议](LICENSE)]\n\n\n使用的开源软件列表，表示感谢\n\n* https://github.com/dchest/captcha\n* https://github.com/golang/freetype\n* https://github.com/bradfitz/gomemcache\n* https://code.google.com/p/zpix/\n"
  },
  {
    "path": "VERSION",
    "content": ""
  },
  {
    "path": "captcha.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"errors\"\n\t\"image\"\n\t\"strings\"\n\t\"time\"\n)\n\n//Captcha is the core captcha struct\ntype Captcha struct {\n\tstore         StoreInterface\n\twordManager   *WordManager\n\tfilterManager *ImageFilterManager\n\n\tcaptchaConfig *CaptchaConfig\n\timageConfig   *ImageConfig\n\tfilterConfig  *FilterConfig\n\tstoreConfig   *StoreConfig\n}\n\n//CreateCaptcha is a method to create new Captcha struct\nfunc CreateCaptcha(wordManager *WordManager, captchaConfig *CaptchaConfig, imageConfig *ImageConfig, filterConfig *FilterConfig, storeConfig *StoreConfig) (*Captcha, error) {\n\tvar retErr error\n\n\tcaptcha := new(Captcha)\n\n\tstore, err := createStore(storeConfig)\n\tif nil == err {\n\t\tcaptcha.store = store\n\t} else {\n\t\tretErr = err\n\t}\n\tif nil != wordManager {\n\t\tcaptcha.wordManager = wordManager\n\t} else {\n\t\tretErr = errors.New(\"CreateCaptcha fail:invalid wordManager\")\n\t}\n\tcaptcha.captchaConfig = captchaConfig\n\tcaptcha.imageConfig = imageConfig\n\tcaptcha.filterConfig = filterConfig\n\n\tcaptcha.filterManager = CreateImageFilterManagerByConfig(filterConfig)\n\n\treturn captcha, retErr\n}\n\n//CreateCaptchaFromConfigFile is a method to create new Captcha struct\nfunc CreateCaptchaFromConfigFile(configFile string) (*Captcha, error) {\n\tvar captcha *Captcha\n\tvar retErr error\n\n\terr, wordDict, captchaConfig, imageConfig, filterConfig, storeConfig := loadConfigFromFile(configFile)\n\tif nil == err {\n\n\t\twordmgr, err := CreateWordManagerFromDataFile(wordDict)\n\t\tif nil == err {\n\t\t\tcaptcha, retErr = CreateCaptcha(wordmgr, captchaConfig, imageConfig, filterConfig, storeConfig)\n\t\t} else {\n\t\t\tretErr = err\n\t\t}\n\t} else {\n\t\tretErr = err\n\t}\n\treturn captcha, retErr\n}\n\n//GetKey will generate a key with required length\nfunc (captcha *Captcha) GetKey(length int) (string, error) {\n\tvar retErr error\n\tvar rst string\n\ttext, err := captcha.wordManager.Get(length)\n\tif nil != err {\n\t\tretErr = err\n\t} else {\n\t\tinfo := new(CaptchaInfo)\n\t\tinfo.Text = text\n\t\tinfo.CreateTime = time.Now()\n\t\tinfo.ShownTimes = 0\n\t\trst = captcha.store.Add(info)\n\t}\n\treturn rst, retErr\n}\n\n//Verify will verify the user's input and the server stored captcha text\nfunc (captcha *Captcha) Verify(key, textToVerify string) (bool, string) {\n\tinfo := captcha.store.Get(key)\n\tif nil == info {\n\t\treturn false, \"captcha info not found\"\n\t}\n\n\tif info.CreateTime.Add(captcha.captchaConfig.LifeTime).Before(time.Now()) {\n\t\treturn false, \"captcha expires\"\n\t}\n\n\tverified := false\n\tif captcha.captchaConfig.CaseSensitive {\n\t\tverified = info.Text == textToVerify\n\t} else {\n\t\tverified = strings.ToLower(info.Text) == strings.ToLower(textToVerify)\n\t}\n\n\tif !verified {\n\t\treturn false, \"captcha text not match\"\n\t}\n\n\tcaptcha.store.Del(key)\n\treturn true, \"\"\n}\n\n//GetImage will generate the binary image data\nfunc (captcha *Captcha) GetImage(key string) (image.Image, error) {\n\n\tinfo := captcha.store.Get(key)\n\tif nil == info {\n\t\treturn nil, errors.New(\"captcha info not found\")\n\t}\n\n\tif info.CreateTime.Add(captcha.captchaConfig.LifeTime).Before(time.Now()) {\n\t\treturn nil, errors.New(\"captcha expires\")\n\t}\n\n\tif captcha.captchaConfig.ChangeTextOnRefresh {\n\t\tif info.ShownTimes > 0 {\n\n\t\t\ttext, err := captcha.wordManager.Get(len(info.Text))\n\t\t\tif nil != err {\n\t\t\t\treturn nil, err\n\t\t\t} else {\n\t\t\t\tinfo.Text = text\n\t\t\t}\n\t\t}\n\n\t\tinfo.ShownTimes++\n\t\tcaptcha.store.Update(key, info)\n\t}\n\n\tcimg := captcha.genImage(info.Text)\n\treturn cimg, nil\n\n}\nfunc createStore(config *StoreConfig) (StoreInterface, error) {\n\tvar store StoreInterface\n\tvar err error\n\tswitch config.Engine {\n\tcase STORE_ENGINE_BUILDIN:\n\t\tstore = CreateCStore(config.LifeTime, config.GcProbability, config.GcDivisor)\n\t\tbreak\n\tcase STORE_ENGINE_MEMCACHE:\n\t\tstore = CreateMCStore(config.LifeTime, config.Servers)\n\t\tbreak\n\tdefault:\n\t\tcreator, has := storeCreators[config.Engine]\n\t\tif !has {\n\t\t\terr = errors.New(\"Not supported engine:'\" + config.Engine + \"'\")\n\t\t\tbreak\n\t\t}\n\t\tstore, err = creator(config)\n\t}\n\treturn store, err\n}\n\nfunc (captcha *Captcha) genImage(text string) *CImage {\n\n\tcimg := CreateCImage(captcha.imageConfig)\n\tcimg.drawString(text)\n\n\tfor _, filter := range captcha.filterManager.GetFilters() {\n\t\tfilter.Proc(cimg)\n\t}\n\n\treturn cimg\n}\n"
  },
  {
    "path": "captcha_test.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestCaptcha(t *testing.T) {\n\n\tcaptcha, err := getCaptcha()\n\tif nil != err {\n\t\tt.Fatalf(\"getCaptcha Error:%s\", err.Error())\n\t}\n\tkey, err := captcha.GetKey(4)\n\tif nil != err {\n\t\tt.Fatalf(\"GetKey Error:%s\", err.Error())\n\t}\n\tcaptcha.GetImage(key)\n\tcaptcha.Verify(key, \"test\")\n\n}\n\nfunc BenchmarkCaptcha(t *testing.B) {\n\tcaptcha, err := getCaptcha()\n\tif nil != err {\n\t\tt.Fatalf(\"getCaptcha Error:%s\", err.Error())\n\t}\n\n\tfor i := 0; i < t.N; i++ {\n\n\t\ts, _ := captcha.GetKey(4)\n\t\tcaptcha.GetImage(s)\n\t\tcaptcha.Verify(s, \"ssss\")\n\t}\n}\n\nfunc BenchmarkCaptchaInternalAPI(t *testing.B) {\n\tcaptcha, err := getCaptcha()\n\tif nil != err {\n\t\tt.Fatalf(\"getCaptcha Error:%s\", err.Error())\n\t}\n\tfor i := 0; i < t.N; i++ {\n\n\t\ts, _ := captcha.GetKey(4)\n\t\tcaptcha.Verify(s, \"ssss\")\n\t}\n}\n\nfunc BenchmarkCaptchaDrawImage(t *testing.B) {\n\tcaptcha, err := getCaptcha()\n\tif nil != err {\n\t\tt.Fatalf(\"getCaptcha Error:%s\", err.Error())\n\t}\n\tfor i := 0; i < t.N; i++ {\n\n\t\ts, _ := captcha.GetKey(4)\n\t\tcaptcha.GetImage(s)\n\t}\n}\n\nfunc getCaptcha() (*Captcha, error) {\n\twordDict, captchaConfig, imageConfig, filterConfig, storeConfig := loadConfig()\n\n\twordmgr, err := CreateWordManagerFromDataFile(wordDict)\n\tcaptcha, err := CreateCaptcha(wordmgr, captchaConfig, imageConfig, filterConfig, storeConfig)\n\treturn captcha, err\n}\n\nfunc loadConfig() (string, *CaptchaConfig, *ImageConfig, *FilterConfig, *StoreConfig) {\n\n\tpwd, _ := os.Getwd()\n\tdata_path := pwd + \"/data/\"\n\n\twordDict := data_path + \"en_phrases\"\n\n\tcaptchaConfig := new(CaptchaConfig)\n\tcaptchaConfig.LifeTime = 10 * time.Second\n\n\timageConfig := new(ImageConfig)\n\timageConfig.FontFiles = []string{data_path + \"zpix.ttf\"}\n\timageConfig.FontSize = 26\n\timageConfig.Height = 40\n\timageConfig.Width = 120\n\n\tfilterConfig := new(FilterConfig)\n\tfilterConfig.Init()\n\tfilterConfig.Filters = []string{\"ImageFilterNoiseLine\", \"ImageFilterNoisePoint\", \"ImageFilterStrike\"}\n\n\tvar filterConfigGroup *FilterConfigGroup\n\tfilterConfigGroup = new(FilterConfigGroup)\n\tfilterConfigGroup.Init()\n\tfilterConfigGroup.SetItem(\"Num\", \"5\")\n\tfilterConfig.SetGroup(\"ImageFilterNoiseLine\", filterConfigGroup)\n\tfilterConfigGroup = new(FilterConfigGroup)\n\tfilterConfigGroup.Init()\n\tfilterConfigGroup.SetItem(\"Num\", \"10\")\n\tfilterConfig.SetGroup(\"ImageFilterNoisePoint\", filterConfigGroup)\n\tfilterConfigGroup = new(FilterConfigGroup)\n\tfilterConfigGroup.Init()\n\tfilterConfigGroup.SetItem(\"Num\", \"3\")\n\tfilterConfig.SetGroup(\"ImageFilterStrike\", filterConfigGroup)\n\n\tstoreConfig := new(StoreConfig)\n\tstoreConfig.Engine = STORE_ENGINE_BUILDIN\n\tstoreConfig.GcDivisor = 100\n\tstoreConfig.GcProbability = 1\n\n\treturn wordDict, captchaConfig, imageConfig, filterConfig, storeConfig\n}\n"
  },
  {
    "path": "captchainfo.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"time\"\n)\n\n// CaptchaInfo is the entity of a captcha\n// text:the content text,for the image display and user to recognize\n// createTime:the time when the captcha is created\ntype CaptchaInfo struct {\n\tText       string\n\tCreateTime time.Time\n\tShownTimes int\n}\n"
  },
  {
    "path": "cimage.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"image\"\n\t\"image/color\"\n\t\"image/draw\"\n\n\t\"github.com/golang/freetype\"\n)\n\nconst (\n\tcolorCount = 20\n)\n\n//CImage is a image process tool\ntype CImage struct {\n\t*image.Paletted\n\tconfig *ImageConfig\n}\n\nfunc (m *CImage) drawHorizLine(fromX, toX, y int, colorIdx uint8) *CImage {\n\tif 0 >= colorIdx || colorIdx > colorCount {\n\t\tcolorIdx = uint8(rnd(0, colorCount))\n\t}\n\tfor x := fromX; x <= toX; x++ {\n\t\tm.SetColorIndex(x, y, colorIdx)\n\t}\n\n\treturn m\n}\n\nfunc (m *CImage) drawCircle(x, y, radius int, colorIdx uint8) {\n\tf := 1 - radius\n\tdfx := 1\n\tdfy := -2 * radius\n\txo := 0\n\tyo := radius\n\n\tm.SetColorIndex(x, y+radius, colorIdx)\n\tm.SetColorIndex(x, y-radius, colorIdx)\n\tm.drawHorizLine(x-radius, x+radius, y, colorIdx)\n\n\tfor xo < yo {\n\t\tif f >= 0 {\n\t\t\tyo--\n\t\t\tdfy += 2\n\t\t\tf += dfy\n\t\t}\n\t\txo++\n\t\tdfx += 2\n\t\tf += dfx\n\t\tm.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)\n\t\tm.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)\n\t\tm.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)\n\t\tm.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)\n\t}\n}\n\nfunc (m *CImage) drawString(text string) *CImage {\n\n\tfg, bg := image.Black, &image.Uniform{color.RGBA{255, 255, 255, 255}}\n\tdraw.Draw(m, m.Bounds(), bg, image.ZP, draw.Src)\n\tc := freetype.NewContext()\n\tc.SetFontSize(m.config.FontSize)\n\tc.SetClip(m.Bounds())\n\tc.SetDst(m)\n\tc.SetSrc(fg)\n\ti := 0\n\tfor _, s := range text {\n\t\tc.SetFont(m.config.fontManager.GetRandomFont())\n\t\tcharX := (int(c.PointToFixed(m.config.FontSize) >> 8)) * i\n\t\tcharY := int(c.PointToFixed(m.config.FontSize) >> 8)\n\t\tcharPt := freetype.Pt(charX, charY)\n\t\tc.DrawString(string(s), charPt)\n\t\ti = i + 1\n\t}\n\n\treturn m\n}\n\n//CreateCImage will create a CImage struct with config\nfunc CreateCImage(config *ImageConfig) *CImage {\n\tr := new(CImage)\n\tr.Paletted = image.NewPaletted(image.Rect(0, 0, config.Width, config.Height), randomPalette())\n\tr.config = config\n\tif nil == r.config.fontManager {\n\t\tfm := CreateFontManager()\n\t\tfor _, fontFile := range config.FontFiles {\n\t\t\tfm.AddFont(fontFile)\n\t\t}\n\t\tr.config.fontManager = fm\n\t}\n\n\treturn r\n}\n\nfunc randomPalette() color.Palette {\n\n\tp := make([]color.Color, colorCount+1)\n\t// Transparent color.\n\tp[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}\n\t// Primary color.\n\tprim := color.RGBA{\n\t\tuint8(rnd(0, 255)),\n\t\tuint8(rnd(0, 255)),\n\t\tuint8(rnd(0, 255)),\n\t\t0xFF,\n\t}\n\tp[1] = prim\n\t// Circle colors.\n\tfor i := 2; i <= colorCount; i++ {\n\t\tp[i] = randomBrightness(prim, 255)\n\t}\n\n\treturn p\n}\n\nfunc randomBrightness(c color.RGBA, max uint8) color.RGBA {\n\tminc := min3(c.R, c.G, c.B)\n\tmaxc := max3(c.R, c.G, c.B)\n\tif maxc > max {\n\t\treturn c\n\t}\n\tn := rnd(0, int(max-maxc)) - int(minc)\n\treturn color.RGBA{\n\t\tuint8(int(c.R) + n),\n\t\tuint8(int(c.G) + n),\n\t\tuint8(int(c.B) + n),\n\t\tuint8(c.A),\n\t}\n}\n\nfunc min3(x, y, z uint8) (m uint8) {\n\tm = x\n\tif y < m {\n\t\tm = y\n\t}\n\tif z < m {\n\t\tm = z\n\t}\n\treturn\n}\n\nfunc max3(x, y, z uint8) (m uint8) {\n\tm = x\n\tif y > m {\n\t\tm = y\n\t}\n\tif z > m {\n\t\tm = z\n\t}\n\treturn\n}\n"
  },
  {
    "path": "confighelper.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/hanguofeng/config\"\n)\n\nconst (\n\tDEFAULT_LIFE_TIME      = time.Minute * 5\n\tDEFAULT_FONT_SIZE      = 26\n\tDEFAULT_WIDTH          = 120\n\tDEFAULT_HEIGHT         = 40\n\tDEFAULT_GC_PROBABILITY = 1\n\tDEFAULT_GC_DIVISOR     = 100\n)\n\nfunc loadConfigFromFile(configFile string) (error, string, *CaptchaConfig, *ImageConfig, *FilterConfig, *StoreConfig) {\n\n\tvar retErr error\n\n\tc, err := config.ReadDefault(configFile)\n\n\t//wordDict\n\twordDict, err := c.String(\"captcha\", \"word_dict\")\n\tif nil != err {\n\t\tretErr = errors.New(\"loadConfigFromFile Fail,Get word_dict options failed:\" + err.Error())\n\t}\n\t//captchaConfig\n\tcaptchaConfig := new(CaptchaConfig)\n\tvar lifeTime time.Duration\n\n\tcfgLifeTime, err := c.String(\"captcha\", \"life_time\")\n\tif nil == err {\n\t\tlifeTime, err = time.ParseDuration(cfgLifeTime)\n\t\tif nil != err {\n\t\t\tlifeTime = DEFAULT_LIFE_TIME\n\t\t}\n\t} else {\n\t\tlifeTime = DEFAULT_LIFE_TIME\n\t}\n\n\tcaptchaConfig.LifeTime = lifeTime\n\n\tcfgChangeTextOnRefresh, err := c.Bool(\"captcha\", \"change_text_on_refresh\")\n\tif nil != err {\n\t\tcfgChangeTextOnRefresh = false\n\t}\n\n\tcaptchaConfig.ChangeTextOnRefresh = cfgChangeTextOnRefresh\n\n\t//imageConfig\n\timageConfig := new(ImageConfig)\n\tvar fontFiles []string\n\tcfgFontFiles, err := c.StringMuti(\"image\", \"font_files\")\n\tif nil != err {\n\t\tretErr = errors.New(\"loadConfigFromFile Fail,font_files options failed:\" + err.Error())\n\t} else {\n\t\tfontFiles = cfgFontFiles\n\t}\n\n\timageConfig.FontFiles = fontFiles\n\n\tvar fontSize float64\n\tcfgFontSize, err := c.Int(\"image\", \"font_size\")\n\tif nil != err {\n\t\tfontSize = DEFAULT_FONT_SIZE\n\t} else {\n\t\tfontSize = float64(cfgFontSize)\n\t}\n\n\timageConfig.FontSize = fontSize\n\n\tvar width int\n\tcfgWidth, err := c.Int(\"image\", \"width\")\n\tif nil != err {\n\t\twidth = DEFAULT_WIDTH\n\t} else {\n\t\twidth = int(cfgWidth)\n\t}\n\timageConfig.Width = width\n\n\tvar height int\n\tcfgHeight, err := c.Int(\"image\", \"height\")\n\tif nil != err {\n\t\theight = DEFAULT_HEIGHT\n\t} else {\n\t\theight = int(cfgHeight)\n\t}\n\timageConfig.Height = height\n\n\t//filterConfig\n\tfilterConfig := new(FilterConfig)\n\tfilterConfig.Init()\n\tcfgOpenFilter, err := c.StringMuti(\"filter\", \"open_filter\")\n\tif nil == err {\n\t\tfilterConfig.Filters = cfgOpenFilter\n\t} else {\n\t\tfilterConfig.Filters = []string{}\n\t}\n\tfor _, section := range c.Sections() {\n\t\tif strings.HasPrefix(section, \"ImageFilter\") {\n\t\t\tfilterConfigGroup := new(FilterConfigGroup)\n\t\t\tfilterConfigGroup.Init()\n\t\t\toptions, err := c.Options(section)\n\t\t\tif nil == err {\n\t\t\t\tfor _, option := range options {\n\t\t\t\t\tv, err := c.String(section, option)\n\t\t\t\t\tif nil == err {\n\t\t\t\t\t\tfilterConfigGroup.SetItem(option, v)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfilterConfig.SetGroup(section, filterConfigGroup)\n\t\t}\n\t}\n\n\t//storeConfig\n\tstoreConfig := new(StoreConfig)\n\tstoreConfig.CaptchaConfig = *captchaConfig\n\tengine, err := c.String(\"store\", \"engine\")\n\tif nil != err {\n\t\tretErr = errors.New(\"loadConfigFromFile Fail,engine options failed\" + err.Error())\n\t} else {\n\t\tstoreConfig.Engine = engine\n\t}\n\tservers, err := c.StringMuti(\"store\", \"servers\")\n\tif nil != err {\n\t\tstoreConfig.Servers = []string{}\n\t} else {\n\t\tstoreConfig.Servers = servers\n\t}\n\tgcProbability, err := c.Int(\"store\", \"gc_probability\")\n\tif nil != err {\n\t\tstoreConfig.GcProbability = gcProbability\n\t} else {\n\t\tstoreConfig.GcProbability = DEFAULT_GC_PROBABILITY\n\t}\n\tgcDivisor, err := c.Int(\"store\", \"gc_divisor\")\n\tif nil != err {\n\t\tstoreConfig.GcDivisor = gcDivisor\n\t} else {\n\t\tstoreConfig.GcDivisor = DEFAULT_GC_DIVISOR\n\t}\n\n\tif nil != err && nil == retErr {\n\t\tretErr = err\n\t}\n\n\treturn retErr, wordDict, captchaConfig, imageConfig, filterConfig, storeConfig\n}\n"
  },
  {
    "path": "configtypes.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"time\"\n)\n\n//CaptchaConfig ,the captcha config\ntype CaptchaConfig struct {\n\tLifeTime            time.Duration\n\tCaseSensitive       bool\n\tChangeTextOnRefresh bool\n}\n\n//FilterConfigGroup\ntype FilterConfigGroup struct {\n\tdata map[string]string\n}\n\nfunc (this *FilterConfigGroup) Init() {\n\tthis.data = map[string]string{}\n}\nfunc (this *FilterConfigGroup) GetItem(key string) (string, bool) {\n\tval, ok := this.data[key]\n\treturn val, ok\n}\nfunc (this *FilterConfigGroup) SetItem(key string, val string) {\n\tthis.data[key] = val\n}\n\ntype FilterConfig struct {\n\tFilters []string\n\tdata    map[string]*FilterConfigGroup\n}\n\nfunc (this *FilterConfig) Init() {\n\tthis.Filters = []string{}\n\tthis.data = map[string]*FilterConfigGroup{}\n}\nfunc (this *FilterConfig) GetGroup(key string) (FilterConfigGroup, bool) {\n\tval, ok := this.data[key]\n\tif !ok {\n\t\tval = new(FilterConfigGroup)\n\t}\n\n\treturn *val, ok\n}\nfunc (this *FilterConfig) SetGroup(key string, group *FilterConfigGroup) {\n\tthis.data[key] = group\n}\n\n//ImageConfig ,the image config\ntype ImageConfig struct {\n\tWidth       int\n\tHeight      int\n\tFontSize    float64\n\tFontFiles   []string\n\tfontManager *FontManager\n}\n\nconst STORE_ENGINE_BUILDIN = \"buildin\"\nconst STORE_ENGINE_MEMCACHE = \"memcache\"\n\n//StoreConfig ,the store engine config\ntype StoreConfig struct {\n\tCaptchaConfig\n\tEngine        string\n\tServers       []string\n\tGcProbability int\n\tGcDivisor     int\n}\n"
  },
  {
    "path": "cstore.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"crypto/md5\"\n\tenc \"encoding/gob\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n)\n\n//CStore is the Captcha info store service\ntype CStore struct {\n\tengine        string\n\tmu            sync.RWMutex\n\tdata          map[string]*CaptchaInfo\n\texpiresTime   time.Duration\n\tgcProbability int\n\tgcDivisor     int\n}\n\n//CreateCStore will create a new CStore\nfunc CreateCStore(expiresTime time.Duration, gcProbability int, gcDivisor int) *CStore {\n\tstore := new(CStore)\n\tstore.engine = STORE_ENGINE_BUILDIN\n\tstore.data = make(map[string]*CaptchaInfo)\n\tstore.expiresTime = expiresTime\n\tstore.gcProbability = gcProbability\n\tstore.gcDivisor = gcDivisor\n\n\treturn store\n}\n\n//Get captcha info by key\nfunc (store *CStore) Get(key string) *CaptchaInfo {\n\tdefer store.gcWrapper() //run gc after get\n\tstore.mu.Lock()\n\tdefer store.mu.Unlock()\n\n\tret := store.data[key]\n\treturn ret\n}\n\n//Add captcha info and get the auto generated key\nfunc (store *CStore) Add(captcha *CaptchaInfo) string {\n\tstore.mu.Lock()\n\tdefer store.mu.Unlock()\n\tkey := fmt.Sprintf(\"%s%s%x\", captcha.Text, randStr(20), time.Now().UnixNano())\n\tkey = hex.EncodeToString(md5.New().Sum([]byte(key)))\n\tkey = key[:32]\n\tstore.data[key] = captcha\n\treturn key\n}\n\n//Update captcha info\nfunc (store *CStore) Update(key string, captcha *CaptchaInfo) bool {\n\tstore.mu.Lock()\n\tdefer store.mu.Unlock()\n\tstore.data[key] = captcha\n\treturn true\n}\n\n//Del captcha info by key\nfunc (store *CStore) Del(key string) {\n\tstore.mu.Lock()\n\tdefer store.mu.Unlock()\n\tdelete(store.data, key)\n}\n\n//Destroy the whole store\nfunc (store *CStore) Destroy() {\n\n\tfor key := range store.data {\n\t\tstore.Del(key)\n\t}\n}\n\n//OnConstruct load data\nfunc (store *CStore) OnConstruct() {\n\n}\n\n//OnDestruct dump data\nfunc (store *CStore) OnDestruct() {\n\n}\n\n//Dump the whole store\nfunc (store *CStore) Dump(file string) error {\n\tstore.mu.Lock()\n\tdefer store.mu.Unlock()\n\n\tpwd, err := os.Getwd()\n\n\tif (nil == err) && (\"\" != pwd) {\n\t\tf, err := os.Create(pwd + \"/\" + file)\n\t\tif nil == err {\n\t\t\tencoder := enc.NewEncoder(f)\n\t\t\terr := encoder.Encode(store.data)\n\t\t\tf.Close()\n\n\t\t\tif nil == err {\n\t\t\t\treturn err\n\t\t\t} else {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t} else {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n//LoadDumped file to store\nfunc (store *CStore) LoadDumped(file string) error {\n\tdata := &map[string]*CaptchaInfo{}\n\tpwd, err := os.Getwd()\n\n\tif (nil == err) && (\"\" != pwd) {\n\t\tf, err := os.Open(pwd + \"/\" + file)\n\t\tif nil == err {\n\t\t\tdecoder := enc.NewDecoder(f)\n\t\t\terr := decoder.Decode(data)\n\t\t\tf.Close()\n\n\t\t\tif nil == err {\n\t\t\t\tstore.data = *data\n\t\t\t\treturn nil\n\n\t\t\t} else {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t} else {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\treturn err\n\t}\n\treturn err\n}\n\nfunc (store *CStore) gcWrapper() {\n\t//run PROBABILITY\n\tif rnd(0, store.gcDivisor) == store.gcProbability {\n\t\tgo store.gc()\n\t}\n}\n\nfunc (store *CStore) gc() {\n\tfor key, val := range store.data {\n\t\tif val.CreateTime.Add(store.expiresTime).Before(time.Now()) {\n\t\t\tstore.Del(key)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cstore_test.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestStore(t *testing.T) {\n\tstore := CreateCStore(10*time.Second, 1, 100) //10 s\n\n\tcaptcha := new(CaptchaInfo)\n\tcaptcha.Text = \"hello\"\n\tcaptcha.CreateTime = time.Now()\n\n\t//test add and get\n\tkey := store.Add(captcha)\n\tretV := store.Get(key)\n\tif retV != captcha {\n\t\tt.Errorf(\"not equal\")\n\t}\n\n\tretV.Text = \"world\"\n\tstore.Update(key, retV)\n\n\tretV = store.Get(key)\n\tif retV.Text != \"world\" {\n\t\tt.Errorf(\"update not equal\")\n\t}\n\n\t//test dump,destroy and loaddumped\n\tstore.Dump(\"data/data.dat\")\n\tstore.Destroy()\n\tretV = store.Get(key)\n\tif nil != retV {\n\t\tt.Errorf(\"Destroy error\")\n\t}\n\tstore.LoadDumped(\"data/data.dat\")\n\tretV = store.Get(key)\n\tif captcha.Text != retV.Text {\n\t\tt.Errorf(\"LoadDumped error\")\n\t}\n\n\tos.Remove(\"data/data.dat\")\n\n\t//test del\n\tstore.Del(key)\n\tretV = store.Get(key)\n\tif nil != retV {\n\t\tt.Errorf(\"not del\")\n\t}\n\n}\n"
  },
  {
    "path": "data/cn_char",
    "content": "你\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",
    "content": "高枕无忧\n各自为政\n华而不实\n好好先生 \n狐假虎威\n汗流浃背\n后来居上\n合浦珠还 \n后起之秀\n后生可畏\n火树银花\n坚壁清野 \n间不容发\n井底之蛙\n鸡口牛后\n江郎才尽\n见猎心喜\n鸡鸣狗盗\n九牛一毛\n胶漆相投\n举一反三\n开诚布公\n空洞无物\n沆瀣—气\n网油鱼卷\n燕窝四字\n抓炒鱼片\n三鲜瑶柱\n芙蓉大虾\n龙井竹荪\n桂花干贝\n金钱吐丝\n凤凰展翅\n炸鸡葫芦\n桃仁鸡丁\n鸭丝掐菜\n肉末烧饼\n龙凤柔情\n鸡沾口蘑\n咖喱菜花\n凤凰趴窝\n宫保兔肉\n熊猫品竹\n御扇豆黄\n炝玉龙片\n糖醋鱼卷\n金鱼鸭掌\n琉璃珠玑\n金糕\n栗子糕\n芝麻卷\n仿膳饽饽\n酥卷佛手\n油焖鲜蘑\n莲子膳粥\n麻辣牛肉\n金丝烧麦\n凤尾群翅\n太极发财燕\n雪月羊肉\n炸春卷\n如意卷\n金糕卷\n兰花豆干\n萝卜桂鱼\n凤穿金衣\n云河段霄\n翠玉豆糕\n四喜饺\n绣球全鱼\n秋菊傲霜\n龙衔海棠\n滑溜鹌鹑\n侉炖羊肉\n玉掌献寿\n炒榛子酱\n发菜黄花\n熊猫蟹肉\n清炸鹌鹑\n双色豆糕\n烤羊腿\n龙井金鱼\n琥珀鸽蛋\n二龙戏珠\n炒黄瓜酱\n雨后春笋\n如意竹荪\n檀扇鸭掌\n鲤跃龙门\n炝黄瓜衣\n菊花里脊\n八宝膳粥\n母子相会\n豌豆黄\n小豆糕\n枣泥糕\n红烧鱼唇\n金鱼角\n凤凰鱼肚\n宫廷排翅\n玉兔白菜\n冰花雪莲\n松鹤延年\n香露苹果\n荷花酥\n金钱鱼肚\n明珠豆腐\n芙蓉鱼骨\n百子冬瓜\n翡翠玉扇\n仙鹤鲍鱼\n薏米膳继\n莲子糕\n日月生辉\n荷包蟹肉\n姜汁扁豆\n佛手金卷\n翠柳凤丝\n燕影金蔬\n白银如意\n黄袍加身\n葵花麻鱼\n五丝菜卷\n佛手广肚\n白梨凤脯\n参婆千子\n杏仁豆腐\n御龙火锅\n糖醋荷藕\n罗汉大虾\n黑米膳粥\n金屋藏娇\n翡翠银耳\n抓炒大虾\n喜鹊登枝\n油攒大虾\n茸鸡待哺\n随滑飞龙\n鸡油香菇\n怪味鸡片\n鹦鹉莴笋\n芝麻锅炸\n芜爆鲜贝\n双色马蹄糕\n口蘑鹿肉\n琵琶大虾\n长春羹\n干煸牛肉丝\n蝴蝶海参\n鸳鸯酥盒\n芜爆山鸡\n燕尾桃花\n麦穗虾卷\n三鲜鸭舌\n金银鸽肉\n红烧鹿筋\n芙蓉鹿尾\n百寿桃\n莲花卷\n三色糕\n玉板翠带\n珍珠雪耳\n清炒鳝丝\n万年长青\n金鸡独立 \n豢蝶大虾\n凤戏牡丹\n荷塘莲香\n一品官燕\n芸豆金鱼\n豆沙糕 \n酥页层层\n金蟾拜月\n奶油菠萝冻\n花篮白菜\n桂花海蜇\n鲍王闹府 \n凤凰出世\n绣球蛋糕\n云片鸽蛋\n烧瓤菜花\n龙凤双锤\n鸡丝汤面 \n红烧鱼骨\n凤脯珍珠\n干烧冬笋\n鸳鸯哺乳\n百鸟还巢\n卧龙戏珠 \n如意乌龙\n金狮绣球\n豆沙凉糕\n凤眼秋波\n红豆膳粥\n棠花吐蕊\n群虾戏荷\n炒豌豆酱\n金镶玉板\n梅竹山石\n芙蓉鱼角\n怀胎桂鱼\n雪里藏珍\n抓炒里脊\n叉烧猪肉\n松树猴头\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",
    "content": "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",
    "content": "hello\nfoo\nbar\nquick\nfox"
  },
  {
    "path": "fontmanager.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"github.com/golang/freetype/truetype\"\n\t\"io/ioutil\"\n\t\"math/rand\"\n\t\"time\"\n)\n\n//FontManager is a Font Manager the manage font list\ntype FontManager struct {\n\tfontFiles   []string\n\tfontObjects map[string]*truetype.Font\n\trandObject  *rand.Rand\n}\n\n//CreateFontManager will create a new Font Manager\nfunc CreateFontManager() *FontManager {\n\tfm := new(FontManager)\n\tfm.fontFiles = []string{}\n\tfm.fontObjects = make(map[string]*truetype.Font)\n\tfm.randObject = rand.New(rand.NewSource(time.Now().UnixNano()))\n\n\treturn fm\n}\n\n//AddFont will add a new font file to the list\nfunc (fm *FontManager) AddFont(pathToFontFile string) error {\n\tfontBytes, err := ioutil.ReadFile(pathToFontFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfont, err := truetype.Parse(fontBytes)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\tfm.fontFiles = append(fm.fontFiles, pathToFontFile)\n\tfm.fontObjects[pathToFontFile] = font\n\n\treturn nil\n}\n\n//GetFont will return a Font struct by path\nfunc (fm *FontManager) GetFont(pathToFontFile string) *truetype.Font {\n\treturn fm.fontObjects[pathToFontFile]\n}\n\n//GetRandomFont will return a random Font struct\nfunc (fm *FontManager) GetRandomFont() *truetype.Font {\n\trandomIndex := fm.randObject.Intn(len(fm.fontFiles))\n\tfontFile := fm.fontFiles[randomIndex]\n\trst := fm.GetFont(fontFile)\n\n\treturn rst\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/hanguofeng/gocaptcha\n\ngo 1.17\n\nrequire (\n\tgithub.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b\n\tgithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0\n\tgithub.com/hanguofeng/config v1.0.0\n\tgopkg.in/redis.v2 v2.3.2\n)\n\nrequire (\n\tgolang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect\n\tgopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e // indirect\n\tgopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=\ngithub.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=\ngithub.com/hanguofeng/config v1.0.0 h1:AIpXdv7iEzK4v4Qyov7jeZWIwepiwBfELzfpd2r0dH0=\ngithub.com/hanguofeng/config v1.0.0/go.mod h1:YX//r7JfVHJCHW/+HP8WqQ2FVDaUGCo0j5z3ddMnrk4=\ngithub.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngolang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=\ngolang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU=\ngopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e/go.mod h1:xsQCaysVCudhrYTfzYWe577fCe7Ceci+6qjO2Rdc0Z4=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/redis.v2 v2.3.2 h1:GPVIIB/JnL1wvfULefy3qXmPu1nfNu2d0yA09FHgwfs=\ngopkg.in/redis.v2 v2.3.2/go.mod h1:4wl9PJ/CqzeHk3LVq1hNLHH8krm3+AXEgut4jVc++LU=\n"
  },
  {
    "path": "imagefilter.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport ()\n\nvar imageFilterCreators = map[string]func(FilterConfigGroup) ImageFilter{}\n\nfunc RegisterImageFilter(id string, f func(FilterConfigGroup) ImageFilter) bool {\n\tif _, has := imageFilterCreators[id]; has {\n\t\treturn false\n\t}\n\timageFilterCreators[id] = f\n\treturn true\n}\n\n//ImageFilter is the interface of image filter\ntype ImageFilter interface {\n\tProc(cimage *CImage)\n\tGetId() string\n\tSetConfig(FilterConfigGroup)\n\tGetConfig() FilterConfigGroup\n}\n\n//ImageFilter is the base class of image filter\ntype ImageFilterBase struct {\n\tconfig FilterConfigGroup\n}\n\nfunc (filter *ImageFilterBase) Proc(cimage *CImage) {\n\tpanic(\"not impl\")\n}\n\nfunc (filter *ImageFilterBase) GetId() string {\n\tpanic(\"not impl\")\n}\n\nfunc (filter *ImageFilterBase) SetConfig(config FilterConfigGroup) {\n\tfilter.config = config\n}\n\nfunc (filter *ImageFilterBase) GetConfig() FilterConfigGroup {\n\treturn filter.config\n}\n"
  },
  {
    "path": "imagefiltermanager.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport ()\n\n//ImageFilterManager\ntype ImageFilterManager struct {\n\tfilters []ImageFilter\n}\n\nfunc init() {\n\tRegisterImageFilter(IMAGE_FILTER_NOISE_LINE, imageFilterNoiseLineCreator)\n\tRegisterImageFilter(IMAGE_FILTER_NOISE_POINT, imageFilterNoisePointCreator)\n\tRegisterImageFilter(IMAGE_FILTER_STRIKE, imageFilterStrikeCreator)\n}\n\nfunc CreateImageFilterManager() *ImageFilterManager {\n\tret := new(ImageFilterManager)\n\tret.filters = []ImageFilter{}\n\treturn ret\n}\n\nfunc (manager *ImageFilterManager) AddFilter(filter ImageFilter) {\n\tmanager.filters = append(manager.filters, filter)\n}\n\nfunc (manager *ImageFilterManager) GetFilters() []ImageFilter {\n\treturn manager.filters\n}\n\nfunc CreateImageFilterManagerByConfig(config *FilterConfig) *ImageFilterManager {\n\tmgr := new(ImageFilterManager)\n\tmgr.filters = []ImageFilter{}\n\n\tfor _, cfgId := range config.Filters {\n\t\tcreator, has := imageFilterCreators[cfgId]\n\t\tif !has {\n\t\t\tcontinue\n\t\t}\n\t\tcfgGroup, has := config.GetGroup(cfgId)\n\t\tif !has {\n\t\t\tcontinue\n\t\t}\n\t\tmgr.AddFilter(creator(cfgGroup))\n\t}\n\n\treturn mgr\n}\n"
  },
  {
    "path": "imagefiltermanager_test.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"testing\"\n)\n\nfunc TestImageFilterManager(t *testing.T) {\n\n\tmanager := CreateImageFilterManager()\n\tmanager.AddFilter(&ImageFilterBase{})\n\tif 1 != len(manager.GetFilters()) {\n\t\tt.Error(\"add or get failed\")\n\t}\n}\n\nfunc TestImageFilterManagerConfig(t *testing.T) {\n\n\tfilterConfig := new(FilterConfig)\n\tfilterConfig.Init()\n\tfilterConfig.Filters = []string{\"ImageFilterNoiseLine\", \"ImageFilterNoisePoint\", \"ImageFilterStrike\"}\n\tvar filterConfigGroup *FilterConfigGroup\n\tfilterConfigGroup = new(FilterConfigGroup)\n\tfilterConfigGroup.Init()\n\tfilterConfigGroup.SetItem(\"Num\", \"5\")\n\tfilterConfig.SetGroup(\"ImageFilterNoiseLine\", filterConfigGroup)\n\tfilterConfigGroup = new(FilterConfigGroup)\n\tfilterConfigGroup.Init()\n\tfilterConfigGroup.SetItem(\"Num\", \"10\")\n\tfilterConfig.SetGroup(\"ImageFilterNoisePoint\", filterConfigGroup)\n\tfilterConfigGroup = new(FilterConfigGroup)\n\tfilterConfigGroup.Init()\n\tfilterConfigGroup.SetItem(\"Num\", \"3\")\n\tfilterConfig.SetGroup(\"ImageFilterStrike\", filterConfigGroup)\n\n\tmanager := CreateImageFilterManagerByConfig(filterConfig)\n\tfilters := manager.GetFilters()\n\n\tif 3 != len(filters) {\n\t\tt.Error(\"create by config failed\")\n\t}\n\n\tfor _, filter := range filters {\n\t\tconfig := filter.GetConfig()\n\t\tvar num string\n\t\tswitch filter.GetId() {\n\t\tcase \"ImageFilterNoiseLine\":\n\n\t\t\tif num, _ = config.GetItem(\"Num\"); \"5\" != num {\n\t\t\t\tt.Error(\"ImageFilterNoiseLine ERROR\")\n\t\t\t}\n\t\t\tbreak\n\t\tcase \"ImageFilterNoisePoint\":\n\t\t\tif num, _ = config.GetItem(\"Num\"); \"10\" != num {\n\t\t\t\tt.Error(\"ImageFilterNoisePoint ERROR\")\n\t\t\t}\n\t\t\tbreak\n\t\tcase \"ImageFilterStrike\":\n\t\t\tif num, _ = config.GetItem(\"Num\"); \"3\" != num {\n\t\t\t\tt.Error(\"ImageFilterStrike ERROR\")\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t}\n\n}\n"
  },
  {
    "path": "imagefilternoiseline.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"strconv\"\n)\n\n//ImageFilter is the interface of image filter\nconst IMAGE_FILTER_NOISE_LINE = \"ImageFilterNoiseLine\"\n\ntype ImageFilterNoiseLine struct {\n\tImageFilterBase\n}\n\nfunc imageFilterNoiseLineCreator(config FilterConfigGroup) ImageFilter {\n\tfilter := ImageFilterNoiseLine{}\n\tfilter.SetConfig(config)\n\treturn &filter\n}\n\n//Proc the image\nfunc (filter *ImageFilterNoiseLine) Proc(cimage *CImage) {\n\n\tvar num int\n\tvar err error\n\tv, ok := filter.config.GetItem(\"Num\")\n\tif ok {\n\t\tnum, err = strconv.Atoi(v)\n\t\tif nil != err {\n\t\t\tnum = 3\n\t\t}\n\t} else {\n\t\tnum = 3\n\t}\n\n\tfor i := 0; i < num; i++ {\n\t\tx := rnd(0, cimage.Bounds().Max.X)\n\t\tcimage.drawHorizLine(int(float32(x)/1.5), x, rnd(0, cimage.Bounds().Max.Y), uint8(rnd(1, colorCount)))\n\t}\n}\n\nfunc (filter *ImageFilterNoiseLine) GetId() string {\n\treturn IMAGE_FILTER_NOISE_LINE\n}\n"
  },
  {
    "path": "imagefilternoisepoint.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"strconv\"\n)\n\nconst IMAGE_FILTER_NOISE_POINT = \"ImageFilterNoisePoint\"\n\n//ImageFilter is the interface of image filter\ntype ImageFilterNoisePoint struct {\n\tImageFilterBase\n}\n\nfunc imageFilterNoisePointCreator(config FilterConfigGroup) ImageFilter {\n\tfilter := ImageFilterNoisePoint{}\n\tfilter.SetConfig(config)\n\treturn &filter\n}\n\n//Proc the image\nfunc (filter *ImageFilterNoisePoint) Proc(cimage *CImage) {\n\tvar num int\n\tvar err error\n\tv, ok := filter.config.GetItem(\"Num\")\n\tif ok {\n\t\tnum, err = strconv.Atoi(v)\n\t\tif nil != err {\n\t\t\tnum = 3\n\t\t}\n\t} else {\n\t\tnum = 3\n\t}\n\n\tfor i := 0; i < num; i++ {\n\t\tcimage.drawCircle(rnd(0, cimage.Bounds().Max.X), rnd(0, cimage.Bounds().Max.Y), rnd(0, 2), uint8(rnd(1, colorCount)))\n\t}\n}\n\nfunc (filter *ImageFilterNoisePoint) GetId() string {\n\treturn IMAGE_FILTER_NOISE_POINT\n}\n"
  },
  {
    "path": "imagefilterstrike.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"math\"\n\t\"strconv\"\n)\n\nconst IMAGE_FILTER_STRIKE = \"ImageFilterStrike\"\n\n//ImageFilter is the interface of image filter\ntype ImageFilterStrike struct {\n\tImageFilterBase\n}\n\nfunc imageFilterStrikeCreator(config FilterConfigGroup) ImageFilter {\n\tfilter := ImageFilterStrike{}\n\tfilter.SetConfig(config)\n\treturn &filter\n}\n\n//Proc the image\nfunc (filter *ImageFilterStrike) Proc(cimage *CImage) {\n\n\tvar num int\n\tvar err error\n\tv, ok := filter.config.GetItem(\"Num\")\n\tif ok {\n\t\tnum, err = strconv.Atoi(v)\n\t\tif nil != err {\n\t\t\tnum = 1\n\t\t}\n\t} else {\n\t\tnum = 1\n\t}\n\n\tmaxx := cimage.Bounds().Max.X\n\tmaxy := cimage.Bounds().Max.Y\n\ty := rnd(maxy/2, maxy-maxy/2)\n\tamplitude := rndf(10, 15)\n\tperiod := rndf(80, 100)\n\tdx := 2.0 * math.Pi / period\n\tfor x := 0; x < maxx; x++ {\n\t\txo := amplitude * math.Cos(float64(y)*dx)\n\t\tyo := amplitude * math.Sin(float64(x)*dx)\n\t\tfor yn := 0; yn < num; yn++ {\n\t\t\tr := rnd(0, 2)\n\t\t\tcimage.drawCircle(x+int(xo), y+int(yo)+(yn*(num+1)), r/2, 1)\n\t\t}\n\t}\n}\n\nfunc (filter *ImageFilterStrike) GetId() string {\n\treturn IMAGE_FILTER_STRIKE\n}\n"
  },
  {
    "path": "mcstore.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\tenc \"encoding/gob\"\n\n\t\"github.com/bradfitz/gomemcache/memcache\"\n)\n\nconst (\n\tMC_KEY_PREFIX = \"gocaptcha-\"\n)\n\n//MCStore is the Captcha info store service\ntype MCStore struct {\n\tengine string\n\tmc     *memcache.Client\n}\n\n//CreateCStore will create a new CStore\nfunc CreateMCStore(expiresTime time.Duration, servers []string) *MCStore {\n\tstore := new(MCStore)\n\n\tstore.mc = memcache.New(servers...)\n\tstore.mc.Timeout = expiresTime\n\n\treturn store\n}\n\n//Get captcha info by key\nfunc (store *MCStore) Get(key string) *CaptchaInfo {\n\titem, err := store.mc.Get(MC_KEY_PREFIX + key)\n\tif nil != err {\n\t\treturn nil\n\t}\n\tret := store.decodeValue(item.Value)\n\treturn ret\n}\n\n//Add captcha info and get the auto generated key\nfunc (store *MCStore) Add(captcha *CaptchaInfo) string {\n\n\tkey := fmt.Sprintf(\"%s%s%x\", captcha.Text, randStr(20), time.Now().UnixNano())\n\tkey = hex.EncodeToString(md5.New().Sum([]byte(key)))\n\tkey = key[:32]\n\n\titem := new(memcache.Item)\n\titem.Key = MC_KEY_PREFIX + key\n\titem.Value = store.encodeValue(captcha)\n\n\terr := store.mc.Add(item)\n\n\tif nil != err {\n\t\tlog.Printf(\"add key in memcache err:%s\", err)\n\t}\n\n\treturn key\n}\n\n//Update captcha info\nfunc (store *MCStore) Update(key string, captcha *CaptchaInfo) bool {\n\titem := new(memcache.Item)\n\titem.Key = MC_KEY_PREFIX + key\n\titem.Value = store.encodeValue(captcha)\n\n\terr := store.mc.Set(item)\n\n\tif nil != err {\n\t\tlog.Printf(\"update key in memcache err:%s\", err)\n\t\treturn false\n\t} else {\n\t\treturn true\n\t}\n}\n\n//Del captcha info by key\nfunc (store *MCStore) Del(key string) {\n\tstore.mc.Delete(MC_KEY_PREFIX + key)\n}\n\n//Destroy the whole store\nfunc (store *MCStore) Destroy() {\n\n}\n\n//OnConstruct load data\nfunc (store *MCStore) OnConstruct() {\n\n}\n\n//OnDestruct dump data\nfunc (store *MCStore) OnDestruct() {\n\n}\n\nfunc (store *MCStore) encodeValue(val *CaptchaInfo) []byte {\n\n\tbuf := bytes.NewBufferString(\"\")\n\tencoder := enc.NewEncoder(buf)\n\terr := encoder.Encode(val)\n\tif nil != err {\n\t\treturn nil\n\t}\n\treturn buf.Bytes()\n}\n\nfunc (store *MCStore) decodeValue(value []byte) *CaptchaInfo {\n\tdata := &CaptchaInfo{}\n\tbuf := bytes.NewBuffer(value)\n\tdecoder := enc.NewDecoder(buf)\n\tdecoder.Decode(data)\n\n\treturn data\n}\n"
  },
  {
    "path": "mcstore_test.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestMCStore(t *testing.T) {\n\tmemcacheServers := []string{\"127.0.0.1:11211\"}\n\tstore := CreateMCStore(100*time.Second, memcacheServers) //100 s\n\n\tcaptcha := new(CaptchaInfo)\n\tcaptcha.Text = \"hello\"\n\tcaptcha.CreateTime = time.Now()\n\n\t//test add and get\n\tkey := store.Add(captcha)\n\tretV := store.Get(key)\n\n\tif retV.Text != captcha.Text {\n\t\tt.Errorf(\"not equal,retV:%#v,captcha:%#v\", retV, captcha)\n\t}\n\n\tretV.Text = \"world\"\n\tstore.Update(key, retV)\n\tretV = store.Get(key)\n\n\tif retV.Text != \"world\" {\n\t\tt.Errorf(\"update not equal,retV:%#v,captcha:%#v\", retV, captcha)\n\t}\n\n\tt.Logf(\"TestMCStore:get from mc:%#v\", retV)\n\n\t//test del\n\tstore.Del(key)\n\tretV = store.Get(key)\n\tif nil != retV {\n\t\tt.Errorf(\"not del\")\n\t}\n\n}\n"
  },
  {
    "path": "randhelper.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"math/rand\"\n\t\"time\"\n)\n\nfunc init() {\n\trand.Seed(time.Now().UnixNano())\n}\n\n// rnd returns a non-crypto pseudorandom int in range [from, to].\nfunc rnd(from, to int) int {\n\treturn rand.Intn(to+1-from) + from\n}\n\n// rndf returns a non-crypto pseudorandom float64 in range [from, to].\nfunc rndf(from, to float64) float64 {\n\treturn (to-from)*rand.Float64() + from\n}\n\nfunc randStr(length int) string {\n\trst := \"\"\n\tcharset := \"abcdefghijklmnopqrstuvwxyz1234567890\"\n\tfor i := 0; i < length; i++ {\n\t\trst = rst + string(charset[rnd(0, len(charset)-1)])\n\t}\n\treturn rst\n}\n"
  },
  {
    "path": "redisstore.go",
    "content": "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\"time\"\n\n\t\"gopkg.in/redis.v2\"\n)\n\nfunc init() {\n\tRegisterStore(storeName, CreateCaptchaRedisStore)\n}\n\nconst (\n\tcaptchaKeyFormat = \"captcha:text:%s;rand:%s;time:%x;\"\n\tstoreName        = \"redis\"\n)\n\ntype CaptchaRedisStore struct {\n\tlifeTime time.Duration\n\tstg      *redis.Client\n}\n\nfunc CreateCaptchaRedisStore(config *StoreConfig) (StoreInterface, error) {\n\tlifeTime := config.LifeTime\n\tif config.Servers == nil || len(config.Servers) == 0 {\n\t\treturn nil, fmt.Errorf(\"servers must not be empty\")\n\t}\n\n\tfullAddr := strings.TrimPrefix(config.Servers[0], \"redis://\")\n\tpieces := strings.SplitN(fullAddr, \"/\", 2)\n\n\tdb := 0\n\taddr := pieces[0]\n\tif len(pieces) == 2 {\n\t\tdb, _ = strconv.Atoi(pieces[1])\n\t}\n\n\topt := redis.Options{}\n\topt.Addr = addr\n\topt.DB = int64(db)\n\topt.PoolSize = 0\n\tstg := redis.NewTCPClient(&opt)\n\n\treturn &CaptchaRedisStore{lifeTime, stg}, nil\n}\n\nfunc (this *CaptchaRedisStore) Get(key string) *CaptchaInfo {\n\ts, err := this.stg.Get(key).Result()\n\tif err != nil {\n\t\tlog.Printf(\"get key in redis error:%s\", err)\n\t\treturn nil\n\t}\n\n\tcaptcha := CaptchaInfo{}\n\tthis.decodeCaptachInfo([]byte(s), &captcha)\n\treturn &captcha\n}\n\nfunc (this *CaptchaRedisStore) Add(captcha *CaptchaInfo) string {\n\tkey := fmt.Sprintf(captchaKeyFormat, captcha.Text, randStr(20), captcha.CreateTime.Unix())\n\tkey = hex.EncodeToString(md5.New().Sum([]byte(key)))\n\tkey = key[:32]\n\n\tval, err := this.encodeCaptchaInfo(captcha)\n\tif err == nil {\n\t\tif seterr := this.stg.SetEx(key, this.lifeTime, string(val)); seterr != nil {\n\t\t\tlog.Printf(\"add key in redis error:%s\", seterr)\n\t\t}\n\t}\n\treturn key\n}\n\nfunc (this *CaptchaRedisStore) Update(key string, captcha *CaptchaInfo) bool {\n\tval, err := this.encodeCaptchaInfo(captcha)\n\tif err == nil {\n\t\tif seterr := this.stg.Set(key, string(val)); seterr != nil {\n\t\t\tlog.Printf(\"set key in redis error:%s\", seterr)\n\t\t\treturn false\n\t\t} else {\n\t\t\treturn true\n\t\t}\n\t} else {\n\t\treturn false\n\t}\n\n}\n\nfunc (this *CaptchaRedisStore) Del(key string) {\n\tthis.stg.Del(key)\n}\n\nfunc (this *CaptchaRedisStore) Destroy() {\n\n}\n\nfunc (this *CaptchaRedisStore) OnConstruct() {\n\n}\n\nfunc (this *CaptchaRedisStore) OnDestruct() {\n\n}\n\nfunc (this *CaptchaRedisStore) encodeCaptchaInfo(captcha *CaptchaInfo) ([]byte, error) {\n\tbuf := new(bytes.Buffer)\n\tencoder := gob.NewEncoder(buf)\n\terr := encoder.Encode(captcha)\n\tif err != nil {\n\t\tlog.Printf(\"encode captcha info error:%s\", err)\n\t\treturn nil, err\n\t}\n\treturn buf.Bytes(), nil\n}\n\nfunc (this *CaptchaRedisStore) decodeCaptachInfo(b []byte, ret *CaptchaInfo) {\n\tbuf := bytes.NewBuffer(b)\n\tdecoder := gob.NewDecoder(buf)\n\tif err := decoder.Decode(ret); err != nil {\n\t\tlog.Printf(\"decode captcha info error:%s\", err)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "redisstore_test.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestRedisStore(t *testing.T) {\n\tstoreConfig := &StoreConfig{}\n\tstoreConfig.CaptchaConfig.LifeTime = time.Second * 100\n\tstoreConfig.Engine = \"redis\"\n\tstoreConfig.Servers = []string{\"127.0.0.1:6379\"}\n\tstore, _ := CreateCaptchaRedisStore(storeConfig)\n\n\tcaptcha := new(CaptchaInfo)\n\tcaptcha.Text = \"hello\"\n\tcaptcha.CreateTime = time.Now()\n\n\t//test add and get\n\tkey := store.Add(captcha)\n\tretV := store.Get(key)\n\n\tif retV.Text != captcha.Text {\n\t\tt.Errorf(\"not equal,retV:%#v,captcha:%#v\", retV, captcha)\n\t}\n\n\tretV.Text = \"world\"\n\tstore.Update(key, retV)\n\tretV = store.Get(key)\n\tif retV.Text != \"world\" {\n\t\tt.Errorf(\"update not equal,retV:%#v,captcha:%#v\", retV, captcha)\n\t}\n\n\tt.Logf(\"TestMCStore:get from redis:%#v\", retV)\n\n\t//test del\n\tstore.Del(key)\n\tretV = store.Get(key)\n\tif nil != retV {\n\t\tt.Errorf(\"not del\")\n\t}\n\n}\n"
  },
  {
    "path": "storeinterface.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport ()\n\nvar storeCreators = map[string]func(*StoreConfig) (StoreInterface, error){}\n\n//StoreInterface is the interface of store\ntype StoreInterface interface {\n\tGet(key string) *CaptchaInfo\n\tAdd(captcha *CaptchaInfo) string\n\tUpdate(key string, captcha *CaptchaInfo) bool\n\tDel(key string)\n\tDestroy()\n\tOnConstruct()\n\tOnDestruct()\n}\n\nfunc RegisterStore(name string, f func(*StoreConfig) (StoreInterface, error)) bool {\n\tif _, has := storeCreators[name]; has {\n\t\treturn false\n\t}\n\tstoreCreators[name] = f\n\treturn true\n}\n"
  },
  {
    "path": "wordmanager.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"encoding/csv\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"unicode/utf8\"\n)\n\n//WordManager is a captcha word manage tool\ntype WordManager struct {\n\twords            []string\n\tisDataSingleChar bool\n\tisValid          bool\n}\n\n//CreateWordManagerFromDataFile will create a entity from a dictionary file\nfunc CreateWordManagerFromDataFile(filename string) (*WordManager, error) {\n\tmgr := &WordManager{}\n\tmgr.words = []string{}\n\tmgr.isValid = false\n\n\tf, err := os.Open(filename)\n\tif nil != err {\n\t\treturn mgr, err\n\t}\n\tdefer f.Close()\n\treader := csv.NewReader(f)\n\tmgr.isDataSingleChar = true\n\tfor {\n\t\trecord, err := reader.Read()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t} else if nil != err {\n\t\t\treturn mgr, err\n\t\t}\n\n\t\tif 1 < len([]rune(record[0])) {\n\t\t\tmgr.isDataSingleChar = false\n\t\t}\n\n\t\tmgr.words = append(mgr.words, strings.TrimSpace(record[0]))\n\t}\n\n\tmgr.isValid = true\n\treturn mgr, nil\n}\n\n//Get a specifical length word\nfunc (mgr *WordManager) Get(length int) (string, error) {\n\tvar retErr error\n\trst := \"\"\n\tif mgr.isValid {\n\t\tif true == mgr.isDataSingleChar {\n\t\t\tif len(mgr.words) < length {\n\t\t\t\treturn \"\", errors.New(\"dict words count is less than your length\")\n\t\t\t}\n\n\t\t\tfor {\n\t\t\t\tline := mgr.getLine()\n\t\t\t\tif false == strings.ContainsRune(rst, []rune(line)[0]) {\n\t\t\t\t\trst = rst + line\n\t\t\t\t}\n\t\t\t\tif utf8.RuneCountInString(rst) >= length {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\n\t\t\trstRune := []rune(rst)\n\t\t\trst = string(rstRune[0:length])\n\t\t} else {\n\t\t\trst = mgr.getLine()\n\t\t}\n\t} else {\n\t\tretErr = errors.New(\"WordManager is invalid\")\n\t}\n\n\treturn rst, retErr\n}\n\nfunc (mgr *WordManager) SetWords(words []string) {\n\tmgr.words = words\n\tmgr.isValid = len(words) > 0\n\tmgr.isDataSingleChar = true\n\tfor _, s := range words {\n\t\tif len([]rune(s)) > 1 {\n\t\t\tmgr.isDataSingleChar = false\n\t\t}\n\t}\n}\n\nfunc (mgr *WordManager) getLine() string {\n\tmaxIndex := len(mgr.words) - 1\n\trstIndex := rnd(0, maxIndex)\n\trst := mgr.words[rstIndex]\n\n\treturn rst\n}\n"
  },
  {
    "path": "wordmanager_test.go",
    "content": "// Copyright 2013 hanguofeng. All rights reserved.\n// Use of this source code is governed by a MIT-style\n// license that can be found in the LICENSE file.\n\npackage gocaptcha\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestWordManager(t *testing.T) {\n\tpwd, err := os.Getwd()\n\tif (nil != err) || \"\" == pwd {\n\t\treturn\n\t}\n\tpath := pwd + \"/data/\"\n\tsingleCharDict := []string{\"cn_char\", \"en_char\"}\n\tphrasesDict := []string{\"cn_phrases\", \"en_phrases\"}\n\tlength := 6\n\n\tfor _, f := range singleCharDict {\n\t\tmgr, err := CreateWordManagerFromDataFile(path + f)\n\t\ts, err := mgr.Get(length)\n\t\tif nil != err {\n\t\t\tt.Errorf(err.Error())\n\t\t} else if length != len([]rune(s)) {\n\t\t\tt.Errorf(\"get no equals length:\" + f)\n\t\t}\n\t}\n\tfor _, f := range phrasesDict {\n\t\tmgr, err := CreateWordManagerFromDataFile(path + f)\n\t\ts, err := mgr.Get(length)\n\t\tif nil != err {\n\t\t\tt.Errorf(err.Error())\n\t\t} else if 0 >= len([]rune(s)) {\n\t\t\tt.Errorf(\"not get:\" + f)\n\t\t}\n\t}\n\n}\n"
  }
]