You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. // Package captcha a middleware that provides captcha service for Macaron.
  5. package captcha
  6. import (
  7. "fmt"
  8. "html/template"
  9. "net/http"
  10. "path"
  11. "strings"
  12. "github.com/Unknwon/macaron"
  13. "github.com/gogits/cache"
  14. "github.com/gogits/gogs/modules/base"
  15. "github.com/gogits/gogs/modules/log"
  16. )
  17. var (
  18. defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
  19. )
  20. const (
  21. // default captcha attributes
  22. challengeNums = 6
  23. expiration = 600
  24. fieldIdName = "captcha_id"
  25. fieldCaptchaName = "captcha"
  26. cachePrefix = "captcha_"
  27. defaultURLPrefix = "/captcha/"
  28. )
  29. // Captcha struct
  30. type Captcha struct {
  31. store cache.Cache
  32. // url prefix for captcha image
  33. URLPrefix string
  34. // specify captcha id input field name
  35. FieldIdName string
  36. // specify captcha result input field name
  37. FieldCaptchaName string
  38. // captcha image width and height
  39. StdWidth int
  40. StdHeight int
  41. // captcha chars nums
  42. ChallengeNums int
  43. // captcha expiration seconds
  44. Expiration int64
  45. // cache key prefix
  46. CachePrefix string
  47. }
  48. // generate key string
  49. func (c *Captcha) key(id string) string {
  50. return c.CachePrefix + id
  51. }
  52. // generate rand chars with default chars
  53. func (c *Captcha) genRandChars() []byte {
  54. return base.RandomCreateBytes(c.ChallengeNums, defaultChars...)
  55. }
  56. // beego filter handler for serve captcha image
  57. func (c *Captcha) Handler(ctx *macaron.Context) {
  58. var chars []byte
  59. id := path.Base(ctx.Req.RequestURI)
  60. if i := strings.Index(id, "."); i != -1 {
  61. id = id[:i]
  62. }
  63. key := c.key(id)
  64. if v, ok := c.store.Get(key).([]byte); ok {
  65. chars = v
  66. } else {
  67. ctx.Status(404)
  68. ctx.Write([]byte("captcha not found"))
  69. return
  70. }
  71. // reload captcha
  72. if len(ctx.Query("reload")) > 0 {
  73. chars = c.genRandChars()
  74. if err := c.store.Put(key, chars, c.Expiration); err != nil {
  75. ctx.Status(500)
  76. ctx.Write([]byte("captcha reload error"))
  77. log.Error(4, "Reload Create Captcha Error: %v", err)
  78. return
  79. }
  80. }
  81. img := NewImage(chars, c.StdWidth, c.StdHeight)
  82. if _, err := img.WriteTo(ctx.RW()); err != nil {
  83. log.Error(4, "Write Captcha Image Error: %v", err)
  84. }
  85. }
  86. // tempalte func for output html
  87. func (c *Captcha) CreateCaptchaHtml() template.HTML {
  88. value, err := c.CreateCaptcha()
  89. if err != nil {
  90. log.Error(4, "Create Captcha Error: %v", err)
  91. return ""
  92. }
  93. // create html
  94. return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`+
  95. `<a class="captcha" href="javascript:">`+
  96. `<img onclick="this.src=('%s%s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%s%s.png">`+
  97. `</a>`, c.FieldIdName, value, c.URLPrefix, value, c.URLPrefix, value))
  98. }
  99. // create a new captcha id
  100. func (c *Captcha) CreateCaptcha() (string, error) {
  101. // generate captcha id
  102. id := string(base.RandomCreateBytes(15))
  103. // get the captcha chars
  104. chars := c.genRandChars()
  105. // save to store
  106. if err := c.store.Put(c.key(id), chars, c.Expiration); err != nil {
  107. return "", err
  108. }
  109. return id, nil
  110. }
  111. // verify from a request
  112. func (c *Captcha) VerifyReq(req *http.Request) bool {
  113. req.ParseForm()
  114. return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName))
  115. }
  116. // direct verify id and challenge string
  117. func (c *Captcha) Verify(id string, challenge string) (success bool) {
  118. if len(challenge) == 0 || len(id) == 0 {
  119. return
  120. }
  121. var chars []byte
  122. key := c.key(id)
  123. if v, ok := c.store.Get(key).([]byte); ok && len(v) == len(challenge) {
  124. chars = v
  125. } else {
  126. return
  127. }
  128. defer func() {
  129. // finally remove it
  130. c.store.Delete(key)
  131. }()
  132. // verify challenge
  133. for i, c := range chars {
  134. if c != challenge[i]-48 {
  135. return
  136. }
  137. }
  138. return true
  139. }
  140. // create a new captcha.Captcha
  141. func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha {
  142. cpt := &Captcha{}
  143. cpt.store = store
  144. cpt.FieldIdName = fieldIdName
  145. cpt.FieldCaptchaName = fieldCaptchaName
  146. cpt.ChallengeNums = challengeNums
  147. cpt.Expiration = expiration
  148. cpt.CachePrefix = cachePrefix
  149. cpt.StdWidth = stdWidth
  150. cpt.StdHeight = stdHeight
  151. if len(urlPrefix) == 0 {
  152. urlPrefix = defaultURLPrefix
  153. }
  154. if urlPrefix[len(urlPrefix)-1] != '/' {
  155. urlPrefix += "/"
  156. }
  157. cpt.URLPrefix = urlPrefix
  158. base.TemplateFuncs["CreateCaptcha"] = cpt.CreateCaptchaHtml
  159. return cpt
  160. }