// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package common import ( "bytes" "fmt" "io" "net/http" "strings" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/reqctx" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/routing" "code.gitea.io/gitea/services/context" ) const tplStatus500 templates.TplName = "status/500" func renderServerErrorPage(w http.ResponseWriter, req *http.Request, respCode int, tmpl templates.TplName, ctxData map[string]any, plainMsg string) { acceptsHTML := false for _, part := range req.Header["Accept"] { if strings.Contains(part, "text/html") { acceptsHTML = true break } } httpcache.SetCacheControlInHeader(w.Header(), &httpcache.CacheControlOptions{NoTransform: true}) if setting.Security.XFrameOptions != "unset" { w.Header().Set(`X-Frame-Options`, setting.Security.XFrameOptions) } tmplCtx := context.NewTemplateContextForWeb(reqctx.FromContext(req.Context()), req, middleware.Locale(w, req)) w.WriteHeader(respCode) outBuf := &bytes.Buffer{} if acceptsHTML { err := templates.PageRenderer().HTML(outBuf, respCode, tmpl, ctxData, tmplCtx) if err != nil { _, _ = w.Write([]byte("Internal server error but failed to render error page template, please collect error logs and report to Gitea issue tracker")) return } } else { outBuf.WriteString(plainMsg) } _, _ = io.Copy(w, outBuf) } // renderPanicErrorPage renders a 500 page with the recovered panic value, it handles the stack trace, and it never panics func renderPanicErrorPage(w http.ResponseWriter, req *http.Request, recovered any) { combinedErr := fmt.Errorf("%v\n%s", recovered, log.Stack(2)) log.Error("PANIC: %v", combinedErr) defer func() { if err := recover(); err != nil { log.Error("Panic occurs again when rendering error page: %v. Stack:\n%s", combinedErr, log.Stack(2)) } }() routing.UpdatePanicError(req.Context(), combinedErr) plainMsg := "Internal Server Error" ctxData := middleware.GetContextData(req.Context()) // This recovery handler could be called without Gitea's web context, so we shouldn't touch that context too much. // Otherwise, the 500-page may cause new panics, eg: cache.GetContextWithData, it makes the developer&users couldn't find the original panic. user, _ := ctxData[middleware.ContextDataKeySignedUser].(*user_model.User) if !setting.IsProd || (user != nil && user.IsAdmin) { plainMsg = "PANIC: " + combinedErr.Error() ctxData["ErrorMsg"] = plainMsg } renderServerErrorPage(w, req, http.StatusInternalServerError, tplStatus500, ctxData, plainMsg) }