diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2021-01-30 16:55:53 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-30 10:55:53 +0200 |
commit | 5e20fd6dbf52ede60ed9ac7944db0d3f6769cf86 (patch) | |
tree | 519259f05f7e7fc4dd1df4da521e000ce469567b /modules/web | |
parent | 0e0424c8ecaf6fa3cdd1fcfc154f188014c63dd8 (diff) | |
download | gitea-5e20fd6dbf52ede60ed9ac7944db0d3f6769cf86.tar.gz gitea-5e20fd6dbf52ede60ed9ac7944db0d3f6769cf86.zip |
Move middlewares to web/middleware (#14480)
Co-authored-by: 6543 <6543@obermui.de>
Diffstat (limited to 'modules/web')
-rw-r--r-- | modules/web/middleware/binding.go | 145 | ||||
-rw-r--r-- | modules/web/middleware/cookie.go | 165 | ||||
-rw-r--r-- | modules/web/middleware/data.go | 10 | ||||
-rw-r--r-- | modules/web/middleware/flash.go | 68 | ||||
-rw-r--r-- | modules/web/middleware/locale.go | 49 | ||||
-rw-r--r-- | modules/web/middleware/request.go | 20 | ||||
-rw-r--r-- | modules/web/route.go | 8 |
7 files changed, 461 insertions, 4 deletions
diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go new file mode 100644 index 0000000000..cd418c9792 --- /dev/null +++ b/modules/web/middleware/binding.go @@ -0,0 +1,145 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package middleware + +import ( + "reflect" + "strings" + + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/modules/validation" + + "gitea.com/go-chi/binding" + "github.com/unknwon/com" +) + +// Form form binding interface +type Form interface { + binding.Validator +} + +func init() { + binding.SetNameMapper(com.ToSnakeCase) +} + +// AssignForm assign form values back to the template data. +func AssignForm(form interface{}, data map[string]interface{}) { + typ := reflect.TypeOf(form) + val := reflect.ValueOf(form) + + for typ.Kind() == reflect.Ptr { + typ = typ.Elem() + val = val.Elem() + } + + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + + fieldName := field.Tag.Get("form") + // Allow ignored fields in the struct + if fieldName == "-" { + continue + } else if len(fieldName) == 0 { + fieldName = com.ToSnakeCase(field.Name) + } + + data[fieldName] = val.Field(i).Interface() + } +} + +func getRuleBody(field reflect.StructField, prefix string) string { + for _, rule := range strings.Split(field.Tag.Get("binding"), ";") { + if strings.HasPrefix(rule, prefix) { + return rule[len(prefix) : len(rule)-1] + } + } + return "" +} + +// GetSize get size int form tag +func GetSize(field reflect.StructField) string { + return getRuleBody(field, "Size(") +} + +// GetMinSize get minimal size in form tag +func GetMinSize(field reflect.StructField) string { + return getRuleBody(field, "MinSize(") +} + +// GetMaxSize get max size in form tag +func GetMaxSize(field reflect.StructField) string { + return getRuleBody(field, "MaxSize(") +} + +// GetInclude get include in form tag +func GetInclude(field reflect.StructField) string { + return getRuleBody(field, "Include(") +} + +// Validate validate TODO: +func Validate(errs binding.Errors, data map[string]interface{}, f Form, l translation.Locale) binding.Errors { + if errs.Len() == 0 { + return errs + } + + data["HasError"] = true + // If the field with name errs[0].FieldNames[0] is not found in form + // somehow, some code later on will panic on Data["ErrorMsg"].(string). + // So initialize it to some default. + data["ErrorMsg"] = l.Tr("form.unknown_error") + AssignForm(f, data) + + typ := reflect.TypeOf(f) + val := reflect.ValueOf(f) + + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + val = val.Elem() + } + + if field, ok := typ.FieldByName(errs[0].FieldNames[0]); ok { + fieldName := field.Tag.Get("form") + if fieldName != "-" { + data["Err_"+field.Name] = true + + trName := field.Tag.Get("locale") + if len(trName) == 0 { + trName = l.Tr("form." + field.Name) + } else { + trName = l.Tr(trName) + } + + switch errs[0].Classification { + case binding.ERR_REQUIRED: + data["ErrorMsg"] = trName + l.Tr("form.require_error") + case binding.ERR_ALPHA_DASH: + data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_error") + case binding.ERR_ALPHA_DASH_DOT: + data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_error") + case validation.ErrGitRefName: + data["ErrorMsg"] = trName + l.Tr("form.git_ref_name_error") + case binding.ERR_SIZE: + data["ErrorMsg"] = trName + l.Tr("form.size_error", GetSize(field)) + case binding.ERR_MIN_SIZE: + data["ErrorMsg"] = trName + l.Tr("form.min_size_error", GetMinSize(field)) + case binding.ERR_MAX_SIZE: + data["ErrorMsg"] = trName + l.Tr("form.max_size_error", GetMaxSize(field)) + case binding.ERR_EMAIL: + data["ErrorMsg"] = trName + l.Tr("form.email_error") + case binding.ERR_URL: + data["ErrorMsg"] = trName + l.Tr("form.url_error") + case binding.ERR_INCLUDE: + data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field)) + case validation.ErrGlobPattern: + data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message) + default: + data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification + } + return errs + } + } + return errs +} diff --git a/modules/web/middleware/cookie.go b/modules/web/middleware/cookie.go new file mode 100644 index 0000000000..83e365f9c4 --- /dev/null +++ b/modules/web/middleware/cookie.go @@ -0,0 +1,165 @@ +// Copyright 2020 The Macaron Authors +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package middleware + +import ( + "net/http" + "net/url" + "time" + + "code.gitea.io/gitea/modules/setting" +) + +// MaxAge sets the maximum age for a provided cookie +func MaxAge(maxAge int) func(*http.Cookie) { + return func(c *http.Cookie) { + c.MaxAge = maxAge + } +} + +// Path sets the path for a provided cookie +func Path(path string) func(*http.Cookie) { + return func(c *http.Cookie) { + c.Path = path + } +} + +// Domain sets the domain for a provided cookie +func Domain(domain string) func(*http.Cookie) { + return func(c *http.Cookie) { + c.Domain = domain + } +} + +// Secure sets the secure setting for a provided cookie +func Secure(secure bool) func(*http.Cookie) { + return func(c *http.Cookie) { + c.Secure = secure + } +} + +// HTTPOnly sets the HttpOnly setting for a provided cookie +func HTTPOnly(httpOnly bool) func(*http.Cookie) { + return func(c *http.Cookie) { + c.HttpOnly = httpOnly + } +} + +// Expires sets the expires and rawexpires for a provided cookie +func Expires(expires time.Time) func(*http.Cookie) { + return func(c *http.Cookie) { + c.Expires = expires + c.RawExpires = expires.Format(time.UnixDate) + } +} + +// SameSite sets the SameSite for a provided cookie +func SameSite(sameSite http.SameSite) func(*http.Cookie) { + return func(c *http.Cookie) { + c.SameSite = sameSite + } +} + +// NewCookie creates a cookie +func NewCookie(name, value string, maxAge int) *http.Cookie { + return &http.Cookie{ + Name: name, + Value: value, + HttpOnly: true, + Path: setting.SessionConfig.CookiePath, + Domain: setting.SessionConfig.Domain, + MaxAge: maxAge, + Secure: setting.SessionConfig.Secure, + } +} + +// SetCookie set the cookies +// TODO: Copied from gitea.com/macaron/macaron and should be improved after macaron removed. +func SetCookie(resp http.ResponseWriter, name string, value string, others ...interface{}) { + cookie := http.Cookie{} + cookie.Name = name + cookie.Value = url.QueryEscape(value) + + if len(others) > 0 { + switch v := others[0].(type) { + case int: + cookie.MaxAge = v + case int64: + cookie.MaxAge = int(v) + case int32: + cookie.MaxAge = int(v) + case func(*http.Cookie): + v(&cookie) + } + } + + cookie.Path = "/" + if len(others) > 1 { + if v, ok := others[1].(string); ok && len(v) > 0 { + cookie.Path = v + } else if v, ok := others[1].(func(*http.Cookie)); ok { + v(&cookie) + } + } + + if len(others) > 2 { + if v, ok := others[2].(string); ok && len(v) > 0 { + cookie.Domain = v + } else if v, ok := others[1].(func(*http.Cookie)); ok { + v(&cookie) + } + } + + if len(others) > 3 { + switch v := others[3].(type) { + case bool: + cookie.Secure = v + case func(*http.Cookie): + v(&cookie) + default: + if others[3] != nil { + cookie.Secure = true + } + } + } + + if len(others) > 4 { + if v, ok := others[4].(bool); ok && v { + cookie.HttpOnly = true + } else if v, ok := others[1].(func(*http.Cookie)); ok { + v(&cookie) + } + } + + if len(others) > 5 { + if v, ok := others[5].(time.Time); ok { + cookie.Expires = v + cookie.RawExpires = v.Format(time.UnixDate) + } else if v, ok := others[1].(func(*http.Cookie)); ok { + v(&cookie) + } + } + + if len(others) > 6 { + for _, other := range others[6:] { + if v, ok := other.(func(*http.Cookie)); ok { + v(&cookie) + } + } + } + + resp.Header().Add("Set-Cookie", cookie.String()) +} + +// GetCookie returns given cookie value from request header. +func GetCookie(req *http.Request, name string) string { + cookie, err := req.Cookie(name) + if err != nil { + return "" + } + val, _ := url.QueryUnescape(cookie.Value) + return val +} diff --git a/modules/web/middleware/data.go b/modules/web/middleware/data.go new file mode 100644 index 0000000000..7de686498a --- /dev/null +++ b/modules/web/middleware/data.go @@ -0,0 +1,10 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package middleware + +// DataStore represents a data store +type DataStore interface { + GetData() map[string]interface{} +} diff --git a/modules/web/middleware/flash.go b/modules/web/middleware/flash.go new file mode 100644 index 0000000000..cd9b089b94 --- /dev/null +++ b/modules/web/middleware/flash.go @@ -0,0 +1,68 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package middleware + +import "net/url" + +// flashes enumerates all the flash types +const ( + SuccessFlash = "SuccessMsg" + ErrorFlash = "ErrorMsg" + WarnFlash = "WarningMsg" + InfoFlash = "InfoMsg" +) + +var ( + // FlashNow FIXME: + FlashNow bool +) + +// Flash represents a one time data transfer between two requests. +type Flash struct { + DataStore + url.Values + ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string +} + +func (f *Flash) set(name, msg string, current ...bool) { + if f.Values == nil { + f.Values = make(map[string][]string) + } + isShow := false + if (len(current) == 0 && FlashNow) || + (len(current) > 0 && current[0]) { + isShow = true + } + + if isShow { + f.GetData()["Flash"] = f + } else { + f.Set(name, msg) + } +} + +// Error sets error message +func (f *Flash) Error(msg string, current ...bool) { + f.ErrorMsg = msg + f.set("error", msg, current...) +} + +// Warning sets warning message +func (f *Flash) Warning(msg string, current ...bool) { + f.WarningMsg = msg + f.set("warning", msg, current...) +} + +// Info sets info message +func (f *Flash) Info(msg string, current ...bool) { + f.InfoMsg = msg + f.set("info", msg, current...) +} + +// Success sets success message +func (f *Flash) Success(msg string, current ...bool) { + f.SuccessMsg = msg + f.set("success", msg, current...) +} diff --git a/modules/web/middleware/locale.go b/modules/web/middleware/locale.go new file mode 100644 index 0000000000..449095f611 --- /dev/null +++ b/modules/web/middleware/locale.go @@ -0,0 +1,49 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package middleware + +import ( + "net/http" + + "code.gitea.io/gitea/modules/translation" + + "github.com/unknwon/i18n" + "golang.org/x/text/language" +) + +// Locale handle locale +func Locale(resp http.ResponseWriter, req *http.Request) translation.Locale { + // 1. Check URL arguments. + lang := req.URL.Query().Get("lang") + var changeLang = lang != "" + + // 2. Get language information from cookies. + if len(lang) == 0 { + ck, _ := req.Cookie("lang") + if ck != nil { + lang = ck.Value + } + } + + // Check again in case someone modify by purpose. + if lang != "" && !i18n.IsExist(lang) { + lang = "" + changeLang = false + } + + // 3. Get language information from 'Accept-Language'. + // The first element in the list is chosen to be the default language automatically. + if len(lang) == 0 { + tags, _, _ := language.ParseAcceptLanguage(req.Header.Get("Accept-Language")) + tag, _, _ := translation.Match(tags...) + lang = tag.String() + } + + if changeLang { + SetCookie(resp, "lang", lang, 1<<31-1) + } + + return translation.NewLocale(lang) +} diff --git a/modules/web/middleware/request.go b/modules/web/middleware/request.go new file mode 100644 index 0000000000..f620da5eea --- /dev/null +++ b/modules/web/middleware/request.go @@ -0,0 +1,20 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package middleware + +import ( + "net/http" + "strings" +) + +// IsAPIPath returns true if the specified URL is an API path +func IsAPIPath(req *http.Request) bool { + return strings.HasPrefix(req.URL.Path, "/api/") +} + +// IsInternalPath returns true if the specified URL is an internal API path +func IsInternalPath(req *http.Request) bool { + return strings.HasPrefix(req.URL.Path, "/api/internal/") +} diff --git a/modules/web/route.go b/modules/web/route.go index 701b3beed2..59e22c5be1 100644 --- a/modules/web/route.go +++ b/modules/web/route.go @@ -11,7 +11,7 @@ import ( "strings" "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/middlewares" + "code.gitea.io/gitea/modules/web/middleware" "gitea.com/go-chi/binding" "github.com/go-chi/chi" @@ -120,17 +120,17 @@ func Bind(obj interface{}) http.HandlerFunc { var theObj = reflect.New(tp).Interface() // create a new form obj for every request but not use obj directly binding.Bind(ctx.Req, theObj) SetForm(ctx, theObj) - middlewares.AssignForm(theObj, ctx.Data) + middleware.AssignForm(theObj, ctx.Data) }) } // SetForm set the form object -func SetForm(data middlewares.DataStore, obj interface{}) { +func SetForm(data middleware.DataStore, obj interface{}) { data.GetData()["__form"] = obj } // GetForm returns the validate form information -func GetForm(data middlewares.DataStore) interface{} { +func GetForm(data middleware.DataStore) interface{} { return data.GetData()["__form"] } |