summaryrefslogtreecommitdiffstats
path: root/modules/middlewares
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2021-01-05 21:05:40 +0800
committerGitHub <noreply@github.com>2021-01-05 21:05:40 +0800
commit15a475b7dbcf7923d9518dff7764b20e404eb774 (patch)
tree8789f1f82c5e41345b442df4e58120bdd5f8bade /modules/middlewares
parent126c9331d6d8789563fae5d5bac2196d63fee0e8 (diff)
downloadgitea-15a475b7dbcf7923d9518dff7764b20e404eb774.tar.gz
gitea-15a475b7dbcf7923d9518dff7764b20e404eb774.zip
Fix recovery middleware to render gitea style page. (#13857)
* Some changes to fix recovery * Move Recovery to middlewares * Remove trace code * Fix lint * add session middleware and remove dependent on macaron for sso * Fix panic 500 page rendering * Fix bugs * Fix fmt * Fix vendor * recover unnecessary change * Fix lint and addd some comments about the copied codes. * Use util.StatDir instead of com.StatDir Co-authored-by: 6543 <6543@obermui.de>
Diffstat (limited to 'modules/middlewares')
-rw-r--r--modules/middlewares/cookie.go104
-rw-r--r--modules/middlewares/locale.go49
-rw-r--r--modules/middlewares/redis.go217
-rw-r--r--modules/middlewares/virtual.go196
4 files changed, 566 insertions, 0 deletions
diff --git a/modules/middlewares/cookie.go b/modules/middlewares/cookie.go
new file mode 100644
index 0000000000..80d0e3b453
--- /dev/null
+++ b/modules/middlewares/cookie.go
@@ -0,0 +1,104 @@
+// 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/http"
+ "net/url"
+ "time"
+
+ "code.gitea.io/gitea/modules/setting"
+)
+
+// 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())
+}
diff --git a/modules/middlewares/locale.go b/modules/middlewares/locale.go
new file mode 100644
index 0000000000..98af890cfd
--- /dev/null
+++ b/modules/middlewares/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 middlewares
+
+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 {
+ hasCookie := false
+
+ // 1. Check URL arguments.
+ lang := req.URL.Query().Get("lang")
+
+ // 2. Get language information from cookies.
+ if len(lang) == 0 {
+ ck, _ := req.Cookie("lang")
+ lang = ck.Value
+ hasCookie = true
+ }
+
+ // Check again in case someone modify by purpose.
+ if !i18n.IsExist(lang) {
+ lang = ""
+ hasCookie = 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 !hasCookie {
+ req.AddCookie(NewCookie("lang", lang, 1<<31-1))
+ }
+
+ return translation.NewLocale(lang)
+}
diff --git a/modules/middlewares/redis.go b/modules/middlewares/redis.go
new file mode 100644
index 0000000000..ced1c1ee81
--- /dev/null
+++ b/modules/middlewares/redis.go
@@ -0,0 +1,217 @@
+// 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
new file mode 100644
index 0000000000..70d780d65d
--- /dev/null
+++ b/modules/middlewares/virtual.go
@@ -0,0 +1,196 @@
+// 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
+}