Skip to content

Commit 5d77691

Browse files
authoredMay 4, 2023
Improve template system and panic recovery (#24461)
Partially for #24457 Major changes: 1. The old `signedUserNameStringPointerKey` is quite hacky, use `ctx.Data[SignedUser]` instead 2. Move duplicate code from `Contexter` to `CommonTemplateContextData` 3. Remove incorrect copying&pasting code `ctx.Data["Err_Password"] = true` in API handlers 4. Use one unique `RenderPanicErrorPage` for panic error page rendering 5. Move `stripSlashesMiddleware` to be the first middleware 6. Install global panic recovery handler, it works for both `install` and `web` 7. Make `500.tmpl` only depend minimal template functions/variables, avoid triggering new panics Screenshot: <details> ![image](https://user-images.githubusercontent.com/2114189/235444895-cecbabb8-e7dc-4360-a31c-b982d11946a7.png) </details>
1 parent 75ea0d5 commit 5d77691

File tree

25 files changed

+276
-363
lines changed

25 files changed

+276
-363
lines changed
 

‎modules/context/access_log.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ package context
55

66
import (
77
"bytes"
8-
"context"
98
"fmt"
109
"net"
1110
"net/http"
1211
"strings"
1312
"text/template"
1413
"time"
1514

15+
user_model "code.gitea.io/gitea/models/user"
1616
"code.gitea.io/gitea/modules/log"
1717
"code.gitea.io/gitea/modules/setting"
18+
"code.gitea.io/gitea/modules/web/middleware"
1819
)
1920

2021
type routerLoggerOptions struct {
@@ -26,8 +27,6 @@ type routerLoggerOptions struct {
2627
RequestID *string
2728
}
2829

29-
var signedUserNameStringPointerKey interface{} = "signedUserNameStringPointerKey"
30-
3130
const keyOfRequestIDInTemplate = ".RequestID"
3231

3332
// According to:
@@ -60,8 +59,6 @@ func AccessLogger() func(http.Handler) http.Handler {
6059
return func(next http.Handler) http.Handler {
6160
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
6261
start := time.Now()
63-
identity := "-"
64-
r := req.WithContext(context.WithValue(req.Context(), signedUserNameStringPointerKey, &identity))
6562

6663
var requestID string
6764
if needRequestID {
@@ -73,9 +70,14 @@ func AccessLogger() func(http.Handler) http.Handler {
7370
reqHost = req.RemoteAddr
7471
}
7572

76-
next.ServeHTTP(w, r)
73+
next.ServeHTTP(w, req)
7774
rw := w.(ResponseWriter)
7875

76+
identity := "-"
77+
data := middleware.GetContextData(req.Context())
78+
if signedUser, ok := data[middleware.ContextDataKeySignedUser].(*user_model.User); ok {
79+
identity = signedUser.Name
80+
}
7981
buf := bytes.NewBuffer([]byte{})
8082
err = logTemplate.Execute(buf, routerLoggerOptions{
8183
req: req,

‎modules/context/api.go

+1-12
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func APIContexter() func(http.Handler) http.Handler {
222222
ctx := APIContext{
223223
Context: &Context{
224224
Resp: NewResponse(w),
225-
Data: map[string]interface{}{},
225+
Data: middleware.GetContextData(req.Context()),
226226
Locale: locale,
227227
Cache: cache.GetCache(),
228228
Repo: &Repository{
@@ -250,17 +250,6 @@ func APIContexter() func(http.Handler) http.Handler {
250250
ctx.Data["Context"] = &ctx
251251

252252
next.ServeHTTP(ctx.Resp, ctx.Req)
253-
254-
// Handle adding signedUserName to the context for the AccessLogger
255-
usernameInterface := ctx.Data["SignedUserName"]
256-
identityPtrInterface := ctx.Req.Context().Value(signedUserNameStringPointerKey)
257-
if usernameInterface != nil && identityPtrInterface != nil {
258-
username := usernameInterface.(string)
259-
identityPtr := identityPtrInterface.(*string)
260-
if identityPtr != nil && username != "" {
261-
*identityPtr = username
262-
}
263-
}
264253
})
265254
}
266255
}

‎modules/context/context.go

+27-46
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ type Render interface {
5555
type Context struct {
5656
Resp ResponseWriter
5757
Req *http.Request
58-
Data map[string]interface{} // data used by MVC templates
58+
Data middleware.ContextData // data used by MVC templates
5959
PageData map[string]interface{} // data used by JavaScript modules in one page, it's `window.config.pageData`
6060
Render Render
6161
translation.Locale
@@ -97,7 +97,7 @@ func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string {
9797
}
9898

9999
// GetData returns the data
100-
func (ctx *Context) GetData() map[string]interface{} {
100+
func (ctx *Context) GetData() middleware.ContextData {
101101
return ctx.Data
102102
}
103103

@@ -219,20 +219,27 @@ const tplStatus500 base.TplName = "status/500"
219219
// HTML calls Context.HTML and renders the template to HTTP response
220220
func (ctx *Context) HTML(status int, name base.TplName) {
221221
log.Debug("Template: %s", name)
222+
222223
tmplStartTime := time.Now()
223224
if !setting.IsProd {
224225
ctx.Data["TemplateName"] = name
225226
}
226227
ctx.Data["TemplateLoadTimes"] = func() string {
227228
return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms"
228229
}
229-
if err := ctx.Render.HTML(ctx.Resp, status, string(name), templates.BaseVars().Merge(ctx.Data)); err != nil {
230-
if status == http.StatusInternalServerError && name == tplStatus500 {
231-
ctx.PlainText(http.StatusInternalServerError, "Unable to find HTML templates, the template system is not initialized, or Gitea can't find your template files.")
232-
return
233-
}
230+
231+
err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data)
232+
if err == nil {
233+
return
234+
}
235+
236+
// if rendering fails, show error page
237+
if name != tplStatus500 {
234238
err = fmt.Errorf("failed to render template: %s, error: %s", name, templates.HandleTemplateRenderingError(err))
235-
ctx.ServerError("Render failed", err)
239+
ctx.ServerError("Render failed", err) // show the 500 error page
240+
} else {
241+
ctx.PlainText(http.StatusInternalServerError, "Unable to render status/500 page, the template system is broken, or Gitea can't find your template files.")
242+
return
236243
}
237244
}
238245

@@ -676,42 +683,38 @@ func getCsrfOpts() CsrfOptions {
676683
}
677684

678685
// Contexter initializes a classic context for a request.
679-
func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
686+
func Contexter() func(next http.Handler) http.Handler {
680687
rnd := templates.HTMLRenderer()
681688
csrfOpts := getCsrfOpts()
682689
if !setting.IsProd {
683690
CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
684691
}
685692
return func(next http.Handler) http.Handler {
686693
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
687-
locale := middleware.Locale(resp, req)
688-
startTime := time.Now()
689-
link := setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/")
690-
691694
ctx := Context{
692695
Resp: NewResponse(resp),
693696
Cache: mc.GetCache(),
694-
Locale: locale,
695-
Link: link,
697+
Locale: middleware.Locale(resp, req),
698+
Link: setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/"),
696699
Render: rnd,
697700
Session: session.GetSession(req),
698701
Repo: &Repository{
699702
PullRequest: &PullRequest{},
700703
},
701-
Org: &Organization{},
702-
Data: map[string]interface{}{
703-
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
704-
"PageStartTime": startTime,
705-
"Link": link,
706-
"RunModeIsProd": setting.IsProd,
707-
},
704+
Org: &Organization{},
705+
Data: middleware.GetContextData(req.Context()),
708706
}
709707
defer ctx.Close()
710708

709+
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
710+
ctx.Data["Context"] = &ctx
711+
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
712+
ctx.Data["Link"] = ctx.Link
713+
ctx.Data["locale"] = ctx.Locale
714+
711715
// PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
712-
ctx.PageData = map[string]interface{}{}
716+
ctx.PageData = map[string]any{}
713717
ctx.Data["PageData"] = ctx.PageData
714-
ctx.Data["Context"] = &ctx
715718

716719
ctx.Req = WithContext(req, &ctx)
717720
ctx.Csrf = PrepareCSRFProtector(csrfOpts, &ctx)
@@ -755,16 +758,6 @@ func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
755758
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
756759

757760
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
758-
ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
759-
ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
760-
ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
761-
762-
ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
763-
ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage
764-
ctx.Data["ShowFooterVersion"] = setting.Other.ShowFooterVersion
765-
766-
ctx.Data["EnableSwagger"] = setting.API.EnableSwagger
767-
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
768761
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
769762
ctx.Data["DisableStars"] = setting.Repository.DisableStars
770763
ctx.Data["EnableActions"] = setting.Actions.Enabled
@@ -777,21 +770,9 @@ func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
777770
ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled()
778771
ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled()
779772

780-
ctx.Data["locale"] = locale
781773
ctx.Data["AllLangs"] = translation.AllLangs()
782774

783775
next.ServeHTTP(ctx.Resp, ctx.Req)
784-
785-
// Handle adding signedUserName to the context for the AccessLogger
786-
usernameInterface := ctx.Data["SignedUserName"]
787-
identityPtrInterface := ctx.Req.Context().Value(signedUserNameStringPointerKey)
788-
if usernameInterface != nil && identityPtrInterface != nil {
789-
username := usernameInterface.(string)
790-
identityPtr := identityPtrInterface.(*string)
791-
if identityPtr != nil && username != "" {
792-
*identityPtr = username
793-
}
794-
}
795776
})
796777
}
797778
}

‎modules/context/package.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"code.gitea.io/gitea/modules/setting"
1717
"code.gitea.io/gitea/modules/structs"
1818
"code.gitea.io/gitea/modules/templates"
19+
"code.gitea.io/gitea/modules/web/middleware"
1920
)
2021

2122
// Package contains owner, access mode and optional the package descriptor
@@ -136,7 +137,7 @@ func PackageContexter(ctx gocontext.Context) func(next http.Handler) http.Handle
136137
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
137138
ctx := Context{
138139
Resp: NewResponse(resp),
139-
Data: map[string]interface{}{},
140+
Data: middleware.GetContextData(req.Context()),
140141
Render: rnd,
141142
}
142143
defer ctx.Close()

‎modules/context/private.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"code.gitea.io/gitea/modules/graceful"
1313
"code.gitea.io/gitea/modules/process"
14+
"code.gitea.io/gitea/modules/web/middleware"
1415
)
1516

1617
// PrivateContext represents a context for private routes
@@ -62,7 +63,7 @@ func PrivateContexter() func(http.Handler) http.Handler {
6263
ctx := &PrivateContext{
6364
Context: &Context{
6465
Resp: NewResponse(w),
65-
Data: map[string]interface{}{},
66+
Data: middleware.GetContextData(req.Context()),
6667
},
6768
}
6869
defer ctx.Close()

‎modules/templates/base.go

-31
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,12 @@ package templates
55

66
import (
77
"strings"
8-
"time"
98

109
"code.gitea.io/gitea/modules/assetfs"
1110
"code.gitea.io/gitea/modules/setting"
1211
"code.gitea.io/gitea/modules/util"
1312
)
1413

15-
// Vars represents variables to be render in golang templates
16-
type Vars map[string]interface{}
17-
18-
// Merge merges another vars to the current, another Vars will override the current
19-
func (vars Vars) Merge(another map[string]interface{}) Vars {
20-
for k, v := range another {
21-
vars[k] = v
22-
}
23-
return vars
24-
}
25-
26-
// BaseVars returns all basic vars
27-
func BaseVars() Vars {
28-
startTime := time.Now()
29-
return map[string]interface{}{
30-
"IsLandingPageHome": setting.LandingPageURL == setting.LandingPageHome,
31-
"IsLandingPageExplore": setting.LandingPageURL == setting.LandingPageExplore,
32-
"IsLandingPageOrganizations": setting.LandingPageURL == setting.LandingPageOrganizations,
33-
34-
"ShowRegistrationButton": setting.Service.ShowRegistrationButton,
35-
"ShowMilestonesDashboardPage": setting.Service.ShowMilestonesDashboardPage,
36-
"ShowFooterVersion": setting.Other.ShowFooterVersion,
37-
"DisableDownloadSourceArchives": setting.Repository.DisableDownloadSourceArchives,
38-
39-
"EnableSwagger": setting.API.EnableSwagger,
40-
"EnableOpenIDSignIn": setting.Service.EnableOpenIDSignIn,
41-
"PageStartTime": startTime,
42-
}
43-
}
44-
4514
func AssetFS() *assetfs.LayeredFS {
4615
return assetfs.Layered(CustomAssets(), BuiltinAssets())
4716
}

‎modules/test/context_tests.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func MockContext(t *testing.T, path string) *context.Context {
3030
resp := &mockResponseWriter{}
3131
ctx := context.Context{
3232
Render: &mockRender{},
33-
Data: make(map[string]interface{}),
33+
Data: make(middleware.ContextData),
3434
Flash: &middleware.Flash{
3535
Values: make(url.Values),
3636
},

‎modules/web/middleware/data.go

+59-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,63 @@
33

44
package middleware
55

6-
// DataStore represents a data store
7-
type DataStore interface {
8-
GetData() map[string]interface{}
6+
import (
7+
"context"
8+
"time"
9+
10+
"code.gitea.io/gitea/modules/setting"
11+
)
12+
13+
// ContextDataStore represents a data store
14+
type ContextDataStore interface {
15+
GetData() ContextData
16+
}
17+
18+
type ContextData map[string]any
19+
20+
func (ds ContextData) GetData() map[string]any {
21+
return ds
22+
}
23+
24+
func (ds ContextData) MergeFrom(other ContextData) ContextData {
25+
for k, v := range other {
26+
ds[k] = v
27+
}
28+
return ds
29+
}
30+
31+
const ContextDataKeySignedUser = "SignedUser"
32+
33+
type contextDataKeyType struct{}
34+
35+
var contextDataKey contextDataKeyType
36+
37+
func WithContextData(c context.Context) context.Context {
38+
return context.WithValue(c, contextDataKey, make(ContextData, 10))
39+
}
40+
41+
func GetContextData(c context.Context) ContextData {
42+
if ds, ok := c.Value(contextDataKey).(ContextData); ok {
43+
return ds
44+
}
45+
return nil
46+
}
47+
48+
func CommonTemplateContextData() ContextData {
49+
return ContextData{
50+
"IsLandingPageHome": setting.LandingPageURL == setting.LandingPageHome,
51+
"IsLandingPageExplore": setting.LandingPageURL == setting.LandingPageExplore,
52+
"IsLandingPageOrganizations": setting.LandingPageURL == setting.LandingPageOrganizations,
53+
54+
"ShowRegistrationButton": setting.Service.ShowRegistrationButton,
55+
"ShowMilestonesDashboardPage": setting.Service.ShowMilestonesDashboardPage,
56+
"ShowFooterVersion": setting.Other.ShowFooterVersion,
57+
"DisableDownloadSourceArchives": setting.Repository.DisableDownloadSourceArchives,
58+
59+
"EnableSwagger": setting.API.EnableSwagger,
60+
"EnableOpenIDSignIn": setting.Service.EnableOpenIDSignIn,
61+
"PageStartTime": time.Now(),
62+
63+
"RunModeIsProd": setting.IsProd,
64+
}
965
}

‎modules/web/middleware/flash.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ var FlashNow bool
1818

1919
// Flash represents a one time data transfer between two requests.
2020
type Flash struct {
21-
DataStore
21+
DataStore ContextDataStore
2222
url.Values
2323
ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string
2424
}
@@ -34,7 +34,7 @@ func (f *Flash) set(name, msg string, current ...bool) {
3434
}
3535

3636
if isShow {
37-
f.GetData()["Flash"] = f
37+
f.DataStore.GetData()["Flash"] = f
3838
} else {
3939
f.Set(name, msg)
4040
}

‎modules/web/middleware/request.go

-5
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,3 @@ import (
1212
func IsAPIPath(req *http.Request) bool {
1313
return strings.HasPrefix(req.URL.Path, "/api/")
1414
}
15-
16-
// IsInternalPath returns true if the specified URL is an internal API path
17-
func IsInternalPath(req *http.Request) bool {
18-
return strings.HasPrefix(req.URL.Path, "/api/internal/")
19-
}

‎modules/web/route.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ func Bind[T any](_ T) any {
2525
}
2626

2727
// SetForm set the form object
28-
func SetForm(data middleware.DataStore, obj interface{}) {
28+
func SetForm(data middleware.ContextDataStore, obj interface{}) {
2929
data.GetData()["__form"] = obj
3030
}
3131

3232
// GetForm returns the validate form information
33-
func GetForm(data middleware.DataStore) interface{} {
33+
func GetForm(data middleware.ContextDataStore) interface{} {
3434
return data.GetData()["__form"]
3535
}
3636

‎routers/api/v1/admin/user.go

-2
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ func CreateUser(ctx *context.APIContext) {
103103
if err != nil {
104104
log.Error(err.Error())
105105
}
106-
ctx.Data["Err_Password"] = true
107106
ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
108107
return
109108
}
@@ -201,7 +200,6 @@ func EditUser(ctx *context.APIContext) {
201200
if err != nil {
202201
log.Error(err.Error())
203202
}
204-
ctx.Data["Err_Password"] = true
205203
ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
206204
return
207205
}

‎routers/api/v1/misc/markup_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"code.gitea.io/gitea/modules/templates"
2020
"code.gitea.io/gitea/modules/util"
2121
"code.gitea.io/gitea/modules/web"
22+
"code.gitea.io/gitea/modules/web/middleware"
2223

2324
"github.com/stretchr/testify/assert"
2425
)
@@ -36,7 +37,7 @@ func createContext(req *http.Request) (*context.Context, *httptest.ResponseRecor
3637
Req: req,
3738
Resp: context.NewResponse(resp),
3839
Render: rnd,
39-
Data: make(map[string]interface{}),
40+
Data: make(middleware.ContextData),
4041
}
4142
defer c.Close()
4243

‎routers/common/errpage.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package common
5+
6+
import (
7+
"fmt"
8+
"net/http"
9+
10+
user_model "code.gitea.io/gitea/models/user"
11+
"code.gitea.io/gitea/modules/base"
12+
"code.gitea.io/gitea/modules/httpcache"
13+
"code.gitea.io/gitea/modules/log"
14+
"code.gitea.io/gitea/modules/setting"
15+
"code.gitea.io/gitea/modules/templates"
16+
"code.gitea.io/gitea/modules/web/middleware"
17+
"code.gitea.io/gitea/modules/web/routing"
18+
)
19+
20+
const tplStatus500 base.TplName = "status/500"
21+
22+
// RenderPanicErrorPage renders a 500 page, and it never panics
23+
func RenderPanicErrorPage(w http.ResponseWriter, req *http.Request, err any) {
24+
combinedErr := fmt.Sprintf("%v\n%s", err, log.Stack(2))
25+
log.Error("PANIC: %s", combinedErr)
26+
27+
defer func() {
28+
if err := recover(); err != nil {
29+
log.Error("Panic occurs again when rendering error page: %v", err)
30+
}
31+
}()
32+
33+
routing.UpdatePanicError(req.Context(), err)
34+
35+
httpcache.SetCacheControlInHeader(w.Header(), 0, "no-transform")
36+
w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
37+
38+
data := middleware.GetContextData(req.Context())
39+
if data["locale"] == nil {
40+
data = middleware.CommonTemplateContextData()
41+
data["locale"] = middleware.Locale(w, req)
42+
}
43+
44+
// This recovery handler could be called without Gitea's web context, so we shouldn't touch that context too much.
45+
// Otherwise, the 500-page may cause new panics, eg: cache.GetContextWithData, it makes the developer&users couldn't find the original panic.
46+
user, _ := data[middleware.ContextDataKeySignedUser].(*user_model.User)
47+
if !setting.IsProd || (user != nil && user.IsAdmin) {
48+
data["ErrorMsg"] = "PANIC: " + combinedErr
49+
}
50+
51+
err = templates.HTMLRenderer().HTML(w, http.StatusInternalServerError, string(tplStatus500), data)
52+
if err != nil {
53+
log.Error("Error occurs again when rendering error page: %v", err)
54+
w.WriteHeader(http.StatusInternalServerError)
55+
_, _ = w.Write([]byte("Internal server error, please collect error logs and report to Gitea issue tracker"))
56+
}
57+
}

‎routers/common/middleware.go

+21-33
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,36 @@ import (
1010

1111
"code.gitea.io/gitea/modules/cache"
1212
"code.gitea.io/gitea/modules/context"
13-
"code.gitea.io/gitea/modules/log"
1413
"code.gitea.io/gitea/modules/process"
1514
"code.gitea.io/gitea/modules/setting"
15+
"code.gitea.io/gitea/modules/web/middleware"
1616
"code.gitea.io/gitea/modules/web/routing"
1717

1818
"gitea.com/go-chi/session"
1919
"github.com/chi-middleware/proxy"
2020
chi "github.com/go-chi/chi/v5"
2121
)
2222

23-
// ProtocolMiddlewares returns HTTP protocol related middlewares
23+
// ProtocolMiddlewares returns HTTP protocol related middlewares, and it provides a global panic recovery
2424
func ProtocolMiddlewares() (handlers []any) {
25+
// first, normalize the URL path
26+
handlers = append(handlers, stripSlashesMiddleware)
27+
28+
// prepare the ContextData and panic recovery
2529
handlers = append(handlers, func(next http.Handler) http.Handler {
2630
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
27-
// First of all escape the URL RawPath to ensure that all routing is done using a correctly escaped URL
28-
req.URL.RawPath = req.URL.EscapedPath()
31+
defer func() {
32+
if err := recover(); err != nil {
33+
RenderPanicErrorPage(resp, req, err) // it should never panic
34+
}
35+
}()
36+
req = req.WithContext(middleware.WithContextData(req.Context()))
37+
next.ServeHTTP(resp, req)
38+
})
39+
})
2940

41+
handlers = append(handlers, func(next http.Handler) http.Handler {
42+
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
3043
ctx, _, finished := process.GetManager().AddTypedContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI), process.RequestProcessType, true)
3144
defer finished()
3245
next.ServeHTTP(context.NewResponse(resp), req.WithContext(cache.WithCacheContext(ctx)))
@@ -47,9 +60,6 @@ func ProtocolMiddlewares() (handlers []any) {
4760
handlers = append(handlers, proxy.ForwardedHeaders(opt))
4861
}
4962

50-
// Strip slashes.
51-
handlers = append(handlers, stripSlashesMiddleware)
52-
5363
if !setting.Log.DisableRouterLog {
5464
handlers = append(handlers, routing.NewLoggerHandler())
5565
}
@@ -58,40 +68,18 @@ func ProtocolMiddlewares() (handlers []any) {
5868
handlers = append(handlers, context.AccessLogger())
5969
}
6070

61-
handlers = append(handlers, func(next http.Handler) http.Handler {
62-
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
63-
// Why we need this? The Recovery() will try to render a beautiful
64-
// error page for user, but the process can still panic again, and other
65-
// middleware like session also may panic then we have to recover twice
66-
// and send a simple error page that should not panic anymore.
67-
defer func() {
68-
if err := recover(); err != nil {
69-
routing.UpdatePanicError(req.Context(), err)
70-
combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, log.Stack(2))
71-
log.Error("%v", combinedErr)
72-
if setting.IsProd {
73-
http.Error(resp, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
74-
} else {
75-
http.Error(resp, combinedErr, http.StatusInternalServerError)
76-
}
77-
}
78-
}()
79-
next.ServeHTTP(resp, req)
80-
})
81-
})
8271
return handlers
8372
}
8473

8574
func stripSlashesMiddleware(next http.Handler) http.Handler {
8675
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
87-
var urlPath string
76+
// First of all escape the URL RawPath to ensure that all routing is done using a correctly escaped URL
77+
req.URL.RawPath = req.URL.EscapedPath()
78+
79+
urlPath := req.URL.RawPath
8880
rctx := chi.RouteContext(req.Context())
8981
if rctx != nil && rctx.RoutePath != "" {
9082
urlPath = rctx.RoutePath
91-
} else if req.URL.RawPath != "" {
92-
urlPath = req.URL.RawPath
93-
} else {
94-
urlPath = req.URL.Path
9583
}
9684

9785
sanitizedPath := &strings.Builder{}

‎routers/install/install.go

+14-16
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package install
66

77
import (
8-
goctx "context"
98
"fmt"
109
"net/http"
1110
"os"
@@ -53,33 +52,32 @@ func getSupportedDbTypeNames() (dbTypeNames []map[string]string) {
5352
return dbTypeNames
5453
}
5554

56-
// Init prepare for rendering installation page
57-
func Init(ctx goctx.Context) func(next http.Handler) http.Handler {
55+
// Contexter prepare for rendering installation page
56+
func Contexter() func(next http.Handler) http.Handler {
5857
rnd := templates.HTMLRenderer()
5958
dbTypeNames := getSupportedDbTypeNames()
6059
return func(next http.Handler) http.Handler {
6160
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
62-
locale := middleware.Locale(resp, req)
63-
startTime := time.Now()
6461
ctx := context.Context{
6562
Resp: context.NewResponse(resp),
6663
Flash: &middleware.Flash{},
67-
Locale: locale,
64+
Locale: middleware.Locale(resp, req),
6865
Render: rnd,
66+
Data: middleware.GetContextData(req.Context()),
6967
Session: session.GetSession(req),
70-
Data: map[string]interface{}{
71-
"locale": locale,
72-
"Title": locale.Tr("install.install"),
73-
"PageIsInstall": true,
74-
"DbTypeNames": dbTypeNames,
75-
"AllLangs": translation.AllLangs(),
76-
"PageStartTime": startTime,
77-
78-
"PasswordHashAlgorithms": hash.RecommendedHashAlgorithms,
79-
},
8068
}
8169
defer ctx.Close()
8270

71+
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
72+
ctx.Data.MergeFrom(middleware.ContextData{
73+
"locale": ctx.Locale,
74+
"Title": ctx.Locale.Tr("install.install"),
75+
"PageIsInstall": true,
76+
"DbTypeNames": dbTypeNames,
77+
"AllLangs": translation.AllLangs(),
78+
79+
"PasswordHashAlgorithms": hash.RecommendedHashAlgorithms,
80+
})
8381
ctx.Req = context.WithContext(req, &ctx)
8482
next.ServeHTTP(resp, ctx.Req)
8583
})

‎routers/install/routes.go

+1-65
Original file line numberDiff line numberDiff line change
@@ -9,86 +9,22 @@ import (
99
"html"
1010
"net/http"
1111

12-
"code.gitea.io/gitea/modules/httpcache"
13-
"code.gitea.io/gitea/modules/log"
1412
"code.gitea.io/gitea/modules/public"
1513
"code.gitea.io/gitea/modules/setting"
16-
"code.gitea.io/gitea/modules/templates"
1714
"code.gitea.io/gitea/modules/web"
18-
"code.gitea.io/gitea/modules/web/middleware"
1915
"code.gitea.io/gitea/routers/common"
2016
"code.gitea.io/gitea/routers/web/healthcheck"
2117
"code.gitea.io/gitea/services/forms"
2218
)
2319

24-
type dataStore map[string]interface{}
25-
26-
func (d *dataStore) GetData() map[string]interface{} {
27-
return *d
28-
}
29-
30-
func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler {
31-
return func(next http.Handler) http.Handler {
32-
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
33-
defer func() {
34-
// Why we need this? The first recover will try to render a beautiful
35-
// error page for user, but the process can still panic again, then
36-
// we have to just recover twice and send a simple error page that
37-
// should not panic anymore.
38-
defer func() {
39-
if err := recover(); err != nil {
40-
combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, log.Stack(2))
41-
log.Error("%s", combinedErr)
42-
if setting.IsProd {
43-
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
44-
} else {
45-
http.Error(w, combinedErr, http.StatusInternalServerError)
46-
}
47-
}
48-
}()
49-
50-
if err := recover(); err != nil {
51-
combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, log.Stack(2))
52-
log.Error("%s", combinedErr)
53-
54-
lc := middleware.Locale(w, req)
55-
store := dataStore{
56-
"Language": lc.Language(),
57-
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
58-
"locale": lc,
59-
"SignedUserID": int64(0),
60-
"SignedUserName": "",
61-
}
62-
63-
httpcache.SetCacheControlInHeader(w.Header(), 0, "no-transform")
64-
w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
65-
66-
if !setting.IsProd {
67-
store["ErrorMsg"] = combinedErr
68-
}
69-
rnd := templates.HTMLRenderer()
70-
err = rnd.HTML(w, http.StatusInternalServerError, "status/500", templates.BaseVars().Merge(store))
71-
if err != nil {
72-
log.Error("%v", err)
73-
}
74-
}
75-
}()
76-
77-
next.ServeHTTP(w, req)
78-
})
79-
}
80-
}
81-
8220
// Routes registers the installation routes
8321
func Routes(ctx goctx.Context) *web.Route {
8422
base := web.NewRoute()
8523
base.Use(common.ProtocolMiddlewares()...)
8624
base.RouteMethods("/assets/*", "GET, HEAD", public.AssetsHandlerFunc("/assets/"))
8725

8826
r := web.NewRoute()
89-
r.Use(common.Sessioner())
90-
r.Use(installRecovery(ctx))
91-
r.Use(Init(ctx))
27+
r.Use(common.Sessioner(), Contexter())
9228
r.Get("/", Install) // it must be on the root, because the "install.js" use the window.location to replace the "localhost" AppURL
9329
r.Post("/", web.Bind(forms.InstallForm{}), SubmitInstall)
9430
r.Get("/post-install", InstallDone)

‎routers/web/base.go

-66
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package web
55

66
import (
7-
goctx "context"
87
"errors"
98
"fmt"
109
"io"
@@ -13,18 +12,12 @@ import (
1312
"path"
1413
"strings"
1514

16-
"code.gitea.io/gitea/modules/context"
1715
"code.gitea.io/gitea/modules/httpcache"
1816
"code.gitea.io/gitea/modules/log"
1917
"code.gitea.io/gitea/modules/setting"
2018
"code.gitea.io/gitea/modules/storage"
21-
"code.gitea.io/gitea/modules/templates"
2219
"code.gitea.io/gitea/modules/util"
23-
"code.gitea.io/gitea/modules/web/middleware"
2420
"code.gitea.io/gitea/modules/web/routing"
25-
"code.gitea.io/gitea/services/auth"
26-
27-
"gitea.com/go-chi/session"
2821
)
2922

3023
func storageHandler(storageSetting setting.Storage, prefix string, objStore storage.ObjectStorage) func(next http.Handler) http.Handler {
@@ -110,62 +103,3 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
110103
})
111104
}
112105
}
113-
114-
type dataStore map[string]interface{}
115-
116-
func (d *dataStore) GetData() map[string]interface{} {
117-
return *d
118-
}
119-
120-
// RecoveryWith500Page returns a middleware that recovers from any panics and writes a 500 and a log if so.
121-
// This error will be created with the gitea 500 page.
122-
func RecoveryWith500Page(ctx goctx.Context) func(next http.Handler) http.Handler {
123-
rnd := templates.HTMLRenderer()
124-
return func(next http.Handler) http.Handler {
125-
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
126-
defer func() {
127-
if err := recover(); err != nil {
128-
routing.UpdatePanicError(req.Context(), err)
129-
combinedErr := fmt.Sprintf("PANIC: %v\n%s", err, log.Stack(2))
130-
log.Error("%s", combinedErr)
131-
132-
sessionStore := session.GetSession(req)
133-
134-
lc := middleware.Locale(w, req)
135-
store := dataStore{
136-
"Language": lc.Language(),
137-
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
138-
"locale": lc,
139-
}
140-
141-
// TODO: this recovery handler is usually called without Gitea's web context, so we shouldn't touch that context too much
142-
// Otherwise, the 500 page may cause new panics, eg: cache.GetContextWithData, it makes the developer&users couldn't find the original panic
143-
user := context.GetContextUser(req) // almost always nil
144-
if user == nil {
145-
// Get user from session if logged in - do not attempt to sign-in
146-
user = auth.SessionUser(sessionStore)
147-
}
148-
149-
httpcache.SetCacheControlInHeader(w.Header(), 0, "no-transform")
150-
w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
151-
152-
if !setting.IsProd || (user != nil && user.IsAdmin) {
153-
store["ErrorMsg"] = combinedErr
154-
}
155-
156-
defer func() {
157-
if err := recover(); err != nil {
158-
log.Error("HTML render in Recovery handler panics again: %v", err)
159-
}
160-
}()
161-
err = rnd.HTML(w, http.StatusInternalServerError, "status/500", templates.BaseVars().Merge(store))
162-
if err != nil {
163-
log.Error("HTML render in Recovery handler fails again: %v", err)
164-
}
165-
}
166-
}()
167-
168-
next.ServeHTTP(w, req)
169-
})
170-
}
171-
}

‎routers/web/web.go

+13-17
Original file line numberDiff line numberDiff line change
@@ -116,62 +116,58 @@ func Routes(ctx gocontext.Context) *web.Route {
116116

117117
_ = templates.HTMLRenderer()
118118

119-
common := []any{
120-
common.Sessioner(),
121-
RecoveryWith500Page(ctx),
122-
}
119+
var mid []any
123120

124121
if setting.EnableGzip {
125122
h, err := gziphandler.GzipHandlerWithOpts(gziphandler.MinSize(GzipMinSize))
126123
if err != nil {
127124
log.Fatal("GzipHandlerWithOpts failed: %v", err)
128125
}
129-
common = append(common, h)
126+
mid = append(mid, h)
130127
}
131128

132129
if setting.Service.EnableCaptcha {
133130
// The captcha http.Handler should only fire on /captcha/* so we can just mount this on that url
134-
routes.RouteMethods("/captcha/*", "GET,HEAD", append(common, captcha.Captchaer(context.GetImageCaptcha()))...)
131+
routes.RouteMethods("/captcha/*", "GET,HEAD", append(mid, captcha.Captchaer(context.GetImageCaptcha()))...)
135132
}
136133

137134
if setting.HasRobotsTxt {
138-
routes.Get("/robots.txt", append(common, misc.RobotsTxt)...)
135+
routes.Get("/robots.txt", append(mid, misc.RobotsTxt)...)
139136
}
140137

141-
// prometheus metrics endpoint - do not need to go through contexter
142138
if setting.Metrics.Enabled {
143139
prometheus.MustRegister(metrics.NewCollector())
144-
routes.Get("/metrics", append(common, Metrics)...)
140+
routes.Get("/metrics", append(mid, Metrics)...)
145141
}
146142

147143
routes.Get("/ssh_info", misc.SSHInfo)
148144
routes.Get("/api/healthz", healthcheck.Check)
149145

150-
common = append(common, context.Contexter(ctx))
146+
mid = append(mid, common.Sessioner(), context.Contexter())
151147

152148
group := buildAuthGroup()
153149
if err := group.Init(ctx); err != nil {
154150
log.Error("Could not initialize '%s' auth method, error: %s", group.Name(), err)
155151
}
156152

157153
// Get user from session if logged in.
158-
common = append(common, auth_service.Auth(group))
154+
mid = append(mid, auth_service.Auth(group))
159155

160156
// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
161-
common = append(common, middleware.GetHead)
157+
mid = append(mid, middleware.GetHead)
162158

163159
if setting.API.EnableSwagger {
164160
// Note: The route is here but no in API routes because it renders a web page
165-
routes.Get("/api/swagger", append(common, misc.Swagger)...) // Render V1 by default
161+
routes.Get("/api/swagger", append(mid, misc.Swagger)...) // Render V1 by default
166162
}
167163

168164
// TODO: These really seem like things that could be folded into Contexter or as helper functions
169-
common = append(common, user.GetNotificationCount)
170-
common = append(common, repo.GetActiveStopwatch)
171-
common = append(common, goGet)
165+
mid = append(mid, user.GetNotificationCount)
166+
mid = append(mid, repo.GetActiveStopwatch)
167+
mid = append(mid, goGet)
172168

173169
others := web.NewRoute()
174-
others.Use(common...)
170+
others.Use(mid...)
175171
registerRoutes(others)
176172
routes.Mount("", others)
177173
return routes

‎services/auth/interface.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
)
1414

1515
// DataStore represents a data store
16-
type DataStore middleware.DataStore
16+
type DataStore middleware.ContextDataStore
1717

1818
// SessionStore represents a session store
1919
type SessionStore session.Store

‎services/auth/middleware.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,11 @@ func authShared(ctx *context.Context, authMethod Method) error {
5151
ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == BasicMethodName
5252
ctx.IsSigned = true
5353
ctx.Data["IsSigned"] = ctx.IsSigned
54-
ctx.Data["SignedUser"] = ctx.Doer
54+
ctx.Data[middleware.ContextDataKeySignedUser] = ctx.Doer
5555
ctx.Data["SignedUserID"] = ctx.Doer.ID
56-
ctx.Data["SignedUserName"] = ctx.Doer.Name
5756
ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin
5857
} else {
5958
ctx.Data["SignedUserID"] = int64(0)
60-
ctx.Data["SignedUserName"] = ""
6159
}
6260
return nil
6361
}

‎templates/base/head_script.tmpl

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ If you introduce mistakes in it, Gitea JavaScript code wouldn't run correctly.
66
<script>
77
window.addEventListener('error', function(e) {window._globalHandlerErrors=window._globalHandlerErrors||[]; window._globalHandlerErrors.push(e);});
88
window.config = {
9-
initCount: (window.config?.initCount ?? 0) + 1,
109
appUrl: '{{AppUrl}}',
1110
appSubUrl: '{{AppSubUrl}}',
1211
assetVersionEncoded: encodeURIComponent('{{AssetVersion}}'), // will be used in URL construction directly

‎templates/status/500.tmpl

+59-34
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,61 @@
1-
{{template "base/head" .}}
2-
<div role="main" aria-label="{{.Title}}" class="page-content status-page-500">
3-
<p class="gt-mt-5 center"><img src="{{AssetUrlPrefix}}/img/500.png" alt="Internal Server Error"></p>
4-
<div class="ui divider"></div>
5-
6-
<div class="ui container gt-mt-5">
7-
{{if .ErrorMsg}}
8-
<p>{{.locale.Tr "error.occurred"}}:</p>
9-
<pre class="gt-whitespace-pre-wrap gt-break-all">{{.ErrorMsg}}</pre>
10-
{{end}}
11-
12-
<div class="center gt-mt-5">
13-
{{if .ShowFooterVersion}}<p>{{.locale.Tr "admin.config.app_ver"}}: {{AppVer}}</p>{{end}}
14-
{{if .IsAdmin}}<p>{{.locale.Tr "error.report_message" | Safe}}</p>{{end}}
1+
{{/* This page should only depend the minimal template functions/variables, to avoid triggering new panics.
2+
* base template functions: AppName, AssetUrlPrefix, AssetVersion, AppSubUrl, DefaultTheme, Str2html
3+
* locale
4+
* ErrorMsg
5+
* SignedUser (optional)
6+
*/}}
7+
<!DOCTYPE html>
8+
<html lang="{{.locale.Lang}}" class="theme-{{if .SignedUser.Theme}}{{.SignedUser.Theme}}{{else}}{{DefaultTheme}}{{end}}">
9+
<head>
10+
<meta charset="utf-8">
11+
<meta name="viewport" content="width=device-width, initial-scale=1">
12+
<title>Internal Server Error - {{AppName}}</title>
13+
<link rel="icon" href="{{AssetUrlPrefix}}/img/favicon.svg" type="image/svg+xml">
14+
<link rel="alternate icon" href="{{AssetUrlPrefix}}/img/favicon.png" type="image/png">
15+
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/index.css?v={{AssetVersion}}">
16+
</head>
17+
<body>
18+
<div class="full height">
19+
<nav class="ui secondary menu following bar light">
20+
<div class="ui container gt-df">
21+
<div class="item brand gt-f1">
22+
<a href="{{AppSubUrl}}/" aria-label="{{.locale.Tr "home"}}">
23+
<img width="30" height="30" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{.locale.Tr "logo"}}" aria-hidden="true">
24+
</a>
25+
</div>
26+
<button class="item ui icon button">{{svg "octicon-three-bars"}}</button>{{/* a fake button to make the UI looks better*/}}
27+
</div>
28+
</nav>
29+
<div role="main" class="page-content status-page-500">
30+
<p class="gt-mt-5 center"><img src="{{AssetUrlPrefix}}/img/500.png" alt="Internal Server Error"></p>
31+
<div class="ui divider"></div>
32+
<div class="ui container gt-mt-5">
33+
{{if .ErrorMsg}}
34+
<p>{{.locale.Tr "error.occurred"}}:</p>
35+
<pre class="gt-whitespace-pre-wrap gt-break-all">{{.ErrorMsg}}</pre>
36+
{{end}}
37+
<div class="center gt-mt-5">
38+
{{if or .SignedUser.IsAdmin .ShowFooterVersion}}<p>{{.locale.Tr "admin.config.app_ver"}}: {{AppVer}}</p>{{end}}
39+
{{if .SignedUser.IsAdmin}}<p>{{.locale.Tr "error.report_message" | Str2html}}</p>{{end}}
40+
</div>
41+
</div>
1542
</div>
1643
</div>
17-
</div>
18-
{{/* when a sub-template triggers an 500 error, its parent template has been partially rendered,
19-
then the 500 page will be rendered after that partially rendered page, the HTML/JS are totally broken.
20-
so use this inline script to try to move it to main viewport */}}
21-
<script type="module">
22-
const embedded = document.querySelector('.page-content .page-content.status-page-500');
23-
if (embedded) {
24-
// move footer to main view
25-
const footer = document.querySelector('footer');
26-
if (footer) document.querySelector('body').append(footer);
27-
// move the 500 error page content to main view
28-
const embeddedParent = embedded.parentNode;
29-
let main = document.querySelector('.page-content');
30-
main = main ?? document.querySelector('body');
31-
main.prepend(document.createElement('hr'));
32-
main.prepend(embedded);
33-
embeddedParent.remove(); // remove the unrelated 500-page elements (eg: the duplicate nav bar)
34-
}
35-
</script>
36-
{{template "base/footer" .}}
44+
45+
{{/* When a sub-template triggers an 500 error, its parent template has been partially rendered, then the 500 page
46+
will be rendered after that partially rendered page, the HTML/JS are totally broken. Use this inline script to try to move it to main viewport.
47+
And this page shouldn't include any other JS file, avoid duplicate JS execution (still due to the partial rendering).*/}}
48+
<script type="module">
49+
const embedded = document.querySelector('.page-content .page-content.status-page-500');
50+
if (embedded) {
51+
// move the 500 error page content to main view
52+
const embeddedParent = embedded.parentNode;
53+
let main = document.querySelector('.page-content');
54+
main = main ?? document.querySelector('body');
55+
main.prepend(document.createElement('hr'));
56+
main.prepend(embedded);
57+
embeddedParent.remove(); // remove the unrelated 500-page elements (eg: the duplicate nav bar)
58+
}
59+
</script>
60+
</body>
61+
</html>

‎templates/user/profile.tmpl

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<div class="ui five wide column">
66
<div class="ui card">
77
<div id="profile-avatar" class="content gt-df">
8-
{{if eq .SignedUserName .ContextUser.Name}}
8+
{{if eq .SignedUserID .ContextUser.ID}}
99
<a class="image" href="{{AppSubUrl}}/user/settings" data-tooltip-content="{{.locale.Tr "user.change_avatar"}}">
1010
{{avatar $.Context .ContextUser 290}}
1111
</a>
@@ -30,7 +30,7 @@
3030
{{if .ContextUser.Location}}
3131
<li>{{svg "octicon-location"}} {{.ContextUser.Location}}</li>
3232
{{end}}
33-
{{if (eq .SignedUserName .ContextUser.Name)}}
33+
{{if (eq .SignedUserID .ContextUser.ID)}}
3434
<li>
3535
{{svg "octicon-mail"}}
3636
<a href="mailto:{{.ContextUser.Email}}" rel="nofollow">{{.ContextUser.Email}}</a>
@@ -100,7 +100,7 @@
100100
</ul>
101101
</li>
102102
{{end}}
103-
{{if and .IsSigned (ne .SignedUserName .ContextUser.Name)}}
103+
{{if and .IsSigned (ne .SignedUserID .ContextUser.ID)}}
104104
<li class="follow">
105105
{{if $.IsFollowing}}
106106
<form method="post" action="{{.Link}}?action=unfollow&redirect_to={{$.Link}}">

‎web_src/js/bootstrap.js

-11
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@ export function showGlobalErrorMessage(msg) {
2020
* @param {ErrorEvent} e
2121
*/
2222
function processWindowErrorEvent(e) {
23-
if (window.config.initCount > 1) {
24-
// the page content has been loaded many times, the HTML/JS are totally broken, don't need to show error message
25-
return;
26-
}
2723
if (!e.error && e.lineno === 0 && e.colno === 0 && e.filename === '' && window.navigator.userAgent.includes('FxiOS/')) {
2824
// At the moment, Firefox (iOS) (10x) has an engine bug. See https://github.com/go-gitea/gitea/issues/20240
2925
// If a script inserts a newly created (and content changed) element into DOM, there will be a nonsense error event reporting: Script error: line 0, col 0.
@@ -37,13 +33,6 @@ function initGlobalErrorHandler() {
3733
if (!window.config) {
3834
showGlobalErrorMessage(`Gitea JavaScript code couldn't run correctly, please check your custom templates`);
3935
}
40-
if (window.config.initCount > 1) {
41-
// when a sub-templates triggers an 500 error, its parent template has been partially rendered,
42-
// then the 500 page will be rendered after that partially rendered page, which will cause the initCount > 1
43-
// in this case, the page is totally broken, so do not do any further error handling
44-
console.error('initGlobalErrorHandler: Gitea global config system has already been initialized, there must be something else wrong');
45-
return;
46-
}
4736
// we added an event handler for window error at the very beginning of <script> of page head
4837
// the handler calls `_globalHandlerErrors.push` (array method) to record all errors occur before this init
4938
// then in this init, we can collect all error events and show them

0 commit comments

Comments
 (0)
Please sign in to comment.