You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

context.go 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2020 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package context
  5. import (
  6. "context"
  7. "crypto/sha256"
  8. "encoding/hex"
  9. "errors"
  10. "fmt"
  11. "html"
  12. "html/template"
  13. "io"
  14. "net"
  15. "net/http"
  16. "net/url"
  17. "path"
  18. "strconv"
  19. "strings"
  20. "time"
  21. "code.gitea.io/gitea/models/db"
  22. "code.gitea.io/gitea/models/unit"
  23. user_model "code.gitea.io/gitea/models/user"
  24. "code.gitea.io/gitea/modules/base"
  25. mc "code.gitea.io/gitea/modules/cache"
  26. "code.gitea.io/gitea/modules/git"
  27. "code.gitea.io/gitea/modules/httpcache"
  28. "code.gitea.io/gitea/modules/json"
  29. "code.gitea.io/gitea/modules/log"
  30. "code.gitea.io/gitea/modules/setting"
  31. "code.gitea.io/gitea/modules/templates"
  32. "code.gitea.io/gitea/modules/translation"
  33. "code.gitea.io/gitea/modules/typesniffer"
  34. "code.gitea.io/gitea/modules/util"
  35. "code.gitea.io/gitea/modules/web/middleware"
  36. "code.gitea.io/gitea/services/auth"
  37. "gitea.com/go-chi/cache"
  38. "gitea.com/go-chi/session"
  39. chi "github.com/go-chi/chi/v5"
  40. "github.com/unrolled/render"
  41. "golang.org/x/crypto/pbkdf2"
  42. )
  43. // Render represents a template render
  44. type Render interface {
  45. TemplateLookup(tmpl string) *template.Template
  46. HTML(w io.Writer, status int, name string, binding interface{}, htmlOpt ...render.HTMLOptions) error
  47. }
  48. // Context represents context of a request.
  49. type Context struct {
  50. Resp ResponseWriter
  51. Req *http.Request
  52. Data map[string]interface{} // data used by MVC templates
  53. PageData map[string]interface{} // data used by JavaScript modules in one page, it's `window.config.pageData`
  54. Render Render
  55. translation.Locale
  56. Cache cache.Cache
  57. csrf CSRFProtector
  58. Flash *middleware.Flash
  59. Session session.Store
  60. Link string // current request URL
  61. EscapedLink string
  62. Doer *user_model.User
  63. IsSigned bool
  64. IsBasicAuth bool
  65. ContextUser *user_model.User
  66. Repo *Repository
  67. Org *Organization
  68. Package *Package
  69. }
  70. // Close frees all resources hold by Context
  71. func (ctx *Context) Close() error {
  72. var err error
  73. if ctx.Req != nil && ctx.Req.MultipartForm != nil {
  74. err = ctx.Req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
  75. }
  76. // TODO: close opened repo, and more
  77. return err
  78. }
  79. // TrHTMLEscapeArgs runs Tr but pre-escapes all arguments with html.EscapeString.
  80. // This is useful if the locale message is intended to only produce HTML content.
  81. func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string {
  82. trArgs := make([]interface{}, len(args))
  83. for i, arg := range args {
  84. trArgs[i] = html.EscapeString(arg)
  85. }
  86. return ctx.Tr(msg, trArgs...)
  87. }
  88. // GetData returns the data
  89. func (ctx *Context) GetData() map[string]interface{} {
  90. return ctx.Data
  91. }
  92. // IsUserSiteAdmin returns true if current user is a site admin
  93. func (ctx *Context) IsUserSiteAdmin() bool {
  94. return ctx.IsSigned && ctx.Doer.IsAdmin
  95. }
  96. // IsUserRepoOwner returns true if current user owns current repo
  97. func (ctx *Context) IsUserRepoOwner() bool {
  98. return ctx.Repo.IsOwner()
  99. }
  100. // IsUserRepoAdmin returns true if current user is admin in current repo
  101. func (ctx *Context) IsUserRepoAdmin() bool {
  102. return ctx.Repo.IsAdmin()
  103. }
  104. // IsUserRepoWriter returns true if current user has write privilege in current repo
  105. func (ctx *Context) IsUserRepoWriter(unitTypes []unit.Type) bool {
  106. for _, unitType := range unitTypes {
  107. if ctx.Repo.CanWrite(unitType) {
  108. return true
  109. }
  110. }
  111. return false
  112. }
  113. // IsUserRepoReaderSpecific returns true if current user can read current repo's specific part
  114. func (ctx *Context) IsUserRepoReaderSpecific(unitType unit.Type) bool {
  115. return ctx.Repo.CanRead(unitType)
  116. }
  117. // IsUserRepoReaderAny returns true if current user can read any part of current repo
  118. func (ctx *Context) IsUserRepoReaderAny() bool {
  119. return ctx.Repo.HasAccess()
  120. }
  121. // RedirectToUser redirect to a differently-named user
  122. func RedirectToUser(ctx *Context, userName string, redirectUserID int64) {
  123. user, err := user_model.GetUserByID(ctx, redirectUserID)
  124. if err != nil {
  125. ctx.ServerError("GetUserByID", err)
  126. return
  127. }
  128. redirectPath := strings.Replace(
  129. ctx.Req.URL.EscapedPath(),
  130. url.PathEscape(userName),
  131. url.PathEscape(user.Name),
  132. 1,
  133. )
  134. if ctx.Req.URL.RawQuery != "" {
  135. redirectPath += "?" + ctx.Req.URL.RawQuery
  136. }
  137. ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect)
  138. }
  139. // HasAPIError returns true if error occurs in form validation.
  140. func (ctx *Context) HasAPIError() bool {
  141. hasErr, ok := ctx.Data["HasError"]
  142. if !ok {
  143. return false
  144. }
  145. return hasErr.(bool)
  146. }
  147. // GetErrMsg returns error message
  148. func (ctx *Context) GetErrMsg() string {
  149. return ctx.Data["ErrorMsg"].(string)
  150. }
  151. // HasError returns true if error occurs in form validation.
  152. // Attention: this function changes ctx.Data and ctx.Flash
  153. func (ctx *Context) HasError() bool {
  154. hasErr, ok := ctx.Data["HasError"]
  155. if !ok {
  156. return false
  157. }
  158. ctx.Flash.ErrorMsg = ctx.Data["ErrorMsg"].(string)
  159. ctx.Data["Flash"] = ctx.Flash
  160. return hasErr.(bool)
  161. }
  162. // HasValue returns true if value of given name exists.
  163. func (ctx *Context) HasValue(name string) bool {
  164. _, ok := ctx.Data[name]
  165. return ok
  166. }
  167. // RedirectToFirst redirects to first not empty URL
  168. func (ctx *Context) RedirectToFirst(location ...string) {
  169. for _, loc := range location {
  170. if len(loc) == 0 {
  171. continue
  172. }
  173. // Unfortunately browsers consider a redirect Location with preceding "//" and "/\" as meaning redirect to "http(s)://REST_OF_PATH"
  174. // Therefore we should ignore these redirect locations to prevent open redirects
  175. if len(loc) > 1 && loc[0] == '/' && (loc[1] == '/' || loc[1] == '\\') {
  176. continue
  177. }
  178. u, err := url.Parse(loc)
  179. if err != nil || ((u.Scheme != "" || u.Host != "") && !strings.HasPrefix(strings.ToLower(loc), strings.ToLower(setting.AppURL))) {
  180. continue
  181. }
  182. ctx.Redirect(loc)
  183. return
  184. }
  185. ctx.Redirect(setting.AppSubURL + "/")
  186. }
  187. // HTML calls Context.HTML and renders the template to HTTP response
  188. func (ctx *Context) HTML(status int, name base.TplName) {
  189. log.Debug("Template: %s", name)
  190. tmplStartTime := time.Now()
  191. if !setting.IsProd {
  192. ctx.Data["TemplateName"] = name
  193. }
  194. ctx.Data["TemplateLoadTimes"] = func() string {
  195. return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms"
  196. }
  197. if err := ctx.Render.HTML(ctx.Resp, status, string(name), templates.BaseVars().Merge(ctx.Data)); err != nil {
  198. if status == http.StatusInternalServerError && name == base.TplName("status/500") {
  199. ctx.PlainText(http.StatusInternalServerError, "Unable to find status/500 template")
  200. return
  201. }
  202. ctx.ServerError("Render failed", err)
  203. }
  204. }
  205. // RenderToString renders the template content to a string
  206. func (ctx *Context) RenderToString(name base.TplName, data map[string]interface{}) (string, error) {
  207. var buf strings.Builder
  208. err := ctx.Render.HTML(&buf, http.StatusOK, string(name), data)
  209. return buf.String(), err
  210. }
  211. // RenderWithErr used for page has form validation but need to prompt error to users.
  212. func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, form interface{}) {
  213. if form != nil {
  214. middleware.AssignForm(form, ctx.Data)
  215. }
  216. ctx.Flash.ErrorMsg = msg
  217. ctx.Data["Flash"] = ctx.Flash
  218. ctx.HTML(http.StatusOK, tpl)
  219. }
  220. // NotFound displays a 404 (Not Found) page and prints the given error, if any.
  221. func (ctx *Context) NotFound(logMsg string, logErr error) {
  222. ctx.notFoundInternal(logMsg, logErr)
  223. }
  224. func (ctx *Context) notFoundInternal(logMsg string, logErr error) {
  225. if logErr != nil {
  226. log.Log(2, log.DEBUG, "%s: %v", logMsg, logErr)
  227. if !setting.IsProd {
  228. ctx.Data["ErrorMsg"] = logErr
  229. }
  230. }
  231. // response simple message if Accept isn't text/html
  232. showHTML := false
  233. for _, part := range ctx.Req.Header["Accept"] {
  234. if strings.Contains(part, "text/html") {
  235. showHTML = true
  236. break
  237. }
  238. }
  239. if !showHTML {
  240. ctx.plainTextInternal(3, http.StatusNotFound, []byte("Not found.\n"))
  241. return
  242. }
  243. ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
  244. ctx.Data["Title"] = "Page Not Found"
  245. ctx.HTML(http.StatusNotFound, base.TplName("status/404"))
  246. }
  247. // ServerError displays a 500 (Internal Server Error) page and prints the given error, if any.
  248. func (ctx *Context) ServerError(logMsg string, logErr error) {
  249. ctx.serverErrorInternal(logMsg, logErr)
  250. }
  251. func (ctx *Context) serverErrorInternal(logMsg string, logErr error) {
  252. if logErr != nil {
  253. log.ErrorWithSkip(2, "%s: %v", logMsg, logErr)
  254. if _, ok := logErr.(*net.OpError); ok || errors.Is(logErr, &net.OpError{}) {
  255. // This is an error within the underlying connection
  256. // and further rendering will not work so just return
  257. return
  258. }
  259. if !setting.IsProd {
  260. ctx.Data["ErrorMsg"] = logErr
  261. }
  262. }
  263. ctx.Data["Title"] = "Internal Server Error"
  264. ctx.HTML(http.StatusInternalServerError, base.TplName("status/500"))
  265. }
  266. // NotFoundOrServerError use error check function to determine if the error
  267. // is about not found. It responds with 404 status code for not found error,
  268. // or error context description for logging purpose of 500 server error.
  269. func (ctx *Context) NotFoundOrServerError(logMsg string, errCheck func(error) bool, err error) {
  270. if errCheck(err) {
  271. ctx.notFoundInternal(logMsg, err)
  272. return
  273. }
  274. ctx.serverErrorInternal(logMsg, err)
  275. }
  276. // PlainTextBytes renders bytes as plain text
  277. func (ctx *Context) plainTextInternal(skip, status int, bs []byte) {
  278. statusPrefix := status / 100
  279. if statusPrefix == 4 || statusPrefix == 5 {
  280. log.Log(skip, log.TRACE, "plainTextInternal (status=%d): %s", status, string(bs))
  281. }
  282. ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
  283. ctx.Resp.Header().Set("X-Content-Type-Options", "nosniff")
  284. ctx.Resp.WriteHeader(status)
  285. if _, err := ctx.Resp.Write(bs); err != nil {
  286. log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
  287. }
  288. }
  289. // PlainTextBytes renders bytes as plain text
  290. func (ctx *Context) PlainTextBytes(status int, bs []byte) {
  291. ctx.plainTextInternal(2, status, bs)
  292. }
  293. // PlainText renders content as plain text
  294. func (ctx *Context) PlainText(status int, text string) {
  295. ctx.plainTextInternal(2, status, []byte(text))
  296. }
  297. // RespHeader returns the response header
  298. func (ctx *Context) RespHeader() http.Header {
  299. return ctx.Resp.Header()
  300. }
  301. type ServeHeaderOptions struct {
  302. ContentType string // defaults to "application/octet-stream"
  303. ContentTypeCharset string
  304. ContentLength *int64
  305. Disposition string // defaults to "attachment"
  306. Filename string
  307. CacheDuration time.Duration // defaults to 5 minutes
  308. LastModified time.Time
  309. }
  310. // SetServeHeaders sets necessary content serve headers
  311. func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
  312. header := ctx.Resp.Header()
  313. contentType := typesniffer.ApplicationOctetStream
  314. if opts.ContentType != "" {
  315. if opts.ContentTypeCharset != "" {
  316. contentType = opts.ContentType + "; charset=" + strings.ToLower(opts.ContentTypeCharset)
  317. } else {
  318. contentType = opts.ContentType
  319. }
  320. }
  321. header.Set("Content-Type", contentType)
  322. header.Set("X-Content-Type-Options", "nosniff")
  323. if opts.ContentLength != nil {
  324. header.Set("Content-Length", strconv.FormatInt(*opts.ContentLength, 10))
  325. }
  326. if opts.Filename != "" {
  327. disposition := opts.Disposition
  328. if disposition == "" {
  329. disposition = "attachment"
  330. }
  331. backslashEscapedName := strings.ReplaceAll(strings.ReplaceAll(opts.Filename, `\`, `\\`), `"`, `\"`) // \ -> \\, " -> \"
  332. header.Set("Content-Disposition", fmt.Sprintf(`%s; filename="%s"; filename*=UTF-8''%s`, disposition, backslashEscapedName, url.PathEscape(opts.Filename)))
  333. header.Set("Access-Control-Expose-Headers", "Content-Disposition")
  334. }
  335. duration := opts.CacheDuration
  336. if duration == 0 {
  337. duration = 5 * time.Minute
  338. }
  339. httpcache.AddCacheControlToHeader(header, duration)
  340. if !opts.LastModified.IsZero() {
  341. header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
  342. }
  343. }
  344. // ServeContent serves content to http request
  345. func (ctx *Context) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) {
  346. ctx.SetServeHeaders(opts)
  347. http.ServeContent(ctx.Resp, ctx.Req, opts.Filename, opts.LastModified, r)
  348. }
  349. // UploadStream returns the request body or the first form file
  350. // Only form files need to get closed.
  351. func (ctx *Context) UploadStream() (rd io.ReadCloser, needToClose bool, err error) {
  352. contentType := strings.ToLower(ctx.Req.Header.Get("Content-Type"))
  353. if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") || strings.HasPrefix(contentType, "multipart/form-data") {
  354. if err := ctx.Req.ParseMultipartForm(32 << 20); err != nil {
  355. return nil, false, err
  356. }
  357. if ctx.Req.MultipartForm.File == nil {
  358. return nil, false, http.ErrMissingFile
  359. }
  360. for _, files := range ctx.Req.MultipartForm.File {
  361. if len(files) > 0 {
  362. r, err := files[0].Open()
  363. return r, true, err
  364. }
  365. }
  366. return nil, false, http.ErrMissingFile
  367. }
  368. return ctx.Req.Body, false, nil
  369. }
  370. // Error returned an error to web browser
  371. func (ctx *Context) Error(status int, contents ...string) {
  372. v := http.StatusText(status)
  373. if len(contents) > 0 {
  374. v = contents[0]
  375. }
  376. http.Error(ctx.Resp, v, status)
  377. }
  378. // JSON render content as JSON
  379. func (ctx *Context) JSON(status int, content interface{}) {
  380. ctx.Resp.Header().Set("Content-Type", "application/json;charset=utf-8")
  381. ctx.Resp.WriteHeader(status)
  382. if err := json.NewEncoder(ctx.Resp).Encode(content); err != nil {
  383. ctx.ServerError("Render JSON failed", err)
  384. }
  385. }
  386. // Redirect redirects the request
  387. func (ctx *Context) Redirect(location string, status ...int) {
  388. code := http.StatusSeeOther
  389. if len(status) == 1 {
  390. code = status[0]
  391. }
  392. http.Redirect(ctx.Resp, ctx.Req, location, code)
  393. }
  394. // SetCookie convenience function to set most cookies consistently
  395. // CSRF and a few others are the exception here
  396. func (ctx *Context) SetCookie(name, value string, expiry int) {
  397. middleware.SetCookie(ctx.Resp, name, value,
  398. expiry,
  399. setting.AppSubURL,
  400. setting.SessionConfig.Domain,
  401. setting.SessionConfig.Secure,
  402. true,
  403. middleware.SameSite(setting.SessionConfig.SameSite))
  404. }
  405. // DeleteCookie convenience function to delete most cookies consistently
  406. // CSRF and a few others are the exception here
  407. func (ctx *Context) DeleteCookie(name string) {
  408. middleware.SetCookie(ctx.Resp, name, "",
  409. -1,
  410. setting.AppSubURL,
  411. setting.SessionConfig.Domain,
  412. setting.SessionConfig.Secure,
  413. true,
  414. middleware.SameSite(setting.SessionConfig.SameSite))
  415. }
  416. // GetCookie returns given cookie value from request header.
  417. func (ctx *Context) GetCookie(name string) string {
  418. return middleware.GetCookie(ctx.Req, name)
  419. }
  420. // GetSuperSecureCookie returns given cookie value from request header with secret string.
  421. func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) {
  422. val := ctx.GetCookie(name)
  423. return ctx.CookieDecrypt(secret, val)
  424. }
  425. // CookieDecrypt returns given value from with secret string.
  426. func (ctx *Context) CookieDecrypt(secret, val string) (string, bool) {
  427. if val == "" {
  428. return "", false
  429. }
  430. text, err := hex.DecodeString(val)
  431. if err != nil {
  432. return "", false
  433. }
  434. key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
  435. text, err = util.AESGCMDecrypt(key, text)
  436. return string(text), err == nil
  437. }
  438. // SetSuperSecureCookie sets given cookie value to response header with secret string.
  439. func (ctx *Context) SetSuperSecureCookie(secret, name, value string, expiry int) {
  440. text := ctx.CookieEncrypt(secret, value)
  441. ctx.SetCookie(name, text, expiry)
  442. }
  443. // CookieEncrypt encrypts a given value using the provided secret
  444. func (ctx *Context) CookieEncrypt(secret, value string) string {
  445. key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
  446. text, err := util.AESGCMEncrypt(key, []byte(value))
  447. if err != nil {
  448. panic("error encrypting cookie: " + err.Error())
  449. }
  450. return hex.EncodeToString(text)
  451. }
  452. // GetCookieInt returns cookie result in int type.
  453. func (ctx *Context) GetCookieInt(name string) int {
  454. r, _ := strconv.Atoi(ctx.GetCookie(name))
  455. return r
  456. }
  457. // GetCookieInt64 returns cookie result in int64 type.
  458. func (ctx *Context) GetCookieInt64(name string) int64 {
  459. r, _ := strconv.ParseInt(ctx.GetCookie(name), 10, 64)
  460. return r
  461. }
  462. // GetCookieFloat64 returns cookie result in float64 type.
  463. func (ctx *Context) GetCookieFloat64(name string) float64 {
  464. v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64)
  465. return v
  466. }
  467. // RemoteAddr returns the client machie ip address
  468. func (ctx *Context) RemoteAddr() string {
  469. return ctx.Req.RemoteAddr
  470. }
  471. // Params returns the param on route
  472. func (ctx *Context) Params(p string) string {
  473. s, _ := url.PathUnescape(chi.URLParam(ctx.Req, strings.TrimPrefix(p, ":")))
  474. return s
  475. }
  476. // ParamsInt64 returns the param on route as int64
  477. func (ctx *Context) ParamsInt64(p string) int64 {
  478. v, _ := strconv.ParseInt(ctx.Params(p), 10, 64)
  479. return v
  480. }
  481. // SetParams set params into routes
  482. func (ctx *Context) SetParams(k, v string) {
  483. chiCtx := chi.RouteContext(ctx)
  484. chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v))
  485. }
  486. // Write writes data to web browser
  487. func (ctx *Context) Write(bs []byte) (int, error) {
  488. return ctx.Resp.Write(bs)
  489. }
  490. // Written returns true if there are something sent to web browser
  491. func (ctx *Context) Written() bool {
  492. return ctx.Resp.Status() > 0
  493. }
  494. // Status writes status code
  495. func (ctx *Context) Status(status int) {
  496. ctx.Resp.WriteHeader(status)
  497. }
  498. // Deadline is part of the interface for context.Context and we pass this to the request context
  499. func (ctx *Context) Deadline() (deadline time.Time, ok bool) {
  500. return ctx.Req.Context().Deadline()
  501. }
  502. // Done is part of the interface for context.Context and we pass this to the request context
  503. func (ctx *Context) Done() <-chan struct{} {
  504. return ctx.Req.Context().Done()
  505. }
  506. // Err is part of the interface for context.Context and we pass this to the request context
  507. func (ctx *Context) Err() error {
  508. return ctx.Req.Context().Err()
  509. }
  510. // Value is part of the interface for context.Context and we pass this to the request context
  511. func (ctx *Context) Value(key interface{}) interface{} {
  512. if key == git.RepositoryContextKey && ctx.Repo != nil {
  513. return ctx.Repo.GitRepo
  514. }
  515. return ctx.Req.Context().Value(key)
  516. }
  517. // SetTotalCountHeader set "X-Total-Count" header
  518. func (ctx *Context) SetTotalCountHeader(total int64) {
  519. ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total))
  520. ctx.AppendAccessControlExposeHeaders("X-Total-Count")
  521. }
  522. // AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
  523. func (ctx *Context) AppendAccessControlExposeHeaders(names ...string) {
  524. val := ctx.RespHeader().Get("Access-Control-Expose-Headers")
  525. if len(val) != 0 {
  526. ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", ")))
  527. } else {
  528. ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", "))
  529. }
  530. }
  531. // Handler represents a custom handler
  532. type Handler func(*Context)
  533. type contextKeyType struct{}
  534. var contextKey interface{} = contextKeyType{}
  535. // WithContext set up install context in request
  536. func WithContext(req *http.Request, ctx *Context) *http.Request {
  537. return req.WithContext(context.WithValue(req.Context(), contextKey, ctx))
  538. }
  539. // GetContext retrieves install context from request
  540. func GetContext(req *http.Request) *Context {
  541. return req.Context().Value(contextKey).(*Context)
  542. }
  543. // GetContextUser returns context user
  544. func GetContextUser(req *http.Request) *user_model.User {
  545. if apiContext, ok := req.Context().Value(apiContextKey).(*APIContext); ok {
  546. return apiContext.Doer
  547. }
  548. if ctx, ok := req.Context().Value(contextKey).(*Context); ok {
  549. return ctx.Doer
  550. }
  551. return nil
  552. }
  553. func getCsrfOpts() CsrfOptions {
  554. return CsrfOptions{
  555. Secret: setting.SecretKey,
  556. Cookie: setting.CSRFCookieName,
  557. SetCookie: true,
  558. Secure: setting.SessionConfig.Secure,
  559. CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
  560. Header: "X-Csrf-Token",
  561. CookieDomain: setting.SessionConfig.Domain,
  562. CookiePath: setting.SessionConfig.CookiePath,
  563. SameSite: setting.SessionConfig.SameSite,
  564. }
  565. }
  566. // Auth converts auth.Auth as a middleware
  567. func Auth(authMethod auth.Method) func(*Context) {
  568. return func(ctx *Context) {
  569. var err error
  570. ctx.Doer, err = authMethod.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session)
  571. if err != nil {
  572. log.Error("Failed to verify user %v: %v", ctx.Req.RemoteAddr, err)
  573. ctx.Error(http.StatusUnauthorized, "Verify")
  574. return
  575. }
  576. if ctx.Doer != nil {
  577. if ctx.Locale.Language() != ctx.Doer.Language {
  578. ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
  579. }
  580. ctx.IsBasicAuth = ctx.Data["AuthedMethod"].(string) == auth.BasicMethodName
  581. ctx.IsSigned = true
  582. ctx.Data["IsSigned"] = ctx.IsSigned
  583. ctx.Data["SignedUser"] = ctx.Doer
  584. ctx.Data["SignedUserID"] = ctx.Doer.ID
  585. ctx.Data["SignedUserName"] = ctx.Doer.Name
  586. ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin
  587. } else {
  588. ctx.Data["SignedUserID"] = int64(0)
  589. ctx.Data["SignedUserName"] = ""
  590. // ensure the session uid is deleted
  591. _ = ctx.Session.Delete("uid")
  592. }
  593. }
  594. }
  595. // Contexter initializes a classic context for a request.
  596. func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
  597. _, rnd := templates.HTMLRenderer(ctx)
  598. csrfOpts := getCsrfOpts()
  599. if !setting.IsProd {
  600. CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose
  601. }
  602. return func(next http.Handler) http.Handler {
  603. return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
  604. locale := middleware.Locale(resp, req)
  605. startTime := time.Now()
  606. link := setting.AppSubURL + strings.TrimSuffix(req.URL.EscapedPath(), "/")
  607. ctx := Context{
  608. Resp: NewResponse(resp),
  609. Cache: mc.GetCache(),
  610. Locale: locale,
  611. Link: link,
  612. Render: rnd,
  613. Session: session.GetSession(req),
  614. Repo: &Repository{
  615. PullRequest: &PullRequest{},
  616. },
  617. Org: &Organization{},
  618. Data: map[string]interface{}{
  619. "CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
  620. "PageStartTime": startTime,
  621. "Link": link,
  622. "RunModeIsProd": setting.IsProd,
  623. },
  624. }
  625. defer ctx.Close()
  626. // PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules
  627. ctx.PageData = map[string]interface{}{}
  628. ctx.Data["PageData"] = ctx.PageData
  629. ctx.Data["Context"] = &ctx
  630. ctx.Req = WithContext(req, &ctx)
  631. ctx.csrf = PrepareCSRFProtector(csrfOpts, &ctx)
  632. // Get flash.
  633. flashCookie := ctx.GetCookie("macaron_flash")
  634. vals, _ := url.ParseQuery(flashCookie)
  635. if len(vals) > 0 {
  636. f := &middleware.Flash{
  637. DataStore: &ctx,
  638. Values: vals,
  639. ErrorMsg: vals.Get("error"),
  640. SuccessMsg: vals.Get("success"),
  641. InfoMsg: vals.Get("info"),
  642. WarningMsg: vals.Get("warning"),
  643. }
  644. ctx.Data["Flash"] = f
  645. }
  646. f := &middleware.Flash{
  647. DataStore: &ctx,
  648. Values: url.Values{},
  649. ErrorMsg: "",
  650. WarningMsg: "",
  651. InfoMsg: "",
  652. SuccessMsg: "",
  653. }
  654. ctx.Resp.Before(func(resp ResponseWriter) {
  655. if flash := f.Encode(); len(flash) > 0 {
  656. middleware.SetCookie(resp, "macaron_flash", flash, 0,
  657. setting.SessionConfig.CookiePath,
  658. middleware.Domain(setting.SessionConfig.Domain),
  659. middleware.HTTPOnly(true),
  660. middleware.Secure(setting.SessionConfig.Secure),
  661. middleware.SameSite(setting.SessionConfig.SameSite),
  662. )
  663. return
  664. }
  665. middleware.SetCookie(ctx.Resp, "macaron_flash", "", -1,
  666. setting.SessionConfig.CookiePath,
  667. middleware.Domain(setting.SessionConfig.Domain),
  668. middleware.HTTPOnly(true),
  669. middleware.Secure(setting.SessionConfig.Secure),
  670. middleware.SameSite(setting.SessionConfig.SameSite),
  671. )
  672. })
  673. ctx.Flash = f
  674. // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
  675. if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
  676. if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
  677. ctx.ServerError("ParseMultipartForm", err)
  678. return
  679. }
  680. }
  681. httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 0, "no-transform")
  682. ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
  683. ctx.Data["CsrfToken"] = ctx.csrf.GetToken()
  684. ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
  685. // FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
  686. ctx.Data["IsLandingPageHome"] = setting.LandingPageURL == setting.LandingPageHome
  687. ctx.Data["IsLandingPageExplore"] = setting.LandingPageURL == setting.LandingPageExplore
  688. ctx.Data["IsLandingPageOrganizations"] = setting.LandingPageURL == setting.LandingPageOrganizations
  689. ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
  690. ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage
  691. ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
  692. ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
  693. ctx.Data["EnableSwagger"] = setting.API.EnableSwagger
  694. ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
  695. ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
  696. ctx.Data["DisableStars"] = setting.Repository.DisableStars
  697. ctx.Data["EnableActions"] = setting.Actions.Enabled
  698. ctx.Data["ManifestData"] = setting.ManifestData
  699. ctx.Data["UnitWikiGlobalDisabled"] = unit.TypeWiki.UnitGlobalDisabled()
  700. ctx.Data["UnitIssuesGlobalDisabled"] = unit.TypeIssues.UnitGlobalDisabled()
  701. ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled()
  702. ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled()
  703. ctx.Data["UnitActionsGlobalDisabled"] = unit.TypeActions.UnitGlobalDisabled()
  704. ctx.Data["locale"] = locale
  705. ctx.Data["AllLangs"] = translation.AllLangs()
  706. next.ServeHTTP(ctx.Resp, ctx.Req)
  707. // Handle adding signedUserName to the context for the AccessLogger
  708. usernameInterface := ctx.Data["SignedUserName"]
  709. identityPtrInterface := ctx.Req.Context().Value(signedUserNameStringPointerKey)
  710. if usernameInterface != nil && identityPtrInterface != nil {
  711. username := usernameInterface.(string)
  712. identityPtr := identityPtrInterface.(*string)
  713. if identityPtr != nil && username != "" {
  714. *identityPtr = username
  715. }
  716. }
  717. })
  718. }
  719. }
  720. // SearchOrderByMap represents all possible search order
  721. var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
  722. "asc": {
  723. "alpha": db.SearchOrderByAlphabetically,
  724. "created": db.SearchOrderByOldest,
  725. "updated": db.SearchOrderByLeastUpdated,
  726. "size": db.SearchOrderBySize,
  727. "id": db.SearchOrderByID,
  728. },
  729. "desc": {
  730. "alpha": db.SearchOrderByAlphabeticallyReverse,
  731. "created": db.SearchOrderByNewest,
  732. "updated": db.SearchOrderByRecentUpdated,
  733. "size": db.SearchOrderBySizeReverse,
  734. "id": db.SearchOrderByIDReverse,
  735. },
  736. }