1// Copyright 2014 beego Author. All Rights Reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package captcha implements generation and verification of image CAPTCHAs. 16// an example for use captcha 17// 18// ``` 19// package controllers 20// 21// import ( 22// "github.com/astaxie/beego" 23// "github.com/astaxie/beego/cache" 24// "github.com/astaxie/beego/utils/captcha" 25// ) 26// 27// var cpt *captcha.Captcha 28// 29// func init() { 30// // use beego cache system store the captcha data 31// store := cache.NewMemoryCache() 32// cpt = captcha.NewWithFilter("/captcha/", store) 33// } 34// 35// type MainController struct { 36// beego.Controller 37// } 38// 39// func (this *MainController) Get() { 40// this.TplName = "index.tpl" 41// } 42// 43// func (this *MainController) Post() { 44// this.TplName = "index.tpl" 45// 46// this.Data["Success"] = cpt.VerifyReq(this.Ctx.Request) 47// } 48// ``` 49// 50// template usage 51// 52// ``` 53// {{.Success}} 54// <form action="/" method="post"> 55// {{create_captcha}} 56// <input name="captcha" type="text"> 57// </form> 58// ``` 59package captcha 60 61import ( 62 "fmt" 63 "html/template" 64 "net/http" 65 "path" 66 "strings" 67 "time" 68 69 "github.com/astaxie/beego" 70 "github.com/astaxie/beego/cache" 71 "github.com/astaxie/beego/context" 72 "github.com/astaxie/beego/logs" 73 "github.com/astaxie/beego/utils" 74) 75 76var ( 77 defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 78) 79 80const ( 81 // default captcha attributes 82 challengeNums = 6 83 expiration = 600 * time.Second 84 fieldIDName = "captcha_id" 85 fieldCaptchaName = "captcha" 86 cachePrefix = "captcha_" 87 defaultURLPrefix = "/captcha/" 88) 89 90// Captcha struct 91type Captcha struct { 92 // beego cache store 93 store cache.Cache 94 95 // url prefix for captcha image 96 URLPrefix string 97 98 // specify captcha id input field name 99 FieldIDName string 100 // specify captcha result input field name 101 FieldCaptchaName string 102 103 // captcha image width and height 104 StdWidth int 105 StdHeight int 106 107 // captcha chars nums 108 ChallengeNums int 109 110 // captcha expiration seconds 111 Expiration time.Duration 112 113 // cache key prefix 114 CachePrefix string 115} 116 117// generate key string 118func (c *Captcha) key(id string) string { 119 return c.CachePrefix + id 120} 121 122// generate rand chars with default chars 123func (c *Captcha) genRandChars() []byte { 124 return utils.RandomCreateBytes(c.ChallengeNums, defaultChars...) 125} 126 127// Handler beego filter handler for serve captcha image 128func (c *Captcha) Handler(ctx *context.Context) { 129 var chars []byte 130 131 id := path.Base(ctx.Request.RequestURI) 132 if i := strings.Index(id, "."); i != -1 { 133 id = id[:i] 134 } 135 136 key := c.key(id) 137 138 if len(ctx.Input.Query("reload")) > 0 { 139 chars = c.genRandChars() 140 if err := c.store.Put(key, chars, c.Expiration); err != nil { 141 ctx.Output.SetStatus(500) 142 ctx.WriteString("captcha reload error") 143 logs.Error("Reload Create Captcha Error:", err) 144 return 145 } 146 } else { 147 if v, ok := c.store.Get(key).([]byte); ok { 148 chars = v 149 } else { 150 ctx.Output.SetStatus(404) 151 ctx.WriteString("captcha not found") 152 return 153 } 154 } 155 156 img := NewImage(chars, c.StdWidth, c.StdHeight) 157 if _, err := img.WriteTo(ctx.ResponseWriter); err != nil { 158 logs.Error("Write Captcha Image Error:", err) 159 } 160} 161 162// CreateCaptchaHTML template func for output html 163func (c *Captcha) CreateCaptchaHTML() template.HTML { 164 value, err := c.CreateCaptcha() 165 if err != nil { 166 logs.Error("Create Captcha Error:", err) 167 return "" 168 } 169 170 // create html 171 return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`+ 172 `<a class="captcha" href="javascript:">`+ 173 `<img onclick="this.src=('%s%s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%s%s.png">`+ 174 `</a>`, c.FieldIDName, value, c.URLPrefix, value, c.URLPrefix, value)) 175} 176 177// CreateCaptcha create a new captcha id 178func (c *Captcha) CreateCaptcha() (string, error) { 179 // generate captcha id 180 id := string(utils.RandomCreateBytes(15)) 181 182 // get the captcha chars 183 chars := c.genRandChars() 184 185 // save to store 186 if err := c.store.Put(c.key(id), chars, c.Expiration); err != nil { 187 return "", err 188 } 189 190 return id, nil 191} 192 193// VerifyReq verify from a request 194func (c *Captcha) VerifyReq(req *http.Request) bool { 195 req.ParseForm() 196 return c.Verify(req.Form.Get(c.FieldIDName), req.Form.Get(c.FieldCaptchaName)) 197} 198 199// Verify direct verify id and challenge string 200func (c *Captcha) Verify(id string, challenge string) (success bool) { 201 if len(challenge) == 0 || len(id) == 0 { 202 return 203 } 204 205 var chars []byte 206 207 key := c.key(id) 208 209 if v, ok := c.store.Get(key).([]byte); ok { 210 chars = v 211 } else { 212 return 213 } 214 215 defer func() { 216 // finally remove it 217 c.store.Delete(key) 218 }() 219 220 if len(chars) != len(challenge) { 221 return 222 } 223 // verify challenge 224 for i, c := range chars { 225 if c != challenge[i]-48 { 226 return 227 } 228 } 229 230 return true 231} 232 233// NewCaptcha create a new captcha.Captcha 234func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha { 235 cpt := &Captcha{} 236 cpt.store = store 237 cpt.FieldIDName = fieldIDName 238 cpt.FieldCaptchaName = fieldCaptchaName 239 cpt.ChallengeNums = challengeNums 240 cpt.Expiration = expiration 241 cpt.CachePrefix = cachePrefix 242 cpt.StdWidth = stdWidth 243 cpt.StdHeight = stdHeight 244 245 if len(urlPrefix) == 0 { 246 urlPrefix = defaultURLPrefix 247 } 248 249 if urlPrefix[len(urlPrefix)-1] != '/' { 250 urlPrefix += "/" 251 } 252 253 cpt.URLPrefix = urlPrefix 254 255 return cpt 256} 257 258// NewWithFilter create a new captcha.Captcha and auto AddFilter for serve captacha image 259// and add a template func for output html 260func NewWithFilter(urlPrefix string, store cache.Cache) *Captcha { 261 cpt := NewCaptcha(urlPrefix, store) 262 263 // create filter for serve captcha image 264 beego.InsertFilter(cpt.URLPrefix+"*", beego.BeforeRouter, cpt.Handler) 265 266 // add to template func map 267 beego.AddFuncMap("create_captcha", cpt.CreateCaptchaHTML) 268 269 return cpt 270} 271