kuvia2/vendor/github.com/flamego/captcha/captcha.go
2022-01-15 00:09:03 +01:00

162 lines
3.9 KiB
Go

// Copyright 2021 Flamego. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package captcha
import (
"fmt"
"html/template"
"image/color"
"net/http"
"reflect"
"strings"
"github.com/flamego/flamego"
"github.com/flamego/flamego/inject"
"github.com/flamego/session"
"github.com/flamego/captcha/internal/captchautil"
)
// Captcha represents a captcha service and is used to validate text in captcha
// images.
type Captcha interface {
// HTML returns the HTML content to display and refresh captcha images.
HTML() template.HTML
// ValidText validates the passed text against the secret text.
ValidText(t string) bool
}
var _ Captcha = (*captcha)(nil)
type captcha struct {
session session.Session
urlPrefix string
}
func (c *captcha) HTML() template.HTML {
return template.HTML(
fmt.Sprintf(`
<a class="captcha" href="javascript:" tabindex="-1">
<img onclick="this.src=('%[1]simage.jpeg?refresh=true')" src="%[1]simage.jpeg">
</a>`,
c.urlPrefix),
)
}
const textKey = "flamego::captcha::text"
func (c *captcha) ValidText(t string) bool {
defer c.session.Delete(textKey)
want, ok := c.session.Get(textKey).(string)
if !ok {
return false
}
return strings.EqualFold(want, t)
}
// Options contains options for the captcha.Captchaer middleware.
type Options struct {
// URLPrefix is the URL path prefix for serving captcha images. Default is
// "/.captcha/".
URLPrefix string
// TextLength is the length of text to be generated in captcha images. Default
// is 4.
TextLength int
// Width is the image width of captcha. Default is 240.
Width int
// Height is the image height of captcha. Default is 80.
Height int
// DPI is the image DPI of captcha. Default is 72.
DPI int
}
var _ inject.FastInvoker = (*captchaInvoker)(nil)
// captchaInvoker is an inject.FastInvoker implementation of
// `func(flamego.Context, session.Session)`.
type captchaInvoker func(flamego.Context, session.Session)
func (invoke captchaInvoker) Invoke(args []interface{}) ([]reflect.Value, error) {
invoke(args[0].(flamego.Context), args[1].(session.Session))
return nil, nil
}
// Captchaer returns a middleware handler that injects captcha.Captcha into the
// request context, which is used for generating and validating text in captcha
// images.
func Captchaer(opts ...Options) flamego.Handler {
var opt Options
if len(opts) > 0 {
opt = opts[0]
}
parseOptions := func(opts Options) Options {
if opts.URLPrefix == "" {
opts.URLPrefix = "/.captcha/"
}
if opts.TextLength <= 0 {
opts.TextLength = 4
}
if opts.Width <= 0 {
opts.Width = 240
}
if opts.Height <= 0 {
opts.Height = 80
}
if opts.DPI <= 0 {
opts.DPI = 72
}
return opts
}
opt = parseOptions(opt)
return captchaInvoker(func(c flamego.Context, s session.Session) {
cpt := &captcha{
session: s,
urlPrefix: opt.URLPrefix,
}
c.MapTo(cpt, (*Captcha)(nil))
if !strings.HasPrefix(c.Request().URL.Path, opt.URLPrefix) {
return
}
text, ok := s.Get(textKey).(string)
if !ok || c.QueryBool("refresh") {
var err error
text, err = captchautil.RandomText(opt.TextLength)
if err != nil {
panic("captcha: generate random text: " + err.Error())
}
s.Set(textKey, text)
}
img, err := captchautil.CreateImage(
opt.Width, opt.Height, opt.DPI, captchautil.RandomLightColor(),
captchautil.Noise(captchautil.ComplexityLow),
captchautil.TextNoise(captchautil.ComplexityLow),
captchautil.Text(text),
captchautil.Curve(),
captchautil.Border(
color.RGBA{R: 170, G: 170, B: 170, A: 255},
),
)
if err != nil {
panic("captcha: create image: " + err.Error())
}
c.ResponseWriter().Header().Set("Cache-Control", "no-store")
c.ResponseWriter().WriteHeader(http.StatusOK)
err = img.Encode(c.ResponseWriter())
if err != nil {
panic("captcha: encode image: " + err.Error())
}
})
}