diff options
Diffstat (limited to 'modules/middlewares')
-rw-r--r-- | modules/middlewares/binding.go | 145 | ||||
-rw-r--r-- | modules/middlewares/cookie.go | 61 | ||||
-rw-r--r-- | modules/middlewares/data.go | 10 | ||||
-rw-r--r-- | modules/middlewares/flash.go | 65 | ||||
-rw-r--r-- | modules/middlewares/locale.go | 8 | ||||
-rw-r--r-- | modules/middlewares/redis.go | 217 | ||||
-rw-r--r-- | modules/middlewares/virtual.go | 196 |
7 files changed, 286 insertions, 416 deletions
diff --git a/modules/middlewares/binding.go b/modules/middlewares/binding.go new file mode 100644 index 0000000000..1dabdbb62e --- /dev/null +++ b/modules/middlewares/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 middlewares + +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/middlewares/cookie.go b/modules/middlewares/cookie.go index 80d0e3b453..d18541833f 100644 --- a/modules/middlewares/cookie.go +++ b/modules/middlewares/cookie.go @@ -1,3 +1,4 @@ +// 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. @@ -12,6 +13,56 @@ import ( "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{ @@ -102,3 +153,13 @@ func SetCookie(resp http.ResponseWriter, name string, value string, others ...in 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/middlewares/data.go b/modules/middlewares/data.go new file mode 100644 index 0000000000..2690289362 --- /dev/null +++ b/modules/middlewares/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 middlewares + +// DataStore represents a data store +type DataStore interface { + GetData() map[string]interface{} +} diff --git a/modules/middlewares/flash.go b/modules/middlewares/flash.go new file mode 100644 index 0000000000..38217288e8 --- /dev/null +++ b/modules/middlewares/flash.go @@ -0,0 +1,65 @@ +// 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 middlewares + +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) { + 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/middlewares/locale.go b/modules/middlewares/locale.go index 98af890cfd..7cfba81bda 100644 --- a/modules/middlewares/locale.go +++ b/modules/middlewares/locale.go @@ -23,12 +23,14 @@ func Locale(resp http.ResponseWriter, req *http.Request) translation.Locale { // 2. Get language information from cookies. if len(lang) == 0 { ck, _ := req.Cookie("lang") - lang = ck.Value - hasCookie = true + if ck != nil { + lang = ck.Value + hasCookie = true + } } // Check again in case someone modify by purpose. - if !i18n.IsExist(lang) { + if lang != "" && !i18n.IsExist(lang) { lang = "" hasCookie = false } diff --git a/modules/middlewares/redis.go b/modules/middlewares/redis.go deleted file mode 100644 index ced1c1ee81..0000000000 --- a/modules/middlewares/redis.go +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2013 Beego Authors -// Copyright 2014 The Macaron Authors -// Copyright 2020 The Gitea Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package middlewares - -import ( - "fmt" - "sync" - "time" - - "code.gitea.io/gitea/modules/nosql" - - "gitea.com/go-chi/session" - "github.com/go-redis/redis/v7" -) - -// RedisStore represents a redis session store implementation. -// TODO: copied from modules/session/redis.go and should remove that one until macaron removed. -type RedisStore struct { - c redis.UniversalClient - prefix, sid string - duration time.Duration - lock sync.RWMutex - data map[interface{}]interface{} -} - -// NewRedisStore creates and returns a redis session store. -func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore { - return &RedisStore{ - c: c, - prefix: prefix, - sid: sid, - duration: dur, - data: kv, - } -} - -// Set sets value to given key in session. -func (s *RedisStore) Set(key, val interface{}) error { - s.lock.Lock() - defer s.lock.Unlock() - - s.data[key] = val - return nil -} - -// Get gets value by given key in session. -func (s *RedisStore) Get(key interface{}) interface{} { - s.lock.RLock() - defer s.lock.RUnlock() - - return s.data[key] -} - -// Delete delete a key from session. -func (s *RedisStore) Delete(key interface{}) error { - s.lock.Lock() - defer s.lock.Unlock() - - delete(s.data, key) - return nil -} - -// ID returns current session ID. -func (s *RedisStore) ID() string { - return s.sid -} - -// Release releases resource and save data to provider. -func (s *RedisStore) Release() error { - // Skip encoding if the data is empty - if len(s.data) == 0 { - return nil - } - - data, err := session.EncodeGob(s.data) - if err != nil { - return err - } - - return s.c.Set(s.prefix+s.sid, string(data), s.duration).Err() -} - -// Flush deletes all session data. -func (s *RedisStore) Flush() error { - s.lock.Lock() - defer s.lock.Unlock() - - s.data = make(map[interface{}]interface{}) - return nil -} - -// RedisProvider represents a redis session provider implementation. -type RedisProvider struct { - c redis.UniversalClient - duration time.Duration - prefix string -} - -// Init initializes redis session provider. -// configs: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,prefix=session; -func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) { - p.duration, err = time.ParseDuration(fmt.Sprintf("%ds", maxlifetime)) - if err != nil { - return err - } - - uri := nosql.ToRedisURI(configs) - - for k, v := range uri.Query() { - switch k { - case "prefix": - p.prefix = v[0] - } - } - - p.c = nosql.GetManager().GetRedisClient(uri.String()) - return p.c.Ping().Err() -} - -// Read returns raw session store by session ID. -func (p *RedisProvider) Read(sid string) (session.RawStore, error) { - psid := p.prefix + sid - if !p.Exist(sid) { - if err := p.c.Set(psid, "", p.duration).Err(); err != nil { - return nil, err - } - } - - var kv map[interface{}]interface{} - kvs, err := p.c.Get(psid).Result() - if err != nil { - return nil, err - } - if len(kvs) == 0 { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob([]byte(kvs)) - if err != nil { - return nil, err - } - } - - return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil -} - -// Exist returns true if session with given ID exists. -func (p *RedisProvider) Exist(sid string) bool { - v, err := p.c.Exists(p.prefix + sid).Result() - return err == nil && v == 1 -} - -// Destroy deletes a session by session ID. -func (p *RedisProvider) Destroy(sid string) error { - return p.c.Del(p.prefix + sid).Err() -} - -// Regenerate regenerates a session store from old session ID to new one. -func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) { - poldsid := p.prefix + oldsid - psid := p.prefix + sid - - if p.Exist(sid) { - return nil, fmt.Errorf("new sid '%s' already exists", sid) - } else if !p.Exist(oldsid) { - // Make a fake old session. - if err = p.c.Set(poldsid, "", p.duration).Err(); err != nil { - return nil, err - } - } - - if err = p.c.Rename(poldsid, psid).Err(); err != nil { - return nil, err - } - - var kv map[interface{}]interface{} - kvs, err := p.c.Get(psid).Result() - if err != nil { - return nil, err - } - - if len(kvs) == 0 { - kv = make(map[interface{}]interface{}) - } else { - kv, err = session.DecodeGob([]byte(kvs)) - if err != nil { - return nil, err - } - } - - return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil -} - -// Count counts and returns number of sessions. -func (p *RedisProvider) Count() int { - return int(p.c.DBSize().Val()) -} - -// GC calls GC to clean expired sessions. -func (*RedisProvider) GC() {} - -func init() { - session.Register("redis", &RedisProvider{}) -} diff --git a/modules/middlewares/virtual.go b/modules/middlewares/virtual.go deleted file mode 100644 index 70d780d65d..0000000000 --- a/modules/middlewares/virtual.go +++ /dev/null @@ -1,196 +0,0 @@ -// 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 middlewares - -import ( - "encoding/json" - "fmt" - "sync" - - "gitea.com/go-chi/session" - couchbase "gitea.com/go-chi/session/couchbase" - memcache "gitea.com/go-chi/session/memcache" - mysql "gitea.com/go-chi/session/mysql" - postgres "gitea.com/go-chi/session/postgres" -) - -// VirtualSessionProvider represents a shadowed session provider implementation. -// TODO: copied from modules/session/redis.go and should remove that one until macaron removed. -type VirtualSessionProvider struct { - lock sync.RWMutex - provider session.Provider -} - -// Init initializes the cookie session provider with given root path. -func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error { - var opts session.Options - if err := json.Unmarshal([]byte(config), &opts); err != nil { - return err - } - // Note that these options are unprepared so we can't just use NewManager here. - // Nor can we access the provider map in session. - // So we will just have to do this by hand. - // This is only slightly more wrong than modules/setting/session.go:23 - switch opts.Provider { - case "memory": - o.provider = &session.MemProvider{} - case "file": - o.provider = &session.FileProvider{} - case "redis": - o.provider = &RedisProvider{} - case "mysql": - o.provider = &mysql.MysqlProvider{} - case "postgres": - o.provider = &postgres.PostgresProvider{} - case "couchbase": - o.provider = &couchbase.CouchbaseProvider{} - case "memcache": - o.provider = &memcache.MemcacheProvider{} - default: - return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider) - } - return o.provider.Init(gclifetime, opts.ProviderConfig) -} - -// Read returns raw session store by session ID. -func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) { - o.lock.RLock() - defer o.lock.RUnlock() - if o.provider.Exist(sid) { - return o.provider.Read(sid) - } - kv := make(map[interface{}]interface{}) - kv["_old_uid"] = "0" - return NewVirtualStore(o, sid, kv), nil -} - -// Exist returns true if session with given ID exists. -func (o *VirtualSessionProvider) Exist(sid string) bool { - return true -} - -// Destroy deletes a session by session ID. -func (o *VirtualSessionProvider) Destroy(sid string) error { - o.lock.Lock() - defer o.lock.Unlock() - return o.provider.Destroy(sid) -} - -// Regenerate regenerates a session store from old session ID to new one. -func (o *VirtualSessionProvider) Regenerate(oldsid, sid string) (session.RawStore, error) { - o.lock.Lock() - defer o.lock.Unlock() - return o.provider.Regenerate(oldsid, sid) -} - -// Count counts and returns number of sessions. -func (o *VirtualSessionProvider) Count() int { - o.lock.RLock() - defer o.lock.RUnlock() - return o.provider.Count() -} - -// GC calls GC to clean expired sessions. -func (o *VirtualSessionProvider) GC() { - o.provider.GC() -} - -func init() { - session.Register("VirtualSession", &VirtualSessionProvider{}) -} - -// VirtualStore represents a virtual session store implementation. -type VirtualStore struct { - p *VirtualSessionProvider - sid string - lock sync.RWMutex - data map[interface{}]interface{} - released bool -} - -// NewVirtualStore creates and returns a virtual session store. -func NewVirtualStore(p *VirtualSessionProvider, sid string, kv map[interface{}]interface{}) *VirtualStore { - return &VirtualStore{ - p: p, - sid: sid, - data: kv, - } -} - -// Set sets value to given key in session. -func (s *VirtualStore) Set(key, val interface{}) error { - s.lock.Lock() - defer s.lock.Unlock() - - s.data[key] = val - return nil -} - -// Get gets value by given key in session. -func (s *VirtualStore) Get(key interface{}) interface{} { - s.lock.RLock() - defer s.lock.RUnlock() - - return s.data[key] -} - -// Delete delete a key from session. -func (s *VirtualStore) Delete(key interface{}) error { - s.lock.Lock() - defer s.lock.Unlock() - - delete(s.data, key) - return nil -} - -// ID returns current session ID. -func (s *VirtualStore) ID() string { - return s.sid -} - -// Release releases resource and save data to provider. -func (s *VirtualStore) Release() error { - s.lock.Lock() - defer s.lock.Unlock() - // Now need to lock the provider - s.p.lock.Lock() - defer s.p.lock.Unlock() - if oldUID, ok := s.data["_old_uid"]; (ok && (oldUID != "0" || len(s.data) > 1)) || (!ok && len(s.data) > 0) { - // Now ensure that we don't exist! - realProvider := s.p.provider - - if !s.released && realProvider.Exist(s.sid) { - // This is an error! - return fmt.Errorf("new sid '%s' already exists", s.sid) - } - realStore, err := realProvider.Read(s.sid) - if err != nil { - return err - } - if err := realStore.Flush(); err != nil { - return err - } - for key, value := range s.data { - if err := realStore.Set(key, value); err != nil { - return err - } - } - err = realStore.Release() - if err == nil { - s.released = true - } - return err - } - return nil -} - -// Flush deletes all session data. -func (s *VirtualStore) Flush() error { - s.lock.Lock() - defer s.lock.Unlock() - - s.data = make(map[interface{}]interface{}) - return nil -} |