197 lines · 5.8 KB · Go
RawDownload
// Copyright 2023 The Gitea Authors. All rights reserved.// Copyright 2024 The Forgejo Authors. All rights reserved.// SPDX-License-Identifier: MIT package context import (	"errors"	"fmt"	"html/template"	"net"	"net/http"	"net/url"	"path"	"strconv"	"strings"	"syscall"	"time" 	user_model "forgejo.org/models/user"	"forgejo.org/modules/base"	"forgejo.org/modules/httplib"	"forgejo.org/modules/log"	"forgejo.org/modules/setting"	"forgejo.org/modules/templates"	"forgejo.org/modules/web/middleware") // RedirectToUser redirect to a differently-named userfunc RedirectToUser(ctx *Base, userName string, redirectUserID int64) {	user, err := user_model.GetUserByID(ctx, redirectUserID)	if err != nil {		ctx.Error(http.StatusInternalServerError, "unable to get user")		return	} 	redirectPath := strings.Replace(		ctx.Req.URL.EscapedPath(),		url.PathEscape(userName),		url.PathEscape(user.Name),		1,	)	if ctx.Req.URL.RawQuery != "" {		redirectPath += "?" + ctx.Req.URL.RawQuery	}	ctx.Redirect(path.Join(setting.AppSubURL, redirectPath), http.StatusTemporaryRedirect)} // RedirectToFirst redirects to first not empty URL which likely belongs to current site.// If no suitable redirection is found, it redirects to the home.// It returns the location it redirected to.func (ctx *Context) RedirectToFirst(location ...string) string {	for _, loc := range location {		if len(loc) == 0 {			continue		} 		if httplib.IsRiskyRedirectURL(loc) {			continue		} 		ctx.Redirect(loc)		return loc	} 	ctx.Redirect(setting.AppSubURL + "/")	return setting.AppSubURL + "/"} const (	tplStatus404 base.TplName = "status/404"	tplStatus500 base.TplName = "status/500") // HTML calls Context.HTML and renders the template to HTTP responsefunc (ctx *Context) HTML(status int, name base.TplName) {	log.Debug("Template: %s", name) 	tmplStartTime := time.Now()	if !setting.IsProd {		ctx.Data["TemplateName"] = name	}	ctx.Data["TemplateLoadTimes"] = func() string {		return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms"	} 	err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data, ctx.TemplateContext)	if err == nil || errors.Is(err, syscall.EPIPE) {		return	} 	// if rendering fails, show error page	if name != tplStatus500 {		err = fmt.Errorf("failed to render template: %s, error: %s", name, templates.HandleTemplateRenderingError(err))		ctx.ServerError("Render failed", err) // show the 500 error page	} else {		ctx.PlainText(http.StatusInternalServerError, "Unable to render status/500 page, the template system is broken, or Gitea can't find your template files.")		return	}} // JSONTemplate renders the template as JSON response// keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscapefunc (ctx *Context) JSONTemplate(tmpl base.TplName) {	t, err := ctx.Render.TemplateLookup(string(tmpl), nil)	if err != nil {		ctx.ServerError("unable to find template", err)		return	}	ctx.Resp.Header().Set("Content-Type", "application/json")	if err = t.Execute(ctx.Resp, ctx.Data); err != nil {		ctx.ServerError("unable to execute template", err)	}} // RenderToHTML renders the template content to a HTML stringfunc (ctx *Context) RenderToHTML(name base.TplName, data map[string]any) (template.HTML, error) {	var buf strings.Builder	err := ctx.Render.HTML(&buf, 0, string(name), data, ctx.TemplateContext)	return template.HTML(buf.String()), err} // RenderWithErr used for page has form validation but need to prompt error to users.func (ctx *Context) RenderWithErr(msg any, tpl base.TplName, form any) {	if form != nil {		middleware.AssignForm(form, ctx.Data)	}	ctx.Flash.Error(msg, true)	ctx.HTML(http.StatusOK, tpl)} // NotFound displays a 404 (Not Found) page and prints the given error, if any.func (ctx *Context) NotFound(logMsg string, logErr error) {	ctx.notFoundInternal(logMsg, logErr)} func (ctx *Context) notFoundInternal(logMsg string, logErr error) {	if logErr != nil {		log.Log(2, log.DEBUG, "%s: %v", logMsg, logErr)		if !setting.IsProd {			ctx.Data["ErrorMsg"] = logErr		}	} 	// response simple message if Accept isn't text/html	showHTML := false	for _, part := range ctx.Req.Header["Accept"] {		if strings.Contains(part, "text/html") {			showHTML = true			break		}	} 	if !showHTML {		ctx.plainTextInternal(3, http.StatusNotFound, []byte("Not found.\n"))		return	} 	ctx.Data["IsRepo"] = ctx.Repo.Repository != nil	ctx.Data["Title"] = ctx.Locale.TrString("error.not_found.title")	ctx.HTML(http.StatusNotFound, tplStatus404)} // ServerError displays a 500 (Internal Server Error) page and prints the given error, if any.func (ctx *Context) ServerError(logMsg string, logErr error) {	ctx.serverErrorInternal(logMsg, logErr)} func (ctx *Context) serverErrorInternal(logMsg string, logErr error) {	if logErr != nil {		log.ErrorWithSkip(2, "%s: %v", logMsg, logErr)		if _, ok := logErr.(*net.OpError); ok || errors.Is(logErr, &net.OpError{}) {			// This is an error within the underlying connection			// and further rendering will not work so just return			return		} 		// it's safe to show internal error to admin users, and it helps		if !setting.IsProd || (ctx.Doer != nil && ctx.Doer.IsAdmin) {			ctx.Data["ErrorMsg"] = fmt.Sprintf("%s, %s", logMsg, logErr)		}	} 	ctx.HTML(http.StatusInternalServerError, tplStatus500)} // NotFoundOrServerError use error check function to determine if the error// is about not found. It responds with 404 status code for not found error,// or error context description for logging purpose of 500 server error.// TODO: remove the "errCheck" and use util.ErrNotFound to checkfunc (ctx *Context) NotFoundOrServerError(logMsg string, errCheck func(error) bool, logErr error) {	if errCheck(logErr) {		ctx.notFoundInternal(logMsg, logErr)		return	}	ctx.serverErrorInternal(logMsg, logErr)}