Stuff
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Kekskurse 2021-11-24 03:27:07 +01:00
parent e887a5e9b8
commit 508f7bf4b7
Signed by: kekskurse
GPG Key ID: 728ACCB59341E7E4
23 changed files with 11717 additions and 41 deletions

View File

@ -1,7 +1,7 @@
CREATE TABLE user (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
mail VARCHAR(250) NOT NULL UNIQUE,
mailValidationCode VARCHAR(25),
mailValidationCode VARCHAR(250),
mailValidate TINYINT(1) DEFAULT '0',
username VARCHAR(250) NOT NULL UNIQUE,
password VARCHAR(250) NOT NuLL,

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.17
require github.com/go-chi/chi/v5 v5.0.5
require (
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/go-chi/jwtauth/v5 v5.0.2
github.com/golang-migrate/migrate/v4 v4.15.1
github.com/jmoiron/sqlx v1.3.1

2
go.sum
View File

@ -284,6 +284,8 @@ github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjI
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=

View File

@ -10,6 +10,7 @@ import (
"github.com/go-chi/jwtauth/v5"
"github.com/jmoiron/sqlx"
gomail "gopkg.in/mail.v2"
"net/http"
)
// content holds our static web server content.
@ -24,7 +25,7 @@ func main() {
Templates: webserver,
Static: webserver,
Migrations: migrationFS,
Bootstrap: func(router chi.Router) {
Bootstrap: func(router chi.Router, template func(http.Handler) http.Handler) {
tokenAuth := jwtauth.New("HS256", []byte("secret"), nil)
db, err := sqlx.Open("mysql", "root:test@tcp(localhost:3306)/test?multiStatements=true")
if err != nil {
@ -38,7 +39,7 @@ func main() {
userconfig := user.NewUserConfig()
sample.Register(router)
user.Register(router, tokenAuth, uc, d, userconfig)
user.Register(router, template, tokenAuth, uc, d, userconfig)
},
}

View File

@ -1,7 +1,29 @@
{{if .msg}}
<div class="alert alert-warning" role="alert">
{{ .msg }}
</div>
{{end}}
<form method="post">
<label>Username</label>
<input type="text" name="username" class="form-control" placeholder="Username"></input>
<label>Password</label>
<input type="password" name="password" class="form-control" placeholder="Password"></input>
<div class="form-floating mb-3">
<input type="text" name="username" class="form-control" id="floatingInput" value="{{ .username }}" placeholder="Username"/>
<label for="floatingInput">Username</label>
</div>
<div class="form-floating mb-3">
<input type="password" name="password" class="form-control" id="floatingInput" placeholder="Password">
<label for="floatingInput">Password</label>
</div>
<input type="text" name="captchaid" style="display: none" class="form-control" id="floatingInput" value="{{ .captcha }}" />
<div class="row">
<div class="col-md-3">
<img src="/captcha/{{ .captcha }}.png" style="height: 56px;"></img>
</div>
<div class="col-md-9">
<div class="form-floating mb-3">
<input type="text" name="captcha" class="form-control" id="floatingInput" placeholder="Captcha code">
<label for="floatingInput">Captcha code</label>
</div>
</div>
</div>
<input type="submit" class="btn btn-success" value="Login"></input>
</form>

View File

@ -1,9 +1,34 @@
{{if .msg}}
<div class="alert alert-warning" role="alert">
{{ .msg }}
</div>
{{end}}
<form method="post">
<label>E-Mail</label>
<input type="text" name="email" class="form-control" placeholder="E-Mail"></input>
<label>Username</label>
<input type="text" name="username" class="form-control" placeholder="Username"></input>
<label>Password</label>
<input type="password" name="password" class="form-control" placeholder="Password"></input>
<div class="form-floating mb-3">
<input name="email" type="email" class="form-control" id="floatingInput" value="{{ .email }}" placeholder="name@example.com"/>
<label for="floatingInput">Email address</label>
</div>
<div class="form-floating mb-3">
<input type="text" name="username" class="form-control" id="floatingInput" value="{{ .username }}" placeholder="Username"/>
<label for="floatingInput">Username</label>
</div>
<div class="form-floating mb-3">
<input type="password" name="password" class="form-control" id="floatingInput" placeholder="Password">
<label for="floatingInput">Password</label>
</div>
<input type="text" name="captchaid" style="display: none" class="form-control" id="floatingInput" value="{{ .captcha }}" />
<div class="row">
<div class="col-md-3">
<img src="/captcha/{{ .captcha }}.png" style="height: 56px;"></img>
</div>
<div class="col-md-9">
<div class="form-floating mb-3">
<input type="text" name="captcha" class="form-control" id="floatingInput" placeholder="Captcha code">
<label for="floatingInput">Captcha code</label>
</div>
</div>
</div>
<input type="submit" class="btn btn-success" value="Register"></input>
</form>

View File

@ -3,10 +3,15 @@ package user
import (
"bytes"
"embed"
"encoding/base64"
"fmt"
"github.com/dchest/captcha"
"github.com/go-chi/chi/v5"
"github.com/go-chi/jwtauth/v5"
"github.com/rs/zerolog/log"
"github.com/unrolled/render"
"net/mail"
"regexp"
gomail "gopkg.in/mail.v2"
"io"
@ -42,13 +47,13 @@ func NewUserConfig() UserConfig {
m.SetHeader("Subject", "Activate your Account")
uc.RegisterMail = m
uc.URLValidationToken = "http://localhost/validate/%s"
uc.URLValidationToken = "http://localhost:3000/validate/%s"
return uc
}
func Register(router chi.Router, token *jwtauth.JWTAuth, userClient UserClient, dialer *gomail.Dialer, config UserConfig) {
func Register(router chi.Router, template func(http.Handler) http.Handler, token *jwtauth.JWTAuth, userClient UserClient, dialer *gomail.Dialer, config UserConfig) {
uc = userClient
userconfig = config
ren = render.New(render.Options{
@ -58,11 +63,17 @@ func Register(router chi.Router, token *jwtauth.JWTAuth, userClient UserClient,
},
})
router.Get("/login", loginForm)
router.Post("/login", login)
router.Get("/register", registerForm)
router.Post("/register", register)
router.Get("/logout", logout)
router.Group(func(r chi.Router) {
r.Use(template)
r.Get("/login", loginForm)
r.Post("/login", login)
r.Get("/register", registerForm)
r.Post("/register", register)
r.Get("/logout", logout)
})
router.Get("/captcha/{id}.png", captchaImage)
tokenAuth = token
@ -84,21 +95,32 @@ func Register(router chi.Router, token *jwtauth.JWTAuth, userClient UserClient,
}
func captchaImage(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
w.Header().Set("Content-Type", "image/png")
captcha.WriteImage(w, id, 400, 200)
}
func loginForm(w http.ResponseWriter, r *http.Request) {
ren.HTML(w, http.StatusOK, "login", nil)
ren.HTML(w, http.StatusOK, "login", map[string]string {"captcha": captcha.New()})
}
func registerForm(w http.ResponseWriter, r *http.Request) {
ren.HTML(w, http.StatusOK, "register", nil)
ren.HTML(w, http.StatusOK, "register", map[string]string {"captcha": captcha.New()})
}
func login(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
if captcha.VerifyString(r.FormValue("captchaid"), r.FormValue("captcha")) == false {
log.Debug().Str("captchaid", r.FormValue("captchaid")).Str("input", r.FormValue("captcha")).Interface("digits", []byte(r.FormValue("captcha"))).Msg("Captcha wrong")
ren.HTML(w, http.StatusOK, "login", map[string]string {"captcha": captcha.New(), "msg":"Captcha code was wrong", "username": r.FormValue("username")})
return
}
res, err := uc.login(r.FormValue("username"), r.FormValue("password"), false)
res, err := uc.login(r.FormValue("username"), r.FormValue("password"), true)
if err != nil {
w.Write([]byte(err.Error()))
ren.HTML(w, http.StatusOK, "login", map[string]string {"captcha": captcha.New(), "msg":err.Error(), "username": r.FormValue("username")})
return
}
if res {
@ -128,9 +150,31 @@ func logout(w http.ResponseWriter, r *http.Request) {
func register(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
if captcha.VerifyString(r.FormValue("captchaid"), r.FormValue("captcha")) == false {
log.Debug().Str("captchaid", r.FormValue("captchaid")).Str("input", r.FormValue("captcha")).Interface("digits", []byte(r.FormValue("captcha"))).Msg("Captcha wrong")
ren.HTML(w, http.StatusOK, "register", map[string]string {"captcha": captcha.New(), "msg":"Captcha code was wrong", "email": r.FormValue("email"), "username": r.FormValue("username")})
return
}
if validEmail(r.FormValue("email")) == false {
ren.HTML(w, http.StatusOK, "register", map[string]string {"captcha": captcha.New(), "msg":"E-Mail address is not valide.", "email": r.FormValue("email"), "username": r.FormValue("username")})
return
}
matched, _ := regexp.MatchString(`^[a-z0-9]{5,25}$`, r.FormValue("username"))
if matched == false {
ren.HTML(w, http.StatusOK, "register", map[string]string {"captcha": captcha.New(), "msg":"Username must be between 5 and 25 lowercase letters and numbers", "email": r.FormValue("email"), "username": r.FormValue("username")})
return
}
if len(r.FormValue("password")) < 8 {
ren.HTML(w, http.StatusOK, "register", map[string]string {"captcha": captcha.New(), "msg":"Your password must be at least 8 letters", "email": r.FormValue("email"), "username": r.FormValue("username")})
return
}
res, err := uc.register(r.FormValue("username"), r.FormValue("password"), r.FormValue("email"))
if err != nil || res == false {
w.Write([]byte("Registration Failed"))
ren.HTML(w, http.StatusOK, "register", map[string]string {"captcha": captcha.New(), "msg":"Registration Failed", "email": r.FormValue("email"), "username": r.FormValue("username")})
return
}
@ -144,7 +188,7 @@ func register(w http.ResponseWriter, r *http.Request) {
panic(err)
}
ren.HTML(rw, http.StatusOK, "token", map[string]string{"username": r.FormValue("username"), "url": fmt.Sprintf(userconfig.URLValidationToken, token)})
ren.HTML(rw, http.StatusOK, "token", map[string]string{"username": r.FormValue("username"), "url": fmt.Sprintf(userconfig.URLValidationToken, string(base64.StdEncoding.EncodeToString([]byte(r.FormValue("email"))) + "/" + token))})
content, err := io.ReadAll(rw.buf)
if err != nil {
@ -153,11 +197,14 @@ func register(w http.ResponseWriter, r *http.Request) {
mail := *userconfig.RegisterMail
mail.SetBody("text/html", string(content))
if err := d.DialAndSend(&mail); err != nil {
fmt.Println(err)
panic(err)
}
w.Write([]byte("registration successful, please check your mail"))
}
type LayoutMiddlewareResponseWriter struct {
@ -170,3 +217,9 @@ func (myrw *LayoutMiddlewareResponseWriter) Write(p []byte) (int, error) {
}
func validEmail(email string) bool {
_, err := mail.ParseAddress(email)
return err == nil
}

View File

@ -111,8 +111,23 @@ func (uc UserClientSql) login(username, password string, requiredMailValidation
}
func (uc UserClientSql) getMailValidationToken(email string, forceRecreate bool) (string, error) {
token := randomString(35)
token = fmt.Sprintf("%v/%v", base64.StdEncoding.EncodeToString([]byte(email)), token)
user := User{}
err := uc.db.Get(&user, "SELECT * FROM `user` WHERE `mail` = ?", email)
if err != nil {
return "", err
}
if user.MailValidationCode != nil && forceRecreate == false {
return *user.MailValidationCode, nil
}
token := randomString(25)
_, err = uc.db.NamedExec("UPDATE `user` SET `mailValidationCode` = :code WHERE `mail` = :mail LIMIT 1", map[string]interface{}{
"code": token,
"mail": email,
})
if err != nil {
return "", err
}
//token = fmt.Sprintf("%v/%v", base64.StdEncoding.EncodeToString([]byte(email)), token)
return token, nil
}

View File

@ -20,7 +20,7 @@ type WebPageConfig struct {
Templates embed.FS
Static embed.FS
Migrations embed.FS
Bootstrap func(router chi.Router)
Bootstrap func(router chi.Router, middlewares func(http.Handler) http.Handler)
}
@ -70,14 +70,14 @@ func runWebpage() error {
r.Handle("/static/*", http.FileServer(http.FS(config.Static)))
r.Group(func(r chi.Router) {
r.Use(TemplateMiddelware)
//r.Use(TemplateMiddelware)
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
render.HTML(w, http.StatusOK, "index", map[string]string {"title": "Soeren"})
})
config.Bootstrap(r)
config.Bootstrap(r, TemplateMiddelware)
})

View File

@ -1,25 +1,75 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>My Layout</title>
<link href="/static/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<link href="/static/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous" />
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-3">
<ul>
<li><a href="/register">Register</a></li>
<li><a href="/login">Login</a></li>
<li><a href="/me">Me</a></li>
<li><a href="/logout">Logout</a></li>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarColor03" aria-controls="navbarColor03" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarColor03">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="#">Home
<span class="visually-hidden">(current)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Features</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Pricing</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">About</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Separated link</a>
</div>
</li>
</ul>
<ul class="navbar-nav me-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Account</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="/login">Login</a>
<a class="dropdown-item" href="/register">Register</a>
<a class="dropdown-item" href="/me">Me</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="/logout">Logout</a>
</div>
</li>
</ul>
</div>
</div>
</nav>
<div class="container" style="margin-top: 20px;">
<div class="row">
<div class="col-md-9"> {{ yield }}</div>
<div class="col-md-3">Werbung</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
</html>

8
vendor/github.com/dchest/captcha/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Generated test captchas
capgen/*.png
capgen/*.wav
# Programs
capgen/capgen
cangensounds/cangensounds
capexample/capexample

19
vendor/github.com/dchest/captcha/LICENSE generated vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2011-2014 Dmitry Chestnykh <dmitry@codingrobots.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

277
vendor/github.com/dchest/captcha/README.md generated vendored Normal file
View File

@ -0,0 +1,277 @@
Package captcha
=====================
**:warning: Warning: this captcha can be broken by advanced OCR captcha breaking algorithms.**
import "github.com/dchest/captcha"
Package captcha implements generation and verification of image and audio
CAPTCHAs.
A captcha solution is the sequence of digits 0-9 with the defined length.
There are two captcha representations: image and audio.
An image representation is a PNG-encoded image with the solution printed on
it in such a way that makes it hard for computers to solve it using OCR.
An audio representation is a WAVE-encoded (8 kHz unsigned 8-bit) sound with the
spoken solution (currently in English, Russian, Chinese, and Japanese). To make
it hard for computers to solve audio captcha, the voice that pronounces numbers
has random speed and pitch, and there is a randomly generated background noise
mixed into the sound.
This package doesn't require external files or libraries to generate captcha
representations; it is self-contained.
To make captchas one-time, the package includes a memory storage that stores
captcha ids, their solutions, and expiration time. Used captchas are removed
from the store immediately after calling Verify or VerifyString, while
unused captchas (user loaded a page with captcha, but didn't submit the
form) are collected automatically after the predefined expiration time.
Developers can also provide custom store (for example, which saves captcha
ids and solutions in database) by implementing Store interface and
registering the object with SetCustomStore.
Captchas are created by calling New, which returns the captcha id. Their
representations, though, are created on-the-fly by calling WriteImage or
WriteAudio functions. Created representations are not stored anywhere, but
subsequent calls to these functions with the same id will write the same
captcha solution. Reload function will create a new different solution for the
provided captcha, allowing users to "reload" captcha if they can't solve the
displayed one without reloading the whole page. Verify and VerifyString are
used to verify that the given solution is the right one for the given captcha
id.
Server provides an http.Handler which can serve image and audio
representations of captchas automatically from the URL. It can also be used
to reload captchas. Refer to Server function documentation for details, or
take a look at the example in "capexample" subdirectory.
Examples
--------
![Image](https://github.com/dchest/captcha/raw/master/capgen/example.png)
[Audio](https://github.com/dchest/captcha/raw/master/capgen/example.wav)
Constants
---------
``` go
const (
// Default number of digits in captcha solution.
DefaultLen = 6
// The number of captchas created that triggers garbage collection used
// by default store.
CollectNum = 100
// Expiration time of captchas used by default store.
Expiration = 10 * time.Minute
)
```
``` go
const (
// Standard width and height of a captcha image.
StdWidth = 240
StdHeight = 80
)
```
Variables
---------
``` go
var (
ErrNotFound = errors.New("captcha: id not found")
)
```
Functions
---------
### func New
func New() string
New creates a new captcha with the standard length, saves it in the internal
storage and returns its id.
### func NewLen
func NewLen(length int) (id string)
NewLen is just like New, but accepts length of a captcha solution as the
argument.
### func RandomDigits
func RandomDigits(length int) (b []byte)
RandomDigits returns a byte slice of the given length containing
pseudorandom numbers in range 0-9. The slice can be used as a captcha
solution.
### func Reload
func Reload(id string) bool
Reload generates and remembers new digits for the given captcha id. This
function returns false if there is no captcha with the given id.
After calling this function, the image or audio presented to a user must be
refreshed to show the new captcha representation (WriteImage and WriteAudio
will write the new one).
### func Server
func Server(imgWidth, imgHeight int) http.Handler
Server returns a handler that serves HTTP requests with image or
audio representations of captchas. Image dimensions are accepted as
arguments. The server decides which captcha to serve based on the last URL
path component: file name part must contain a captcha id, file extension —
its format (PNG or WAV).
For example, for file name "LBm5vMjHDtdUfaWYXiQX.png" it serves an image captcha
with id "LBm5vMjHDtdUfaWYXiQX", and for "LBm5vMjHDtdUfaWYXiQX.wav" it serves the
same captcha in audio format.
To serve a captcha as a downloadable file, the URL must be constructed in
such a way as if the file to serve is in the "download" subdirectory:
"/download/LBm5vMjHDtdUfaWYXiQX.wav".
To reload captcha (get a different solution for the same captcha id), append
"?reload=x" to URL, where x may be anything (for example, current time or a
random number to make browsers refetch an image instead of loading it from
cache).
By default, the Server serves audio in English language. To serve audio
captcha in one of the other supported languages, append "lang" value, for
example, "?lang=ru".
### func SetCustomStore
func SetCustomStore(s Store)
SetCustomStore sets custom storage for captchas, replacing the default
memory store. This function must be called before generating any captchas.
### func Verify
func Verify(id string, digits []byte) bool
Verify returns true if the given digits are the ones that were used to
create the given captcha id.
The function deletes the captcha with the given id from the internal
storage, so that the same captcha can't be verified anymore.
### func VerifyString
func VerifyString(id string, digits string) bool
VerifyString is like Verify, but accepts a string of digits. It removes
spaces and commas from the string, but any other characters, apart from
digits and listed above, will cause the function to return false.
### func WriteAudio
func WriteAudio(w io.Writer, id string, lang string) error
WriteAudio writes WAV-encoded audio representation of the captcha with the
given id and the given language. If there are no sounds for the given
language, English is used.
### func WriteImage
func WriteImage(w io.Writer, id string, width, height int) error
WriteImage writes PNG-encoded image representation of the captcha with the
given id. The image will have the given width and height.
Types
-----
``` go
type Audio struct {
// contains unexported fields
}
```
### func NewAudio
func NewAudio(id string, digits []byte, lang string) *Audio
NewAudio returns a new audio captcha with the given digits, where each digit
must be in range 0-9. Digits are pronounced in the given language. If there
are no sounds for the given language, English is used.
Possible values for lang are "en", "ja", "ru", "zh".
### func (*Audio) EncodedLen
func (a *Audio) EncodedLen() int
EncodedLen returns the length of WAV-encoded audio captcha.
### func (*Audio) WriteTo
func (a *Audio) WriteTo(w io.Writer) (n int64, err error)
WriteTo writes captcha audio in WAVE format into the given io.Writer, and
returns the number of bytes written and an error if any.
``` go
type Image struct {
*image.Paletted
// contains unexported fields
}
```
### func NewImage
func NewImage(id string, digits []byte, width, height int) *Image
NewImage returns a new captcha image of the given width and height with the
given digits, where each digit must be in range 0-9.
### func (*Image) WriteTo
func (m *Image) WriteTo(w io.Writer) (int64, error)
WriteTo writes captcha image in PNG format into the given writer.
``` go
type Store interface {
// Set sets the digits for the captcha id.
Set(id string, digits []byte)
// Get returns stored digits for the captcha id. Clear indicates
// whether the captcha must be deleted from the store.
Get(id string, clear bool) (digits []byte)
}
```
An object implementing Store interface can be registered with SetCustomStore
function to handle storage and retrieval of captcha ids and solutions for
them, replacing the default memory store.
It is the responsibility of an object to delete expired and used captchas
when necessary (for example, the default memory store collects them in Set
method after the certain amount of captchas has been stored.)
### func NewMemoryStore
func NewMemoryStore(collectNum int, expiration time.Duration) Store
NewMemoryStore returns a new standard memory store for captchas with the
given collection threshold and expiration time in seconds. The returned
store must be registered with SetCustomStore to replace the default one.

232
vendor/github.com/dchest/captcha/audio.go generated vendored Normal file
View File

@ -0,0 +1,232 @@
// Copyright 2011-2014 Dmitry Chestnykh. 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 (
"bytes"
"encoding/binary"
"io"
"math"
)
const sampleRate = 8000 // Hz
var endingBeepSound []byte
func init() {
endingBeepSound = changeSpeed(beepSound, 1.4)
}
type Audio struct {
body *bytes.Buffer
digitSounds [][]byte
rng siprng
}
// NewAudio returns a new audio captcha with the given digits, where each digit
// must be in range 0-9. Digits are pronounced in the given language. If there
// are no sounds for the given language, English is used.
//
// Possible values for lang are "en", "ja", "ru", "zh".
func NewAudio(id string, digits []byte, lang string) *Audio {
a := new(Audio)
// Initialize PRNG.
a.rng.Seed(deriveSeed(audioSeedPurpose, id, digits))
if sounds, ok := digitSounds[lang]; ok {
a.digitSounds = sounds
} else {
a.digitSounds = digitSounds["en"]
}
numsnd := make([][]byte, len(digits))
nsdur := 0
for i, n := range digits {
snd := a.randomizedDigitSound(n)
nsdur += len(snd)
numsnd[i] = snd
}
// Random intervals between digits (including beginning).
intervals := make([]int, len(digits)+1)
intdur := 0
for i := range intervals {
dur := a.rng.Int(sampleRate, sampleRate*3) // 1 to 3 seconds
intdur += dur
intervals[i] = dur
}
// Generate background sound.
bg := a.makeBackgroundSound(a.longestDigitSndLen()*len(digits) + intdur)
// Create buffer and write audio to it.
sil := makeSilence(sampleRate / 5)
bufcap := 3*len(beepSound) + 2*len(sil) + len(bg) + len(endingBeepSound)
a.body = bytes.NewBuffer(make([]byte, 0, bufcap))
// Write prelude, three beeps.
a.body.Write(beepSound)
a.body.Write(sil)
a.body.Write(beepSound)
a.body.Write(sil)
a.body.Write(beepSound)
// Write digits.
pos := intervals[0]
for i, v := range numsnd {
mixSound(bg[pos:], v)
pos += len(v) + intervals[i+1]
}
a.body.Write(bg)
// Write ending (one beep).
a.body.Write(endingBeepSound)
return a
}
// WriteTo writes captcha audio in WAVE format into the given io.Writer, and
// returns the number of bytes written and an error if any.
func (a *Audio) WriteTo(w io.Writer) (n int64, err error) {
// Calculate padded length of PCM chunk data.
bodyLen := uint32(a.body.Len())
paddedBodyLen := bodyLen
if bodyLen%2 != 0 {
paddedBodyLen++
}
totalLen := uint32(len(waveHeader)) - 4 + paddedBodyLen
// Header.
header := make([]byte, len(waveHeader)+4) // includes 4 bytes for chunk size
copy(header, waveHeader)
// Put the length of whole RIFF chunk.
binary.LittleEndian.PutUint32(header[4:], totalLen)
// Put the length of WAVE chunk.
binary.LittleEndian.PutUint32(header[len(waveHeader):], bodyLen)
// Write header.
nn, err := w.Write(header)
n = int64(nn)
if err != nil {
return
}
// Write data.
n, err = a.body.WriteTo(w)
n += int64(nn)
if err != nil {
return
}
// Pad byte if chunk length is odd.
// (As header has even length, we can check if n is odd, not chunk).
if bodyLen != paddedBodyLen {
w.Write([]byte{0})
n++
}
return
}
// EncodedLen returns the length of WAV-encoded audio captcha.
func (a *Audio) EncodedLen() int {
return len(waveHeader) + 4 + a.body.Len()
}
func (a *Audio) makeBackgroundSound(length int) []byte {
b := a.makeWhiteNoise(length, 4)
for i := 0; i < length/(sampleRate/10); i++ {
snd := reversedSound(a.digitSounds[a.rng.Intn(10)])
snd = changeSpeed(snd, a.rng.Float(0.8, 1.4))
place := a.rng.Intn(len(b) - len(snd))
setSoundLevel(snd, a.rng.Float(0.2, 0.5))
mixSound(b[place:], snd)
}
return b
}
func (a *Audio) randomizedDigitSound(n byte) []byte {
s := a.randomSpeed(a.digitSounds[n])
setSoundLevel(s, a.rng.Float(0.75, 1.2))
return s
}
func (a *Audio) longestDigitSndLen() int {
n := 0
for _, v := range a.digitSounds {
if n < len(v) {
n = len(v)
}
}
return n
}
func (a *Audio) randomSpeed(b []byte) []byte {
pitch := a.rng.Float(0.9, 1.2)
return changeSpeed(b, pitch)
}
func (a *Audio) makeWhiteNoise(length int, level uint8) []byte {
noise := a.rng.Bytes(length)
adj := 128 - level/2
for i, v := range noise {
v %= level
v += adj
noise[i] = v
}
return noise
}
// mixSound mixes src into dst. Dst must have length equal to or greater than
// src length.
func mixSound(dst, src []byte) {
for i, v := range src {
av := int(v)
bv := int(dst[i])
if av < 128 && bv < 128 {
dst[i] = byte(av * bv / 128)
} else {
dst[i] = byte(2*(av+bv) - av*bv/128 - 256)
}
}
}
func setSoundLevel(a []byte, level float64) {
for i, v := range a {
av := float64(v)
switch {
case av > 128:
if av = (av-128)*level + 128; av < 128 {
av = 128
}
case av < 128:
if av = 128 - (128-av)*level; av > 128 {
av = 128
}
default:
continue
}
a[i] = byte(av)
}
}
// changeSpeed returns new PCM bytes from the bytes with the speed and pitch
// changed to the given value that must be in range [0, x].
func changeSpeed(a []byte, speed float64) []byte {
b := make([]byte, int(math.Floor(float64(len(a))*speed)))
var p float64
for _, v := range a {
for i := int(p); i < int(p+speed); i++ {
b[i] = v
}
p += speed
}
return b
}
func makeSilence(length int) []byte {
b := make([]byte, length)
for i := range b {
b[i] = 128
}
return b
}
func reversedSound(a []byte) []byte {
n := len(a)
b := make([]byte, n)
for i, v := range a {
b[n-1-i] = v
}
return b
}

167
vendor/github.com/dchest/captcha/captcha.go generated vendored Normal file
View File

@ -0,0 +1,167 @@
// Copyright 2011 Dmitry Chestnykh. 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 implements generation and verification of image and audio
// CAPTCHAs.
//
// A captcha solution is the sequence of digits 0-9 with the defined length.
// There are two captcha representations: image and audio.
//
// An image representation is a PNG-encoded image with the solution printed on
// it in such a way that makes it hard for computers to solve it using OCR.
//
// An audio representation is a WAVE-encoded (8 kHz unsigned 8-bit) sound with
// the spoken solution (currently in English, Russian, Chinese, and Japanese).
// To make it hard for computers to solve audio captcha, the voice that
// pronounces numbers has random speed and pitch, and there is a randomly
// generated background noise mixed into the sound.
//
// This package doesn't require external files or libraries to generate captcha
// representations; it is self-contained.
//
// To make captchas one-time, the package includes a memory storage that stores
// captcha ids, their solutions, and expiration time. Used captchas are removed
// from the store immediately after calling Verify or VerifyString, while
// unused captchas (user loaded a page with captcha, but didn't submit the
// form) are collected automatically after the predefined expiration time.
// Developers can also provide custom store (for example, which saves captcha
// ids and solutions in database) by implementing Store interface and
// registering the object with SetCustomStore.
//
// Captchas are created by calling New, which returns the captcha id. Their
// representations, though, are created on-the-fly by calling WriteImage or
// WriteAudio functions. Created representations are not stored anywhere, but
// subsequent calls to these functions with the same id will write the same
// captcha solution. Reload function will create a new different solution for
// the provided captcha, allowing users to "reload" captcha if they can't solve
// the displayed one without reloading the whole page. Verify and VerifyString
// are used to verify that the given solution is the right one for the given
// captcha id.
//
// Server provides an http.Handler which can serve image and audio
// representations of captchas automatically from the URL. It can also be used
// to reload captchas. Refer to Server function documentation for details, or
// take a look at the example in "capexample" subdirectory.
package captcha
import (
"bytes"
"errors"
"io"
"time"
"fmt"
)
const (
// Default number of digits in captcha solution.
DefaultLen = 6
// The number of captchas created that triggers garbage collection used
// by default store.
CollectNum = 100
// Expiration time of captchas used by default store.
Expiration = 10 * time.Minute
)
var (
ErrNotFound = errors.New("captcha: id not found")
// globalStore is a shared storage for captchas, generated by New function.
globalStore = NewMemoryStore(CollectNum, Expiration)
)
// SetCustomStore sets custom storage for captchas, replacing the default
// memory store. This function must be called before generating any captchas.
func SetCustomStore(s Store) {
globalStore = s
}
// New creates a new captcha with the standard length, saves it in the internal
// storage and returns its id.
func New() string {
return NewLen(DefaultLen)
}
// NewLen is just like New, but accepts length of a captcha solution as the
// argument.
func NewLen(length int) (id string) {
id = randomId()
globalStore.Set(id, RandomDigits(length))
return
}
// Reload generates and remembers new digits for the given captcha id. This
// function returns false if there is no captcha with the given id.
//
// After calling this function, the image or audio presented to a user must be
// refreshed to show the new captcha representation (WriteImage and WriteAudio
// will write the new one).
func Reload(id string) bool {
old := globalStore.Get(id, false)
if old == nil {
return false
}
globalStore.Set(id, RandomDigits(len(old)))
return true
}
// WriteImage writes PNG-encoded image representation of the captcha with the
// given id. The image will have the given width and height.
func WriteImage(w io.Writer, id string, width, height int) error {
d := globalStore.Get(id, false)
if d == nil {
return ErrNotFound
}
_, err := NewImage(id, d, width, height).WriteTo(w)
return err
}
// WriteAudio writes WAV-encoded audio representation of the captcha with the
// given id and the given language. If there are no sounds for the given
// language, English is used.
func WriteAudio(w io.Writer, id string, lang string) error {
d := globalStore.Get(id, false)
if d == nil {
return ErrNotFound
}
_, err := NewAudio(id, d, lang).WriteTo(w)
return err
}
// Verify returns true if the given digits are the ones that were used to
// create the given captcha id.
//
// The function deletes the captcha with the given id from the internal
// storage, so that the same captcha can't be verified anymore.
func Verify(id string, digits []byte) bool {
if digits == nil || len(digits) == 0 {
return false
}
reald := globalStore.Get(id, true)
fmt.Println(reald)
if reald == nil {
return false
}
return bytes.Equal(digits, reald)
}
// VerifyString is like Verify, but accepts a string of digits. It removes
// spaces and commas from the string, but any other characters, apart from
// digits and listed above, will cause the function to return false.
func VerifyString(id string, digits string) bool {
if digits == "" {
return false
}
ns := make([]byte, len(digits))
for i := range ns {
d := digits[i]
switch {
case '0' <= d && d <= '9':
ns[i] = d - '0'
case d == ' ' || d == ',':
// ignore
default:
return false
}
}
return Verify(id, ns)
}

214
vendor/github.com/dchest/captcha/font.go generated vendored Normal file
View File

@ -0,0 +1,214 @@
// Copyright 2011 Dmitry Chestnykh. 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
const (
fontWidth = 11
fontHeight = 18
blackChar = 1
)
var font = [][]byte{
{ // 0
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 1
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0,
0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
},
{ // 2
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
},
{ // 3
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 4
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
},
{ // 5
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 6
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0,
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 7
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
},
{ // 8
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
},
{ // 9
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
},
}

271
vendor/github.com/dchest/captcha/image.go generated vendored Normal file
View File

@ -0,0 +1,271 @@
// Copyright 2011-2014 Dmitry Chestnykh. 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 (
"bytes"
"image"
"image/color"
"image/png"
"io"
"math"
)
const (
// Standard width and height of a captcha image.
StdWidth = 240
StdHeight = 80
// Maximum absolute skew factor of a single digit.
maxSkew = 0.7
// Number of background circles.
circleCount = 20
)
type Image struct {
*image.Paletted
numWidth int
numHeight int
dotSize int
rng siprng
}
// NewImage returns a new captcha image of the given width and height with the
// given digits, where each digit must be in range 0-9.
func NewImage(id string, digits []byte, width, height int) *Image {
m := new(Image)
// Initialize PRNG.
m.rng.Seed(deriveSeed(imageSeedPurpose, id, digits))
m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), m.getRandomPalette())
m.calculateSizes(width, height, len(digits))
// Randomly position captcha inside the image.
maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize
maxy := height - m.numHeight - m.dotSize*2
var border int
if width > height {
border = height / 5
} else {
border = width / 5
}
x := m.rng.Int(border, maxx-border)
y := m.rng.Int(border, maxy-border)
// Draw digits.
for _, n := range digits {
m.drawDigit(font[n], x, y)
x += m.numWidth + m.dotSize
}
// Draw strike-through line.
m.strikeThrough()
// Apply wave distortion.
m.distort(m.rng.Float(5, 10), m.rng.Float(100, 200))
// Fill image with random circles.
m.fillWithCircles(circleCount, m.dotSize)
return m
}
func (m *Image) getRandomPalette() color.Palette {
p := make([]color.Color, circleCount+1)
// Transparent color.
p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}
// Primary color.
prim := color.RGBA{
uint8(m.rng.Intn(129)),
uint8(m.rng.Intn(129)),
uint8(m.rng.Intn(129)),
0xFF,
}
p[1] = prim
// Circle colors.
for i := 2; i <= circleCount; i++ {
p[i] = m.randomBrightness(prim, 255)
}
return p
}
// encodedPNG encodes an image to PNG and returns
// the result as a byte slice.
func (m *Image) encodedPNG() []byte {
var buf bytes.Buffer
if err := png.Encode(&buf, m.Paletted); err != nil {
panic(err.Error())
}
return buf.Bytes()
}
// WriteTo writes captcha image in PNG format into the given writer.
func (m *Image) WriteTo(w io.Writer) (int64, error) {
n, err := w.Write(m.encodedPNG())
return int64(n), err
}
func (m *Image) calculateSizes(width, height, ncount int) {
// Goal: fit all digits inside the image.
var border int
if width > height {
border = height / 4
} else {
border = width / 4
}
// Convert everything to floats for calculations.
w := float64(width - border*2)
h := float64(height - border*2)
// fw takes into account 1-dot spacing between digits.
fw := float64(fontWidth + 1)
fh := float64(fontHeight)
nc := float64(ncount)
// Calculate the width of a single digit taking into account only the
// width of the image.
nw := w / nc
// Calculate the height of a digit from this width.
nh := nw * fh / fw
// Digit too high?
if nh > h {
// Fit digits based on height.
nh = h
nw = fw / fh * nh
}
// Calculate dot size.
m.dotSize = int(nh / fh)
if m.dotSize < 1 {
m.dotSize = 1
}
// Save everything, making the actual width smaller by 1 dot to account
// for spacing between digits.
m.numWidth = int(nw) - m.dotSize
m.numHeight = int(nh)
}
func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) {
for x := fromX; x <= toX; x++ {
m.SetColorIndex(x, y, colorIdx)
}
}
func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) {
f := 1 - radius
dfx := 1
dfy := -2 * radius
xo := 0
yo := radius
m.SetColorIndex(x, y+radius, colorIdx)
m.SetColorIndex(x, y-radius, colorIdx)
m.drawHorizLine(x-radius, x+radius, y, colorIdx)
for xo < yo {
if f >= 0 {
yo--
dfy += 2
f += dfy
}
xo++
dfx += 2
f += dfx
m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)
m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)
m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)
m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)
}
}
func (m *Image) fillWithCircles(n, maxradius int) {
maxx := m.Bounds().Max.X
maxy := m.Bounds().Max.Y
for i := 0; i < n; i++ {
colorIdx := uint8(m.rng.Int(1, circleCount-1))
r := m.rng.Int(1, maxradius)
m.drawCircle(m.rng.Int(r, maxx-r), m.rng.Int(r, maxy-r), r, colorIdx)
}
}
func (m *Image) strikeThrough() {
maxx := m.Bounds().Max.X
maxy := m.Bounds().Max.Y
y := m.rng.Int(maxy/3, maxy-maxy/3)
amplitude := m.rng.Float(5, 20)
period := m.rng.Float(80, 180)
dx := 2.0 * math.Pi / period
for x := 0; x < maxx; x++ {
xo := amplitude * math.Cos(float64(y)*dx)
yo := amplitude * math.Sin(float64(x)*dx)
for yn := 0; yn < m.dotSize; yn++ {
r := m.rng.Int(0, m.dotSize)
m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1)
}
}
}
func (m *Image) drawDigit(digit []byte, x, y int) {
skf := m.rng.Float(-maxSkew, maxSkew)
xs := float64(x)
r := m.dotSize / 2
y += m.rng.Int(-r, r)
for yo := 0; yo < fontHeight; yo++ {
for xo := 0; xo < fontWidth; xo++ {
if digit[yo*fontWidth+xo] != blackChar {
continue
}
m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1)
}
xs += skf
x = int(xs)
}
}
func (m *Image) distort(amplude float64, period float64) {
w := m.Bounds().Max.X
h := m.Bounds().Max.Y
oldm := m.Paletted
newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette)
dx := 2.0 * math.Pi / period
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
xo := amplude * math.Sin(float64(y)*dx)
yo := amplude * math.Cos(float64(x)*dx)
newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo)))
}
}
m.Paletted = newm
}
func (m *Image) randomBrightness(c color.RGBA, max uint8) color.RGBA {
minc := min3(c.R, c.G, c.B)
maxc := max3(c.R, c.G, c.B)
if maxc > max {
return c
}
n := m.rng.Intn(int(max-maxc)) - int(minc)
return color.RGBA{
uint8(int(c.R) + n),
uint8(int(c.G) + n),
uint8(int(c.B) + n),
uint8(c.A),
}
}
func min3(x, y, z uint8) (m uint8) {
m = x
if y < m {
m = y
}
if z < m {
m = z
}
return
}
func max3(x, y, z uint8) (m uint8) {
m = x
if y > m {
m = y
}
if z > m {
m = z
}
return
}

108
vendor/github.com/dchest/captcha/random.go generated vendored Normal file
View File

@ -0,0 +1,108 @@
// Copyright 2011-2014 Dmitry Chestnykh. 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 (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"io"
)
// idLen is a length of captcha id string.
// (20 bytes of 62-letter alphabet give ~119 bits.)
const idLen = 20
// idChars are characters allowed in captcha id.
var idChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
// rngKey is a secret key used to deterministically derive seeds for
// PRNGs used in image and audio. Generated once during initialization.
var rngKey [32]byte
func init() {
if _, err := io.ReadFull(rand.Reader, rngKey[:]); err != nil {
panic("captcha: error reading random source: " + err.Error())
}
}
// Purposes for seed derivation. The goal is to make deterministic PRNG produce
// different outputs for images and audio by using different derived seeds.
const (
imageSeedPurpose = 0x01
audioSeedPurpose = 0x02
)
// deriveSeed returns a 16-byte PRNG seed from rngKey, purpose, id and digits.
// Same purpose, id and digits will result in the same derived seed for this
// instance of running application.
//
// out = HMAC(rngKey, purpose || id || 0x00 || digits) (cut to 16 bytes)
//
func deriveSeed(purpose byte, id string, digits []byte) (out [16]byte) {
var buf [sha256.Size]byte
h := hmac.New(sha256.New, rngKey[:])
h.Write([]byte{purpose})
io.WriteString(h, id)
h.Write([]byte{0})
h.Write(digits)
sum := h.Sum(buf[:0])
copy(out[:], sum)
return
}
// RandomDigits returns a byte slice of the given length containing
// pseudorandom numbers in range 0-9. The slice can be used as a captcha
// solution.
func RandomDigits(length int) []byte {
return randomBytesMod(length, 10)
}
// randomBytes returns a byte slice of the given length read from CSPRNG.
func randomBytes(length int) (b []byte) {
b = make([]byte, length)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
panic("captcha: error reading random source: " + err.Error())
}
return
}
// randomBytesMod returns a byte slice of the given length, where each byte is
// a random number modulo mod.
func randomBytesMod(length int, mod byte) (b []byte) {
if length == 0 {
return nil
}
if mod == 0 {
panic("captcha: bad mod argument for randomBytesMod")
}
maxrb := 255 - byte(256%int(mod))
b = make([]byte, length)
i := 0
for {
r := randomBytes(length + (length / 4))
for _, c := range r {
if c > maxrb {
// Skip this number to avoid modulo bias.
continue
}
b[i] = c % mod
i++
if i == length {
return
}
}
}
}
// randomId returns a new random id string.
func randomId() string {
b := randomBytesMod(idLen, byte(len(idChars)))
for i, c := range b {
b[i] = idChars[c]
}
return string(b)
}

87
vendor/github.com/dchest/captcha/server.go generated vendored Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2011 Dmitry Chestnykh. 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 (
"bytes"
"net/http"
"path"
"strings"
"time"
)
type captchaHandler struct {
imgWidth int
imgHeight int
}
// Server returns a handler that serves HTTP requests with image or
// audio representations of captchas. Image dimensions are accepted as
// arguments. The server decides which captcha to serve based on the last URL
// path component: file name part must contain a captcha id, file extension —
// its format (PNG or WAV).
//
// For example, for file name "LBm5vMjHDtdUfaWYXiQX.png" it serves an image captcha
// with id "LBm5vMjHDtdUfaWYXiQX", and for "LBm5vMjHDtdUfaWYXiQX.wav" it serves the
// same captcha in audio format.
//
// To serve a captcha as a downloadable file, the URL must be constructed in
// such a way as if the file to serve is in the "download" subdirectory:
// "/download/LBm5vMjHDtdUfaWYXiQX.wav".
//
// To reload captcha (get a different solution for the same captcha id), append
// "?reload=x" to URL, where x may be anything (for example, current time or a
// random number to make browsers refetch an image instead of loading it from
// cache).
//
// By default, the Server serves audio in English language. To serve audio
// captcha in one of the other supported languages, append "lang" value, for
// example, "?lang=ru".
func Server(imgWidth, imgHeight int) http.Handler {
return &captchaHandler{imgWidth, imgHeight}
}
func (h *captchaHandler) serve(w http.ResponseWriter, r *http.Request, id, ext, lang string, download bool) error {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
var content bytes.Buffer
switch ext {
case ".png":
w.Header().Set("Content-Type", "image/png")
WriteImage(&content, id, h.imgWidth, h.imgHeight)
case ".wav":
w.Header().Set("Content-Type", "audio/x-wav")
WriteAudio(&content, id, lang)
default:
return ErrNotFound
}
if download {
w.Header().Set("Content-Type", "application/octet-stream")
}
http.ServeContent(w, r, id+ext, time.Time{}, bytes.NewReader(content.Bytes()))
return nil
}
func (h *captchaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
dir, file := path.Split(r.URL.Path)
ext := path.Ext(file)
id := file[:len(file)-len(ext)]
if ext == "" || id == "" {
http.NotFound(w, r)
return
}
if r.FormValue("reload") != "" {
Reload(id)
}
lang := strings.ToLower(r.FormValue("lang"))
download := path.Base(dir) == "download"
if h.serve(w, r, id, ext, lang, download) == ErrNotFound {
http.NotFound(w, r)
}
// Ignore other errors.
}

278
vendor/github.com/dchest/captcha/siprng.go generated vendored Normal file
View File

@ -0,0 +1,278 @@
// Copyright 2014 Dmitry Chestnykh. 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 "encoding/binary"
// siprng is PRNG based on SipHash-2-4.
// (Note: it's not safe to use a single siprng from multiple goroutines.)
type siprng struct {
k0, k1, ctr uint64
}
// siphash implements SipHash-2-4, accepting a uint64 as a message.
func siphash(k0, k1, m uint64) uint64 {
// Initialization.
v0 := k0 ^ 0x736f6d6570736575
v1 := k1 ^ 0x646f72616e646f6d
v2 := k0 ^ 0x6c7967656e657261
v3 := k1 ^ 0x7465646279746573
t := uint64(8) << 56
// Compression.
v3 ^= m
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
v0 ^= m
// Compress last block.
v3 ^= t
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
v0 ^= t
// Finalization.
v2 ^= 0xff
// Round 1.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 2.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 3.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
// Round 4.
v0 += v1
v1 = v1<<13 | v1>>(64-13)
v1 ^= v0
v0 = v0<<32 | v0>>(64-32)
v2 += v3
v3 = v3<<16 | v3>>(64-16)
v3 ^= v2
v0 += v3
v3 = v3<<21 | v3>>(64-21)
v3 ^= v0
v2 += v1
v1 = v1<<17 | v1>>(64-17)
v1 ^= v2
v2 = v2<<32 | v2>>(64-32)
return v0 ^ v1 ^ v2 ^ v3
}
// Seed sets a new secret seed for PRNG.
func (p *siprng) Seed(k [16]byte) {
p.k0 = binary.LittleEndian.Uint64(k[0:8])
p.k1 = binary.LittleEndian.Uint64(k[8:16])
p.ctr = 1
}
// Uint64 returns a new pseudorandom uint64.
func (p *siprng) Uint64() uint64 {
v := siphash(p.k0, p.k1, p.ctr)
p.ctr++
return v
}
func (p *siprng) Bytes(n int) []byte {
// Since we don't have a buffer for generated bytes in siprng state,
// we just generate enough 8-byte blocks and then cut the result to the
// required length. Doing it this way, we lose generated bytes, and we
// don't get the strictly sequential deterministic output from PRNG:
// calling Uint64() and then Bytes(3) produces different output than
// when calling them in the reverse order, but for our applications
// this is OK.
numBlocks := (n + 8 - 1) / 8
b := make([]byte, numBlocks*8)
for i := 0; i < len(b); i += 8 {
binary.LittleEndian.PutUint64(b[i:], p.Uint64())
}
return b[:n]
}
func (p *siprng) Int63() int64 {
return int64(p.Uint64() & 0x7fffffffffffffff)
}
func (p *siprng) Uint32() uint32 {
return uint32(p.Uint64())
}
func (p *siprng) Int31() int32 {
return int32(p.Uint32() & 0x7fffffff)
}
func (p *siprng) Intn(n int) int {
if n <= 0 {
panic("invalid argument to Intn")
}
if n <= 1<<31-1 {
return int(p.Int31n(int32(n)))
}
return int(p.Int63n(int64(n)))
}
func (p *siprng) Int63n(n int64) int64 {
if n <= 0 {
panic("invalid argument to Int63n")
}
max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
v := p.Int63()
for v > max {
v = p.Int63()
}
return v % n
}
func (p *siprng) Int31n(n int32) int32 {
if n <= 0 {
panic("invalid argument to Int31n")
}
max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
v := p.Int31()
for v > max {
v = p.Int31()
}
return v % n
}
func (p *siprng) Float64() float64 { return float64(p.Int63()) / (1 << 63) }
// Int returns a pseudorandom int in range [from, to].
func (p *siprng) Int(from, to int) int {
return p.Intn(to+1-from) + from
}
// Float returns a pseudorandom float64 in range [from, to].
func (p *siprng) Float(from, to float64) float64 {
return (to-from)*p.Float64() + from
}

9726
vendor/github.com/dchest/captcha/sounds.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

117
vendor/github.com/dchest/captcha/store.go generated vendored Normal file
View File

@ -0,0 +1,117 @@
// Copyright 2011 Dmitry Chestnykh. 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 (
"container/list"
"sync"
"time"
)
// An object implementing Store interface can be registered with SetCustomStore
// function to handle storage and retrieval of captcha ids and solutions for
// them, replacing the default memory store.
//
// It is the responsibility of an object to delete expired and used captchas
// when necessary (for example, the default memory store collects them in Set
// method after the certain amount of captchas has been stored.)
type Store interface {
// Set sets the digits for the captcha id.
Set(id string, digits []byte)
// Get returns stored digits for the captcha id. Clear indicates
// whether the captcha must be deleted from the store.
Get(id string, clear bool) (digits []byte)
}
// expValue stores timestamp and id of captchas. It is used in the list inside
// memoryStore for indexing generated captchas by timestamp to enable garbage
// collection of expired captchas.
type idByTimeValue struct {
timestamp time.Time
id string
}
// memoryStore is an internal store for captcha ids and their values.
type memoryStore struct {
sync.RWMutex
digitsById map[string][]byte
idByTime *list.List
// Number of items stored since last collection.
numStored int
// Number of saved items that triggers collection.
collectNum int
// Expiration time of captchas.
expiration time.Duration
}
// NewMemoryStore returns a new standard memory store for captchas with the
// given collection threshold and expiration time (duration). The returned
// store must be registered with SetCustomStore to replace the default one.
func NewMemoryStore(collectNum int, expiration time.Duration) Store {
s := new(memoryStore)
s.digitsById = make(map[string][]byte)
s.idByTime = list.New()
s.collectNum = collectNum
s.expiration = expiration
return s
}
func (s *memoryStore) Set(id string, digits []byte) {
s.Lock()
s.digitsById[id] = digits
s.idByTime.PushBack(idByTimeValue{time.Now(), id})
s.numStored++
if s.numStored <= s.collectNum {
s.Unlock()
return
}
s.Unlock()
go s.collect()
}
func (s *memoryStore) Get(id string, clear bool) (digits []byte) {
if !clear {
// When we don't need to clear captcha, acquire read lock.
s.RLock()
defer s.RUnlock()
} else {
s.Lock()
defer s.Unlock()
}
digits, ok := s.digitsById[id]
if !ok {
return
}
if clear {
delete(s.digitsById, id)
// XXX(dchest) Index (s.idByTime) will be cleaned when
// collecting expired captchas. Can't clean it here, because
// we don't store reference to expValue in the map.
// Maybe store it?
}
return
}
func (s *memoryStore) collect() {
now := time.Now()
s.Lock()
defer s.Unlock()
s.numStored = 0
for e := s.idByTime.Front(); e != nil; {
ev, ok := e.Value.(idByTimeValue)
if !ok {
return
}
if ev.timestamp.Add(s.expiration).Before(now) {
delete(s.digitsById, ev.id)
next := e.Next()
s.idByTime.Remove(e)
e = next
} else {
return
}
}
}

3
vendor/modules.txt vendored
View File

@ -1,6 +1,9 @@
# github.com/cpuguy83/go-md2man/v2 v2.0.0
## explicit; go 1.12
github.com/cpuguy83/go-md2man/v2/md2man
# github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
## explicit
github.com/dchest/captcha
# github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d
## explicit; go 1.13
github.com/decred/dcrd/dcrec/secp256k1/v4