mirror of https://github.com/go-gitea/gitea.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
136 lines
4.9 KiB
136 lines
4.9 KiB
// Copyright 2021 The Gitea Authors. All rights reserved. |
|
// SPDX-License-Identifier: MIT |
|
|
|
package common |
|
|
|
import ( |
|
"fmt" |
|
"log" |
|
"net/http" |
|
"strings" |
|
|
|
"code.gitea.io/gitea/modules/cache" |
|
"code.gitea.io/gitea/modules/gtprof" |
|
"code.gitea.io/gitea/modules/httplib" |
|
"code.gitea.io/gitea/modules/reqctx" |
|
"code.gitea.io/gitea/modules/setting" |
|
"code.gitea.io/gitea/modules/web/routing" |
|
"code.gitea.io/gitea/services/context" |
|
|
|
"gitea.com/go-chi/session" |
|
"github.com/chi-middleware/proxy" |
|
"github.com/go-chi/chi/v5" |
|
) |
|
|
|
// ProtocolMiddlewares returns HTTP protocol related middlewares, and it provides a global panic recovery |
|
func ProtocolMiddlewares() (handlers []any) { |
|
// the order is important |
|
handlers = append(handlers, ChiRoutePathHandler()) // make sure chi has correct paths |
|
handlers = append(handlers, RequestContextHandler()) // prepare the context and panic recovery |
|
|
|
if setting.ReverseProxyLimit > 0 && len(setting.ReverseProxyTrustedProxies) > 0 { |
|
handlers = append(handlers, ForwardedHeadersHandler(setting.ReverseProxyLimit, setting.ReverseProxyTrustedProxies)) |
|
} |
|
|
|
if setting.IsRouteLogEnabled() { |
|
handlers = append(handlers, routing.NewLoggerHandler()) |
|
} |
|
|
|
if setting.IsAccessLogEnabled() { |
|
handlers = append(handlers, context.AccessLogger()) |
|
} |
|
|
|
return handlers |
|
} |
|
|
|
func RequestContextHandler() func(h http.Handler) http.Handler { |
|
return func(next http.Handler) http.Handler { |
|
return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) { |
|
// this response writer might not be the same as the one in context.Base.Resp |
|
// because there might be a "gzip writer" in the middle, so the "written size" here is the compressed size |
|
respWriter := context.WrapResponseWriter(respOrig) |
|
|
|
profDesc := fmt.Sprintf("HTTP: %s %s", req.Method, req.RequestURI) |
|
ctx, finished := reqctx.NewRequestContext(req.Context(), profDesc) |
|
defer finished() |
|
|
|
ctx, span := gtprof.GetTracer().Start(ctx, gtprof.TraceSpanHTTP) |
|
req = req.WithContext(ctx) |
|
defer func() { |
|
chiCtx := chi.RouteContext(req.Context()) |
|
span.SetAttributeString(gtprof.TraceAttrHTTPRoute, chiCtx.RoutePattern()) |
|
span.End() |
|
}() |
|
|
|
defer func() { |
|
if err := recover(); err != nil { |
|
RenderPanicErrorPage(respWriter, req, err) // it should never panic |
|
} |
|
}() |
|
|
|
ds := reqctx.GetRequestDataStore(ctx) |
|
req = req.WithContext(cache.WithCacheContext(ctx)) |
|
ds.SetContextValue(httplib.RequestContextKey, req) |
|
ds.AddCleanUp(func() { |
|
// TODO: GOLANG-HTTP-TMPDIR: Golang saves the uploaded files to temp directory (TMPDIR) when parsing multipart-form. |
|
// The "req" might have changed due to the new "req.WithContext" calls |
|
// For example: in NewBaseContext, a new "req" with context is created, and the multipart-form is parsed there. |
|
// So we always use the latest "req" from the data store. |
|
ctxReq := ds.GetContextValue(httplib.RequestContextKey).(*http.Request) |
|
if ctxReq.MultipartForm != nil { |
|
_ = ctxReq.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory |
|
} |
|
}) |
|
next.ServeHTTP(respWriter, req) |
|
}) |
|
} |
|
} |
|
|
|
func ChiRoutePathHandler() func(h http.Handler) http.Handler { |
|
// make sure chi uses EscapedPath(RawPath) as RoutePath, then "%2f" could be handled correctly |
|
return func(next http.Handler) http.Handler { |
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { |
|
chiCtx := chi.RouteContext(req.Context()) |
|
if req.URL.RawPath == "" { |
|
chiCtx.RoutePath = req.URL.EscapedPath() |
|
} else { |
|
chiCtx.RoutePath = req.URL.RawPath |
|
} |
|
next.ServeHTTP(resp, req) |
|
}) |
|
} |
|
} |
|
|
|
func ForwardedHeadersHandler(limit int, trustedProxies []string) func(h http.Handler) http.Handler { |
|
opt := proxy.NewForwardedHeadersOptions().WithForwardLimit(limit).ClearTrustedProxies() |
|
for _, n := range trustedProxies { |
|
if !strings.Contains(n, "/") { |
|
opt.AddTrustedProxy(n) |
|
} else { |
|
opt.AddTrustedNetwork(n) |
|
} |
|
} |
|
return proxy.ForwardedHeaders(opt) |
|
} |
|
|
|
func MustInitSessioner() func(next http.Handler) http.Handler { |
|
// TODO: CHI-SESSION-GOB-REGISTER: chi-session has a design problem: it calls gob.Register for "Set" |
|
// But if the server restarts, then the first "Get" will fail to decode the previously stored session data because the structs are not registered yet. |
|
// So each package should make sure their structs are registered correctly during startup for session storage. |
|
|
|
middleware, err := session.Sessioner(session.Options{ |
|
Provider: setting.SessionConfig.Provider, |
|
ProviderConfig: setting.SessionConfig.ProviderConfig, |
|
CookieName: setting.SessionConfig.CookieName, |
|
CookiePath: setting.SessionConfig.CookiePath, |
|
Gclifetime: setting.SessionConfig.Gclifetime, |
|
Maxlifetime: setting.SessionConfig.Maxlifetime, |
|
Secure: setting.SessionConfig.Secure, |
|
SameSite: setting.SessionConfig.SameSite, |
|
Domain: setting.SessionConfig.Domain, |
|
}) |
|
if err != nil { |
|
log.Fatalf("common.Sessioner failed: %v", err) |
|
} |
|
return middleware |
|
}
|
|
|