summaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
authorwxiaoguang <wxiaoguang@gmail.com>2023-04-27 14:06:45 +0800
committerGitHub <noreply@github.com>2023-04-27 02:06:45 -0400
commit92fd3fc4fd369b6a8c0a022a32a80dec2340223a (patch)
treec721a4988b5ec250a029b19274ef1c2dc4c19faa /modules
parent1c875ef5bef206b8214c33437f65af929967df46 (diff)
downloadgitea-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.go1
-rw-r--r--modules/context/context.go20
-rw-r--r--modules/context/context_test.go40
-rw-r--r--modules/web/handler.go38
-rw-r--r--modules/web/route.go20
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)
}