aboutsummaryrefslogtreecommitdiffstats
path: root/modules/web/middleware
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2021-01-30 16:55:53 +0800
committerGitHub <noreply@github.com>2021-01-30 10:55:53 +0200
commit5e20fd6dbf52ede60ed9ac7944db0d3f6769cf86 (patch)
tree519259f05f7e7fc4dd1df4da521e000ce469567b /modules/web/middleware
parent0e0424c8ecaf6fa3cdd1fcfc154f188014c63dd8 (diff)
downloadgitea-5e20fd6dbf52ede60ed9ac7944db0d3f6769cf86.tar.gz
gitea-5e20fd6dbf52ede60ed9ac7944db0d3f6769cf86.zip
Move middlewares to web/middleware (#14480)
Co-authored-by: 6543 <6543@obermui.de>
Diffstat (limited to 'modules/web/middleware')
-rw-r--r--modules/web/middleware/binding.go145
-rw-r--r--modules/web/middleware/cookie.go165
-rw-r--r--modules/web/middleware/data.go10
-rw-r--r--modules/web/middleware/flash.go68
-rw-r--r--modules/web/middleware/locale.go49
-rw-r--r--modules/web/middleware/request.go20
6 files changed, 457 insertions, 0 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/")
+}