diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2023-04-27 14:06:45 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-27 02:06:45 -0400 |
commit | 92fd3fc4fd369b6a8c0a022a32a80dec2340223a (patch) | |
tree | c721a4988b5ec250a029b19274ef1c2dc4c19faa /modules | |
parent | 1c875ef5bef206b8214c33437f65af929967df46 (diff) | |
download | gitea-92fd3fc4fd369b6a8c0a022a32a80dec2340223a.tar.gz gitea-92fd3fc4fd369b6a8c0a022a32a80dec2340223a.zip |
Refactor "route" related code, fix Safari cookie bug (#24330)
Fix #24176
Clean some misuses of route package, clean some legacy FIXMEs
---------
Co-authored-by: Giteabot <teabot@gitea.io>
Diffstat (limited to 'modules')
-rw-r--r-- | modules/context/api.go | 1 | ||||
-rw-r--r-- | modules/context/context.go | 20 | ||||
-rw-r--r-- | modules/context/context_test.go | 40 | ||||
-rw-r--r-- | modules/web/handler.go | 38 | ||||
-rw-r--r-- | modules/web/route.go | 20 |
5 files changed, 75 insertions, 44 deletions
diff --git a/modules/context/api.go b/modules/context/api.go index d405c9972b..ae245ec1cb 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -343,6 +343,7 @@ func RepoRefForAPI(next http.Handler) http.Handler { } ctx.Repo.Commit = commit ctx.Repo.TreePath = ctx.Params("*") + next.ServeHTTP(w, req) return } diff --git a/modules/context/context.go b/modules/context/context.go index f262c7cce7..702da8a965 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -446,6 +446,17 @@ func (ctx *Context) JSON(status int, content interface{}) { } } +func removeSessionCookieHeader(w http.ResponseWriter) { + cookies := w.Header()["Set-Cookie"] + w.Header().Del("Set-Cookie") + for _, cookie := range cookies { + if strings.HasPrefix(cookie, setting.SessionConfig.CookieName+"=") { + continue + } + w.Header().Add("Set-Cookie", cookie) + } +} + // Redirect redirects the request func (ctx *Context) Redirect(location string, status ...int) { code := http.StatusSeeOther @@ -453,6 +464,15 @@ func (ctx *Context) Redirect(location string, status ...int) { code = status[0] } + if strings.Contains(location, "://") || strings.HasPrefix(location, "//") { + // Some browsers (Safari) have buggy behavior for Cookie + Cache + External Redirection, eg: /my-path => https://other/path + // 1. the first request to "/my-path" contains cookie + // 2. some time later, the request to "/my-path" doesn't contain cookie (caused by Prevent web tracking) + // 3. Gitea's Sessioner doesn't see the session cookie, so it generates a new session id, and returns it to browser + // 4. then the browser accepts the empty session, then the user is logged out + // So in this case, we should remove the session cookie from the response header + removeSessionCookieHeader(ctx.Resp) + } http.Redirect(ctx.Resp, ctx.Req, location, code) } diff --git a/modules/context/context_test.go b/modules/context/context_test.go new file mode 100644 index 0000000000..e1460c1fd7 --- /dev/null +++ b/modules/context/context_test.go @@ -0,0 +1,40 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package context + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + + "github.com/stretchr/testify/assert" +) + +type mockResponseWriter struct { + header http.Header +} + +func (m *mockResponseWriter) Header() http.Header { + return m.header +} + +func (m *mockResponseWriter) Write(bytes []byte) (int, error) { + panic("implement me") +} + +func (m *mockResponseWriter) WriteHeader(statusCode int) { + panic("implement me") +} + +func TestRemoveSessionCookieHeader(t *testing.T) { + w := &mockResponseWriter{} + w.header = http.Header{} + w.header.Add("Set-Cookie", (&http.Cookie{Name: setting.SessionConfig.CookieName, Value: "foo"}).String()) + w.header.Add("Set-Cookie", (&http.Cookie{Name: "other", Value: "bar"}).String()) + assert.Len(t, w.Header().Values("Set-Cookie"), 2) + removeSessionCookieHeader(w) + assert.Len(t, w.Header().Values("Set-Cookie"), 1) + assert.Contains(t, "other=bar", w.Header().Get("Set-Cookie")) +} diff --git a/modules/web/handler.go b/modules/web/handler.go index 8a44673f12..bfb83820c8 100644 --- a/modules/web/handler.go +++ b/modules/web/handler.go @@ -8,7 +8,6 @@ import ( "fmt" "net/http" "reflect" - "strings" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/web/routing" @@ -131,16 +130,22 @@ func hasResponseBeenWritten(argsIn []reflect.Value) bool { // toHandlerProvider converts a handler to a handler provider // A handler provider is a function that takes a "next" http.Handler, it can be used as a middleware func toHandlerProvider(handler any) func(next http.Handler) http.Handler { - if hp, ok := handler.(func(next http.Handler) http.Handler); ok { - return hp - } - funcInfo := routing.GetFuncInfo(handler) fn := reflect.ValueOf(handler) if fn.Type().Kind() != reflect.Func { panic(fmt.Sprintf("handler must be a function, but got %s", fn.Type())) } + if hp, ok := handler.(func(next http.Handler) http.Handler); ok { + return func(next http.Handler) http.Handler { + h := hp(next) // this handle could be dynamically generated, so we can't use it for debug info + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + routing.UpdateFuncInfo(req.Context(), funcInfo) + h.ServeHTTP(resp, req) + }) + } + } + provider := func(next http.Handler) http.Handler { return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) { // wrap the response writer to check whether the response has been written @@ -175,26 +180,3 @@ func toHandlerProvider(handler any) func(next http.Handler) http.Handler { provider(nil).ServeHTTP(nil, nil) // do a pre-check to make sure all arguments and return values are supported return provider } - -// MiddlewareWithPrefix wraps a handler function at a prefix, and make it as a middleware -// TODO: this design is incorrect, the asset handler should not be a middleware -func MiddlewareWithPrefix(pathPrefix string, middleware func(handler http.Handler) http.Handler, handlerFunc http.HandlerFunc) func(next http.Handler) http.Handler { - funcInfo := routing.GetFuncInfo(handlerFunc) - handler := http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { - routing.UpdateFuncInfo(req.Context(), funcInfo) - handlerFunc(resp, req) - }) - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { - if !strings.HasPrefix(req.URL.Path, pathPrefix) { - next.ServeHTTP(resp, req) - return - } - if middleware != nil { - middleware(handler).ServeHTTP(resp, req) - } else { - handler.ServeHTTP(resp, req) - } - }) - } -} diff --git a/modules/web/route.go b/modules/web/route.go index fe35880849..6fd60f4ca7 100644 --- a/modules/web/route.go +++ b/modules/web/route.go @@ -44,23 +44,13 @@ type Route struct { // NewRoute creates a new route func NewRoute() *Route { r := chi.NewRouter() - return &Route{ - R: r, - curGroupPrefix: "", - curMiddlewares: []interface{}{}, - } + return &Route{R: r} } // Use supports two middlewares func (r *Route) Use(middlewares ...interface{}) { - if r.curGroupPrefix != "" { - // FIXME: this behavior is incorrect, should use "With" instead - r.curMiddlewares = append(r.curMiddlewares, middlewares...) - } else { - // FIXME: another misuse, the "Use" with empty middlewares is called after "Mount" - for _, m := range middlewares { - r.R.Use(toHandlerProvider(m)) - } + for _, m := range middlewares { + r.R.Use(toHandlerProvider(m)) } } @@ -116,9 +106,7 @@ func (r *Route) Methods(method, pattern string, h []any) { // Mount attaches another Route along ./pattern/* func (r *Route) Mount(pattern string, subR *Route) { - middlewares := make([]interface{}, len(r.curMiddlewares)) - copy(middlewares, r.curMiddlewares) - subR.Use(middlewares...) + subR.Use(r.curMiddlewares...) r.R.Mount(r.getPattern(pattern), subR.R) } |