captcha.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // Copyright 2013 Beego Authors
  2. // Copyright 2014 The Macaron Authors
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  5. // not use this file except in compliance with the License. You may obtain
  6. // a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. // License for the specific language governing permissions and limitations
  14. // under the License.
  15. // Package captcha a middleware that provides captcha service for Macaron.
  16. package captcha
  17. import (
  18. "fmt"
  19. "html/template"
  20. "path"
  21. "strings"
  22. "github.com/Unknwon/com"
  23. "github.com/go-macaron/cache"
  24. "gopkg.in/macaron.v1"
  25. )
  26. const _VERSION = "0.1.0"
  27. func Version() string {
  28. return _VERSION
  29. }
  30. var (
  31. defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
  32. )
  33. // Captcha represents a captcha service.
  34. type Captcha struct {
  35. store cache.Cache
  36. SubURL string
  37. URLPrefix string
  38. FieldIdName string
  39. FieldCaptchaName string
  40. StdWidth int
  41. StdHeight int
  42. ChallengeNums int
  43. Expiration int64
  44. CachePrefix string
  45. }
  46. // generate key string
  47. func (c *Captcha) key(id string) string {
  48. return c.CachePrefix + id
  49. }
  50. // generate rand chars with default chars
  51. func (c *Captcha) genRandChars() string {
  52. return string(com.RandomCreateBytes(c.ChallengeNums, defaultChars...))
  53. }
  54. // tempalte func for output html
  55. func (c *Captcha) CreateHtml() template.HTML {
  56. value, err := c.CreateCaptcha()
  57. if err != nil {
  58. panic(fmt.Errorf("fail to create captcha: %v", err))
  59. }
  60. return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">
  61. <a class="captcha" href="javascript:" tabindex="-1">
  62. <img onclick="this.src=('%s%s%s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%s%s%s.png">
  63. </a>`, c.FieldIdName, value, c.SubURL, c.URLPrefix, value, c.SubURL, c.URLPrefix, value))
  64. }
  65. // create a new captcha id
  66. func (c *Captcha) CreateCaptcha() (string, error) {
  67. id := string(com.RandomCreateBytes(15))
  68. if err := c.store.Put(c.key(id), c.genRandChars(), c.Expiration); err != nil {
  69. return "", err
  70. }
  71. return id, nil
  72. }
  73. // verify from a request
  74. func (c *Captcha) VerifyReq(req macaron.Request) bool {
  75. req.ParseForm()
  76. return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName))
  77. }
  78. // direct verify id and challenge string
  79. func (c *Captcha) Verify(id string, challenge string) bool {
  80. if len(challenge) == 0 || len(id) == 0 {
  81. return false
  82. }
  83. var chars string
  84. key := c.key(id)
  85. if v, ok := c.store.Get(key).(string); ok {
  86. chars = v
  87. } else {
  88. return false
  89. }
  90. defer c.store.Delete(key)
  91. if len(chars) != len(challenge) {
  92. return false
  93. }
  94. // verify challenge
  95. for i, c := range []byte(chars) {
  96. if c != challenge[i]-48 {
  97. return false
  98. }
  99. }
  100. return true
  101. }
  102. type Options struct {
  103. // Suburl path. Default is empty.
  104. SubURL string
  105. // URL prefix of getting captcha pictures. Default is "/captcha/".
  106. URLPrefix string
  107. // Hidden input element ID. Default is "captcha_id".
  108. FieldIdName string
  109. // User input value element name in request form. Default is "captcha".
  110. FieldCaptchaName string
  111. // Challenge number. Default is 6.
  112. ChallengeNums int
  113. // Captcha image width. Default is 240.
  114. Width int
  115. // Captcha image height. Default is 80.
  116. Height int
  117. // Captcha expiration time in seconds. Default is 600.
  118. Expiration int64
  119. // Cache key prefix captcha characters. Default is "captcha_".
  120. CachePrefix string
  121. }
  122. func prepareOptions(options []Options) Options {
  123. var opt Options
  124. if len(options) > 0 {
  125. opt = options[0]
  126. }
  127. opt.SubURL = strings.TrimSuffix(opt.SubURL, "/")
  128. // Defaults.
  129. if len(opt.URLPrefix) == 0 {
  130. opt.URLPrefix = "/captcha/"
  131. } else if opt.URLPrefix[len(opt.URLPrefix)-1] != '/' {
  132. opt.URLPrefix += "/"
  133. }
  134. if len(opt.FieldIdName) == 0 {
  135. opt.FieldIdName = "captcha_id"
  136. }
  137. if len(opt.FieldCaptchaName) == 0 {
  138. opt.FieldCaptchaName = "captcha"
  139. }
  140. if opt.ChallengeNums == 0 {
  141. opt.ChallengeNums = 6
  142. }
  143. if opt.Width == 0 {
  144. opt.Width = stdWidth
  145. }
  146. if opt.Height == 0 {
  147. opt.Height = stdHeight
  148. }
  149. if opt.Expiration == 0 {
  150. opt.Expiration = 600
  151. }
  152. if len(opt.CachePrefix) == 0 {
  153. opt.CachePrefix = "captcha_"
  154. }
  155. return opt
  156. }
  157. // NewCaptcha initializes and returns a captcha with given options.
  158. func NewCaptcha(opt Options) *Captcha {
  159. return &Captcha{
  160. SubURL: opt.SubURL,
  161. URLPrefix: opt.URLPrefix,
  162. FieldIdName: opt.FieldIdName,
  163. FieldCaptchaName: opt.FieldCaptchaName,
  164. StdWidth: opt.Width,
  165. StdHeight: opt.Height,
  166. ChallengeNums: opt.ChallengeNums,
  167. Expiration: opt.Expiration,
  168. CachePrefix: opt.CachePrefix,
  169. }
  170. }
  171. // Captchaer is a middleware that maps a captcha.Captcha service into the Macaron handler chain.
  172. // An single variadic captcha.Options struct can be optionally provided to configure.
  173. // This should be register after cache.Cacher.
  174. func Captchaer(options ...Options) macaron.Handler {
  175. return func(ctx *macaron.Context, cache cache.Cache) {
  176. cpt := NewCaptcha(prepareOptions(options))
  177. cpt.store = cache
  178. if strings.HasPrefix(ctx.Req.URL.Path, cpt.URLPrefix) {
  179. var chars string
  180. id := path.Base(ctx.Req.URL.Path)
  181. if i := strings.Index(id, "."); i > -1 {
  182. id = id[:i]
  183. }
  184. key := cpt.key(id)
  185. // Reload captcha.
  186. if len(ctx.Query("reload")) > 0 {
  187. chars = cpt.genRandChars()
  188. if err := cpt.store.Put(key, chars, cpt.Expiration); err != nil {
  189. ctx.Status(500)
  190. ctx.Write([]byte("captcha reload error"))
  191. panic(fmt.Errorf("reload captcha: %v", err))
  192. }
  193. } else {
  194. if v, ok := cpt.store.Get(key).(string); ok {
  195. chars = v
  196. } else {
  197. ctx.Status(404)
  198. ctx.Write([]byte("captcha not found"))
  199. return
  200. }
  201. }
  202. if _, err := NewImage([]byte(chars), cpt.StdWidth, cpt.StdHeight).WriteTo(ctx.Resp); err != nil {
  203. panic(fmt.Errorf("write captcha: %v", err))
  204. }
  205. return
  206. }
  207. ctx.Data["Captcha"] = cpt
  208. ctx.Map(cpt)
  209. }
  210. }