loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Decouple the different contexts from each other (#24786)

Replace #16455

Close #21803

Mixing different Gitea contexts together causes some problems:

1. Unable to respond proper content when error occurs, eg: Web should
respond HTML while API should respond JSON
2. Unclear dependency, eg: it's unclear when Context is used in
APIContext, which fields should be initialized, which methods are
necessary.


To make things clear, this PR introduces a Base context, it only
provides basic Req/Resp/Data features.

This PR mainly moves code. There are still many legacy problems and
TODOs in code, leave unrelated changes to future PRs.

authored by

wxiaoguang and committed by
GitHub
6b33152b 6ba4f897

+882 -778
+83 -32
modules/context/api.go
··· 13 13 14 14 "code.gitea.io/gitea/models/auth" 15 15 repo_model "code.gitea.io/gitea/models/repo" 16 - "code.gitea.io/gitea/modules/cache" 16 + "code.gitea.io/gitea/models/unit" 17 + user_model "code.gitea.io/gitea/models/user" 18 + mc "code.gitea.io/gitea/modules/cache" 17 19 "code.gitea.io/gitea/modules/git" 18 20 "code.gitea.io/gitea/modules/httpcache" 19 21 "code.gitea.io/gitea/modules/log" 20 22 "code.gitea.io/gitea/modules/setting" 21 - "code.gitea.io/gitea/modules/web/middleware" 23 + 24 + "gitea.com/go-chi/cache" 22 25 ) 23 26 24 27 // APIContext is a specific context for API service 25 28 type APIContext struct { 26 - *Context 27 - Org *APIOrganization 29 + *Base 30 + 31 + Cache cache.Cache 32 + 33 + Doer *user_model.User // current signed-in user 34 + IsSigned bool 35 + IsBasicAuth bool 36 + 37 + ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer 38 + 39 + Repo *Repository 40 + Org *APIOrganization 41 + Package *Package 28 42 } 29 43 30 44 // Currently, we have the following common fields in error response: ··· 128 142 129 143 var apiContextKey = apiContextKeyType{} 130 144 131 - // WithAPIContext set up api context in request 132 - func WithAPIContext(req *http.Request, ctx *APIContext) *http.Request { 133 - return req.WithContext(context.WithValue(req.Context(), apiContextKey, ctx)) 134 - } 135 - 136 145 // GetAPIContext returns a context for API routes 137 146 func GetAPIContext(req *http.Request) *APIContext { 138 147 return req.Context().Value(apiContextKey).(*APIContext) ··· 195 204 } 196 205 197 206 otpHeader := ctx.Req.Header.Get("X-Gitea-OTP") 198 - twofa, err := auth.GetTwoFactorByUID(ctx.Context.Doer.ID) 207 + twofa, err := auth.GetTwoFactorByUID(ctx.Doer.ID) 199 208 if err != nil { 200 209 if auth.IsErrTwoFactorNotEnrolled(err) { 201 210 return // No 2FA enrollment for this user 202 211 } 203 - ctx.Context.Error(http.StatusInternalServerError) 212 + ctx.Error(http.StatusInternalServerError, "GetTwoFactorByUID", err) 204 213 return 205 214 } 206 215 ok, err := twofa.ValidateTOTP(otpHeader) 207 216 if err != nil { 208 - ctx.Context.Error(http.StatusInternalServerError) 217 + ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err) 209 218 return 210 219 } 211 220 if !ok { 212 - ctx.Context.Error(http.StatusUnauthorized) 221 + ctx.Error(http.StatusUnauthorized, "", nil) 213 222 return 214 223 } 215 224 } ··· 218 227 func APIContexter() func(http.Handler) http.Handler { 219 228 return func(next http.Handler) http.Handler { 220 229 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 221 - locale := middleware.Locale(w, req) 222 - ctx := APIContext{ 223 - Context: &Context{ 224 - Resp: NewResponse(w), 225 - Data: middleware.GetContextData(req.Context()), 226 - Locale: locale, 227 - Cache: cache.GetCache(), 228 - Repo: &Repository{ 229 - PullRequest: &PullRequest{}, 230 - }, 231 - Org: &Organization{}, 232 - }, 233 - Org: &APIOrganization{}, 230 + base, baseCleanUp := NewBaseContext(w, req) 231 + ctx := &APIContext{ 232 + Base: base, 233 + Cache: mc.GetCache(), 234 + Repo: &Repository{PullRequest: &PullRequest{}}, 235 + Org: &APIOrganization{}, 234 236 } 235 - defer ctx.Close() 237 + defer baseCleanUp() 236 238 237 - ctx.Req = WithAPIContext(WithContext(req, ctx.Context), &ctx) 239 + ctx.Base.AppendContextValue(apiContextKey, ctx) 240 + ctx.Base.AppendContextValueFunc(git.RepositoryContextKey, func() any { return ctx.Repo.GitRepo }) 238 241 239 242 // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. 240 243 if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { ··· 246 249 247 250 httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform") 248 251 ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) 249 - 250 - ctx.Data["Context"] = &ctx 251 252 252 253 next.ServeHTTP(ctx.Resp, ctx.Req) 253 254 }) ··· 301 302 return func() { 302 303 // If it's been set to nil then assume someone else has closed it. 303 304 if ctx.Repo.GitRepo != nil { 304 - ctx.Repo.GitRepo.Close() 305 + _ = ctx.Repo.GitRepo.Close() 305 306 } 306 307 } 307 308 } ··· 337 338 } 338 339 339 340 var err error 340 - refName := getRefName(ctx.Context, RepoRefAny) 341 + refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny) 341 342 342 343 if ctx.Repo.GitRepo.IsBranchExist(refName) { 343 344 ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName) ··· 368 369 next.ServeHTTP(w, req) 369 370 }) 370 371 } 372 + 373 + // HasAPIError returns true if error occurs in form validation. 374 + func (ctx *APIContext) HasAPIError() bool { 375 + hasErr, ok := ctx.Data["HasError"] 376 + if !ok { 377 + return false 378 + } 379 + return hasErr.(bool) 380 + } 381 + 382 + // GetErrMsg returns error message in form validation. 383 + func (ctx *APIContext) GetErrMsg() string { 384 + msg, _ := ctx.Data["ErrorMsg"].(string) 385 + if msg == "" { 386 + msg = "invalid form data" 387 + } 388 + return msg 389 + } 390 + 391 + // NotFoundOrServerError use error check function to determine if the error 392 + // is about not found. It responds with 404 status code for not found error, 393 + // or error context description for logging purpose of 500 server error. 394 + func (ctx *APIContext) NotFoundOrServerError(logMsg string, errCheck func(error) bool, logErr error) { 395 + if errCheck(logErr) { 396 + ctx.JSON(http.StatusNotFound, nil) 397 + return 398 + } 399 + ctx.Error(http.StatusInternalServerError, "NotFoundOrServerError", logMsg) 400 + } 401 + 402 + // IsUserSiteAdmin returns true if current user is a site admin 403 + func (ctx *APIContext) IsUserSiteAdmin() bool { 404 + return ctx.IsSigned && ctx.Doer.IsAdmin 405 + } 406 + 407 + // IsUserRepoAdmin returns true if current user is admin in current repo 408 + func (ctx *APIContext) IsUserRepoAdmin() bool { 409 + return ctx.Repo.IsAdmin() 410 + } 411 + 412 + // IsUserRepoWriter returns true if current user has write privilege in current repo 413 + func (ctx *APIContext) IsUserRepoWriter(unitTypes []unit.Type) bool { 414 + for _, unitType := range unitTypes { 415 + if ctx.Repo.CanWrite(unitType) { 416 + return true 417 + } 418 + } 419 + 420 + return false 421 + }
+300
modules/context/base.go
··· 1 + // Copyright 2020 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package context 5 + 6 + import ( 7 + "context" 8 + "fmt" 9 + "io" 10 + "net/http" 11 + "net/url" 12 + "strconv" 13 + "strings" 14 + "time" 15 + 16 + "code.gitea.io/gitea/modules/httplib" 17 + "code.gitea.io/gitea/modules/json" 18 + "code.gitea.io/gitea/modules/log" 19 + "code.gitea.io/gitea/modules/translation" 20 + "code.gitea.io/gitea/modules/util" 21 + "code.gitea.io/gitea/modules/web/middleware" 22 + 23 + "github.com/go-chi/chi/v5" 24 + ) 25 + 26 + type contextValuePair struct { 27 + key any 28 + valueFn func() any 29 + } 30 + 31 + type Base struct { 32 + originCtx context.Context 33 + contextValues []contextValuePair 34 + 35 + Resp ResponseWriter 36 + Req *http.Request 37 + 38 + // Data is prepared by ContextDataStore middleware, this field only refers to the pre-created/prepared ContextData. 39 + // Although it's mainly used for MVC templates, sometimes it's also used to pass data between middlewares/handler 40 + Data middleware.ContextData 41 + 42 + // Locale is mainly for Web context, although the API context also uses it in some cases: message response, form validation 43 + Locale translation.Locale 44 + } 45 + 46 + func (b *Base) Deadline() (deadline time.Time, ok bool) { 47 + return b.originCtx.Deadline() 48 + } 49 + 50 + func (b *Base) Done() <-chan struct{} { 51 + return b.originCtx.Done() 52 + } 53 + 54 + func (b *Base) Err() error { 55 + return b.originCtx.Err() 56 + } 57 + 58 + func (b *Base) Value(key any) any { 59 + for _, pair := range b.contextValues { 60 + if pair.key == key { 61 + return pair.valueFn() 62 + } 63 + } 64 + return b.originCtx.Value(key) 65 + } 66 + 67 + func (b *Base) AppendContextValueFunc(key any, valueFn func() any) any { 68 + b.contextValues = append(b.contextValues, contextValuePair{key, valueFn}) 69 + return b 70 + } 71 + 72 + func (b *Base) AppendContextValue(key, value any) any { 73 + b.contextValues = append(b.contextValues, contextValuePair{key, func() any { return value }}) 74 + return b 75 + } 76 + 77 + func (b *Base) GetData() middleware.ContextData { 78 + return b.Data 79 + } 80 + 81 + // AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header 82 + func (b *Base) AppendAccessControlExposeHeaders(names ...string) { 83 + val := b.RespHeader().Get("Access-Control-Expose-Headers") 84 + if len(val) != 0 { 85 + b.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", "))) 86 + } else { 87 + b.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", ")) 88 + } 89 + } 90 + 91 + // SetTotalCountHeader set "X-Total-Count" header 92 + func (b *Base) SetTotalCountHeader(total int64) { 93 + b.RespHeader().Set("X-Total-Count", fmt.Sprint(total)) 94 + b.AppendAccessControlExposeHeaders("X-Total-Count") 95 + } 96 + 97 + // Written returns true if there are something sent to web browser 98 + func (b *Base) Written() bool { 99 + return b.Resp.Status() > 0 100 + } 101 + 102 + // Status writes status code 103 + func (b *Base) Status(status int) { 104 + b.Resp.WriteHeader(status) 105 + } 106 + 107 + // Write writes data to web browser 108 + func (b *Base) Write(bs []byte) (int, error) { 109 + return b.Resp.Write(bs) 110 + } 111 + 112 + // RespHeader returns the response header 113 + func (b *Base) RespHeader() http.Header { 114 + return b.Resp.Header() 115 + } 116 + 117 + // Error returned an error to web browser 118 + func (b *Base) Error(status int, contents ...string) { 119 + v := http.StatusText(status) 120 + if len(contents) > 0 { 121 + v = contents[0] 122 + } 123 + http.Error(b.Resp, v, status) 124 + } 125 + 126 + // JSON render content as JSON 127 + func (b *Base) JSON(status int, content interface{}) { 128 + b.Resp.Header().Set("Content-Type", "application/json;charset=utf-8") 129 + b.Resp.WriteHeader(status) 130 + if err := json.NewEncoder(b.Resp).Encode(content); err != nil { 131 + log.Error("Render JSON failed: %v", err) 132 + } 133 + } 134 + 135 + // RemoteAddr returns the client machine ip address 136 + func (b *Base) RemoteAddr() string { 137 + return b.Req.RemoteAddr 138 + } 139 + 140 + // Params returns the param on route 141 + func (b *Base) Params(p string) string { 142 + s, _ := url.PathUnescape(chi.URLParam(b.Req, strings.TrimPrefix(p, ":"))) 143 + return s 144 + } 145 + 146 + // ParamsInt64 returns the param on route as int64 147 + func (b *Base) ParamsInt64(p string) int64 { 148 + v, _ := strconv.ParseInt(b.Params(p), 10, 64) 149 + return v 150 + } 151 + 152 + // SetParams set params into routes 153 + func (b *Base) SetParams(k, v string) { 154 + chiCtx := chi.RouteContext(b) 155 + chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v)) 156 + } 157 + 158 + // FormString returns the first value matching the provided key in the form as a string 159 + func (b *Base) FormString(key string) string { 160 + return b.Req.FormValue(key) 161 + } 162 + 163 + // FormStrings returns a string slice for the provided key from the form 164 + func (b *Base) FormStrings(key string) []string { 165 + if b.Req.Form == nil { 166 + if err := b.Req.ParseMultipartForm(32 << 20); err != nil { 167 + return nil 168 + } 169 + } 170 + if v, ok := b.Req.Form[key]; ok { 171 + return v 172 + } 173 + return nil 174 + } 175 + 176 + // FormTrim returns the first value for the provided key in the form as a space trimmed string 177 + func (b *Base) FormTrim(key string) string { 178 + return strings.TrimSpace(b.Req.FormValue(key)) 179 + } 180 + 181 + // FormInt returns the first value for the provided key in the form as an int 182 + func (b *Base) FormInt(key string) int { 183 + v, _ := strconv.Atoi(b.Req.FormValue(key)) 184 + return v 185 + } 186 + 187 + // FormInt64 returns the first value for the provided key in the form as an int64 188 + func (b *Base) FormInt64(key string) int64 { 189 + v, _ := strconv.ParseInt(b.Req.FormValue(key), 10, 64) 190 + return v 191 + } 192 + 193 + // FormBool returns true if the value for the provided key in the form is "1", "true" or "on" 194 + func (b *Base) FormBool(key string) bool { 195 + s := b.Req.FormValue(key) 196 + v, _ := strconv.ParseBool(s) 197 + v = v || strings.EqualFold(s, "on") 198 + return v 199 + } 200 + 201 + // FormOptionalBool returns an OptionalBoolTrue or OptionalBoolFalse if the value 202 + // for the provided key exists in the form else it returns OptionalBoolNone 203 + func (b *Base) FormOptionalBool(key string) util.OptionalBool { 204 + value := b.Req.FormValue(key) 205 + if len(value) == 0 { 206 + return util.OptionalBoolNone 207 + } 208 + s := b.Req.FormValue(key) 209 + v, _ := strconv.ParseBool(s) 210 + v = v || strings.EqualFold(s, "on") 211 + return util.OptionalBoolOf(v) 212 + } 213 + 214 + func (b *Base) SetFormString(key, value string) { 215 + _ = b.Req.FormValue(key) // force parse form 216 + b.Req.Form.Set(key, value) 217 + } 218 + 219 + // PlainTextBytes renders bytes as plain text 220 + func (b *Base) plainTextInternal(skip, status int, bs []byte) { 221 + statusPrefix := status / 100 222 + if statusPrefix == 4 || statusPrefix == 5 { 223 + log.Log(skip, log.TRACE, "plainTextInternal (status=%d): %s", status, string(bs)) 224 + } 225 + b.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") 226 + b.Resp.Header().Set("X-Content-Type-Options", "nosniff") 227 + b.Resp.WriteHeader(status) 228 + if _, err := b.Resp.Write(bs); err != nil { 229 + log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err) 230 + } 231 + } 232 + 233 + // PlainTextBytes renders bytes as plain text 234 + func (b *Base) PlainTextBytes(status int, bs []byte) { 235 + b.plainTextInternal(2, status, bs) 236 + } 237 + 238 + // PlainText renders content as plain text 239 + func (b *Base) PlainText(status int, text string) { 240 + b.plainTextInternal(2, status, []byte(text)) 241 + } 242 + 243 + // Redirect redirects the request 244 + func (b *Base) Redirect(location string, status ...int) { 245 + code := http.StatusSeeOther 246 + if len(status) == 1 { 247 + code = status[0] 248 + } 249 + 250 + if strings.Contains(location, "://") || strings.HasPrefix(location, "//") { 251 + // Some browsers (Safari) have buggy behavior for Cookie + Cache + External Redirection, eg: /my-path => https://other/path 252 + // 1. the first request to "/my-path" contains cookie 253 + // 2. some time later, the request to "/my-path" doesn't contain cookie (caused by Prevent web tracking) 254 + // 3. Gitea's Sessioner doesn't see the session cookie, so it generates a new session id, and returns it to browser 255 + // 4. then the browser accepts the empty session, then the user is logged out 256 + // So in this case, we should remove the session cookie from the response header 257 + removeSessionCookieHeader(b.Resp) 258 + } 259 + http.Redirect(b.Resp, b.Req, location, code) 260 + } 261 + 262 + type ServeHeaderOptions httplib.ServeHeaderOptions 263 + 264 + func (b *Base) SetServeHeaders(opt *ServeHeaderOptions) { 265 + httplib.ServeSetHeaders(b.Resp, (*httplib.ServeHeaderOptions)(opt)) 266 + } 267 + 268 + // ServeContent serves content to http request 269 + func (b *Base) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) { 270 + httplib.ServeSetHeaders(b.Resp, (*httplib.ServeHeaderOptions)(opts)) 271 + http.ServeContent(b.Resp, b.Req, opts.Filename, opts.LastModified, r) 272 + } 273 + 274 + // Close frees all resources hold by Context 275 + func (b *Base) cleanUp() { 276 + if b.Req != nil && b.Req.MultipartForm != nil { 277 + _ = b.Req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory 278 + } 279 + } 280 + 281 + func (b *Base) Tr(msg string, args ...any) string { 282 + return b.Locale.Tr(msg, args...) 283 + } 284 + 285 + func (b *Base) TrN(cnt any, key1, keyN string, args ...any) string { 286 + return b.Locale.TrN(cnt, key1, keyN, args...) 287 + } 288 + 289 + func NewBaseContext(resp http.ResponseWriter, req *http.Request) (b *Base, closeFunc func()) { 290 + b = &Base{ 291 + originCtx: req.Context(), 292 + Req: req, 293 + Resp: WrapResponseWriter(resp), 294 + Locale: middleware.Locale(resp, req), 295 + Data: middleware.GetContextData(req.Context()), 296 + } 297 + b.AppendContextValue(translation.ContextKey, b.Locale) 298 + b.Req = b.Req.WithContext(b) 299 + return b, b.cleanUp 300 + }
+60 -77
modules/context/context.go
··· 5 5 package context 6 6 7 7 import ( 8 - "context" 9 8 "html" 10 9 "html/template" 11 10 "io" ··· 36 35 37 36 // Context represents context of a request. 38 37 type Context struct { 39 - Resp ResponseWriter 40 - Req *http.Request 41 - Render Render 38 + *Base 42 39 43 - Data middleware.ContextData // data used by MVC templates 44 - PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData` 40 + Render Render 41 + PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData` 45 42 46 - Locale translation.Locale 47 43 Cache cache.Cache 48 44 Csrf CSRFProtector 49 45 Flash *middleware.Flash 50 46 Session session.Store 51 47 52 - Link string // current request URL (without query string) 53 - Doer *user_model.User 48 + Link string // current request URL (without query string) 49 + 50 + Doer *user_model.User // current signed-in user 54 51 IsSigned bool 55 52 IsBasicAuth bool 56 53 57 - ContextUser *user_model.User 58 - Repo *Repository 59 - Org *Organization 60 - Package *Package 61 - } 54 + ContextUser *user_model.User // the user which is being visited, in most cases it differs from Doer 62 55 63 - // Close frees all resources hold by Context 64 - func (ctx *Context) Close() error { 65 - var err error 66 - if ctx.Req != nil && ctx.Req.MultipartForm != nil { 67 - err = ctx.Req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory 68 - } 69 - // TODO: close opened repo, and more 70 - return err 56 + Repo *Repository 57 + Org *Organization 58 + Package *Package 71 59 } 72 60 73 61 // TrHTMLEscapeArgs runs ".Locale.Tr()" but pre-escapes all arguments with html.EscapeString. ··· 80 68 return ctx.Locale.Tr(msg, trArgs...) 81 69 } 82 70 83 - func (ctx *Context) Tr(msg string, args ...any) string { 84 - return ctx.Locale.Tr(msg, args...) 85 - } 71 + type contextKeyType struct{} 86 72 87 - func (ctx *Context) TrN(cnt any, key1, keyN string, args ...any) string { 88 - return ctx.Locale.TrN(cnt, key1, keyN, args...) 89 - } 73 + var contextKey interface{} = contextKeyType{} 90 74 91 - // Deadline is part of the interface for context.Context and we pass this to the request context 92 - func (ctx *Context) Deadline() (deadline time.Time, ok bool) { 93 - return ctx.Req.Context().Deadline() 75 + func GetContext(req *http.Request) *Context { 76 + ctx, _ := req.Context().Value(contextKey).(*Context) 77 + return ctx 94 78 } 95 79 96 - // Done is part of the interface for context.Context and we pass this to the request context 97 - func (ctx *Context) Done() <-chan struct{} { 98 - return ctx.Req.Context().Done() 99 - } 100 - 101 - // Err is part of the interface for context.Context and we pass this to the request context 102 - func (ctx *Context) Err() error { 103 - return ctx.Req.Context().Err() 80 + // ValidateContext is a special context for form validation middleware. It may be different from other contexts. 81 + type ValidateContext struct { 82 + *Base 104 83 } 105 84 106 - // Value is part of the interface for context.Context and we pass this to the request context 107 - func (ctx *Context) Value(key interface{}) interface{} { 108 - if key == git.RepositoryContextKey && ctx.Repo != nil { 109 - return ctx.Repo.GitRepo 85 + // GetValidateContext gets a context for middleware form validation 86 + func GetValidateContext(req *http.Request) (ctx *ValidateContext) { 87 + if ctxAPI, ok := req.Context().Value(apiContextKey).(*APIContext); ok { 88 + ctx = &ValidateContext{Base: ctxAPI.Base} 89 + } else if ctxWeb, ok := req.Context().Value(contextKey).(*Context); ok { 90 + ctx = &ValidateContext{Base: ctxWeb.Base} 91 + } else { 92 + panic("invalid context, expect either APIContext or Context") 110 93 } 111 - if key == translation.ContextKey && ctx.Locale != nil { 112 - return ctx.Locale 113 - } 114 - return ctx.Req.Context().Value(key) 115 - } 116 - 117 - type contextKeyType struct{} 118 - 119 - var contextKey interface{} = contextKeyType{} 120 - 121 - // WithContext set up install context in request 122 - func WithContext(req *http.Request, ctx *Context) *http.Request { 123 - return req.WithContext(context.WithValue(req.Context(), contextKey, ctx)) 124 - } 125 - 126 - // GetContext retrieves install context from request 127 - func GetContext(req *http.Request) *Context { 128 - if ctx, ok := req.Context().Value(contextKey).(*Context); ok { 129 - return ctx 130 - } 131 - return nil 94 + return ctx 132 95 } 133 96 134 97 // Contexter initializes a classic context for a request. ··· 150 113 } 151 114 return func(next http.Handler) http.Handler { 152 115 return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { 153 - ctx := Context{ 154 - Resp: NewResponse(resp), 116 + base, baseCleanUp := NewBaseContext(resp, req) 117 + ctx := &Context{ 118 + Base: base, 155 119 Cache: mc.GetCache(), 156 - Locale: middleware.Locale(resp, req), 157 120 Link: setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/"), 158 121 Render: rnd, 159 122 Session: session.GetSession(req), 160 - Repo: &Repository{ 161 - PullRequest: &PullRequest{}, 162 - }, 163 - Org: &Organization{}, 164 - Data: middleware.GetContextData(req.Context()), 123 + Repo: &Repository{PullRequest: &PullRequest{}}, 124 + Org: &Organization{}, 165 125 } 166 - defer ctx.Close() 126 + defer baseCleanUp() 167 127 168 128 ctx.Data.MergeFrom(middleware.CommonTemplateContextData()) 169 129 ctx.Data["Context"] = &ctx ··· 175 135 ctx.PageData = map[string]any{} 176 136 ctx.Data["PageData"] = ctx.PageData 177 137 178 - ctx.Req = WithContext(req, &ctx) 179 - ctx.Csrf = PrepareCSRFProtector(csrfOpts, &ctx) 138 + ctx.Base.AppendContextValue(contextKey, ctx) 139 + ctx.Base.AppendContextValueFunc(git.RepositoryContextKey, func() any { return ctx.Repo.GitRepo }) 140 + 141 + ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx) 180 142 181 143 // Get the last flash message from cookie 182 144 lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash) 183 145 if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 { 184 146 // store last Flash message into the template data, to render it 185 147 ctx.Data["Flash"] = &middleware.Flash{ 186 - DataStore: &ctx, 148 + DataStore: ctx, 187 149 Values: vals, 188 150 ErrorMsg: vals.Get("error"), 189 151 SuccessMsg: vals.Get("success"), ··· 193 155 } 194 156 195 157 // prepare an empty Flash message for current request 196 - ctx.Flash = &middleware.Flash{DataStore: &ctx, Values: url.Values{}} 158 + ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}} 197 159 ctx.Resp.Before(func(resp ResponseWriter) { 198 160 if val := ctx.Flash.Encode(); val != "" { 199 161 middleware.SetSiteCookie(ctx.Resp, CookieNameFlash, val, 0) ··· 235 197 }) 236 198 } 237 199 } 200 + 201 + // HasError returns true if error occurs in form validation. 202 + // Attention: this function changes ctx.Data and ctx.Flash 203 + func (ctx *Context) HasError() bool { 204 + hasErr, ok := ctx.Data["HasError"] 205 + if !ok { 206 + return false 207 + } 208 + ctx.Flash.ErrorMsg = ctx.GetErrMsg() 209 + ctx.Data["Flash"] = ctx.Flash 210 + return hasErr.(bool) 211 + } 212 + 213 + // GetErrMsg returns error message in form validation. 214 + func (ctx *Context) GetErrMsg() string { 215 + msg, _ := ctx.Data["ErrorMsg"].(string) 216 + if msg == "" { 217 + msg = "invalid form data" 218 + } 219 + return msg 220 + }
-43
modules/context/context_data.go
··· 1 - // Copyright 2023 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package context 5 - 6 - import "code.gitea.io/gitea/modules/web/middleware" 7 - 8 - // GetData returns the data 9 - func (ctx *Context) GetData() middleware.ContextData { 10 - return ctx.Data 11 - } 12 - 13 - // HasAPIError returns true if error occurs in form validation. 14 - func (ctx *Context) HasAPIError() bool { 15 - hasErr, ok := ctx.Data["HasError"] 16 - if !ok { 17 - return false 18 - } 19 - return hasErr.(bool) 20 - } 21 - 22 - // GetErrMsg returns error message 23 - func (ctx *Context) GetErrMsg() string { 24 - return ctx.Data["ErrorMsg"].(string) 25 - } 26 - 27 - // HasError returns true if error occurs in form validation. 28 - // Attention: this function changes ctx.Data and ctx.Flash 29 - func (ctx *Context) HasError() bool { 30 - hasErr, ok := ctx.Data["HasError"] 31 - if !ok { 32 - return false 33 - } 34 - ctx.Flash.ErrorMsg = ctx.Data["ErrorMsg"].(string) 35 - ctx.Data["Flash"] = ctx.Flash 36 - return hasErr.(bool) 37 - } 38 - 39 - // HasValue returns true if value of given name exists. 40 - func (ctx *Context) HasValue(name string) bool { 41 - _, ok := ctx.Data[name] 42 - return ok 43 - }
-72
modules/context/context_form.go
··· 1 - // Copyright 2021 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package context 5 - 6 - import ( 7 - "strconv" 8 - "strings" 9 - 10 - "code.gitea.io/gitea/modules/util" 11 - ) 12 - 13 - // FormString returns the first value matching the provided key in the form as a string 14 - func (ctx *Context) FormString(key string) string { 15 - return ctx.Req.FormValue(key) 16 - } 17 - 18 - // FormStrings returns a string slice for the provided key from the form 19 - func (ctx *Context) FormStrings(key string) []string { 20 - if ctx.Req.Form == nil { 21 - if err := ctx.Req.ParseMultipartForm(32 << 20); err != nil { 22 - return nil 23 - } 24 - } 25 - if v, ok := ctx.Req.Form[key]; ok { 26 - return v 27 - } 28 - return nil 29 - } 30 - 31 - // FormTrim returns the first value for the provided key in the form as a space trimmed string 32 - func (ctx *Context) FormTrim(key string) string { 33 - return strings.TrimSpace(ctx.Req.FormValue(key)) 34 - } 35 - 36 - // FormInt returns the first value for the provided key in the form as an int 37 - func (ctx *Context) FormInt(key string) int { 38 - v, _ := strconv.Atoi(ctx.Req.FormValue(key)) 39 - return v 40 - } 41 - 42 - // FormInt64 returns the first value for the provided key in the form as an int64 43 - func (ctx *Context) FormInt64(key string) int64 { 44 - v, _ := strconv.ParseInt(ctx.Req.FormValue(key), 10, 64) 45 - return v 46 - } 47 - 48 - // FormBool returns true if the value for the provided key in the form is "1", "true" or "on" 49 - func (ctx *Context) FormBool(key string) bool { 50 - s := ctx.Req.FormValue(key) 51 - v, _ := strconv.ParseBool(s) 52 - v = v || strings.EqualFold(s, "on") 53 - return v 54 - } 55 - 56 - // FormOptionalBool returns an OptionalBoolTrue or OptionalBoolFalse if the value 57 - // for the provided key exists in the form else it returns OptionalBoolNone 58 - func (ctx *Context) FormOptionalBool(key string) util.OptionalBool { 59 - value := ctx.Req.FormValue(key) 60 - if len(value) == 0 { 61 - return util.OptionalBoolNone 62 - } 63 - s := ctx.Req.FormValue(key) 64 - v, _ := strconv.ParseBool(s) 65 - v = v || strings.EqualFold(s, "on") 66 - return util.OptionalBoolOf(v) 67 - } 68 - 69 - func (ctx *Context) SetFormString(key, value string) { 70 - _ = ctx.Req.FormValue(key) // force parse form 71 - ctx.Req.Form.Set(key, value) 72 - }
-27
modules/context/context_request.go
··· 6 6 import ( 7 7 "io" 8 8 "net/http" 9 - "net/url" 10 - "strconv" 11 9 "strings" 12 - 13 - "github.com/go-chi/chi/v5" 14 10 ) 15 - 16 - // RemoteAddr returns the client machine ip address 17 - func (ctx *Context) RemoteAddr() string { 18 - return ctx.Req.RemoteAddr 19 - } 20 - 21 - // Params returns the param on route 22 - func (ctx *Context) Params(p string) string { 23 - s, _ := url.PathUnescape(chi.URLParam(ctx.Req, strings.TrimPrefix(p, ":"))) 24 - return s 25 - } 26 - 27 - // ParamsInt64 returns the param on route as int64 28 - func (ctx *Context) ParamsInt64(p string) int64 { 29 - v, _ := strconv.ParseInt(ctx.Params(p), 10, 64) 30 - return v 31 - } 32 - 33 - // SetParams set params into routes 34 - func (ctx *Context) SetParams(k, v string) { 35 - chiCtx := chi.RouteContext(ctx) 36 - chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v)) 37 - } 38 11 39 12 // UploadStream returns the request body or the first form file 40 13 // Only form files need to get closed.
+2 -100
modules/context/context_response.go
··· 16 16 17 17 user_model "code.gitea.io/gitea/models/user" 18 18 "code.gitea.io/gitea/modules/base" 19 - "code.gitea.io/gitea/modules/json" 20 19 "code.gitea.io/gitea/modules/log" 21 20 "code.gitea.io/gitea/modules/setting" 22 21 "code.gitea.io/gitea/modules/templates" 23 22 "code.gitea.io/gitea/modules/web/middleware" 24 23 ) 25 24 26 - // SetTotalCountHeader set "X-Total-Count" header 27 - func (ctx *Context) SetTotalCountHeader(total int64) { 28 - ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total)) 29 - ctx.AppendAccessControlExposeHeaders("X-Total-Count") 30 - } 31 - 32 - // AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header 33 - func (ctx *Context) AppendAccessControlExposeHeaders(names ...string) { 34 - val := ctx.RespHeader().Get("Access-Control-Expose-Headers") 35 - if len(val) != 0 { 36 - ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", "))) 37 - } else { 38 - ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", ")) 39 - } 40 - } 41 - 42 - // Written returns true if there are something sent to web browser 43 - func (ctx *Context) Written() bool { 44 - return ctx.Resp.Status() > 0 45 - } 46 - 47 - // Status writes status code 48 - func (ctx *Context) Status(status int) { 49 - ctx.Resp.WriteHeader(status) 50 - } 51 - 52 - // Write writes data to web browser 53 - func (ctx *Context) Write(bs []byte) (int, error) { 54 - return ctx.Resp.Write(bs) 55 - } 56 - 57 25 // RedirectToUser redirect to a differently-named user 58 - func RedirectToUser(ctx *Context, userName string, redirectUserID int64) { 26 + func RedirectToUser(ctx *Base, userName string, redirectUserID int64) { 59 27 user, err := user_model.GetUserByID(ctx, redirectUserID) 60 28 if err != nil { 61 - ctx.ServerError("GetUserByID", err) 29 + ctx.Error(http.StatusInternalServerError, "unable to get user") 62 30 return 63 31 } 64 32 ··· 211 179 } 212 180 ctx.serverErrorInternal(logMsg, logErr) 213 181 } 214 - 215 - // PlainTextBytes renders bytes as plain text 216 - func (ctx *Context) plainTextInternal(skip, status int, bs []byte) { 217 - statusPrefix := status / 100 218 - if statusPrefix == 4 || statusPrefix == 5 { 219 - log.Log(skip, log.TRACE, "plainTextInternal (status=%d): %s", status, string(bs)) 220 - } 221 - ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") 222 - ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff") 223 - ctx.Resp.WriteHeader(status) 224 - if _, err := ctx.Resp.Write(bs); err != nil { 225 - log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err) 226 - } 227 - } 228 - 229 - // PlainTextBytes renders bytes as plain text 230 - func (ctx *Context) PlainTextBytes(status int, bs []byte) { 231 - ctx.plainTextInternal(2, status, bs) 232 - } 233 - 234 - // PlainText renders content as plain text 235 - func (ctx *Context) PlainText(status int, text string) { 236 - ctx.plainTextInternal(2, status, []byte(text)) 237 - } 238 - 239 - // RespHeader returns the response header 240 - func (ctx *Context) RespHeader() http.Header { 241 - return ctx.Resp.Header() 242 - } 243 - 244 - // Error returned an error to web browser 245 - func (ctx *Context) Error(status int, contents ...string) { 246 - v := http.StatusText(status) 247 - if len(contents) > 0 { 248 - v = contents[0] 249 - } 250 - http.Error(ctx.Resp, v, status) 251 - } 252 - 253 - // JSON render content as JSON 254 - func (ctx *Context) JSON(status int, content interface{}) { 255 - ctx.Resp.Header().Set("Content-Type", "application/json;charset=utf-8") 256 - ctx.Resp.WriteHeader(status) 257 - if err := json.NewEncoder(ctx.Resp).Encode(content); err != nil { 258 - ctx.ServerError("Render JSON failed", err) 259 - } 260 - } 261 - 262 - // Redirect redirects the request 263 - func (ctx *Context) Redirect(location string, status ...int) { 264 - code := http.StatusSeeOther 265 - if len(status) == 1 { 266 - code = status[0] 267 - } 268 - 269 - if strings.Contains(location, "://") || strings.HasPrefix(location, "//") { 270 - // Some browsers (Safari) have buggy behavior for Cookie + Cache + External Redirection, eg: /my-path => https://other/path 271 - // 1. the first request to "/my-path" contains cookie 272 - // 2. some time later, the request to "/my-path" doesn't contain cookie (caused by Prevent web tracking) 273 - // 3. Gitea's Sessioner doesn't see the session cookie, so it generates a new session id, and returns it to browser 274 - // 4. then the browser accepts the empty session, then the user is logged out 275 - // So in this case, we should remove the session cookie from the response header 276 - removeSessionCookieHeader(ctx.Resp) 277 - } 278 - http.Redirect(ctx.Resp, ctx.Req, location, code) 279 - }
-23
modules/context/context_serve.go
··· 1 - // Copyright 2023 The Gitea Authors. All rights reserved. 2 - // SPDX-License-Identifier: MIT 3 - 4 - package context 5 - 6 - import ( 7 - "io" 8 - "net/http" 9 - 10 - "code.gitea.io/gitea/modules/httplib" 11 - ) 12 - 13 - type ServeHeaderOptions httplib.ServeHeaderOptions 14 - 15 - func (ctx *Context) SetServeHeaders(opt *ServeHeaderOptions) { 16 - httplib.ServeSetHeaders(ctx.Resp, (*httplib.ServeHeaderOptions)(opt)) 17 - } 18 - 19 - // ServeContent serves content to http request 20 - func (ctx *Context) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) { 21 - httplib.ServeSetHeaders(ctx.Resp, (*httplib.ServeHeaderOptions)(opts)) 22 - http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, r) 23 - }
+1 -1
modules/context/org.go
··· 47 47 if organization.IsErrOrgNotExist(err) { 48 48 redirectUserID, err := user_model.LookupUserRedirect(orgName) 49 49 if err == nil { 50 - RedirectToUser(ctx, orgName, redirectUserID) 50 + RedirectToUser(ctx.Base, orgName, redirectUserID) 51 51 } else if user_model.IsErrUserRedirectNotExist(err) { 52 52 ctx.NotFound("GetUserByName", err) 53 53 } else {
+42 -35
modules/context/package.go
··· 4 4 package context 5 5 6 6 import ( 7 - gocontext "context" 8 7 "fmt" 9 8 "net/http" 10 9 ··· 16 15 "code.gitea.io/gitea/modules/setting" 17 16 "code.gitea.io/gitea/modules/structs" 18 17 "code.gitea.io/gitea/modules/templates" 19 - "code.gitea.io/gitea/modules/web/middleware" 20 18 ) 21 19 22 20 // Package contains owner, access mode and optional the package descriptor ··· 26 24 Descriptor *packages_model.PackageDescriptor 27 25 } 28 26 27 + type packageAssignmentCtx struct { 28 + *Base 29 + Doer *user_model.User 30 + ContextUser *user_model.User 31 + } 32 + 29 33 // PackageAssignment returns a middleware to handle Context.Package assignment 30 34 func PackageAssignment() func(ctx *Context) { 31 35 return func(ctx *Context) { 32 - packageAssignment(ctx, func(status int, title string, obj interface{}) { 36 + errorFn := func(status int, title string, obj interface{}) { 33 37 err, ok := obj.(error) 34 38 if !ok { 35 39 err = fmt.Errorf("%s", obj) ··· 39 43 } else { 40 44 ctx.ServerError(title, err) 41 45 } 42 - }) 46 + } 47 + paCtx := &packageAssignmentCtx{Base: ctx.Base, Doer: ctx.Doer, ContextUser: ctx.ContextUser} 48 + ctx.Package = packageAssignment(paCtx, errorFn) 43 49 } 44 50 } 45 51 46 52 // PackageAssignmentAPI returns a middleware to handle Context.Package assignment 47 53 func PackageAssignmentAPI() func(ctx *APIContext) { 48 54 return func(ctx *APIContext) { 49 - packageAssignment(ctx.Context, ctx.Error) 55 + paCtx := &packageAssignmentCtx{Base: ctx.Base, Doer: ctx.Doer, ContextUser: ctx.ContextUser} 56 + ctx.Package = packageAssignment(paCtx, ctx.Error) 50 57 } 51 58 } 52 59 53 - func packageAssignment(ctx *Context, errCb func(int, string, interface{})) { 54 - ctx.Package = &Package{ 60 + func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, string, interface{})) *Package { 61 + pkg := &Package{ 55 62 Owner: ctx.ContextUser, 56 63 } 57 - 58 64 var err error 59 - ctx.Package.AccessMode, err = determineAccessMode(ctx) 65 + pkg.AccessMode, err = determineAccessMode(ctx.Base, pkg, ctx.Doer) 60 66 if err != nil { 61 67 errCb(http.StatusInternalServerError, "determineAccessMode", err) 62 - return 68 + return pkg 63 69 } 64 70 65 71 packageType := ctx.Params("type") 66 72 name := ctx.Params("name") 67 73 version := ctx.Params("version") 68 74 if packageType != "" && name != "" && version != "" { 69 - pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.Type(packageType), name, version) 75 + pv, err := packages_model.GetVersionByNameAndVersion(ctx, pkg.Owner.ID, packages_model.Type(packageType), name, version) 70 76 if err != nil { 71 77 if err == packages_model.ErrPackageNotExist { 72 78 errCb(http.StatusNotFound, "GetVersionByNameAndVersion", err) 73 79 } else { 74 80 errCb(http.StatusInternalServerError, "GetVersionByNameAndVersion", err) 75 81 } 76 - return 82 + return pkg 77 83 } 78 84 79 - ctx.Package.Descriptor, err = packages_model.GetPackageDescriptor(ctx, pv) 85 + pkg.Descriptor, err = packages_model.GetPackageDescriptor(ctx, pv) 80 86 if err != nil { 81 87 errCb(http.StatusInternalServerError, "GetPackageDescriptor", err) 82 - return 88 + return pkg 83 89 } 84 90 } 91 + 92 + return pkg 85 93 } 86 94 87 - func determineAccessMode(ctx *Context) (perm.AccessMode, error) { 88 - if setting.Service.RequireSignInView && ctx.Doer == nil { 95 + func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.AccessMode, error) { 96 + if setting.Service.RequireSignInView && doer == nil { 89 97 return perm.AccessModeNone, nil 90 98 } 91 99 92 - if ctx.Doer != nil && !ctx.Doer.IsGhost() && (!ctx.Doer.IsActive || ctx.Doer.ProhibitLogin) { 100 + if doer != nil && !doer.IsGhost() && (!doer.IsActive || doer.ProhibitLogin) { 93 101 return perm.AccessModeNone, nil 94 102 } 95 103 96 104 // TODO: ActionUser permission check 97 105 accessMode := perm.AccessModeNone 98 - if ctx.Package.Owner.IsOrganization() { 99 - org := organization.OrgFromUser(ctx.Package.Owner) 106 + if pkg.Owner.IsOrganization() { 107 + org := organization.OrgFromUser(pkg.Owner) 100 108 101 - if ctx.Doer != nil && !ctx.Doer.IsGhost() { 109 + if doer != nil && !doer.IsGhost() { 102 110 // 1. If user is logged in, check all team packages permissions 103 - teams, err := organization.GetUserOrgTeams(ctx, org.ID, ctx.Doer.ID) 111 + teams, err := organization.GetUserOrgTeams(ctx, org.ID, doer.ID) 104 112 if err != nil { 105 113 return accessMode, err 106 114 } ··· 110 118 accessMode = perm 111 119 } 112 120 } 113 - } else if organization.HasOrgOrUserVisible(ctx, ctx.Package.Owner, ctx.Doer) { 121 + } else if organization.HasOrgOrUserVisible(ctx, pkg.Owner, doer) { 114 122 // 2. If user is non-login, check if org is visible to non-login user 115 123 accessMode = perm.AccessModeRead 116 124 } 117 125 } else { 118 - if ctx.Doer != nil && !ctx.Doer.IsGhost() { 126 + if doer != nil && !doer.IsGhost() { 119 127 // 1. Check if user is package owner 120 - if ctx.Doer.ID == ctx.Package.Owner.ID { 128 + if doer.ID == pkg.Owner.ID { 121 129 accessMode = perm.AccessModeOwner 122 - } else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic || ctx.Package.Owner.Visibility == structs.VisibleTypeLimited { // 2. Check if package owner is public or limited 130 + } else if pkg.Owner.Visibility == structs.VisibleTypePublic || pkg.Owner.Visibility == structs.VisibleTypeLimited { // 2. Check if package owner is public or limited 123 131 accessMode = perm.AccessModeRead 124 132 } 125 - } else if ctx.Package.Owner.Visibility == structs.VisibleTypePublic { // 3. Check if package owner is public 133 + } else if pkg.Owner.Visibility == structs.VisibleTypePublic { // 3. Check if package owner is public 126 134 accessMode = perm.AccessModeRead 127 135 } 128 136 } ··· 131 139 } 132 140 133 141 // PackageContexter initializes a package context for a request. 134 - func PackageContexter(ctx gocontext.Context) func(next http.Handler) http.Handler { 135 - rnd := templates.HTMLRenderer() 142 + func PackageContexter() func(next http.Handler) http.Handler { 143 + renderer := templates.HTMLRenderer() 136 144 return func(next http.Handler) http.Handler { 137 145 return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { 138 - ctx := Context{ 139 - Resp: NewResponse(resp), 140 - Data: middleware.GetContextData(req.Context()), 141 - Render: rnd, 146 + base, baseCleanUp := NewBaseContext(resp, req) 147 + ctx := &Context{ 148 + Base: base, 149 + Render: renderer, // it is still needed when rendering 500 page in a package handler 142 150 } 143 - defer ctx.Close() 151 + defer baseCleanUp() 144 152 145 - ctx.Req = WithContext(req, &ctx) 146 - 153 + ctx.Base.AppendContextValue(contextKey, ctx) 147 154 next.ServeHTTP(ctx.Resp, ctx.Req) 148 155 }) 149 156 }
+10 -19
modules/context/private.go
··· 11 11 12 12 "code.gitea.io/gitea/modules/graceful" 13 13 "code.gitea.io/gitea/modules/process" 14 - "code.gitea.io/gitea/modules/web/middleware" 15 14 ) 16 15 17 16 // PrivateContext represents a context for private routes 18 17 type PrivateContext struct { 19 - *Context 18 + *Base 20 19 Override context.Context 20 + 21 + Repo *Repository 21 22 } 22 23 23 24 // Deadline is part of the interface for context.Context and we pass this to the request context ··· 25 26 if ctx.Override != nil { 26 27 return ctx.Override.Deadline() 27 28 } 28 - return ctx.Req.Context().Deadline() 29 + return ctx.Base.Deadline() 29 30 } 30 31 31 32 // Done is part of the interface for context.Context and we pass this to the request context ··· 33 34 if ctx.Override != nil { 34 35 return ctx.Override.Done() 35 36 } 36 - return ctx.Req.Context().Done() 37 + return ctx.Base.Done() 37 38 } 38 39 39 40 // Err is part of the interface for context.Context and we pass this to the request context ··· 41 42 if ctx.Override != nil { 42 43 return ctx.Override.Err() 43 44 } 44 - return ctx.Req.Context().Err() 45 + return ctx.Base.Err() 45 46 } 46 47 47 48 var privateContextKey interface{} = "default_private_context" 48 49 49 - // WithPrivateContext set up private context in request 50 - func WithPrivateContext(req *http.Request, ctx *PrivateContext) *http.Request { 51 - return req.WithContext(context.WithValue(req.Context(), privateContextKey, ctx)) 52 - } 53 - 54 50 // GetPrivateContext returns a context for Private routes 55 51 func GetPrivateContext(req *http.Request) *PrivateContext { 56 52 return req.Context().Value(privateContextKey).(*PrivateContext) ··· 60 56 func PrivateContexter() func(http.Handler) http.Handler { 61 57 return func(next http.Handler) http.Handler { 62 58 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 63 - ctx := &PrivateContext{ 64 - Context: &Context{ 65 - Resp: NewResponse(w), 66 - Data: middleware.GetContextData(req.Context()), 67 - }, 68 - } 69 - defer ctx.Close() 59 + base, baseCleanUp := NewBaseContext(w, req) 60 + ctx := &PrivateContext{Base: base} 61 + defer baseCleanUp() 62 + ctx.Base.AppendContextValue(privateContextKey, ctx) 70 63 71 - ctx.Req = WithPrivateContext(req, ctx) 72 - ctx.Data["Context"] = ctx 73 64 next.ServeHTTP(ctx.Resp, ctx.Req) 74 65 }) 75 66 }
+21 -20
modules/context/repo.go
··· 331 331 } 332 332 333 333 // RedirectToRepo redirect to a differently-named repository 334 - func RedirectToRepo(ctx *Context, redirectRepoID int64) { 334 + func RedirectToRepo(ctx *Base, redirectRepoID int64) { 335 335 ownerName := ctx.Params(":username") 336 336 previousRepoName := ctx.Params(":reponame") 337 337 338 338 repo, err := repo_model.GetRepositoryByID(ctx, redirectRepoID) 339 339 if err != nil { 340 - ctx.ServerError("GetRepositoryByID", err) 340 + log.Error("GetRepositoryByID: %v", err) 341 + ctx.Error(http.StatusInternalServerError, "GetRepositoryByID") 341 342 return 342 343 } 343 344 ··· 456 457 } 457 458 458 459 if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil { 459 - RedirectToUser(ctx, userName, redirectUserID) 460 + RedirectToUser(ctx.Base, userName, redirectUserID) 460 461 } else if user_model.IsErrUserRedirectNotExist(err) { 461 462 ctx.NotFound("GetUserByName", nil) 462 463 } else { ··· 498 499 if repo_model.IsErrRepoNotExist(err) { 499 500 redirectRepoID, err := repo_model.LookupRedirect(owner.ID, repoName) 500 501 if err == nil { 501 - RedirectToRepo(ctx, redirectRepoID) 502 + RedirectToRepo(ctx.Base, redirectRepoID) 502 503 } else if repo_model.IsErrRedirectNotExist(err) { 503 504 if ctx.FormString("go-get") == "1" { 504 505 EarlyResponseForGoGetMeta(ctx) ··· 781 782 return false 782 783 } 783 784 784 - func getRefNameFromPath(ctx *Context, path string, isExist func(string) bool) string { 785 + func getRefNameFromPath(ctx *Base, repo *Repository, path string, isExist func(string) bool) string { 785 786 refName := "" 786 787 parts := strings.Split(path, "/") 787 788 for i, part := range parts { 788 789 refName = strings.TrimPrefix(refName+"/"+part, "/") 789 790 if isExist(refName) { 790 - ctx.Repo.TreePath = strings.Join(parts[i+1:], "/") 791 + repo.TreePath = strings.Join(parts[i+1:], "/") 791 792 return refName 792 793 } 793 794 } 794 795 return "" 795 796 } 796 797 797 - func getRefName(ctx *Context, pathType RepoRefType) string { 798 + func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string { 798 799 path := ctx.Params("*") 799 800 switch pathType { 800 801 case RepoRefLegacy, RepoRefAny: 801 - if refName := getRefName(ctx, RepoRefBranch); len(refName) > 0 { 802 + if refName := getRefName(ctx, repo, RepoRefBranch); len(refName) > 0 { 802 803 return refName 803 804 } 804 - if refName := getRefName(ctx, RepoRefTag); len(refName) > 0 { 805 + if refName := getRefName(ctx, repo, RepoRefTag); len(refName) > 0 { 805 806 return refName 806 807 } 807 808 // For legacy and API support only full commit sha 808 809 parts := strings.Split(path, "/") 809 810 if len(parts) > 0 && len(parts[0]) == git.SHAFullLength { 810 - ctx.Repo.TreePath = strings.Join(parts[1:], "/") 811 + repo.TreePath = strings.Join(parts[1:], "/") 811 812 return parts[0] 812 813 } 813 - if refName := getRefName(ctx, RepoRefBlob); len(refName) > 0 { 814 + if refName := getRefName(ctx, repo, RepoRefBlob); len(refName) > 0 { 814 815 return refName 815 816 } 816 - ctx.Repo.TreePath = path 817 - return ctx.Repo.Repository.DefaultBranch 817 + repo.TreePath = path 818 + return repo.Repository.DefaultBranch 818 819 case RepoRefBranch: 819 - ref := getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsBranchExist) 820 + ref := getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsBranchExist) 820 821 if len(ref) == 0 { 821 822 // maybe it's a renamed branch 822 - return getRefNameFromPath(ctx, path, func(s string) bool { 823 - b, exist, err := git_model.FindRenamedBranch(ctx, ctx.Repo.Repository.ID, s) 823 + return getRefNameFromPath(ctx, repo, path, func(s string) bool { 824 + b, exist, err := git_model.FindRenamedBranch(ctx, repo.Repository.ID, s) 824 825 if err != nil { 825 826 log.Error("FindRenamedBranch", err) 826 827 return false ··· 839 840 840 841 return ref 841 842 case RepoRefTag: 842 - return getRefNameFromPath(ctx, path, ctx.Repo.GitRepo.IsTagExist) 843 + return getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsTagExist) 843 844 case RepoRefCommit: 844 845 parts := strings.Split(path, "/") 845 846 if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= git.SHAFullLength { 846 - ctx.Repo.TreePath = strings.Join(parts[1:], "/") 847 + repo.TreePath = strings.Join(parts[1:], "/") 847 848 return parts[0] 848 849 } 849 850 case RepoRefBlob: 850 - _, err := ctx.Repo.GitRepo.GetBlob(path) 851 + _, err := repo.GitRepo.GetBlob(path) 851 852 if err != nil { 852 853 return "" 853 854 } ··· 922 923 } 923 924 ctx.Repo.IsViewBranch = true 924 925 } else { 925 - refName = getRefName(ctx, refType) 926 + refName = getRefName(ctx.Base, ctx.Repo, refType) 926 927 ctx.Repo.RefName = refName 927 928 isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool) 928 929 if isRenamedBranch && has {
+3 -10
modules/context/response.go
··· 10 10 // ResponseWriter represents a response writer for HTTP 11 11 type ResponseWriter interface { 12 12 http.ResponseWriter 13 - Flush() 13 + http.Flusher 14 14 Status() int 15 15 Before(func(ResponseWriter)) 16 - Size() int 17 16 } 18 17 19 18 var _ ResponseWriter = &Response{} ··· 27 26 beforeExecuted bool 28 27 } 29 28 30 - // Size return written size 31 - func (r *Response) Size() int { 32 - return r.written 33 - } 34 - 35 29 // Write writes bytes to HTTP endpoint 36 30 func (r *Response) Write(bs []byte) (int, error) { 37 31 if !r.beforeExecuted { ··· 65 59 } 66 60 } 67 61 68 - // Flush flush cached data 62 + // Flush flushes cached data 69 63 func (r *Response) Flush() { 70 64 if f, ok := r.ResponseWriter.(http.Flusher); ok { 71 65 f.Flush() ··· 83 77 r.befores = append(r.befores, f) 84 78 } 85 79 86 - // NewResponse creates a response 87 - func NewResponse(resp http.ResponseWriter) *Response { 80 + func WrapResponseWriter(resp http.ResponseWriter) *Response { 88 81 if v, ok := resp.(*Response); ok { 89 82 return v 90 83 }
+2 -2
modules/context/utils.go
··· 10 10 ) 11 11 12 12 // GetQueryBeforeSince return parsed time (unix format) from URL query's before and since 13 - func GetQueryBeforeSince(ctx *Context) (before, since int64, err error) { 13 + func GetQueryBeforeSince(ctx *Base) (before, since int64, err error) { 14 14 qCreatedBefore, err := prepareQueryArg(ctx, "before") 15 15 if err != nil { 16 16 return 0, 0, err ··· 48 48 } 49 49 50 50 // prepareQueryArg unescape and trim a query arg 51 - func prepareQueryArg(ctx *Context, name string) (value string, err error) { 51 + func prepareQueryArg(ctx *Base, name string) (value string, err error) { 52 52 value, err = url.PathUnescape(ctx.FormString(name)) 53 53 value = strings.TrimSpace(value) 54 54 return value, err
+77 -50
modules/test/context_tests.go
··· 4 4 package test 5 5 6 6 import ( 7 - scontext "context" 7 + gocontext "context" 8 8 "io" 9 9 "net/http" 10 10 "net/http/httptest" ··· 28 28 // MockContext mock context for unit tests 29 29 // TODO: move this function to other packages, because it depends on "models" package 30 30 func MockContext(t *testing.T, path string) *context.Context { 31 - resp := &mockResponseWriter{} 32 - ctx := context.Context{ 31 + resp := httptest.NewRecorder() 32 + requestURL, err := url.Parse(path) 33 + assert.NoError(t, err) 34 + req := &http.Request{ 35 + URL: requestURL, 36 + Form: url.Values{}, 37 + } 38 + 39 + base, baseCleanUp := context.NewBaseContext(resp, req) 40 + base.Data = middleware.ContextData{} 41 + base.Locale = &translation.MockLocale{} 42 + ctx := &context.Context{ 43 + Base: base, 33 44 Render: &mockRender{}, 34 - Data: make(middleware.ContextData), 35 - Flash: &middleware.Flash{ 36 - Values: make(url.Values), 37 - }, 38 - Resp: context.NewResponse(resp), 39 - Locale: &translation.MockLocale{}, 45 + Flash: &middleware.Flash{Values: url.Values{}}, 40 46 } 41 - defer ctx.Close() 47 + _ = baseCleanUp // during test, it doesn't need to do clean up. TODO: this can be improved later 42 48 49 + chiCtx := chi.NewRouteContext() 50 + ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx) 51 + return ctx 52 + } 53 + 54 + // MockAPIContext mock context for unit tests 55 + // TODO: move this function to other packages, because it depends on "models" package 56 + func MockAPIContext(t *testing.T, path string) *context.APIContext { 57 + resp := httptest.NewRecorder() 43 58 requestURL, err := url.Parse(path) 44 59 assert.NoError(t, err) 45 60 req := &http.Request{ ··· 47 62 Form: url.Values{}, 48 63 } 49 64 65 + base, baseCleanUp := context.NewBaseContext(resp, req) 66 + base.Data = middleware.ContextData{} 67 + base.Locale = &translation.MockLocale{} 68 + ctx := &context.APIContext{Base: base} 69 + _ = baseCleanUp // during test, it doesn't need to do clean up. TODO: this can be improved later 70 + 50 71 chiCtx := chi.NewRouteContext() 51 - req = req.WithContext(scontext.WithValue(req.Context(), chi.RouteCtxKey, chiCtx)) 52 - ctx.Req = context.WithContext(req, &ctx) 53 - return &ctx 72 + ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx) 73 + return ctx 54 74 } 55 75 56 76 // LoadRepo load a repo into a test context. 57 - func LoadRepo(t *testing.T, ctx *context.Context, repoID int64) { 58 - ctx.Repo = &context.Repository{} 59 - ctx.Repo.Repository = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) 77 + func LoadRepo(t *testing.T, ctx gocontext.Context, repoID int64) { 78 + var doer *user_model.User 79 + repo := &context.Repository{} 80 + switch ctx := ctx.(type) { 81 + case *context.Context: 82 + ctx.Repo = repo 83 + doer = ctx.Doer 84 + case *context.APIContext: 85 + ctx.Repo = repo 86 + doer = ctx.Doer 87 + default: 88 + assert.Fail(t, "context is not *context.Context or *context.APIContext") 89 + return 90 + } 91 + 92 + repo.Repository = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID}) 60 93 var err error 61 - ctx.Repo.Owner, err = user_model.GetUserByID(ctx, ctx.Repo.Repository.OwnerID) 94 + repo.Owner, err = user_model.GetUserByID(ctx, repo.Repository.OwnerID) 62 95 assert.NoError(t, err) 63 - ctx.Repo.RepoLink = ctx.Repo.Repository.Link() 64 - ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, ctx.Doer) 96 + repo.RepoLink = repo.Repository.Link() 97 + repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo.Repository, doer) 65 98 assert.NoError(t, err) 66 99 } 67 100 68 101 // LoadRepoCommit loads a repo's commit into a test context. 69 - func LoadRepoCommit(t *testing.T, ctx *context.Context) { 70 - gitRepo, err := git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath()) 102 + func LoadRepoCommit(t *testing.T, ctx gocontext.Context) { 103 + var repo *context.Repository 104 + switch ctx := ctx.(type) { 105 + case *context.Context: 106 + repo = ctx.Repo 107 + case *context.APIContext: 108 + repo = ctx.Repo 109 + default: 110 + assert.Fail(t, "context is not *context.Context or *context.APIContext") 111 + return 112 + } 113 + 114 + gitRepo, err := git.OpenRepository(ctx, repo.Repository.RepoPath()) 71 115 assert.NoError(t, err) 72 116 defer gitRepo.Close() 73 117 branch, err := gitRepo.GetHEADBranch() 74 118 assert.NoError(t, err) 75 119 assert.NotNil(t, branch) 76 120 if branch != nil { 77 - ctx.Repo.Commit, err = gitRepo.GetBranchCommit(branch.Name) 121 + repo.Commit, err = gitRepo.GetBranchCommit(branch.Name) 78 122 assert.NoError(t, err) 79 123 } 80 124 } 81 125 82 126 // LoadUser load a user into a test context. 83 - func LoadUser(t *testing.T, ctx *context.Context, userID int64) { 84 - ctx.Doer = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID}) 127 + func LoadUser(t *testing.T, ctx gocontext.Context, userID int64) { 128 + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID}) 129 + switch ctx := ctx.(type) { 130 + case *context.Context: 131 + ctx.Doer = doer 132 + case *context.APIContext: 133 + ctx.Doer = doer 134 + default: 135 + assert.Fail(t, "context is not *context.Context or *context.APIContext") 136 + return 137 + } 85 138 } 86 139 87 140 // LoadGitRepo load a git repo into a test context. Requires that ctx.Repo has ··· 91 144 var err error 92 145 ctx.Repo.GitRepo, err = git.OpenRepository(ctx, ctx.Repo.Repository.RepoPath()) 93 146 assert.NoError(t, err) 94 - } 95 - 96 - type mockResponseWriter struct { 97 - httptest.ResponseRecorder 98 - size int 99 - } 100 - 101 - func (rw *mockResponseWriter) Write(b []byte) (int, error) { 102 - rw.size += len(b) 103 - return rw.ResponseRecorder.Write(b) 104 - } 105 - 106 - func (rw *mockResponseWriter) Status() int { 107 - return rw.ResponseRecorder.Code 108 - } 109 - 110 - func (rw *mockResponseWriter) Written() bool { 111 - return rw.ResponseRecorder.Code > 0 112 - } 113 - 114 - func (rw *mockResponseWriter) Size() int { 115 - return rw.size 116 - } 117 - 118 - func (rw *mockResponseWriter) Push(target string, opts *http.PushOptions) error { 119 - return nil 120 147 } 121 148 122 149 type mockRender struct{}
+11 -3
modules/translation/translation.go
··· 38 38 } 39 39 40 40 var ( 41 - lock *sync.RWMutex 41 + lock *sync.RWMutex 42 + 43 + allLangs []*LangType 44 + allLangMap map[string]*LangType 45 + 42 46 matcher language.Matcher 43 - allLangs []*LangType 44 - allLangMap map[string]*LangType 45 47 supportedTags []language.Tag 46 48 ) 47 49 ··· 251 253 } 252 254 return l.msgPrinter.Sprintf("%v", number.Decimal(v)) 253 255 } 256 + 257 + func init() { 258 + // prepare a default matcher, especially for tests 259 + supportedTags = []language.Tag{language.English} 260 + matcher = language.NewMatcher(supportedTags) 261 + }
+13 -2
modules/web/handler.go
··· 10 10 "reflect" 11 11 12 12 "code.gitea.io/gitea/modules/context" 13 + "code.gitea.io/gitea/modules/log" 13 14 "code.gitea.io/gitea/modules/web/routing" 14 15 ) 15 16 ··· 23 24 reflect.TypeOf(&context.APIContext{}): func(req *http.Request) ResponseStatusProvider { return context.GetAPIContext(req) }, 24 25 reflect.TypeOf(&context.Context{}): func(req *http.Request) ResponseStatusProvider { return context.GetContext(req) }, 25 26 reflect.TypeOf(&context.PrivateContext{}): func(req *http.Request) ResponseStatusProvider { return context.GetPrivateContext(req) }, 27 + } 28 + 29 + func RegisterHandleTypeProvider[T any](fn func(req *http.Request) ResponseStatusProvider) { 30 + argTypeProvider[reflect.TypeOf((*T)(nil)).Elem()] = fn 26 31 } 27 32 28 33 // responseWriter is a wrapper of http.ResponseWriter, to check whether the response has been written ··· 78 83 } 79 84 } 80 85 81 - func prepareHandleArgsIn(resp http.ResponseWriter, req *http.Request, fn reflect.Value) []reflect.Value { 86 + func prepareHandleArgsIn(resp http.ResponseWriter, req *http.Request, fn reflect.Value, fnInfo *routing.FuncInfo) []reflect.Value { 87 + defer func() { 88 + if err := recover(); err != nil { 89 + log.Error("unable to prepare handler arguments for %s: %v", fnInfo.String(), err) 90 + panic(err) 91 + } 92 + }() 82 93 isPreCheck := req == nil 83 94 84 95 argsIn := make([]reflect.Value, fn.Type().NumIn()) ··· 155 166 } 156 167 157 168 // prepare the arguments for the handler and do pre-check 158 - argsIn := prepareHandleArgsIn(resp, req, fn) 169 + argsIn := prepareHandleArgsIn(resp, req, fn, funcInfo) 159 170 if req == nil { 160 171 preCheckHandler(fn, argsIn) 161 172 return // it's doing pre-check, just return
+55 -44
routers/api/actions/artifacts.go
··· 3 3 4 4 package actions 5 5 6 - // Github Actions Artifacts API Simple Description 6 + // GitHub Actions Artifacts API Simple Description 7 7 // 8 8 // 1. Upload artifact 9 9 // 1.1. Post upload url ··· 63 63 64 64 import ( 65 65 "compress/gzip" 66 - gocontext "context" 67 66 "crypto/md5" 68 67 "encoding/base64" 69 68 "errors" ··· 92 91 93 92 const artifactRouteBase = "/_apis/pipelines/workflows/{run_id}/artifacts" 94 93 95 - func ArtifactsRoutes(goctx gocontext.Context, prefix string) *web.Route { 94 + type artifactContextKeyType struct{} 95 + 96 + var artifactContextKey = artifactContextKeyType{} 97 + 98 + type ArtifactContext struct { 99 + *context.Base 100 + 101 + ActionTask *actions.ActionTask 102 + } 103 + 104 + func init() { 105 + web.RegisterHandleTypeProvider[*ArtifactContext](func(req *http.Request) web.ResponseStatusProvider { 106 + return req.Context().Value(artifactContextKey).(*ArtifactContext) 107 + }) 108 + } 109 + 110 + func ArtifactsRoutes(prefix string) *web.Route { 96 111 m := web.NewRoute() 97 - m.Use(withContexter(goctx)) 112 + m.Use(ArtifactContexter()) 98 113 99 114 r := artifactRoutes{ 100 115 prefix: prefix, ··· 115 130 return m 116 131 } 117 132 118 - // withContexter initializes a package context for a request. 119 - func withContexter(goctx gocontext.Context) func(next http.Handler) http.Handler { 133 + func ArtifactContexter() func(next http.Handler) http.Handler { 120 134 return func(next http.Handler) http.Handler { 121 135 return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { 122 - ctx := context.Context{ 123 - Resp: context.NewResponse(resp), 124 - Data: map[string]interface{}{}, 125 - } 126 - defer ctx.Close() 136 + base, baseCleanUp := context.NewBaseContext(resp, req) 137 + defer baseCleanUp() 138 + 139 + ctx := &ArtifactContext{Base: base} 140 + ctx.AppendContextValue(artifactContextKey, ctx) 127 141 128 142 // action task call server api with Bearer ACTIONS_RUNTIME_TOKEN 129 143 // we should verify the ACTIONS_RUNTIME_TOKEN ··· 132 146 ctx.Error(http.StatusUnauthorized, "Bad authorization header") 133 147 return 134 148 } 149 + 135 150 authToken := strings.TrimPrefix(authHeader, "Bearer ") 136 151 task, err := actions.GetRunningTaskByToken(req.Context(), authToken) 137 152 if err != nil { ··· 139 154 ctx.Error(http.StatusInternalServerError, "Error runner api getting task") 140 155 return 141 156 } 142 - ctx.Data["task"] = task 143 157 144 - if err := task.LoadJob(goctx); err != nil { 158 + if err := task.LoadJob(req.Context()); err != nil { 145 159 log.Error("Error runner api getting job: %v", err) 146 160 ctx.Error(http.StatusInternalServerError, "Error runner api getting job") 147 161 return 148 162 } 149 163 150 - ctx.Req = context.WithContext(req, &ctx) 151 - 164 + ctx.ActionTask = task 152 165 next.ServeHTTP(ctx.Resp, ctx.Req) 153 166 }) 154 167 } ··· 175 188 FileContainerResourceURL string `json:"fileContainerResourceUrl"` 176 189 } 177 190 178 - func (ar artifactRoutes) validateRunID(ctx *context.Context) (*actions.ActionTask, int64, bool) { 179 - task, ok := ctx.Data["task"].(*actions.ActionTask) 180 - if !ok { 181 - log.Error("Error getting task in context") 182 - ctx.Error(http.StatusInternalServerError, "Error getting task in context") 183 - return nil, 0, false 184 - } 191 + func (ar artifactRoutes) validateRunID(ctx *ArtifactContext) (*actions.ActionTask, int64, bool) { 192 + task := ctx.ActionTask 185 193 runID := ctx.ParamsInt64("run_id") 186 194 if task.Job.RunID != runID { 187 195 log.Error("Error runID not match") ··· 192 200 } 193 201 194 202 // getUploadArtifactURL generates a URL for uploading an artifact 195 - func (ar artifactRoutes) getUploadArtifactURL(ctx *context.Context) { 203 + func (ar artifactRoutes) getUploadArtifactURL(ctx *ArtifactContext) { 196 204 task, runID, ok := ar.validateRunID(ctx) 197 205 if !ok { 198 206 return ··· 220 228 221 229 // getUploadFileSize returns the size of the file to be uploaded. 222 230 // The raw size is the size of the file as reported by the header X-TFS-FileLength. 223 - func (ar artifactRoutes) getUploadFileSize(ctx *context.Context) (int64, int64, error) { 231 + func (ar artifactRoutes) getUploadFileSize(ctx *ArtifactContext) (int64, int64, error) { 224 232 contentLength := ctx.Req.ContentLength 225 233 xTfsLength, _ := strconv.ParseInt(ctx.Req.Header.Get(artifactXTfsFileLengthHeader), 10, 64) 226 234 if xTfsLength > 0 { ··· 229 237 return contentLength, contentLength, nil 230 238 } 231 239 232 - func (ar artifactRoutes) saveUploadChunk(ctx *context.Context, 240 + func (ar artifactRoutes) saveUploadChunk(ctx *ArtifactContext, 233 241 artifact *actions.ActionArtifact, 234 242 contentSize, runID int64, 235 243 ) (int64, error) { ··· 273 281 // The rules are from https://github.com/actions/toolkit/blob/main/packages/artifact/src/internal/path-and-artifact-name-validation.ts#L32 274 282 var invalidArtifactNameChars = strings.Join([]string{"\\", "/", "\"", ":", "<", ">", "|", "*", "?", "\r", "\n"}, "") 275 283 276 - func (ar artifactRoutes) uploadArtifact(ctx *context.Context) { 284 + func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) { 277 285 _, runID, ok := ar.validateRunID(ctx) 278 286 if !ok { 279 287 return ··· 341 349 342 350 // comfirmUploadArtifact comfirm upload artifact. 343 351 // if all chunks are uploaded, merge them to one file. 344 - func (ar artifactRoutes) comfirmUploadArtifact(ctx *context.Context) { 352 + func (ar artifactRoutes) comfirmUploadArtifact(ctx *ArtifactContext) { 345 353 _, runID, ok := ar.validateRunID(ctx) 346 354 if !ok { 347 355 return ··· 364 372 Path string 365 373 } 366 374 367 - func (ar artifactRoutes) mergeArtifactChunks(ctx *context.Context, runID int64) error { 375 + func (ar artifactRoutes) mergeArtifactChunks(ctx *ArtifactContext, runID int64) error { 368 376 storageDir := fmt.Sprintf("tmp%d", runID) 369 377 var chunks []*chunkItem 370 378 if err := ar.fs.IterateObjects(storageDir, func(path string, obj storage.Object) error { ··· 415 423 416 424 // use multiReader 417 425 readers := make([]io.Reader, 0, len(allChunks)) 418 - readerClosers := make([]io.Closer, 0, len(allChunks)) 426 + closeReaders := func() { 427 + for _, r := range readers { 428 + _ = r.(io.Closer).Close() // it guarantees to be io.Closer by the following loop's Open function 429 + } 430 + readers = nil 431 + } 432 + defer closeReaders() 433 + 419 434 for _, c := range allChunks { 420 - reader, err := ar.fs.Open(c.Path) 421 - if err != nil { 435 + var readCloser io.ReadCloser 436 + if readCloser, err = ar.fs.Open(c.Path); err != nil { 422 437 return fmt.Errorf("open chunk error: %v, %s", err, c.Path) 423 438 } 424 - readers = append(readers, reader) 425 - readerClosers = append(readerClosers, reader) 439 + readers = append(readers, readCloser) 426 440 } 427 441 mergedReader := io.MultiReader(readers...) 428 442 ··· 445 459 return fmt.Errorf("merged file size is not equal to chunk length") 446 460 } 447 461 448 - // close readers 449 - for _, r := range readerClosers { 450 - r.Close() 451 - } 452 - 453 462 // save storage path to artifact 454 463 log.Debug("[artifact] merge chunks to artifact: %d, %s", artifact.ID, storagePath) 455 464 artifact.StoragePath = storagePath ··· 457 466 if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { 458 467 return fmt.Errorf("update artifact error: %v", err) 459 468 } 469 + 470 + closeReaders() // close before delete 460 471 461 472 // drop chunks 462 473 for _, c := range cs { ··· 479 490 } 480 491 ) 481 492 482 - func (ar artifactRoutes) listArtifacts(ctx *context.Context) { 493 + func (ar artifactRoutes) listArtifacts(ctx *ArtifactContext) { 483 494 _, runID, ok := ar.validateRunID(ctx) 484 495 if !ok { 485 496 return 486 497 } 487 498 488 - artficats, err := actions.ListArtifactsByRunID(ctx, runID) 499 + artifacts, err := actions.ListArtifactsByRunID(ctx, runID) 489 500 if err != nil { 490 501 log.Error("Error getting artifacts: %v", err) 491 502 ctx.Error(http.StatusInternalServerError, err.Error()) 492 503 return 493 504 } 494 505 495 - artficatsData := make([]listArtifactsResponseItem, 0, len(artficats)) 496 - for _, a := range artficats { 506 + artficatsData := make([]listArtifactsResponseItem, 0, len(artifacts)) 507 + for _, a := range artifacts { 497 508 artficatsData = append(artficatsData, listArtifactsResponseItem{ 498 509 Name: a.ArtifactName, 499 510 FileContainerResourceURL: ar.buildArtifactURL(runID, a.ID, "path"), ··· 517 528 } 518 529 ) 519 530 520 - func (ar artifactRoutes) getDownloadArtifactURL(ctx *context.Context) { 531 + func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) { 521 532 _, runID, ok := ar.validateRunID(ctx) 522 533 if !ok { 523 534 return ··· 546 557 ctx.JSON(http.StatusOK, respData) 547 558 } 548 559 549 - func (ar artifactRoutes) downloadArtifact(ctx *context.Context) { 560 + func (ar artifactRoutes) downloadArtifact(ctx *ArtifactContext) { 550 561 _, runID, ok := ar.validateRunID(ctx) 551 562 if !ok { 552 563 return
+2 -2
routers/api/packages/api.go
··· 98 98 func CommonRoutes(ctx gocontext.Context) *web.Route { 99 99 r := web.NewRoute() 100 100 101 - r.Use(context.PackageContexter(ctx)) 101 + r.Use(context.PackageContexter()) 102 102 103 103 verifyAuth(r, []auth.Method{ 104 104 &auth.OAuth2{}, ··· 574 574 func ContainerRoutes(ctx gocontext.Context) *web.Route { 575 575 r := web.NewRoute() 576 576 577 - r.Use(context.PackageContexter(ctx)) 577 + r.Use(context.PackageContexter()) 578 578 579 579 verifyAuth(r, []auth.Method{ 580 580 &auth.Basic{},
+8 -8
routers/api/v1/api.go
··· 149 149 if err != nil { 150 150 if user_model.IsErrUserNotExist(err) { 151 151 if redirectUserID, err := user_model.LookupUserRedirect(userName); err == nil { 152 - context.RedirectToUser(ctx.Context, userName, redirectUserID) 152 + context.RedirectToUser(ctx.Base, userName, redirectUserID) 153 153 } else if user_model.IsErrUserRedirectNotExist(err) { 154 154 ctx.NotFound("GetUserByName", err) 155 155 } else { ··· 170 170 if repo_model.IsErrRepoNotExist(err) { 171 171 redirectRepoID, err := repo_model.LookupRedirect(owner.ID, repoName) 172 172 if err == nil { 173 - context.RedirectToRepo(ctx.Context, redirectRepoID) 173 + context.RedirectToRepo(ctx.Base, redirectRepoID) 174 174 } else if repo_model.IsErrRedirectNotExist(err) { 175 175 ctx.NotFound() 176 176 } else { ··· 274 274 ctx.Error(http.StatusForbidden, "reqToken", "token does not have required scope: "+requiredScope) 275 275 return 276 276 } 277 - if ctx.Context.IsBasicAuth { 277 + if ctx.IsBasicAuth { 278 278 ctx.CheckForOTP() 279 279 return 280 280 } ··· 295 295 296 296 func reqBasicAuth() func(ctx *context.APIContext) { 297 297 return func(ctx *context.APIContext) { 298 - if !ctx.Context.IsBasicAuth { 298 + if !ctx.IsBasicAuth { 299 299 ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required") 300 300 return 301 301 } ··· 375 375 // reqOrgOwnership user should be an organization owner, or a site admin 376 376 func reqOrgOwnership() func(ctx *context.APIContext) { 377 377 return func(ctx *context.APIContext) { 378 - if ctx.Context.IsUserSiteAdmin() { 378 + if ctx.IsUserSiteAdmin() { 379 379 return 380 380 } 381 381 ··· 407 407 // reqTeamMembership user should be an team member, or a site admin 408 408 func reqTeamMembership() func(ctx *context.APIContext) { 409 409 return func(ctx *context.APIContext) { 410 - if ctx.Context.IsUserSiteAdmin() { 410 + if ctx.IsUserSiteAdmin() { 411 411 return 412 412 } 413 413 if ctx.Org.Team == nil { ··· 444 444 // reqOrgMembership user should be an organization member, or a site admin 445 445 func reqOrgMembership() func(ctx *context.APIContext) { 446 446 return func(ctx *context.APIContext) { 447 - if ctx.Context.IsUserSiteAdmin() { 447 + if ctx.IsUserSiteAdmin() { 448 448 return 449 449 } 450 450 ··· 512 512 if organization.IsErrOrgNotExist(err) { 513 513 redirectUserID, err := user_model.LookupUserRedirect(ctx.Params(":org")) 514 514 if err == nil { 515 - context.RedirectToUser(ctx.Context, ctx.Params(":org"), redirectUserID) 515 + context.RedirectToUser(ctx.Base, ctx.Params(":org"), redirectUserID) 516 516 } else if user_model.IsErrUserRedirectNotExist(err) { 517 517 ctx.NotFound("GetOrgByName", err) 518 518 } else {
+2 -2
routers/api/v1/misc/markup.go
··· 41 41 return 42 42 } 43 43 44 - common.RenderMarkup(ctx.Context, form.Mode, form.Text, form.Context, form.FilePath, form.Wiki) 44 + common.RenderMarkup(ctx.Base, ctx.Repo, form.Mode, form.Text, form.Context, form.FilePath, form.Wiki) 45 45 } 46 46 47 47 // Markdown render markdown document to HTML ··· 76 76 mode = form.Mode 77 77 } 78 78 79 - common.RenderMarkup(ctx.Context, mode, form.Text, form.Context, "", form.Wiki) 79 + common.RenderMarkup(ctx.Base, ctx.Repo, mode, form.Text, form.Context, "", form.Wiki) 80 80 } 81 81 82 82 // MarkdownRaw render raw markdown HTML
+9 -24
routers/api/v1/misc/markup_test.go
··· 16 16 "code.gitea.io/gitea/modules/markup" 17 17 "code.gitea.io/gitea/modules/setting" 18 18 api "code.gitea.io/gitea/modules/structs" 19 - "code.gitea.io/gitea/modules/templates" 20 19 "code.gitea.io/gitea/modules/util" 21 20 "code.gitea.io/gitea/modules/web" 22 21 "code.gitea.io/gitea/modules/web/middleware" ··· 30 29 AppSubURL = AppURL + Repo + "/" 31 30 ) 32 31 33 - func createContext(req *http.Request) (*context.Context, *httptest.ResponseRecorder) { 34 - rnd := templates.HTMLRenderer() 32 + func createAPIContext(req *http.Request) (*context.APIContext, *httptest.ResponseRecorder) { 35 33 resp := httptest.NewRecorder() 36 - c := &context.Context{ 37 - Req: req, 38 - Resp: context.NewResponse(resp), 39 - Render: rnd, 40 - Data: make(middleware.ContextData), 41 - } 42 - defer c.Close() 34 + base, baseCleanUp := context.NewBaseContext(resp, req) 35 + base.Data = middleware.ContextData{} 36 + c := &context.APIContext{Base: base} 37 + _ = baseCleanUp // during test, it doesn't need to do clean up. TODO: this can be improved later 43 38 44 39 return c, resp 45 - } 46 - 47 - func wrap(ctx *context.Context) *context.APIContext { 48 - return &context.APIContext{ 49 - Context: ctx, 50 - } 51 40 } 52 41 53 42 func testRenderMarkup(t *testing.T, mode, filePath, text, responseBody string, responseCode int) { ··· 65 54 Method: "POST", 66 55 URL: requrl, 67 56 } 68 - m, resp := createContext(req) 69 - ctx := wrap(m) 57 + ctx, resp := createAPIContext(req) 70 58 71 59 options.Text = text 72 60 web.SetForm(ctx, &options) ··· 90 78 Method: "POST", 91 79 URL: requrl, 92 80 } 93 - m, resp := createContext(req) 94 - ctx := wrap(m) 81 + ctx, resp := createAPIContext(req) 95 82 96 83 options.Text = text 97 84 web.SetForm(ctx, &options) ··· 211 198 Method: "POST", 212 199 URL: requrl, 213 200 } 214 - m, resp := createContext(req) 215 - ctx := wrap(m) 201 + ctx, resp := createAPIContext(req) 216 202 217 203 for i := 0; i < len(simpleCases); i += 2 { 218 204 options.Text = simpleCases[i] ··· 231 217 Method: "POST", 232 218 URL: requrl, 233 219 } 234 - m, resp := createContext(req) 235 - ctx := wrap(m) 220 + ctx, resp := createAPIContext(req) 236 221 237 222 for i := 0; i < len(simpleCases); i += 2 { 238 223 ctx.Req.Body = io.NopCloser(strings.NewReader(simpleCases[i]))
+1 -1
routers/api/v1/notify/notifications.go
··· 25 25 } 26 26 27 27 func getFindNotificationOptions(ctx *context.APIContext) *activities_model.FindNotificationOptions { 28 - before, since, err := context.GetQueryBeforeSince(ctx.Context) 28 + before, since, err := context.GetQueryBeforeSince(ctx.Base) 29 29 if err != nil { 30 30 ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) 31 31 return nil
+6 -6
routers/api/v1/repo/file.go
··· 80 80 81 81 ctx.RespHeader().Set(giteaObjectTypeHeader, string(files_service.GetObjectTypeFromTreeEntry(entry))) 82 82 83 - if err := common.ServeBlob(ctx.Context, blob, lastModified); err != nil { 83 + if err := common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified); err != nil { 84 84 ctx.Error(http.StatusInternalServerError, "ServeBlob", err) 85 85 } 86 86 } ··· 137 137 } 138 138 139 139 // OK not cached - serve! 140 - if err := common.ServeBlob(ctx.Context, blob, lastModified); err != nil { 140 + if err := common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified); err != nil { 141 141 ctx.ServerError("ServeBlob", err) 142 142 } 143 143 return ··· 159 159 } 160 160 161 161 if err := dataRc.Close(); err != nil { 162 - log.Error("Error whilst closing blob %s reader in %-v. Error: %v", blob.ID, ctx.Context.Repo.Repository, err) 162 + log.Error("Error whilst closing blob %s reader in %-v. Error: %v", blob.ID, ctx.Repo.Repository, err) 163 163 } 164 164 165 165 // Check if the blob represents a pointer ··· 173 173 } 174 174 175 175 // OK not cached - serve! 176 - common.ServeContentByReader(ctx.Context, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf)) 176 + common.ServeContentByReader(ctx.Base, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf)) 177 177 return 178 178 } 179 179 ··· 187 187 return 188 188 } 189 189 190 - common.ServeContentByReader(ctx.Context, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf)) 190 + common.ServeContentByReader(ctx.Base, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf)) 191 191 return 192 192 } else if err != nil { 193 193 ctx.ServerError("GetLFSMetaObjectByOid", err) ··· 215 215 } 216 216 defer lfsDataRc.Close() 217 217 218 - common.ServeContentByReadSeeker(ctx.Context, ctx.Repo.TreePath, lastModified, lfsDataRc) 218 + common.ServeContentByReadSeeker(ctx.Base, ctx.Repo.TreePath, lastModified, lfsDataRc) 219 219 } 220 220 221 221 func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEntry, lastModified time.Time) {
+2 -3
routers/api/v1/repo/hook_test.go
··· 9 9 10 10 "code.gitea.io/gitea/models/unittest" 11 11 "code.gitea.io/gitea/models/webhook" 12 - "code.gitea.io/gitea/modules/context" 13 12 "code.gitea.io/gitea/modules/test" 14 13 15 14 "github.com/stretchr/testify/assert" ··· 18 17 func TestTestHook(t *testing.T) { 19 18 unittest.PrepareTestEnv(t) 20 19 21 - ctx := test.MockContext(t, "user2/repo1/wiki/_pages") 20 + ctx := test.MockAPIContext(t, "user2/repo1/wiki/_pages") 22 21 ctx.SetParams(":id", "1") 23 22 test.LoadRepo(t, ctx, 1) 24 23 test.LoadRepoCommit(t, ctx) 25 24 test.LoadUser(t, ctx, 2) 26 - TestHook(&context.APIContext{Context: ctx, Org: nil}) 25 + TestHook(ctx) 27 26 assert.EqualValues(t, http.StatusNoContent, ctx.Resp.Status()) 28 27 29 28 unittest.AssertExistsAndLoadBean(t, &webhook.HookTask{
+2 -2
routers/api/v1/repo/issue.go
··· 116 116 // "200": 117 117 // "$ref": "#/responses/IssueList" 118 118 119 - before, since, err := context.GetQueryBeforeSince(ctx.Context) 119 + before, since, err := context.GetQueryBeforeSince(ctx.Base) 120 120 if err != nil { 121 121 ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) 122 122 return ··· 368 368 // responses: 369 369 // "200": 370 370 // "$ref": "#/responses/IssueList" 371 - before, since, err := context.GetQueryBeforeSince(ctx.Context) 371 + before, since, err := context.GetQueryBeforeSince(ctx.Base) 372 372 if err != nil { 373 373 ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) 374 374 return
+3 -3
routers/api/v1/repo/issue_comment.go
··· 59 59 // "200": 60 60 // "$ref": "#/responses/CommentList" 61 61 62 - before, since, err := context.GetQueryBeforeSince(ctx.Context) 62 + before, since, err := context.GetQueryBeforeSince(ctx.Base) 63 63 if err != nil { 64 64 ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) 65 65 return ··· 156 156 // "200": 157 157 // "$ref": "#/responses/TimelineList" 158 158 159 - before, since, err := context.GetQueryBeforeSince(ctx.Context) 159 + before, since, err := context.GetQueryBeforeSince(ctx.Base) 160 160 if err != nil { 161 161 ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) 162 162 return ··· 259 259 // "200": 260 260 // "$ref": "#/responses/CommentList" 261 261 262 - before, since, err := context.GetQueryBeforeSince(ctx.Context) 262 + before, since, err := context.GetQueryBeforeSince(ctx.Base) 263 263 if err != nil { 264 264 ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) 265 265 return
+3 -3
routers/api/v1/repo/issue_tracked_time.go
··· 103 103 opts.UserID = user.ID 104 104 } 105 105 106 - if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Context); err != nil { 106 + if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Base); err != nil { 107 107 ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) 108 108 return 109 109 } ··· 522 522 } 523 523 524 524 var err error 525 - if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Context); err != nil { 525 + if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Base); err != nil { 526 526 ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) 527 527 return 528 528 } ··· 596 596 } 597 597 598 598 var err error 599 - if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Context); err != nil { 599 + if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Base); err != nil { 600 600 ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) 601 601 return 602 602 }
+1 -1
routers/api/v1/repo/migrate.go
··· 79 79 return 80 80 } 81 81 82 - if ctx.HasError() { 82 + if ctx.HasAPIError() { 83 83 ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg()) 84 84 return 85 85 }
+6 -9
routers/api/v1/repo/repo_test.go
··· 9 9 10 10 repo_model "code.gitea.io/gitea/models/repo" 11 11 "code.gitea.io/gitea/models/unittest" 12 - "code.gitea.io/gitea/modules/context" 13 12 api "code.gitea.io/gitea/modules/structs" 14 13 "code.gitea.io/gitea/modules/test" 15 14 "code.gitea.io/gitea/modules/web" ··· 20 19 func TestRepoEdit(t *testing.T) { 21 20 unittest.PrepareTestEnv(t) 22 21 23 - ctx := test.MockContext(t, "user2/repo1") 22 + ctx := test.MockAPIContext(t, "user2/repo1") 24 23 test.LoadRepo(t, ctx, 1) 25 24 test.LoadUser(t, ctx, 2) 26 25 ctx.Repo.Owner = ctx.Doer ··· 54 53 Archived: &archived, 55 54 } 56 55 57 - apiCtx := &context.APIContext{Context: ctx, Org: nil} 58 - web.SetForm(apiCtx, &opts) 59 - Edit(apiCtx) 56 + web.SetForm(ctx, &opts) 57 + Edit(ctx) 60 58 61 59 assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) 62 60 unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ ··· 67 65 func TestRepoEditNameChange(t *testing.T) { 68 66 unittest.PrepareTestEnv(t) 69 67 70 - ctx := test.MockContext(t, "user2/repo1") 68 + ctx := test.MockAPIContext(t, "user2/repo1") 71 69 test.LoadRepo(t, ctx, 1) 72 70 test.LoadUser(t, ctx, 2) 73 71 ctx.Repo.Owner = ctx.Doer ··· 76 74 Name: &name, 77 75 } 78 76 79 - apiCtx := &context.APIContext{Context: ctx, Org: nil} 80 - web.SetForm(apiCtx, &opts) 81 - Edit(apiCtx) 77 + web.SetForm(ctx, &opts) 78 + Edit(ctx) 82 79 assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) 83 80 84 81 unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{
+1 -1
routers/api/v1/repo/status.go
··· 183 183 ctx.Error(http.StatusBadRequest, "ref/sha not given", nil) 184 184 return 185 185 } 186 - sha = utils.MustConvertToSHA1(ctx.Context, sha) 186 + sha = utils.MustConvertToSHA1(ctx.Base, ctx.Repo, sha) 187 187 repo := ctx.Repo.Repository 188 188 189 189 listOptions := utils.GetListOptions(ctx)
+1 -1
routers/api/v1/user/helper.go
··· 17 17 if err != nil { 18 18 if user_model.IsErrUserNotExist(err) { 19 19 if redirectUserID, err2 := user_model.LookupUserRedirect(username); err2 == nil { 20 - context.RedirectToUser(ctx.Context, username, redirectUserID) 20 + context.RedirectToUser(ctx.Base, username, redirectUserID) 21 21 } else { 22 22 ctx.NotFound("GetUserByName", err) 23 23 }
+6 -5
routers/api/v1/utils/git.go
··· 4 4 package utils 5 5 6 6 import ( 7 + gocontext "context" 7 8 "fmt" 8 9 "net/http" 9 10 ··· 33 34 } 34 35 } 35 36 36 - sha = MustConvertToSHA1(ctx.Context, sha) 37 + sha = MustConvertToSHA1(ctx, ctx.Repo, sha) 37 38 38 39 if ctx.Repo.GitRepo != nil { 39 40 err := ctx.Repo.GitRepo.AddLastCommitCache(ctx.Repo.Repository.GetCommitsCountCacheKey(ref, ref != sha), ctx.Repo.Repository.FullName(), sha) ··· 69 70 } 70 71 71 72 // ConvertToSHA1 returns a full-length SHA1 from a potential ID string 72 - func ConvertToSHA1(ctx *context.Context, commitID string) (git.SHA1, error) { 73 + func ConvertToSHA1(ctx gocontext.Context, repo *context.Repository, commitID string) (git.SHA1, error) { 73 74 if len(commitID) == git.SHAFullLength && git.IsValidSHAPattern(commitID) { 74 75 sha1, err := git.NewIDFromString(commitID) 75 76 if err == nil { ··· 77 78 } 78 79 } 79 80 80 - gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, ctx.Repo.Repository.RepoPath()) 81 + gitRepo, closer, err := git.RepositoryFromContextOrOpen(ctx, repo.Repository.RepoPath()) 81 82 if err != nil { 82 83 return git.SHA1{}, fmt.Errorf("RepositoryFromContextOrOpen: %w", err) 83 84 } ··· 87 88 } 88 89 89 90 // MustConvertToSHA1 returns a full-length SHA1 string from a potential ID string, or returns origin input if it can't convert to SHA1 90 - func MustConvertToSHA1(ctx *context.Context, commitID string) string { 91 - sha, err := ConvertToSHA1(ctx, commitID) 91 + func MustConvertToSHA1(ctx gocontext.Context, repo *context.Repository, commitID string) string { 92 + sha, err := ConvertToSHA1(ctx, repo, commitID) 92 93 if err != nil { 93 94 return commitID 94 95 }
+4 -4
routers/common/markup.go
··· 19 19 ) 20 20 21 21 // RenderMarkup renders markup text for the /markup and /markdown endpoints 22 - func RenderMarkup(ctx *context.Context, mode, text, urlPrefix, filePath string, wiki bool) { 22 + func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPrefix, filePath string, wiki bool) { 23 23 var markupType string 24 24 relativePath := "" 25 25 ··· 63 63 } 64 64 65 65 meta := map[string]string{} 66 - if ctx.Repo != nil && ctx.Repo.Repository != nil { 66 + if repo != nil && repo.Repository != nil { 67 67 if mode == "comment" { 68 - meta = ctx.Repo.Repository.ComposeMetas() 68 + meta = repo.Repository.ComposeMetas() 69 69 } else { 70 - meta = ctx.Repo.Repository.ComposeDocumentMetas() 70 + meta = repo.Repository.ComposeDocumentMetas() 71 71 } 72 72 } 73 73 if mode != "comment" {
+1 -1
routers/common/middleware.go
··· 42 42 return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { 43 43 ctx, _, finished := process.GetManager().AddTypedContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI), process.RequestProcessType, true) 44 44 defer finished() 45 - next.ServeHTTP(context.NewResponse(resp), req.WithContext(cache.WithCacheContext(ctx))) 45 + next.ServeHTTP(context.WrapResponseWriter(resp), req.WithContext(cache.WithCacheContext(ctx))) 46 46 }) 47 47 }) 48 48
+4 -4
routers/common/serve.go
··· 15 15 ) 16 16 17 17 // ServeBlob download a git.Blob 18 - func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) error { 18 + func ServeBlob(ctx *context.Base, filePath string, blob *git.Blob, lastModified time.Time) error { 19 19 if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { 20 20 return nil 21 21 } ··· 30 30 } 31 31 }() 32 32 33 - httplib.ServeContentByReader(ctx.Req, ctx.Resp, ctx.Repo.TreePath, blob.Size(), dataRc) 33 + httplib.ServeContentByReader(ctx.Req, ctx.Resp, filePath, blob.Size(), dataRc) 34 34 return nil 35 35 } 36 36 37 - func ServeContentByReader(ctx *context.Context, filePath string, size int64, reader io.Reader) { 37 + func ServeContentByReader(ctx *context.Base, filePath string, size int64, reader io.Reader) { 38 38 httplib.ServeContentByReader(ctx.Req, ctx.Resp, filePath, size, reader) 39 39 } 40 40 41 - func ServeContentByReadSeeker(ctx *context.Context, filePath string, modTime time.Time, reader io.ReadSeeker) { 41 + func ServeContentByReadSeeker(ctx *context.Base, filePath string, modTime time.Time, reader io.ReadSeeker) { 42 42 httplib.ServeContentByReadSeeker(ctx.Req, ctx.Resp, filePath, modTime, reader) 43 43 }
+1 -1
routers/init.go
··· 198 198 // In Github, it uses ACTIONS_RUNTIME_URL=https://pipelines.actions.githubusercontent.com/fLgcSHkPGySXeIFrg8W8OBSfeg3b5Fls1A1CwX566g8PayEGlg/ 199 199 // TODO: this prefix should be generated with a token string with runner ? 200 200 prefix = "/api/actions_pipeline" 201 - r.Mount(prefix, actions_router.ArtifactsRoutes(ctx, prefix)) 201 + r.Mount(prefix, actions_router.ArtifactsRoutes(prefix)) 202 202 } 203 203 204 204 return r
+5 -14
routers/install/install.go
··· 58 58 dbTypeNames := getSupportedDbTypeNames() 59 59 return func(next http.Handler) http.Handler { 60 60 return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { 61 + base, baseCleanUp := context.NewBaseContext(resp, req) 61 62 ctx := context.Context{ 62 - Resp: context.NewResponse(resp), 63 + Base: base, 63 64 Flash: &middleware.Flash{}, 64 - Locale: middleware.Locale(resp, req), 65 65 Render: rnd, 66 - Data: middleware.GetContextData(req.Context()), 67 66 Session: session.GetSession(req), 68 67 } 69 - defer ctx.Close() 68 + defer baseCleanUp() 70 69 71 70 ctx.Data.MergeFrom(middleware.CommonTemplateContextData()) 72 71 ctx.Data.MergeFrom(middleware.ContextData{ ··· 78 77 79 78 "PasswordHashAlgorithms": hash.RecommendedHashAlgorithms, 80 79 }) 81 - ctx.Req = context.WithContext(req, &ctx) 82 80 next.ServeHTTP(resp, ctx.Req) 83 81 }) 84 82 } ··· 249 247 ctx.Data["CurDbType"] = form.DbType 250 248 251 249 if ctx.HasError() { 252 - if ctx.HasValue("Err_SMTPUser") { 253 - ctx.Data["Err_SMTP"] = true 254 - } 255 - if ctx.HasValue("Err_AdminName") || 256 - ctx.HasValue("Err_AdminPasswd") || 257 - ctx.HasValue("Err_AdminEmail") { 258 - ctx.Data["Err_Admin"] = true 259 - } 260 - 250 + ctx.Data["Err_SMTP"] = ctx.Data["Err_SMTPUser"] != nil 251 + ctx.Data["Err_Admin"] = ctx.Data["Err_AdminName"] != nil || ctx.Data["Err_AdminPasswd"] != nil || ctx.Data["Err_AdminEmail"] != nil 261 252 ctx.HTML(http.StatusOK, tplInstall) 262 253 return 263 254 }
+1 -9
routers/web/misc/markup.go
··· 5 5 package misc 6 6 7 7 import ( 8 - "net/http" 9 - 10 8 "code.gitea.io/gitea/modules/context" 11 9 api "code.gitea.io/gitea/modules/structs" 12 10 "code.gitea.io/gitea/modules/web" ··· 16 14 // Markup render markup document to HTML 17 15 func Markup(ctx *context.Context) { 18 16 form := web.GetForm(ctx).(*api.MarkupOption) 19 - 20 - if ctx.HasAPIError() { 21 - ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg()) 22 - return 23 - } 24 - 25 - common.RenderMarkup(ctx, form.Mode, form.Text, form.Context, form.FilePath, form.Wiki) 17 + common.RenderMarkup(ctx.Base, ctx.Repo, form.Mode, form.Text, form.Context, form.FilePath, form.Wiki) 26 18 }
+1 -1
routers/web/repo/attachment.go
··· 153 153 } 154 154 defer fr.Close() 155 155 156 - common.ServeContentByReadSeeker(ctx, attach.Name, attach.CreatedUnix.AsTime(), fr) 156 + common.ServeContentByReadSeeker(ctx.Base, attach.Name, attach.CreatedUnix.AsTime(), fr) 157 157 } 158 158 159 159 // GetAttachment serve attachments
+5 -5
routers/web/repo/download.go
··· 47 47 log.Error("ServeBlobOrLFS: Close: %v", err) 48 48 } 49 49 closed = true 50 - return common.ServeBlob(ctx, blob, lastModified) 50 + return common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified) 51 51 } 52 52 if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`) { 53 53 return nil ··· 71 71 log.Error("ServeBlobOrLFS: Close: %v", err) 72 72 } 73 73 }() 74 - common.ServeContentByReadSeeker(ctx, ctx.Repo.TreePath, lastModified, lfsDataRc) 74 + common.ServeContentByReadSeeker(ctx.Base, ctx.Repo.TreePath, lastModified, lfsDataRc) 75 75 return nil 76 76 } 77 77 if err = dataRc.Close(); err != nil { ··· 79 79 } 80 80 closed = true 81 81 82 - return common.ServeBlob(ctx, blob, lastModified) 82 + return common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified) 83 83 } 84 84 85 85 func getBlobForEntry(ctx *context.Context) (blob *git.Blob, lastModified time.Time) { ··· 120 120 return 121 121 } 122 122 123 - if err := common.ServeBlob(ctx, blob, lastModified); err != nil { 123 + if err := common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified); err != nil { 124 124 ctx.ServerError("ServeBlob", err) 125 125 } 126 126 } ··· 148 148 } 149 149 return 150 150 } 151 - if err = common.ServeBlob(ctx, blob, time.Time{}); err != nil { 151 + if err = common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, time.Time{}); err != nil { 152 152 ctx.ServerError("ServeBlob", err) 153 153 } 154 154 }
+1 -1
routers/web/repo/http.go
··· 109 109 if err != nil { 110 110 if repo_model.IsErrRepoNotExist(err) { 111 111 if redirectRepoID, err := repo_model.LookupRedirect(owner.ID, reponame); err == nil { 112 - context.RedirectToRepo(ctx, redirectRepoID) 112 + context.RedirectToRepo(ctx.Base, redirectRepoID) 113 113 return 114 114 } 115 115 repoExist = false
+2 -2
routers/web/repo/issue.go
··· 2344 2344 2345 2345 // SearchIssues searches for issues across the repositories that the user has access to 2346 2346 func SearchIssues(ctx *context.Context) { 2347 - before, since, err := context.GetQueryBeforeSince(ctx) 2347 + before, since, err := context.GetQueryBeforeSince(ctx.Base) 2348 2348 if err != nil { 2349 2349 ctx.Error(http.StatusUnprocessableEntity, err.Error()) 2350 2350 return ··· 2545 2545 2546 2546 // ListIssues list the issues of a repository 2547 2547 func ListIssues(ctx *context.Context) { 2548 - before, since, err := context.GetQueryBeforeSince(ctx) 2548 + before, since, err := context.GetQueryBeforeSince(ctx.Base) 2549 2549 if err != nil { 2550 2550 ctx.Error(http.StatusUnprocessableEntity, err.Error()) 2551 2551 return
+1 -1
routers/web/repo/wiki.go
··· 671 671 } 672 672 673 673 if entry != nil { 674 - if err = common.ServeBlob(ctx, entry.Blob(), time.Time{}); err != nil { 674 + if err = common.ServeBlob(ctx.Base, ctx.Repo.TreePath, entry.Blob(), time.Time{}); err != nil { 675 675 ctx.ServerError("ServeBlob", err) 676 676 } 677 677 return
+34 -18
services/auth/middleware.go
··· 8 8 "strings" 9 9 10 10 "code.gitea.io/gitea/models/auth" 11 + user_model "code.gitea.io/gitea/models/user" 11 12 "code.gitea.io/gitea/modules/context" 12 13 "code.gitea.io/gitea/modules/log" 13 14 "code.gitea.io/gitea/modules/setting" ··· 17 18 // Auth is a middleware to authenticate a web user 18 19 func Auth(authMethod Method) func(*context.Context) { 19 20 return func(ctx *context.Context) { 20 - if err := authShared(ctx, authMethod); err != nil { 21 + ar, err := authShared(ctx.Base, ctx.Session, authMethod) 22 + if err != nil { 21 23 log.Error("Failed to verify user: %v", err) 22 24 ctx.Error(http.StatusUnauthorized, "Verify") 23 25 return 24 26 } 27 + ctx.Doer = ar.Doer 28 + ctx.IsSigned = ar.Doer != nil 29 + ctx.IsBasicAuth = ar.IsBasicAuth 25 30 if ctx.Doer == nil { 26 31 // ensure the session uid is deleted 27 32 _ = ctx.Session.Delete("uid") ··· 32 37 // APIAuth is a middleware to authenticate an api user 33 38 func APIAuth(authMethod Method) func(*context.APIContext) { 34 39 return func(ctx *context.APIContext) { 35 - if err := authShared(ctx.Context, authMethod); err != nil { 40 + ar, err := authShared(ctx.Base, nil, authMethod) 41 + if err != nil { 36 42 ctx.Error(http.StatusUnauthorized, "APIAuth", err) 43 + return 37 44 } 45 + ctx.Doer = ar.Doer 46 + ctx.IsSigned = ar.Doer != nil 47 + ctx.IsBasicAuth = ar.IsBasicAuth 38 48 } 39 49 } 40 50 41 - func authShared(ctx *context.Context, authMethod Method) error { 42 - var err error 43 - ctx.Doer, err = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session) 51 + type authResult struct { 52 + Doer *user_model.User 53 + IsBasicAuth bool 54 + } 55 + 56 + func authShared(ctx *context.Base, sessionStore SessionStore, authMethod Method) (ar authResult, err error) { 57 + ar.Doer, err = authMethod.Verify(ctx.Req, ctx.Resp, ctx, sessionStore) 44 58 if err != nil { 45 - return err 59 + return ar, err 46 60 } 47 - if ctx.Doer != nil { 48 - if ctx.Locale.Language() != ctx.Doer.Language { 61 + if ar.Doer != nil { 62 + if ctx.Locale.Language() != ar.Doer.Language { 49 63 ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req) 50 64 } 51 - ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == BasicMethodName 52 - ctx.IsSigned = true 53 - ctx.Data["IsSigned"] = ctx.IsSigned 54 - ctx.Data[middleware.ContextDataKeySignedUser] = ctx.Doer 55 - ctx.Data["SignedUserID"] = ctx.Doer.ID 56 - ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin 65 + ar.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == BasicMethodName 66 + 67 + ctx.Data["IsSigned"] = true 68 + ctx.Data[middleware.ContextDataKeySignedUser] = ar.Doer 69 + ctx.Data["SignedUserID"] = ar.Doer.ID 70 + ctx.Data["IsAdmin"] = ar.Doer.IsAdmin 57 71 } else { 58 72 ctx.Data["SignedUserID"] = int64(0) 59 73 } 60 - return nil 74 + return ar, nil 61 75 } 62 76 63 77 // VerifyOptions contains required or check options ··· 68 82 DisableCSRF bool 69 83 } 70 84 71 - // Checks authentication according to options 85 + // VerifyAuthWithOptions checks authentication according to options 72 86 func VerifyAuthWithOptions(options *VerifyOptions) func(ctx *context.Context) { 73 87 return func(ctx *context.Context) { 74 88 // Check prohibit login users. ··· 153 167 } 154 168 } 155 169 156 - // Checks authentication according to options 170 + // VerifyAuthWithOptionsAPI checks authentication according to options 157 171 func VerifyAuthWithOptionsAPI(options *VerifyOptions) func(ctx *context.APIContext) { 158 172 return func(ctx *context.APIContext) { 159 173 // Check prohibit login users. ··· 197 211 return 198 212 } else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm { 199 213 ctx.Data["Title"] = ctx.Tr("auth.active_your_account") 200 - ctx.HTML(http.StatusOK, "user/auth/activate") 214 + ctx.JSON(http.StatusForbidden, map[string]string{ 215 + "message": "This account is not activated.", 216 + }) 201 217 return 202 218 } 203 219 if ctx.IsSigned && ctx.IsBasicAuth {
+9 -7
services/context/user.go
··· 15 15 // UserAssignmentWeb returns a middleware to handle context-user assignment for web routes 16 16 func UserAssignmentWeb() func(ctx *context.Context) { 17 17 return func(ctx *context.Context) { 18 - userAssignment(ctx, func(status int, title string, obj interface{}) { 18 + errorFn := func(status int, title string, obj interface{}) { 19 19 err, ok := obj.(error) 20 20 if !ok { 21 21 err = fmt.Errorf("%s", obj) ··· 25 25 } else { 26 26 ctx.ServerError(title, err) 27 27 } 28 - }) 28 + } 29 + ctx.ContextUser = userAssignment(ctx.Base, ctx.Doer, errorFn) 29 30 } 30 31 } 31 32 ··· 53 54 // UserAssignmentAPI returns a middleware to handle context-user assignment for api routes 54 55 func UserAssignmentAPI() func(ctx *context.APIContext) { 55 56 return func(ctx *context.APIContext) { 56 - userAssignment(ctx.Context, ctx.Error) 57 + ctx.ContextUser = userAssignment(ctx.Base, ctx.Doer, ctx.Error) 57 58 } 58 59 } 59 60 60 - func userAssignment(ctx *context.Context, errCb func(int, string, interface{})) { 61 + func userAssignment(ctx *context.Base, doer *user_model.User, errCb func(int, string, interface{})) (contextUser *user_model.User) { 61 62 username := ctx.Params(":username") 62 63 63 - if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(username) { 64 - ctx.ContextUser = ctx.Doer 64 + if doer != nil && doer.LowerName == strings.ToLower(username) { 65 + contextUser = doer 65 66 } else { 66 67 var err error 67 - ctx.ContextUser, err = user_model.GetUserByName(ctx, username) 68 + contextUser, err = user_model.GetUserByName(ctx, username) 68 69 if err != nil { 69 70 if user_model.IsErrUserNotExist(err) { 70 71 if redirectUserID, err := user_model.LookupUserRedirect(username); err == nil { ··· 79 80 } 80 81 } 81 82 } 83 + return contextUser 82 84 }
+3 -3
services/forms/admin.go
··· 27 27 28 28 // Validate validates form fields 29 29 func (f *AdminCreateUserForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 30 - ctx := context.GetContext(req) 30 + ctx := context.GetValidateContext(req) 31 31 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 32 32 } 33 33 ··· 55 55 56 56 // Validate validates form fields 57 57 func (f *AdminEditUserForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 58 - ctx := context.GetContext(req) 58 + ctx := context.GetValidateContext(req) 59 59 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 60 60 } 61 61 ··· 67 67 68 68 // Validate validates form fields 69 69 func (f *AdminDashboardForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 70 - ctx := context.GetContext(req) 70 + ctx := context.GetValidateContext(req) 71 71 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 72 72 }
+1 -1
services/forms/auth_form.go
··· 86 86 87 87 // Validate validates fields 88 88 func (f *AuthenticationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 89 - ctx := context.GetContext(req) 89 + ctx := context.GetValidateContext(req) 90 90 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 91 91 }
+3 -3
services/forms/org.go
··· 30 30 31 31 // Validate validates the fields 32 32 func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 33 - ctx := context.GetContext(req) 33 + ctx := context.GetValidateContext(req) 34 34 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 35 35 } 36 36 ··· 48 48 49 49 // Validate validates the fields 50 50 func (f *UpdateOrgSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 51 - ctx := context.GetContext(req) 51 + ctx := context.GetValidateContext(req) 52 52 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 53 53 } 54 54 ··· 70 70 71 71 // Validate validates the fields 72 72 func (f *CreateTeamForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 73 - ctx := context.GetContext(req) 73 + ctx := context.GetValidateContext(req) 74 74 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 75 75 }
+1 -1
services/forms/package_form.go
··· 25 25 } 26 26 27 27 func (f *PackageCleanupRuleForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 28 - ctx := context.GetContext(req) 28 + ctx := context.GetValidateContext(req) 29 29 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 30 30 }
+2 -2
services/forms/repo_branch_form.go
··· 21 21 22 22 // Validate validates the fields 23 23 func (f *NewBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 24 - ctx := context.GetContext(req) 24 + ctx := context.GetValidateContext(req) 25 25 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 26 26 } 27 27 ··· 33 33 34 34 // Validate validates the fields 35 35 func (f *RenameBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 36 - ctx := context.GetContext(req) 36 + ctx := context.GetValidateContext(req) 37 37 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 38 38 }
+36 -36
services/forms/repo_form.go
··· 54 54 55 55 // Validate validates the fields 56 56 func (f *CreateRepoForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 57 - ctx := context.GetContext(req) 57 + ctx := context.GetValidateContext(req) 58 58 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 59 59 } 60 60 ··· 87 87 88 88 // Validate validates the fields 89 89 func (f *MigrateRepoForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 90 - ctx := context.GetContext(req) 90 + ctx := context.GetValidateContext(req) 91 91 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 92 92 } 93 93 ··· 176 176 177 177 // Validate validates the fields 178 178 func (f *RepoSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 179 - ctx := context.GetContext(req) 179 + ctx := context.GetValidateContext(req) 180 180 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 181 181 } 182 182 ··· 215 215 216 216 // Validate validates the fields 217 217 func (f *ProtectBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 218 - ctx := context.GetContext(req) 218 + ctx := context.GetValidateContext(req) 219 219 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 220 220 } 221 221 ··· 280 280 281 281 // Validate validates the fields 282 282 func (f *NewWebhookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 283 - ctx := context.GetContext(req) 283 + ctx := context.GetValidateContext(req) 284 284 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 285 285 } 286 286 ··· 294 294 295 295 // Validate validates the fields 296 296 func (f *NewGogshookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 297 - ctx := context.GetContext(req) 297 + ctx := context.GetValidateContext(req) 298 298 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 299 299 } 300 300 ··· 310 310 311 311 // Validate validates the fields 312 312 func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 313 - ctx := context.GetContext(req) 313 + ctx := context.GetValidateContext(req) 314 314 if !webhook.IsValidSlackChannel(strings.TrimSpace(f.Channel)) { 315 315 errs = append(errs, binding.Error{ 316 316 FieldNames: []string{"Channel"}, ··· 331 331 332 332 // Validate validates the fields 333 333 func (f *NewDiscordHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 334 - ctx := context.GetContext(req) 334 + ctx := context.GetValidateContext(req) 335 335 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 336 336 } 337 337 ··· 343 343 344 344 // Validate validates the fields 345 345 func (f *NewDingtalkHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 346 - ctx := context.GetContext(req) 346 + ctx := context.GetValidateContext(req) 347 347 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 348 348 } 349 349 ··· 356 356 357 357 // Validate validates the fields 358 358 func (f *NewTelegramHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 359 - ctx := context.GetContext(req) 359 + ctx := context.GetValidateContext(req) 360 360 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 361 361 } 362 362 ··· 370 370 371 371 // Validate validates the fields 372 372 func (f *NewMatrixHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 373 - ctx := context.GetContext(req) 373 + ctx := context.GetValidateContext(req) 374 374 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 375 375 } 376 376 ··· 382 382 383 383 // Validate validates the fields 384 384 func (f *NewMSTeamsHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 385 - ctx := context.GetContext(req) 385 + ctx := context.GetValidateContext(req) 386 386 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 387 387 } 388 388 ··· 394 394 395 395 // Validate validates the fields 396 396 func (f *NewFeishuHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 397 - ctx := context.GetContext(req) 397 + ctx := context.GetValidateContext(req) 398 398 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 399 399 } 400 400 ··· 406 406 407 407 // Validate validates the fields 408 408 func (f *NewWechatWorkHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 409 - ctx := context.GetContext(req) 409 + ctx := context.GetValidateContext(req) 410 410 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 411 411 } 412 412 ··· 420 420 421 421 // Validate validates the fields 422 422 func (f *NewPackagistHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 423 - ctx := context.GetContext(req) 423 + ctx := context.GetValidateContext(req) 424 424 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 425 425 } 426 426 ··· 447 447 448 448 // Validate validates the fields 449 449 func (f *CreateIssueForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 450 - ctx := context.GetContext(req) 450 + ctx := context.GetValidateContext(req) 451 451 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 452 452 } 453 453 ··· 460 460 461 461 // Validate validates the fields 462 462 func (f *CreateCommentForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 463 - ctx := context.GetContext(req) 463 + ctx := context.GetValidateContext(req) 464 464 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 465 465 } 466 466 ··· 471 471 472 472 // Validate validates the fields 473 473 func (f *ReactionForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 474 - ctx := context.GetContext(req) 474 + ctx := context.GetValidateContext(req) 475 475 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 476 476 } 477 477 ··· 482 482 483 483 // Validate validates the fields 484 484 func (i *IssueLockForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 485 - ctx := context.GetContext(req) 485 + ctx := context.GetValidateContext(req) 486 486 return middleware.Validate(errs, ctx.Data, i, ctx.Locale) 487 487 } 488 488 ··· 550 550 551 551 // Validate validates the fields 552 552 func (f *CreateMilestoneForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 553 - ctx := context.GetContext(req) 553 + ctx := context.GetValidateContext(req) 554 554 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 555 555 } 556 556 ··· 572 572 573 573 // Validate validates the fields 574 574 func (f *CreateLabelForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 575 - ctx := context.GetContext(req) 575 + ctx := context.GetValidateContext(req) 576 576 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 577 577 } 578 578 ··· 583 583 584 584 // Validate validates the fields 585 585 func (f *InitializeLabelsForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 586 - ctx := context.GetContext(req) 586 + ctx := context.GetValidateContext(req) 587 587 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 588 588 } 589 589 ··· 611 611 612 612 // Validate validates the fields 613 613 func (f *MergePullRequestForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 614 - ctx := context.GetContext(req) 614 + ctx := context.GetValidateContext(req) 615 615 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 616 616 } 617 617 ··· 629 629 630 630 // Validate validates the fields 631 631 func (f *CodeCommentForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 632 - ctx := context.GetContext(req) 632 + ctx := context.GetValidateContext(req) 633 633 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 634 634 } 635 635 ··· 643 643 644 644 // Validate validates the fields 645 645 func (f *SubmitReviewForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 646 - ctx := context.GetContext(req) 646 + ctx := context.GetValidateContext(req) 647 647 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 648 648 } 649 649 ··· 704 704 705 705 // Validate validates the fields 706 706 func (f *NewReleaseForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 707 - ctx := context.GetContext(req) 707 + ctx := context.GetValidateContext(req) 708 708 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 709 709 } 710 710 ··· 719 719 720 720 // Validate validates the fields 721 721 func (f *EditReleaseForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 722 - ctx := context.GetContext(req) 722 + ctx := context.GetValidateContext(req) 723 723 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 724 724 } 725 725 ··· 740 740 // Validate validates the fields 741 741 // FIXME: use code generation to generate this method. 742 742 func (f *NewWikiForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 743 - ctx := context.GetContext(req) 743 + ctx := context.GetValidateContext(req) 744 744 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 745 745 } 746 746 ··· 765 765 766 766 // Validate validates the fields 767 767 func (f *EditRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 768 - ctx := context.GetContext(req) 768 + ctx := context.GetValidateContext(req) 769 769 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 770 770 } 771 771 ··· 776 776 777 777 // Validate validates the fields 778 778 func (f *EditPreviewDiffForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 779 - ctx := context.GetContext(req) 779 + ctx := context.GetValidateContext(req) 780 780 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 781 781 } 782 782 ··· 800 800 801 801 // Validate validates the fields 802 802 func (f *CherryPickForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 803 - ctx := context.GetContext(req) 803 + ctx := context.GetValidateContext(req) 804 804 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 805 805 } 806 806 ··· 825 825 826 826 // Validate validates the fields 827 827 func (f *UploadRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 828 - ctx := context.GetContext(req) 828 + ctx := context.GetValidateContext(req) 829 829 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 830 830 } 831 831 ··· 836 836 837 837 // Validate validates the fields 838 838 func (f *RemoveUploadFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 839 - ctx := context.GetContext(req) 839 + ctx := context.GetValidateContext(req) 840 840 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 841 841 } 842 842 ··· 859 859 860 860 // Validate validates the fields 861 861 func (f *DeleteRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 862 - ctx := context.GetContext(req) 862 + ctx := context.GetValidateContext(req) 863 863 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 864 864 } 865 865 ··· 878 878 879 879 // Validate validates the fields 880 880 func (f *AddTimeManuallyForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 881 - ctx := context.GetContext(req) 881 + ctx := context.GetValidateContext(req) 882 882 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 883 883 } 884 884 ··· 894 894 895 895 // Validate validates the fields 896 896 func (f *DeadlineForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 897 - ctx := context.GetContext(req) 897 + ctx := context.GetValidateContext(req) 898 898 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 899 899 }
+1 -1
services/forms/repo_tag_form.go
··· 21 21 22 22 // Validate validates the fields 23 23 func (f *ProtectTagForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 24 - ctx := context.GetContext(req) 24 + ctx := context.GetValidateContext(req) 25 25 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 26 26 }
+1 -1
services/forms/runner.go
··· 20 20 21 21 // Validate validates form fields 22 22 func (f *EditRunnerForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 23 - ctx := context.GetContext(req) 23 + ctx := context.GetValidateContext(req) 24 24 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 25 25 }
+24 -24
services/forms/user_form.go
··· 78 78 79 79 // Validate validates the fields 80 80 func (f *InstallForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 81 - ctx := context.GetContext(req) 81 + ctx := context.GetValidateContext(req) 82 82 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 83 83 } 84 84 ··· 99 99 100 100 // Validate validates the fields 101 101 func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 102 - ctx := context.GetContext(req) 102 + ctx := context.GetValidateContext(req) 103 103 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 104 104 } 105 105 ··· 148 148 149 149 // Validate validates the fields 150 150 func (f *MustChangePasswordForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 151 - ctx := context.GetContext(req) 151 + ctx := context.GetValidateContext(req) 152 152 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 153 153 } 154 154 ··· 162 162 163 163 // Validate validates the fields 164 164 func (f *SignInForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 165 - ctx := context.GetContext(req) 165 + ctx := context.GetValidateContext(req) 166 166 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 167 167 } 168 168 ··· 182 182 183 183 // Validate validates the fields 184 184 func (f *AuthorizationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 185 - ctx := context.GetContext(req) 185 + ctx := context.GetValidateContext(req) 186 186 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 187 187 } 188 188 ··· 197 197 198 198 // Validate validates the fields 199 199 func (f *GrantApplicationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 200 - ctx := context.GetContext(req) 200 + ctx := context.GetValidateContext(req) 201 201 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 202 202 } 203 203 ··· 216 216 217 217 // Validate validates the fields 218 218 func (f *AccessTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 219 - ctx := context.GetContext(req) 219 + ctx := context.GetValidateContext(req) 220 220 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 221 221 } 222 222 ··· 227 227 228 228 // Validate validates the fields 229 229 func (f *IntrospectTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 230 - ctx := context.GetContext(req) 230 + ctx := context.GetValidateContext(req) 231 231 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 232 232 } 233 233 ··· 252 252 253 253 // Validate validates the fields 254 254 func (f *UpdateProfileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 255 - ctx := context.GetContext(req) 255 + ctx := context.GetValidateContext(req) 256 256 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 257 257 } 258 258 ··· 263 263 264 264 // Validate validates the fields 265 265 func (f *UpdateLanguageForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 266 - ctx := context.GetContext(req) 266 + ctx := context.GetValidateContext(req) 267 267 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 268 268 } 269 269 ··· 283 283 284 284 // Validate validates the fields 285 285 func (f *AvatarForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 286 - ctx := context.GetContext(req) 286 + ctx := context.GetValidateContext(req) 287 287 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 288 288 } 289 289 ··· 294 294 295 295 // Validate validates the fields 296 296 func (f *AddEmailForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 297 - ctx := context.GetContext(req) 297 + ctx := context.GetValidateContext(req) 298 298 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 299 299 } 300 300 ··· 305 305 306 306 // Validate validates the field 307 307 func (f *UpdateThemeForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 308 - ctx := context.GetContext(req) 308 + ctx := context.GetValidateContext(req) 309 309 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 310 310 } 311 311 ··· 332 332 333 333 // Validate validates the fields 334 334 func (f *ChangePasswordForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 335 - ctx := context.GetContext(req) 335 + ctx := context.GetValidateContext(req) 336 336 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 337 337 } 338 338 ··· 343 343 344 344 // Validate validates the fields 345 345 func (f *AddOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 346 - ctx := context.GetContext(req) 346 + ctx := context.GetValidateContext(req) 347 347 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 348 348 } 349 349 ··· 360 360 361 361 // Validate validates the fields 362 362 func (f *AddKeyForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 363 - ctx := context.GetContext(req) 363 + ctx := context.GetValidateContext(req) 364 364 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 365 365 } 366 366 ··· 372 372 373 373 // Validate validates the fields 374 374 func (f *AddSecretForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 375 - ctx := context.GetContext(req) 375 + ctx := context.GetValidateContext(req) 376 376 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 377 377 } 378 378 ··· 384 384 385 385 // Validate validates the fields 386 386 func (f *NewAccessTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 387 - ctx := context.GetContext(req) 387 + ctx := context.GetValidateContext(req) 388 388 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 389 389 } 390 390 ··· 403 403 404 404 // Validate validates the fields 405 405 func (f *EditOAuth2ApplicationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 406 - ctx := context.GetContext(req) 406 + ctx := context.GetValidateContext(req) 407 407 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 408 408 } 409 409 ··· 414 414 415 415 // Validate validates the fields 416 416 func (f *TwoFactorAuthForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 417 - ctx := context.GetContext(req) 417 + ctx := context.GetValidateContext(req) 418 418 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 419 419 } 420 420 ··· 425 425 426 426 // Validate validates the fields 427 427 func (f *TwoFactorScratchAuthForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 428 - ctx := context.GetContext(req) 428 + ctx := context.GetValidateContext(req) 429 429 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 430 430 } 431 431 ··· 436 436 437 437 // Validate validates the fields 438 438 func (f *WebauthnRegistrationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 439 - ctx := context.GetContext(req) 439 + ctx := context.GetValidateContext(req) 440 440 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 441 441 } 442 442 ··· 447 447 448 448 // Validate validates the fields 449 449 func (f *WebauthnDeleteForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 450 - ctx := context.GetContext(req) 450 + ctx := context.GetValidateContext(req) 451 451 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 452 452 } 453 453 ··· 459 459 460 460 // Validate validates the fields 461 461 func (f *PackageSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 462 - ctx := context.GetContext(req) 462 + ctx := context.GetValidateContext(req) 463 463 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 464 464 }
+3 -3
services/forms/user_form_auth_openid.go
··· 20 20 21 21 // Validate validates the fields 22 22 func (f *SignInOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 23 - ctx := context.GetContext(req) 23 + ctx := context.GetValidateContext(req) 24 24 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 25 25 } 26 26 ··· 32 32 33 33 // Validate validates the fields 34 34 func (f *SignUpOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 35 - ctx := context.GetContext(req) 35 + ctx := context.GetValidateContext(req) 36 36 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 37 37 } 38 38 ··· 44 44 45 45 // Validate validates the fields 46 46 func (f *ConnectOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { 47 - ctx := context.GetContext(req) 47 + ctx := context.GetValidateContext(req) 48 48 return middleware.Validate(errs, ctx.Data, f, ctx.Locale) 49 49 }
+5 -4
services/markup/processorhelper_test.go
··· 6 6 import ( 7 7 "context" 8 8 "net/http" 9 + "net/http/httptest" 9 10 "testing" 10 11 11 12 "code.gitea.io/gitea/models/db" ··· 36 37 assert.False(t, ProcessorHelper().IsUsernameMentionable(context.Background(), userNoSuch)) 37 38 38 39 // when using web context, use user.IsUserVisibleToViewer to check 39 - var err error 40 - giteaCtx := &gitea_context.Context{} 41 - giteaCtx.Req, err = http.NewRequest("GET", "/", nil) 40 + req, err := http.NewRequest("GET", "/", nil) 42 41 assert.NoError(t, err) 42 + base, baseCleanUp := gitea_context.NewBaseContext(httptest.NewRecorder(), req) 43 + defer baseCleanUp() 44 + giteaCtx := &gitea_context.Context{Base: base} 43 45 44 - giteaCtx.Doer = nil 45 46 assert.True(t, ProcessorHelper().IsUsernameMentionable(giteaCtx, userPublic)) 46 47 assert.False(t, ProcessorHelper().IsUsernameMentionable(giteaCtx, userPrivate)) 47 48