This commit is contained in:
Hank Shen
2023-09-24 14:32:54 +08:00
parent 36ad05c2ae
commit 4bd2c1733a
21 changed files with 290 additions and 172 deletions

View File

@@ -1,8 +1,10 @@
package formbuilder
import (
"github.com/webx-top/echo"
"errors"
"github.com/webx-top/echo/formfilter"
"github.com/webx-top/validation"
)
type MethodHook func() error
@@ -17,7 +19,7 @@ func (hooks MethodHooks) On(method string, funcs ...MethodHook) {
func (hooks MethodHooks) Off(methods ...string) {
for _, method := range methods {
if _, ok := hooks[method]; !ok {
if _, ok := hooks[method]; ok {
delete(hooks, method)
}
}
@@ -31,35 +33,34 @@ func (hooks MethodHooks) OffAll() {
func (hooks MethodHooks) Fire(method string) error {
funcs, ok := hooks[method]
if ok {
for _, fn := range funcs {
if err := fn(); err != nil {
return err
}
if !ok {
return nil
}
var err error
for _, fn := range funcs {
if err = fn(); err != nil {
return err
}
}
return nil
return err
}
func BindModel(ctx echo.Context, form *FormBuilder) MethodHook {
func BindModel(form *FormBuilder) MethodHook {
return func() error {
opts := []formfilter.Options{formfilter.Include(form.Config().GetNames()...)}
if customs, ok := ctx.Internal().Get(`formfilter.Options`).([]formfilter.Options); ok {
opts = append(opts, customs...)
}
return ctx.MustBind(form.Model, formfilter.Build(opts...))
opts = append(opts, form.filters...)
return form.ctx.MustBind(form.Model, formfilter.Build(opts...))
}
}
func ValidModel(ctx echo.Context, form *FormBuilder) MethodHook {
func ValidModel(form *FormBuilder) MethodHook {
return func() error {
form.ValidFromConfig()
valid := form.Validate()
var err error
if valid.HasError() {
err = valid.Errors[0]
ctx.Data().SetInfo(valid.Errors[0].Message, 0).SetZone(valid.Errors[0].Field)
err := form.Validate().Error()
if !errors.Is(err, validation.NoError) {
form.ctx.Data().SetInfo(err.Message, 0).SetZone(err.Field)
return err
}
return err
return nil
}
}

View File

@@ -10,9 +10,12 @@ import (
"github.com/coscms/forms/common"
"github.com/coscms/forms/config"
"github.com/coscms/forms/fields"
"gopkg.in/yaml.v3"
"github.com/webx-top/db/lib/factory"
"github.com/webx-top/echo"
"github.com/webx-top/echo/formfilter"
echoMw "github.com/webx-top/echo/middleware"
"github.com/webx-top/echo/middleware/render/driver"
)
@@ -27,8 +30,8 @@ func New(ctx echo.Context, model interface{}, options ...Option) *FormBuilder {
ctx: ctx,
}
defaultHooks := []MethodHook{
BindModel(ctx, f),
ValidModel(ctx, f),
BindModel(f),
ValidModel(f),
}
f.OnPost(defaultHooks...)
f.OnPut(defaultHooks...)
@@ -38,7 +41,7 @@ func New(ctx echo.Context, model interface{}, options ...Option) *FormBuilder {
if option == nil {
continue
}
option(ctx, f)
option(f)
}
f.SetLabelFunc(func(txt string) string {
return ctx.T(txt)
@@ -49,10 +52,9 @@ func New(ctx echo.Context, model interface{}, options ...Option) *FormBuilder {
f.Elements(fields.HiddenField(echo.DefaultNextURLVarName).SetValue(nextURL))
}
})
csrfToken, ok := ctx.Get(`csrf`).(string)
if ok {
if csrfToken := ctx.Internal().String(echoMw.DefaultCSRFConfig.ContextKey); len(csrfToken) > 0 {
f.AddBeforeRender(func() {
f.Elements(fields.HiddenField(`csrf`).SetValue(csrfToken))
f.Elements(fields.HiddenField(echoMw.DefaultCSRFConfig.ContextKey).SetValue(csrfToken))
})
}
ctx.Set(`forms`, f.Forms)
@@ -69,6 +71,7 @@ type FormBuilder struct {
configFile string
dbi *factory.DBI
defaults map[string]string
filters []formfilter.Options
}
// Exited 是否需要退出后续处理。此时一般有err值用于记录错误原因
@@ -103,35 +106,63 @@ func (f *FormBuilder) Error() error {
}
// ParseConfigFile 解析配置文件 xxx.form.json
func (f *FormBuilder) ParseConfigFile() error {
jsonFile := f.configFile
func (f *FormBuilder) ParseConfigFile(jsonformat ...bool) error {
configFile := f.configFile + `.form`
var cfg *config.Config
renderer := f.ctx.Renderer().(driver.Driver)
jsonFile += `.form.json`
jsonFile = renderer.TmplPath(f.ctx, jsonFile)
if len(jsonFile) == 0 {
renderer, ok := f.ctx.Renderer().(driver.Driver)
if !ok {
return fmt.Errorf(`FormBuilder: Expected renderer is "driver.Driver", but got "%T"`, f.ctx.Renderer())
}
var isJSON bool
if len(jsonformat) > 0 {
isJSON = jsonformat[0]
}
if isJSON {
configFile += `.json`
} else {
configFile += `.yaml`
}
configFile = renderer.TmplPath(f.ctx, configFile)
if len(configFile) == 0 {
return ErrJSONConfigFileNameInvalid
}
b, err := renderer.RawContent(jsonFile)
b, err := renderer.RawContent(configFile)
if err != nil {
if !os.IsNotExist(err) /* && !strings.Contains(err.Error(), `file does not exist`)*/ || renderer.Manager() == nil {
return fmt.Errorf(`read file %s: %w`, jsonFile, err)
if !os.IsNotExist(err) || renderer.Manager() == nil {
return fmt.Errorf(`read file %s: %w`, configFile, err)
}
cfg = f.ToConfig()
var jsonb []byte
jsonb, err = f.ToJSONBlob(cfg)
if err != nil {
return fmt.Errorf(`[form.ToJSONBlob] %s: %w`, jsonFile, err)
if isJSON {
b, err = f.ToJSONBlob(cfg)
if err != nil {
return fmt.Errorf(`[form.ToJSONBlob] %s: %w`, configFile, err)
}
} else {
b, err = yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf(`[form:yaml.Marshal] %s: %w`, configFile, err)
}
}
err = renderer.Manager().SetTemplate(jsonFile, jsonb)
err = renderer.Manager().SetTemplate(configFile, b)
if err != nil {
return fmt.Errorf(`%s: %w`, jsonFile, err)
return fmt.Errorf(`%s: %w`, configFile, err)
}
f.ctx.Logger().Infof(f.ctx.T(`生成表单配置文件“%v”成功。`), jsonFile)
f.ctx.Logger().Infof(f.ctx.T(`生成表单配置文件“%v”成功。`), configFile)
} else {
cfg, err = forms.Unmarshal(b, jsonFile)
if err != nil {
return fmt.Errorf(`[forms.Unmarshal] %s: %w`, jsonFile, err)
if isJSON {
cfg, err = forms.Unmarshal(b, configFile)
if err != nil {
return fmt.Errorf(`[forms.Unmarshal] %s: %w`, configFile, err)
}
} else {
cfg, err = common.GetOrSetCachedConfig(configFile, func() (*config.Config, error) {
cfg := &config.Config{}
err := yaml.Unmarshal(b, cfg)
return cfg, err
})
if err != nil {
return fmt.Errorf(`[form:yaml.Unmarshal] %s: %w`, configFile, err)
}
}
}
if cfg == nil {
@@ -188,12 +219,13 @@ func (f *FormBuilder) DefaultValue(fieldName string) string {
// RecvSubmission 接收客户端的提交
func (f *FormBuilder) RecvSubmission() error {
method := strings.ToUpper(f.ctx.Method())
ctx := f.ctx
method := strings.ToUpper(ctx.Method())
if f.err = f.on.Fire(method); f.err != nil {
return f.err
}
f.err = f.on.Fire(`*`)
if f.ctx.Response().Committed() {
if ctx.Response().Committed() {
f.exit = true
}
return f.err

View File

@@ -0,0 +1,107 @@
package formbuilder_test
import (
"fmt"
"os"
"regexp"
"testing"
"github.com/admpub/log"
"github.com/admpub/nging/v5/application/dbschema"
"github.com/admpub/nging/v5/application/library/formbuilder"
"github.com/stretchr/testify/assert"
"github.com/webx-top/com"
"github.com/webx-top/echo"
"github.com/webx-top/echo/defaults"
"github.com/webx-top/echo/middleware/render"
"github.com/webx-top/echo/param"
)
var (
_ echo.BeforeValidate = &TestRequest{}
_ echo.AfterValidate = &TestRequest{}
)
type TestRequest struct {
Name string `json:"name"`
Age uint `json:"age"`
}
func (t *TestRequest) BeforeValidate(ctx echo.Context) error {
return nil
}
func (t *TestRequest) AfterValidate(ctx echo.Context) error {
return nil
}
func TestFormbuilder(t *testing.T) {
defer log.Close()
com.MkdirAll(`testdata/template`, os.ModePerm)
d := render.New(`standard`, `testdata/template`)
d.Init()
defaults.Use(render.Middleware(d))
bean := &TestRequest{}
ctx := defaults.NewMockContext()
ctx.SetRenderer(d)
form := formbuilder.New(ctx, bean,
formbuilder.DBI(dbschema.DBI),
formbuilder.ConfigFile(`test`))
form.OnPost(func() error {
var err error
fmt.Printf("%#v\n", bean)
return err
})
form.Generate()
//fmt.Printf("%#v\n", ctx.Get(`forms`))
assert.Equal(t, form.Forms, ctx.Get(`forms`))
htmlResult := string(form.Render())
var spaceClearRegex = regexp.MustCompile(`(>)[\s]+(&|<)`)
htmlResult = spaceClearRegex.ReplaceAllString(htmlResult, `$1$2`)
expected := `<form generator="forms" class="form-horizontal" id="Forms" role="form" method="POST" action="" required-redstar="true">
<div class="form-group">
<label class="col-sm-2 control-label">Name</label>
<div class="col-sm-8">
<input type="text" name="Name" class="form-control">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Age</label>
<div class="col-sm-8">
<input type="text" name="Age" class="form-control" value="0">
</div>
</div>
<div class="form-group form-submit-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class='btn btn-lg btn-primary'>
<i class="fa fa-check"></i>
Submit
</button>
&nbsp; &nbsp; &nbsp;
<button type="reset" class='btn btn-lg'>
<i class="fa fa-undo"></i>
Reset
</button>
&nbsp; &nbsp; &nbsp;
</div>
</div>
</form>`
expected = spaceClearRegex.ReplaceAllString(expected, `$1$2`)
//assert.Equal(t, expected, htmlResult)
// -----
expectedReq := &TestRequest{
Name: `test`,
Age: 123,
}
ctx.Request().SetMethod(`POST`)
ctx.Request().Form().Set(`name`, expectedReq.Name)
ctx.Request().Form().Set(`age`, param.AsString(expectedReq.Age))
err := form.RecvSubmission()
if form.Exited() {
err = form.Error()
}
assert.NoError(t, err)
assert.Equal(t, expectedReq, bean)
}

View File

@@ -2,43 +2,35 @@ package formbuilder
import (
"github.com/webx-top/db/lib/factory"
"github.com/webx-top/echo"
"github.com/webx-top/echo/formfilter"
)
type Option func(echo.Context, *FormBuilder)
type Option func(*FormBuilder)
// IgnoreFields 疏略某些字段的验证
func IgnoreFields(ignoreFields ...string) Option {
return func(_ echo.Context, form *FormBuilder) {
return func(form *FormBuilder) {
form.CloseValid(ignoreFields...)
}
}
// ValidFields 指定仅仅验证某些字段
func ValidFields(validFields ...string) Option {
return func(c echo.Context, form *FormBuilder) {
c.Internal().Set(`formbuilder.validFields`, validFields)
}
}
// Theme 设置forms模板风格
func Theme(theme string) Option {
return func(_ echo.Context, form *FormBuilder) {
return func(form *FormBuilder) {
form.Theme = theme
}
}
// FormFilter 设置表单过滤
func FormFilter(filters ...formfilter.Options) Option {
return func(c echo.Context, _ *FormBuilder) {
c.Internal().Set(`formfilter.Options`, filters)
return func(form *FormBuilder) {
form.filters = filters
}
}
// ConfigFile 指定要解析的配置文件。如果silent=true则仅仅设置配置文件而不解析
func ConfigFile(jsonFile string, silent ...bool) Option {
return func(_ echo.Context, f *FormBuilder) {
return func(f *FormBuilder) {
f.configFile = jsonFile
if len(silent) > 0 && silent[0] {
return
@@ -51,14 +43,14 @@ func ConfigFile(jsonFile string, silent ...bool) Option {
// RenderBefore 设置渲染表单前的钩子函数
func RenderBefore(fn func()) Option {
return func(_ echo.Context, f *FormBuilder) {
return func(f *FormBuilder) {
f.AddBeforeRender(fn)
}
}
// DBI 指定模型数据表所属DBI(数据库信息)
func DBI(dbi *factory.DBI) Option {
return func(_ echo.Context, f *FormBuilder) {
return func(f *FormBuilder) {
f.dbi = dbi
}
}

6
go.mod
View File

@@ -84,7 +84,7 @@ require (
github.com/aws/aws-sdk-go v1.45.12
github.com/caddy-plugins/webdav v1.2.10
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/coscms/forms v1.12.1
github.com/coscms/forms v1.12.2
github.com/coscms/go-imgparse v0.0.0-20150925144422-3e3a099f7856
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
github.com/edwingeng/doublejump v1.0.1 // indirect
@@ -124,7 +124,7 @@ require (
github.com/webx-top/codec v0.3.0
github.com/webx-top/com v1.0.1
github.com/webx-top/db v1.27.0
github.com/webx-top/echo v2.38.4+incompatible
github.com/webx-top/echo v2.39.2+incompatible
github.com/webx-top/image v0.1.0
github.com/webx-top/pagination v0.2.7 // indirect
golang.org/x/crypto v0.13.0
@@ -158,6 +158,7 @@ require (
github.com/webx-top/validation v0.0.3
github.com/webx-top/validator v0.2.0
golang.org/x/text v0.13.0
gopkg.in/yaml.v3 v3.0.1
)
require (
@@ -374,6 +375,5 @@ require (
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/qr v0.2.0 // indirect
)

8
go.sum
View File

@@ -813,8 +813,8 @@ github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coscms/forms v1.12.1 h1:eBaLlNE+rVFShEq2qflMWn0LhtFLTJmrBwGQ/gKYlf4=
github.com/coscms/forms v1.12.1/go.mod h1:WsTYDKG+4cXt1wBNsqv9hFJ34sdkRHai9n/scu4ap2g=
github.com/coscms/forms v1.12.2 h1:ag3NvoXNWiVTUcbeO4cukkMPhe5XAMYbq462ZG8x2Zg=
github.com/coscms/forms v1.12.2/go.mod h1:WsTYDKG+4cXt1wBNsqv9hFJ34sdkRHai9n/scu4ap2g=
github.com/coscms/go-imgparse v0.0.0-20150925144422-3e3a099f7856 h1:P8+R76nNN/kIx12ptxkfbwhhiuDVwf5hLA6qVZSfe5M=
github.com/coscms/go-imgparse v0.0.0-20150925144422-3e3a099f7856/go.mod h1:hMGWSVn9gPkG9ERZdzfh1DIP5Yvued94t9cgVzErOC0=
github.com/coscms/webauthn v0.2.7 h1:bIPnIgld0sXf7nV79UZp5DGA3NVzDuyPNXHnjU8iFNg=
@@ -2074,8 +2074,8 @@ github.com/webx-top/db v1.27.0/go.mod h1:roceE5x2EX0eKOcQpW4URy+8iNfqKGiP6n+wrgj
github.com/webx-top/echo v2.18.3+incompatible/go.mod h1:ufVP//GwP1suggBSQW7l8G9XcKxZM+FIfCMElLAQqF8=
github.com/webx-top/echo v2.33.3+incompatible/go.mod h1:ufVP//GwP1suggBSQW7l8G9XcKxZM+FIfCMElLAQqF8=
github.com/webx-top/echo v2.35.2+incompatible/go.mod h1:ufVP//GwP1suggBSQW7l8G9XcKxZM+FIfCMElLAQqF8=
github.com/webx-top/echo v2.38.4+incompatible h1:xX3T+WdGu/Obgw09C+MiKSFQ5f7bkvP+UsFq5QEWDeg=
github.com/webx-top/echo v2.38.4+incompatible/go.mod h1:ufVP//GwP1suggBSQW7l8G9XcKxZM+FIfCMElLAQqF8=
github.com/webx-top/echo v2.39.2+incompatible h1:qJ8ln154yd+WQDmhGO9UtsSz7fqvtWsyoqCqMdYekFw=
github.com/webx-top/echo v2.39.2+incompatible/go.mod h1:ufVP//GwP1suggBSQW7l8G9XcKxZM+FIfCMElLAQqF8=
github.com/webx-top/echo-prometheus v1.0.7/go.mod h1:d+/TpxZTP8k7yG1vo1Nlxyk5YVQ4p9Mow0mjqJUk1uU=
github.com/webx-top/echo-prometheus v1.1.1 h1:Z41F5A7R4pC/oLlQta/1uEZybOfnY1BCUvNC3dM5RZg=
github.com/webx-top/echo-prometheus v1.1.1/go.mod h1:T4mJg71spjV3J8IJQ+2EN/hQUB+FgWWsSXZCQ0KoalE=

2
updatemod.sh Executable file
View File

@@ -0,0 +1,2 @@
go mod tidy
go mod vendor

View File

@@ -16,13 +16,14 @@
*/
//Package common This package provides basic constants used by forms packages.
// Package common This package provides basic constants used by forms packages.
package common
import (
"html/template"
"io"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
)
@@ -35,7 +36,7 @@ func ParseFiles(files ...string) (*template.Template, error) {
return ParseFS(FileSystem, files...)
}
name := filepath.Base(files[0])
b, err := ioutil.ReadFile(files[0])
b, err := os.ReadFile(files[0])
if err != nil {
return nil, err
}
@@ -56,7 +57,7 @@ func ParseFS(fs fs.FS, files ...string) (*template.Template, error) {
if err != nil {
return tmpl, err
}
b, err := ioutil.ReadAll(fp)
b, err := io.ReadAll(fp)
fp.Close()
if err != nil {
return tmpl, err

View File

@@ -343,23 +343,29 @@ func (f *Form) GenChoicesForField(name string, lenType interface{}, fnType inter
}
// GenChoices generate choices
// type Data struct{
// ID string
// Name string
// }
// data:=[]*Data{
// &Data{ID:"a",Name:"One"},
// &Data{ID:"b",Name:"Two"},
// }
// GenChoices(len(data), func(index int) (string, string, bool){
// return data[index].ID,data[index].Name,false
// })
//
// type Data struct{
// ID string
// Name string
// }
//
// data:=[]*Data{
// &Data{ID:"a",Name:"One"},
// &Data{ID:"b",Name:"Two"},
// }
//
// GenChoices(len(data), func(index int) (string, string, bool){
// return data[index].ID,data[index].Name,false
// })
//
// or
// GenChoices(map[string]int{
// "":len(data),
// }, func(group string,index int) (string, string, bool){
// return data[index].ID,data[index].Name,false
// })
//
// GenChoices(map[string]int{
// "":len(data),
// }, func(group string,index int) (string, string, bool){
//
// return data[index].ID,data[index].Name,false
// })
func (f *Form) GenChoices(lenType interface{}, fnType interface{}) interface{} {
return GenChoices(lenType, fnType)
}
@@ -396,9 +402,9 @@ func (form *Form) unWindStructure(m interface{}, baseName string) ([]interface{}
options[opt] = struct{}{}
}
}
}
if _, ok := options["-"]; ok {
continue
if _, ok := options["-"]; ok {
continue
}
}
widget := common.TagVal(t, i, "form_widget")
var f fields.FieldInterface

View File

@@ -18,8 +18,8 @@ package forms
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"reflect"
"strconv"
@@ -41,7 +41,7 @@ func UnmarshalFile(filename string) (r *config.Config, err error) {
return
}
return common.GetOrSetCachedConfig(filename, func() (*config.Config, error) {
b, err := ioutil.ReadFile(filename)
b, err := os.ReadFile(filename)
if err != nil {
return nil, err
}

View File

@@ -8,7 +8,6 @@ import (
"context"
"encoding/base64"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
@@ -122,7 +121,7 @@ func (r *Request) SetMethod(method string) {
}
func (r *Request) Body() io.ReadCloser {
return ioutil.NopCloser(bytes.NewBuffer(r.context.PostBody()))
return io.NopCloser(bytes.NewBuffer(r.context.PostBody()))
}
// SetBody implements `engine.Request#SetBody` function.

View File

@@ -4,7 +4,6 @@ import (
"bufio"
"bytes"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
@@ -239,7 +238,7 @@ func (r *Response) StdResponseWriter() http.ResponseWriter {
func NewResponseWriter(w *http.Response) http.ResponseWriter {
b := bytes.NewBuffer(nil)
w.Body = ioutil.NopCloser(b)
w.Body = io.NopCloser(b)
return &ResponseWriter{
Response: w,
bytes: b,

View File

@@ -3,7 +3,6 @@ package standard
import (
"context"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"sync"
@@ -119,7 +118,7 @@ func (r *Request) SetBody(reader io.Reader) {
if readCloser, ok := reader.(io.ReadCloser); ok {
r.request.Body = readCloser
} else {
r.request.Body = ioutil.NopCloser(reader)
r.request.Body = io.NopCloser(reader)
}
}

View File

@@ -23,8 +23,8 @@ import (
"fmt"
"html"
"html/template"
"io/ioutil"
"net/http"
"os"
"runtime"
"strconv"
"strings"
@@ -378,7 +378,7 @@ func (p *PanicError) AddTrace(trace *Trace, content ...string) *PanicError {
}
func (p *PanicError) ExtractFileSnippets(file string, curLineNum int, index int) error {
content, err := ioutil.ReadFile(file)
content, err := os.ReadFile(file)
if err != nil {
return err
}

View File

@@ -20,7 +20,7 @@ package bindata
import (
"errors"
"io/ioutil"
"io"
"net/http"
"github.com/webx-top/echo/middleware/render/driver"
@@ -46,6 +46,6 @@ func (a *TmplManager) GetTemplate(fileName string) ([]byte, error) {
return nil, err
}
defer file.Close()
b, err := ioutil.ReadAll(file)
b, err := io.ReadAll(file)
return b, err
}

View File

@@ -4,7 +4,6 @@ import (
"bufio"
"compress/gzip"
"io"
"io/ioutil"
"net"
"net/http"
"strings"
@@ -135,7 +134,7 @@ func GzipWithConfig(config *GzipConfig) echo.MiddlewareFunc {
// nothing is written to body or error is returned.
// See issue #424, #407.
resp.SetWriter(rw)
w.Reset(ioutil.Discard)
w.Reset(io.Discard)
}
w.Close()
pool.Put(w)
@@ -150,7 +149,7 @@ func GzipWithConfig(config *GzipConfig) echo.MiddlewareFunc {
func gzipCompressPool(config *GzipConfig) sync.Pool {
return sync.Pool{
New: func() interface{} {
w, err := gzip.NewWriterLevel(ioutil.Discard, config.Level)
w, err := gzip.NewWriterLevel(io.Discard, config.Level)
if err != nil {
return err
}

View File

@@ -34,29 +34,9 @@ type (
// Optional. Default value "csrf".
ContextKey string `json:"context_key"`
// Name of the CSRF cookie. This cookie will store CSRF token.
// Optional. Default value "csrf".
CookieName string `json:"cookie_name"`
// Domain of the CSRF cookie.
// Optional. Default value none.
CookieDomain string `json:"cookie_domain"`
// Path of the CSRF cookie.
// Optional. Default value none.
CookiePath string `json:"cookie_path"`
// Max age (in seconds) of the CSRF cookie.
// Optional. Default value 86400 (24hr).
CookieMaxAge int `json:"cookie_max_age"`
// Indicates if CSRF cookie is secure.
// Optional. Default value false.
CookieSecure bool `json:"cookie_secure"`
// Indicates if CSRF cookie is HTTP only.
// Optional. Default value false.
CookieHTTPOnly bool `json:"cookie_http_only"`
// Name of the CSRF session. This session will store CSRF token.
// Optional. Default value "_csrf".
SessionName string `json:"session_name"`
}
// csrfTokenExtractor defines a function that takes `echo.Context` and returns
@@ -67,12 +47,11 @@ type (
var (
// DefaultCSRFConfig is the default CSRF middleware config.
DefaultCSRFConfig = CSRFConfig{
Skipper: echo.DefaultSkipper,
TokenLength: 32,
TokenLookup: "header:" + echo.HeaderXCSRFToken,
ContextKey: "csrf",
CookieName: "_csrf",
CookieMaxAge: 86400,
Skipper: echo.DefaultSkipper,
TokenLength: 32,
TokenLookup: "header:" + echo.HeaderXCSRFToken,
ContextKey: "csrf",
SessionName: "_csrf",
}
ErrCSRFTokenInvalid = errors.New("csrf token is invalid")
ErrCSRFTokenIsEmpty = errors.New("empty csrf token")
@@ -102,21 +81,19 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFuncd {
if config.ContextKey == "" {
config.ContextKey = DefaultCSRFConfig.ContextKey
}
if config.CookieName == "" {
config.CookieName = DefaultCSRFConfig.CookieName
if config.SessionName == "" {
config.SessionName = DefaultCSRFConfig.SessionName
}
if config.CookieMaxAge == 0 {
config.CookieMaxAge = DefaultCSRFConfig.CookieMaxAge
}
// Initialize
parts := strings.Split(config.TokenLookup, ":")
parts := strings.SplitN(config.TokenLookup, ":", 2)
extractor := csrfTokenFromHeader(parts[1])
switch parts[0] {
case "form":
extractor = csrfTokenFromForm(parts[1])
case "query":
extractor = csrfTokenFromQuery(parts[1])
case "any":
extractor = csrfTokenFromAny(parts[1])
}
return func(next echo.Handler) echo.HandlerFunc {
@@ -125,8 +102,7 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFuncd {
return next.Handle(c)
}
req := c.Request()
token := c.GetCookie(config.CookieName)
token, _ := c.Session().Get(config.SessionName).(string)
if len(token) == 0 {
// Generate token
token = random.String(config.TokenLength)
@@ -144,20 +120,12 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFuncd {
return echo.NewHTTPError(http.StatusForbidden, ErrCSRFTokenInvalid.Error()).SetRaw(ErrCSRFTokenInvalid)
}
}
// Set CSRF cookie
cookie := echo.NewCookie(config.CookieName, token, c.CookieOptions())
if len(config.CookiePath) > 0 {
cookie.Path = config.CookiePath
}
if len(config.CookieDomain) > 0 {
cookie.Domain = config.CookieDomain
}
echo.CookieMaxAge(cookie, config.CookieMaxAge)
cookie.Secure = config.CookieSecure
cookie.HttpOnly = config.CookieHTTPOnly
// Store CSRF
c.Session().Set(config.SessionName, token)
// Store token in the context
c.Set(config.ContextKey, token)
c.Internal().Set(config.ContextKey, token)
// Protect clients from caching the response
c.Response().Header().Add(echo.HeaderVary, echo.HeaderCookie)
@@ -199,6 +167,21 @@ func csrfTokenFromQuery(param string) csrfTokenExtractor {
}
}
func csrfTokenFromAny(key string) csrfTokenExtractor {
return func(c echo.Context) (string, error) {
token := c.Request().Header().Get(key)
if len(token) > 0 {
return token, nil
}
token = c.Form(key)
if len(token) > 0 {
return token, nil
}
token = c.Query(key)
return token, nil
}
}
func validateCSRFToken(token, clientToken string) bool {
return subtle.ConstantTimeCompare([]byte(token), []byte(clientToken)) == 1
}

View File

@@ -20,7 +20,6 @@ package manager
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -254,7 +253,7 @@ func (m *Manager) watch() error {
}
m.onChange(ev.Name, "file", "create")
if m.allowCached(ev.Name) {
content, err := ioutil.ReadFile(ev.Name)
content, err := os.ReadFile(ev.Name)
if err != nil {
m.Logger.Infof("loaded template %v failed: %v", ev.Name, err)
return
@@ -279,7 +278,7 @@ func (m *Manager) watch() error {
}
m.onChange(ev.Name, "file", "modify")
if m.allowCached(ev.Name) {
content, err := ioutil.ReadFile(ev.Name)
content, err := os.ReadFile(ev.Name)
if err != nil {
m.Logger.Errorf("reloaded template %v failed: %v", ev.Name, err)
return
@@ -324,7 +323,7 @@ func (m *Manager) cacheAll(rootDir string) error {
return nil
}
if _, ok := m.ignores[filepath.Base(f)]; !ok {
content, err := ioutil.ReadFile(f)
content, err := os.ReadFile(f)
if err != nil {
m.Logger.Debugf("load template %s error: %v", f, err)
return err
@@ -348,7 +347,7 @@ func (m *Manager) GetTemplate(tmpl string) ([]byte, error) {
return nil, err
}
if !m.allowCached(tmplPath) {
return ioutil.ReadFile(tmplPath)
return os.ReadFile(tmplPath)
}
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -356,7 +355,7 @@ func (m *Manager) GetTemplate(tmpl string) ([]byte, error) {
m.Logger.Debugf("load template %v from cache", tmplPath)
return content, nil
}
content, err := ioutil.ReadFile(tmplPath)
content, err := os.ReadFile(tmplPath)
if err != nil {
return nil, err
}
@@ -372,7 +371,7 @@ func (m *Manager) SetTemplate(tmpl string, content []byte) error {
}
m.mutex.Lock()
defer m.mutex.Unlock()
err = ioutil.WriteFile(tmplPath, content, 0666)
err = os.WriteFile(tmplPath, content, 0666)
if err != nil {
return err
}

View File

@@ -27,7 +27,7 @@ import (
"fmt"
htmlTpl "html/template"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
@@ -572,7 +572,7 @@ func (a *Standard) RawContent(tmpl string) (b []byte, e error) {
if a.TemplateMgr != nil {
b, e = a.TemplateMgr.GetTemplate(tmpl)
} else {
b, e = ioutil.ReadFile(tmpl)
b, e = os.ReadFile(tmpl)
}
if e != nil {
return

View File

@@ -2,7 +2,6 @@ package session
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"sync"
@@ -61,7 +60,7 @@ func registerSessionStore() error {
}
r := &cookieSecretKey{}
cookieSecretFile := filepath.Join(tempDir, `cookiesecret`)
b, err := ioutil.ReadFile(cookieSecretFile)
b, err := os.ReadFile(cookieSecretFile)
if err != nil {
if !os.IsNotExist(err) {
return err
@@ -75,7 +74,7 @@ func registerSessionStore() error {
if err != nil {
return err
}
err = ioutil.WriteFile(cookieSecretFile, b, os.ModePerm)
err = os.WriteFile(cookieSecretFile, b, os.ModePerm)
}
//==================================

4
vendor/modules.txt vendored
View File

@@ -564,7 +564,7 @@ github.com/chromedp/sysutil
# github.com/coreos/go-oidc v2.2.1+incompatible
## explicit
github.com/coreos/go-oidc
# github.com/coscms/forms v1.12.1
# github.com/coscms/forms v1.12.2
## explicit; go 1.16
github.com/coscms/forms
github.com/coscms/forms/common
@@ -1490,7 +1490,7 @@ github.com/webx-top/db/lib/reflectx
github.com/webx-top/db/lib/sqlbuilder
github.com/webx-top/db/mysql
github.com/webx-top/db/sqlite
# github.com/webx-top/echo v2.38.4+incompatible
# github.com/webx-top/echo v2.39.2+incompatible
## explicit
github.com/webx-top/echo
github.com/webx-top/echo/code