diff --git a/models/issues/comment.go b/models/issues/comment.go
index 34ce7f3500..84a7150b9f 100644
--- a/models/issues/comment.go
+++ b/models/issues/comment.go
@@ -24,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/structs"
@@ -543,6 +544,12 @@ func (c *Comment) EventTag() string {
return fmt.Sprintf("event-%d", c.ID)
}
+func (c *Comment) GetSanitizedContentHTML() template.HTML {
+ // mainly for type=4 CommentTypeCommitRef
+ // the content is a link like message title (from CreateRefComment)
+ return markup.Sanitize(c.Content)
+}
+
// LoadLabel if comment.Type is CommentTypeLabel, then load Label
func (c *Comment) LoadLabel(ctx context.Context) error {
var label Label
diff --git a/modules/markup/html.go b/modules/markup/html.go
index 1c2ae6918d..0fe37ae305 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -6,6 +6,7 @@ package markup
import (
"bytes"
"fmt"
+ "html/template"
"io"
"regexp"
"slices"
@@ -149,9 +150,9 @@ func PostProcessDefault(ctx *RenderContext, input io.Reader, output io.Writer) e
return postProcess(ctx, procs, input, output)
}
-// PostProcessCommitMessage will use the same logic as PostProcess, but will disable
-// the shortLinkProcessor.
-func PostProcessCommitMessage(ctx *RenderContext, content string) (string, error) {
+// PostProcessCommitMessage will use the same logic as PostProcess, but will disable the shortLinkProcessor.
+// FIXME: this function and its family have a very strange design: it takes HTML as input and output, processes the "escaped" content.
+func PostProcessCommitMessage(ctx *RenderContext, content template.HTML) (template.HTML, error) {
procs := []processor{
fullIssuePatternProcessor,
comparePatternProcessor,
@@ -165,7 +166,8 @@ func PostProcessCommitMessage(ctx *RenderContext, content string) (string, error
emojiProcessor,
emojiShortCodeProcessor,
}
- return postProcessString(ctx, procs, content)
+ s, err := postProcessString(ctx, procs, string(content))
+ return template.HTML(s), err
}
var emojiProcessors = []processor{
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index f81be1255a..3e4289c8ad 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -32,13 +32,12 @@ func newFuncMapWebPage() template.FuncMap {
// -----------------------------------------------------------------
// html/template related functions
- "dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
- "Iif": iif,
- "Eval": evalTokens,
- "HTMLFormat": htmlFormat,
- "QueryEscape": queryEscape,
- "QueryBuild": QueryBuild,
- "SanitizeHTML": SanitizeHTML,
+ "dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
+ "Iif": iif,
+ "Eval": evalTokens,
+ "HTMLFormat": htmlFormat,
+ "QueryEscape": queryEscape,
+ "QueryBuild": QueryBuild,
"PathEscape": url.PathEscape,
"PathEscapeSegments": util.PathEscapeSegments,
@@ -146,9 +145,8 @@ func newFuncMapWebPage() template.FuncMap {
}
}
-// SanitizeHTML sanitizes the input by default sanitization rules.
-func SanitizeHTML(s string) template.HTML {
- return markup.Sanitize(s)
+func sanitizeHTML(msg string) template.HTML {
+ return markup.Sanitize(msg)
}
func htmlFormat(s any, args ...any) template.HTML {
diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go
index f90818c0ad..cf1db32476 100644
--- a/modules/templates/helper_test.go
+++ b/modules/templates/helper_test.go
@@ -58,7 +58,7 @@ func TestSubjectBodySeparator(t *testing.T) {
}
func TestSanitizeHTML(t *testing.T) {
- assert.Equal(t, template.HTML(`link xss
inline
`), SanitizeHTML(`link xss inline
`))
+ assert.Equal(t, template.HTML(`link xss inline
`), sanitizeHTML(`link xss inline
`))
}
func TestTemplateIif(t *testing.T) {
diff --git a/modules/templates/mail.go b/modules/templates/mail.go
index 181c6312b0..f81073902f 100644
--- a/modules/templates/mail.go
+++ b/modules/templates/mail.go
@@ -65,13 +65,16 @@ func mailBodyFuncMap() template.FuncMap {
"NIL": func() any { return nil },
// html/template related functions
- "dict": dict,
- "Iif": iif,
- "Eval": evalTokens,
- "HTMLFormat": htmlFormat,
- "QueryEscape": queryEscape,
- "QueryBuild": QueryBuild,
- "SanitizeHTML": SanitizeHTML,
+ "dict": dict,
+ "Iif": iif,
+ "Eval": evalTokens,
+ "HTMLFormat": htmlFormat,
+ "QueryEscape": queryEscape,
+ "QueryBuild": QueryBuild,
+
+ // deprecated, use "HTMLFormat" instead, but some user custom mail templates still use it
+ // see: https://github.com/go-gitea/gitea/issues/36049
+ "SanitizeHTML": sanitizeHTML,
"PathEscape": url.PathEscape,
"PathEscapeSegments": util.PathEscapeSegments,
diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go
index 081a13fb9e..6641ee7959 100644
--- a/modules/templates/util_render.go
+++ b/modules/templates/util_render.go
@@ -40,7 +40,7 @@ func NewRenderUtils(ctx reqctx.RequestContext) *RenderUtils {
// RenderCommitMessage renders commit message with XSS-safe and special links.
func (ut *RenderUtils) RenderCommitMessage(msg string, repo *repo.Repository) template.HTML {
- cleanMsg := template.HTMLEscapeString(msg)
+ cleanMsg := template.HTML(template.HTMLEscapeString(msg))
// we can safely assume that it will not return any error, since there shouldn't be any special HTML.
// "repo" can be nil when rendering commit messages for deleted repositories in a user's dashboard feed.
fullMessage, err := markup.PostProcessCommitMessage(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), cleanMsg)
@@ -48,7 +48,7 @@ func (ut *RenderUtils) RenderCommitMessage(msg string, repo *repo.Repository) te
log.Error("PostProcessCommitMessage: %v", err)
return ""
}
- msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n")
+ msgLines := strings.Split(strings.TrimSpace(string(fullMessage)), "\n")
if len(msgLines) == 0 {
return ""
}
@@ -91,12 +91,14 @@ func (ut *RenderUtils) RenderCommitBody(msg string, repo *repo.Repository) templ
return ""
}
- renderedMessage, err := markup.PostProcessCommitMessage(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), template.HTMLEscapeString(msgLine))
+ rctx := renderhelper.NewRenderContextRepoComment(ut.ctx, repo)
+ htmlContent := template.HTML(template.HTMLEscapeString(msgLine))
+ renderedMessage, err := markup.PostProcessCommitMessage(rctx, htmlContent)
if err != nil {
log.Error("PostProcessCommitMessage: %v", err)
return ""
}
- return template.HTML(renderedMessage)
+ return renderedMessage
}
// Match text that is between back ticks.
@@ -279,6 +281,35 @@ func (ut *RenderUtils) RenderThemeItem(info *webtheme.ThemeMetaInfo, iconSize in
return htmlutil.HTMLFormat(``, info.GetDescription(), icon, info.DisplayName, extraIcon)
}
+func (ut *RenderUtils) RenderFlashMessage(typ, msg string) template.HTML {
+ msg = strings.TrimSpace(msg)
+ if msg == "" {
+ return ""
+ }
+
+ cls := typ
+ // legacy logic: "negative" for error, "positive" for success
+ switch cls {
+ case "error":
+ cls = "negative"
+ case "success":
+ cls = "positive"
+ }
+
+ var msgContent template.HTML
+ if strings.Contains(msg, "") || strings.Contains(msg, "") || strings.Contains(msg, "") || strings.Contains(msg, "") {
+ // If the message contains some known "block" elements, no need to do more alignment or line-break processing, just sanitize it directly.
+ msgContent = sanitizeHTML(msg)
+ } else if !strings.Contains(msg, "\n") {
+ // If the message is a single line, center-align it by wrapping it
+ msgContent = htmlutil.HTMLFormat(`%s
`, sanitizeHTML(msg))
+ } else {
+ // For a multi-line message, preserve line breaks, and left-align it.
+ msgContent = htmlutil.HTMLFormat(`%s`, sanitizeHTML(strings.ReplaceAll(msg, "\n", " ")))
+ }
+ return htmlutil.HTMLFormat(`%s
`, cls, typ, msgContent)
+}
+
func (ut *RenderUtils) RenderUnicodeEscapeToggleButton(escapeStatus *charset.EscapeStatus) template.HTML {
if escapeStatus == nil || !escapeStatus.Escaped {
return ""
diff --git a/routers/utils/utils.go b/routers/utils/utils.go
index 3035073d5c..47ca13b2aa 100644
--- a/routers/utils/utils.go
+++ b/routers/utils/utils.go
@@ -5,10 +5,11 @@ package utils
import (
"html"
- "strings"
+ "html/template"
)
-// SanitizeFlashErrorString will sanitize a flash error string
-func SanitizeFlashErrorString(x string) string {
- return strings.ReplaceAll(html.EscapeString(x), "\n", " ")
+// EscapeFlashErrorString will escape the flash error string
+// Maybe do more sanitization in the future, e.g.: hide sensitive information, etc.
+func EscapeFlashErrorString(x string) template.HTML {
+ return template.HTML(html.EscapeString(x))
}
diff --git a/routers/utils/utils_test.go b/routers/utils/utils_test.go
index cc7c888a75..5dfc544777 100644
--- a/routers/utils/utils_test.go
+++ b/routers/utils/utils_test.go
@@ -4,16 +4,17 @@
package utils
import (
+ "html/template"
"testing"
"github.com/stretchr/testify/assert"
)
-func TestSanitizeFlashErrorString(t *testing.T) {
+func TestEscapeFlashErrorString(t *testing.T) {
tests := []struct {
name string
arg string
- want string
+ want template.HTML
}{
{
name: "no error",
@@ -28,13 +29,13 @@ func TestSanitizeFlashErrorString(t *testing.T) {
{
name: "line break error",
arg: "some error:\n\nawesome!",
- want: "some error: awesome!",
+ want: "some error:\n\nawesome!",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got := SanitizeFlashErrorString(tt.arg)
+ got := EscapeFlashErrorString(tt.arg)
assert.Equal(t, tt.want, got)
})
}
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index c103d20064..8645aedbde 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -79,7 +79,7 @@ func SignInOAuthCallback(ctx *context.Context) {
}
}
sort.Strings(errorKeyValues)
- ctx.Flash.Error(strings.Join(errorKeyValues, " "), true)
+ ctx.Flash.Error(strings.Join(errorKeyValues, "\n"), true)
}
// first look if the provider is still active
diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go
index 5cfe08c7fe..8bc5947df8 100644
--- a/routers/web/devtest/devtest.go
+++ b/routers/web/devtest/devtest.go
@@ -45,8 +45,8 @@ func List(ctx *context.Context) {
func FetchActionTest(ctx *context.Context) {
_ = ctx.Req.ParseForm()
- ctx.Flash.Info("fetch-action: " + ctx.Req.Method + " " + ctx.Req.RequestURI + " " +
- "Form: " + ctx.Req.Form.Encode() + " " +
+ ctx.Flash.Info("fetch-action: " + ctx.Req.Method + " " + ctx.Req.RequestURI + "\n" +
+ "Form: " + ctx.Req.Form.Encode() + "\n" +
"PostForm: " + ctx.Req.PostForm.Encode(),
)
time.Sleep(2 * time.Second)
@@ -192,11 +192,31 @@ func prepareMockData(ctx *context.Context) {
prepareMockDataBadgeActionsSvg(ctx)
case "/devtest/relative-time":
prepareMockDataRelativeTime(ctx)
+ case "/devtest/toast-and-message":
+ prepareMockDataToastAndMessage(ctx)
case "/devtest/unicode-escape":
prepareMockDataUnicodeEscape(ctx)
}
}
+func prepareMockDataToastAndMessage(ctx *context.Context) {
+ msgWithDetails, _ := ctx.RenderToHTML("base/alert_details", map[string]any{
+ "Message": "message with details ",
+ "Summary": "summary with details",
+ "Details": "details line 1\n details line 2\n details line 3",
+ })
+ msgWithSummary, _ := ctx.RenderToHTML("base/alert_details", map[string]any{
+ "Message": "message with summary ",
+ "Summary": "summary only",
+ })
+
+ ctx.Flash.ErrorMsg = string(msgWithDetails)
+ ctx.Flash.WarningMsg = string(msgWithSummary)
+ ctx.Flash.InfoMsg = "a long message with line break\nthe second line "
+ ctx.Flash.SuccessMsg = "single line message "
+ ctx.Data["Flash"] = ctx.Flash
+}
+
func prepareMockDataUnicodeEscape(ctx *context.Context) {
content := "// demo code\n"
content += "if accessLevel != \"user\u202E \u2066// Check if admin (invisible char)\u2069 \u2066\" { }\n"
@@ -223,8 +243,8 @@ func TmplCommon(ctx *context.Context) {
prepareMockData(ctx)
if ctx.Req.Method == http.MethodPost {
_ = ctx.Req.ParseForm()
- ctx.Flash.Info("form: "+ctx.Req.Method+" "+ctx.Req.RequestURI+" "+
- "Form: "+ctx.Req.Form.Encode()+" "+
+ ctx.Flash.Info("form: "+ctx.Req.Method+" "+ctx.Req.RequestURI+"\n"+
+ "Form: "+ctx.Req.Form.Encode()+"\n"+
"PostForm: "+ctx.Req.PostForm.Encode(),
true,
)
diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go
index a5c379e01a..5d208bb286 100644
--- a/routers/web/feed/convert.go
+++ b/routers/web/feed/convert.go
@@ -15,6 +15,7 @@ import (
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/renderhelper"
repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
@@ -237,7 +238,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
}
}
if len(content) == 0 {
- content = templates.SanitizeHTML(desc)
+ content = markup.Sanitize(desc)
}
items = append(items, &feeds.Item{
diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go
index 5e5cfec5c2..c566e465e9 100644
--- a/routers/web/repo/branch.go
+++ b/routers/web/repo/branch.go
@@ -231,7 +231,7 @@ func CreateBranch(ctx *context.Context) {
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.push_rejected"),
"Summary": ctx.Tr("repo.editor.push_rejected_summary"),
- "Details": utils.SanitizeFlashErrorString(e.Message),
+ "Details": utils.EscapeFlashErrorString(e.Message),
})
if err != nil {
ctx.ServerError("UpdatePullRequest.HTMLString", err)
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index 168d959494..34e588b141 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -410,7 +410,8 @@ func Diff(ctx *context.Context) {
ctx.Data["NoteCommit"] = note.Commit
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
rctx := renderhelper.NewRenderContextRepoComment(ctx, ctx.Repo.Repository, renderhelper.RepoCommentOptions{CurrentRefPath: path.Join("commit", util.PathEscapeSegments(commitID))})
- ctx.Data["NoteRendered"], err = markup.PostProcessCommitMessage(rctx, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
+ htmlMessage := template.HTML(template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{}))))
+ ctx.Data["NoteRendered"], err = markup.PostProcessCommitMessage(rctx, htmlMessage)
if err != nil {
ctx.ServerError("PostProcessCommitMessage", err)
return
diff --git a/routers/web/repo/editor_error.go b/routers/web/repo/editor_error.go
index e1473a34b3..f23b2738e5 100644
--- a/routers/web/repo/editor_error.go
+++ b/routers/web/repo/editor_error.go
@@ -27,13 +27,13 @@ func editorHandleFileOperationErrorRender(ctx *context_service.Context, message,
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": message,
"Summary": summary,
- "Details": utils.SanitizeFlashErrorString(details),
+ "Details": utils.EscapeFlashErrorString(details),
})
if err == nil {
ctx.JSONError(flashError)
} else {
- log.Error("RenderToHTML: %v", err)
- ctx.JSONError(message + "\n" + summary + "\n" + utils.SanitizeFlashErrorString(details))
+ log.Error("RenderToHTML(%q, %q, %q), error: %v", message, summary, details, err)
+ ctx.JSONError("Unable to render error details, see server logs") // it should never happen
}
}
diff --git a/routers/web/repo/issue_new.go b/routers/web/repo/issue_new.go
index 98fb842ddf..592d902ba8 100644
--- a/routers/web/repo/issue_new.go
+++ b/routers/web/repo/issue_new.go
@@ -170,7 +170,7 @@ func renderErrorOfTemplates(ctx *context.Context, errs map[string]error) templat
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.issues.choose.ignore_invalid_templates"),
"Summary": ctx.Tr("repo.issues.choose.invalid_templates", len(errs)),
- "Details": utils.SanitizeFlashErrorString(strings.Join(lines, "\n")),
+ "Details": utils.EscapeFlashErrorString(strings.Join(lines, "\n")),
})
if err != nil {
log.Debug("render flash error: %v", err)
diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go
index 250a54fc24..f678f83878 100644
--- a/routers/web/repo/issue_view.go
+++ b/routers/web/repo/issue_view.go
@@ -29,7 +29,6 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/templates/vars"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
@@ -781,14 +780,14 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue
} else if comment.Type == issues_model.CommentTypeAddTimeManual ||
comment.Type == issues_model.CommentTypeStopTracking ||
comment.Type == issues_model.CommentTypeDeleteTimeManual {
- // drop error since times could be pruned from DB..
+ // drop error since times could be pruned from DB
_ = comment.LoadTime(ctx)
if comment.Content != "" {
// Content before v1.21 did store the formatted string instead of seconds,
// so "|" is used as delimiter to mark the new format
if comment.Content[0] != '|' {
// handle old time comments that have formatted text stored
- comment.RenderedContent = templates.SanitizeHTML(comment.Content)
+ comment.RenderedContent = markup.Sanitize(comment.Content)
comment.Content = ""
} else {
// else it's just a duration in seconds to pass on to the frontend
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index e312fc9d2a..efcdaac674 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -1042,7 +1042,7 @@ func UpdatePullRequest(ctx *context.Context) {
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.merge_conflict"),
"Summary": ctx.Tr("repo.pulls.merge_conflict_summary"),
- "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + " " + utils.SanitizeFlashErrorString(conflictError.StdOut),
+ "Details": utils.EscapeFlashErrorString(conflictError.StdErr) + "\n" + utils.EscapeFlashErrorString(conflictError.StdOut),
})
if err != nil {
ctx.ServerError("UpdatePullRequest.HTMLString", err)
@@ -1054,9 +1054,9 @@ func UpdatePullRequest(ctx *context.Context) {
} else if pull_service.IsErrRebaseConflicts(err) {
conflictError := err.(pull_service.ErrRebaseConflicts)
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
- "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)),
+ "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.EscapeFlashErrorString(conflictError.CommitSHA)),
"Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"),
- "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + " " + utils.SanitizeFlashErrorString(conflictError.StdOut),
+ "Details": utils.EscapeFlashErrorString(conflictError.StdErr) + "\n" + utils.EscapeFlashErrorString(conflictError.StdOut),
})
if err != nil {
ctx.ServerError("UpdatePullRequest.HTMLString", err)
@@ -1191,7 +1191,7 @@ func MergePullRequest(ctx *context.Context) {
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.editor.merge_conflict"),
"Summary": ctx.Tr("repo.editor.merge_conflict_summary"),
- "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + " " + utils.SanitizeFlashErrorString(conflictError.StdOut),
+ "Details": utils.EscapeFlashErrorString(conflictError.StdErr) + "\n" + utils.EscapeFlashErrorString(conflictError.StdOut),
})
if err != nil {
ctx.ServerError("MergePullRequest.HTMLString", err)
@@ -1202,9 +1202,9 @@ func MergePullRequest(ctx *context.Context) {
} else if pull_service.IsErrRebaseConflicts(err) {
conflictError := err.(pull_service.ErrRebaseConflicts)
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
- "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.SanitizeFlashErrorString(conflictError.CommitSHA)),
+ "Message": ctx.Tr("repo.pulls.rebase_conflict", utils.EscapeFlashErrorString(conflictError.CommitSHA)),
"Summary": ctx.Tr("repo.pulls.rebase_conflict_summary"),
- "Details": utils.SanitizeFlashErrorString(conflictError.StdErr) + " " + utils.SanitizeFlashErrorString(conflictError.StdOut),
+ "Details": utils.EscapeFlashErrorString(conflictError.StdErr) + "\n" + utils.EscapeFlashErrorString(conflictError.StdOut),
})
if err != nil {
ctx.ServerError("MergePullRequest.HTMLString", err)
@@ -1234,7 +1234,7 @@ func MergePullRequest(ctx *context.Context) {
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.push_rejected"),
"Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
- "Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
+ "Details": utils.EscapeFlashErrorString(pushrejErr.Message),
})
if err != nil {
ctx.ServerError("MergePullRequest.HTMLString", err)
@@ -1454,7 +1454,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
flashError, err := ctx.RenderToHTML(tplAlertDetails, map[string]any{
"Message": ctx.Tr("repo.pulls.push_rejected"),
"Summary": ctx.Tr("repo.pulls.push_rejected_summary"),
- "Details": utils.SanitizeFlashErrorString(pushrejErr.Message),
+ "Details": utils.EscapeFlashErrorString(pushrejErr.Message),
})
if err != nil {
ctx.ServerError("CompareAndPullRequest.HTMLString", err)
diff --git a/templates/base/alert.tmpl b/templates/base/alert.tmpl
index 5ebe191771..242f6278ea 100644
--- a/templates/base/alert.tmpl
+++ b/templates/base/alert.tmpl
@@ -1,25 +1,9 @@
-{{- if .Flash.ErrorMsg -}}
-
-
{{.Flash.ErrorMsg | SanitizeHTML}}
-
-{{- end -}}
-{{- if .Flash.SuccessMsg -}}
-
-
{{.Flash.SuccessMsg | SanitizeHTML}}
-
-{{- end -}}
-{{- if .Flash.InfoMsg -}}
-
-
{{.Flash.InfoMsg | SanitizeHTML}}
-
-{{- end -}}
-{{- if .Flash.WarningMsg -}}
-
-
{{.Flash.WarningMsg | SanitizeHTML}}
-
-{{- end -}}
+{{- if .Flash.ErrorMsg}}{{ctx.RenderUtils.RenderFlashMessage "error" .Flash.ErrorMsg}}{{end -}}
+{{- if .Flash.WarningMsg}}{{ctx.RenderUtils.RenderFlashMessage "warning" .Flash.WarningMsg}}{{end -}}
+{{- if .Flash.InfoMsg}}{{ctx.RenderUtils.RenderFlashMessage "info" .Flash.InfoMsg}}{{end -}}
+{{- if .Flash.SuccessMsg}}{{ctx.RenderUtils.RenderFlashMessage "success" .Flash.SuccessMsg}}{{end -}}
{{- if .ShowTwoFactorRequiredMessage -}}
-
-
{{ctx.Locale.Tr "auth.twofa_required"}}
+
{{- end -}}
diff --git a/templates/base/alert_details.tmpl b/templates/base/alert_details.tmpl
index 6380a72498..da8aba452a 100644
--- a/templates/base/alert_details.tmpl
+++ b/templates/base/alert_details.tmpl
@@ -2,10 +2,8 @@
{{if .Details}}
{{.Summary}}
- {{.Details | SanitizeHTML}}
+ {{.Details}}
{{else}}
-
- {{.Summary}}
-
+
{{.Summary}}
{{end}}
diff --git a/templates/devtest/devtest-header.tmpl b/templates/devtest/devtest-header.tmpl
index 628e4388a0..c9d7b3047f 100644
--- a/templates/devtest/devtest-header.tmpl
+++ b/templates/devtest/devtest-header.tmpl
@@ -1,4 +1,4 @@
{{template "base/head" ctx.RootData}}
-{{template "base/alert" .}}
+
{{template "base/alert" ctx.RootData}}
diff --git a/templates/devtest/fetch-action.tmpl b/templates/devtest/fetch-action.tmpl
index 4ee824f04b..cd4da52aac 100644
--- a/templates/devtest/fetch-action.tmpl
+++ b/templates/devtest/fetch-action.tmpl
@@ -1,6 +1,5 @@
{{template "devtest/devtest-header"}}
- {{template "base/alert" .}}
link-action
@@ -17,29 +16,20 @@
form-fetch-action
Use "window.fetch" to send a form request to backend
-
-
{{template "devtest/devtest-footer"}}
diff --git a/templates/devtest/toast.tmpl b/templates/devtest/toast-and-message.tmpl
similarity index 96%
rename from templates/devtest/toast.tmpl
rename to templates/devtest/toast-and-message.tmpl
index 597b415469..c4056b6fc6 100644
--- a/templates/devtest/toast.tmpl
+++ b/templates/devtest/toast-and-message.tmpl
@@ -1,5 +1,5 @@
{{template "devtest/devtest-header"}}
-
+
Toast
Show Info Toast
diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl
index 179f6018d3..8451a6f3cf 100644
--- a/templates/repo/commit_page.tmpl
+++ b/templates/repo/commit_page.tmpl
@@ -195,7 +195,7 @@
{{DateUtils.TimeSince .NoteCommit.Author.When}}
-
{{.NoteRendered | SanitizeHTML}}
+
{{.NoteRendered}}
{{end}}
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index 8c08f1c1f1..6a67ef754d 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -164,7 +164,7 @@
{{svg "octicon-git-commit"}}
{{/* the content is a link like
message title (from CreateRefComment) */}}
-
+
{{else if eq .Type 7}}
diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go
index 53709e6ff4..d0213962ee 100644
--- a/tests/integration/pull_merge_test.go
+++ b/tests/integration/pull_merge_test.go
@@ -317,9 +317,8 @@ func TestPullCleanUpAfterMerge(t *testing.T) {
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
- resultMsg := htmlDoc.doc.Find(".ui.message>p").Text()
-
- assert.Equal(t, "Branch \"user1/repo1:feature/test\" has been deleted.", resultMsg)
+ resultMsg := strings.TrimSpace(htmlDoc.doc.Find(".ui.message.flash-message").Text())
+ assert.Equal(t, `Branch "user1/repo1:feature/test" has been deleted.`, resultMsg)
})
}
diff --git a/tests/integration/signin_test.go b/tests/integration/signin_test.go
index ff35baae9d..4000c7ebe1 100644
--- a/tests/integration/signin_test.go
+++ b/tests/integration/signin_test.go
@@ -36,8 +36,7 @@ func testLoginFailed(t *testing.T, username, password, message string) {
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
- resultMsg := htmlDoc.doc.Find(".ui.message>p").Text()
-
+ resultMsg := strings.TrimSpace(htmlDoc.doc.Find(".ui.message.flash-message").Text())
assert.Equal(t, message, resultMsg)
}
diff --git a/tests/integration/user_settings_test.go b/tests/integration/user_settings_test.go
index 20c758dc85..b3527dd467 100644
--- a/tests/integration/user_settings_test.go
+++ b/tests/integration/user_settings_test.go
@@ -5,6 +5,7 @@ package integration
import (
"net/http"
+ "strings"
"testing"
"code.gitea.io/gitea/modules/container"
@@ -309,8 +310,7 @@ func TestUserSettingsApplications(t *testing.T) {
})
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
-
- msg := doc.Find(".flash-error p").Text()
+ msg := strings.TrimSpace(doc.Find(".ui.message.flash-message").Text())
assert.Equal(t, `form.RedirectURIs"ftp://127.0.0.1" is not a valid URL.`, msg)
})
diff --git a/web_src/css/base.css b/web_src/css/base.css
index c278132a00..4448af68e0 100644
--- a/web_src/css/base.css
+++ b/web_src/css/base.css
@@ -431,8 +431,9 @@ img.ui.avatar,
margin-top: calc(var(--page-spacing) - 1rem);
}
-.ui .message.flash-message {
- text-align: center;
+.ui.message.flash-message pre {
+ white-space: pre-line;
+ margin: 0;
}
.ui .header > i + .content {
@@ -865,6 +866,13 @@ table th[data-sortt-desc] .svg {
align-items: stretch;
}
+/* can be used to replace "ui relaxed list" or "tw-flex tw-flex-col tw-gap-xxx" when we need more flexible layout */
+.flex-relaxed-list {
+ display: flex;
+ flex-direction: column;
+ gap: var(--gap-block);
+}
+
.ui.list.flex-items-block > .item,
.ui.vertical.menu.flex-items-block > .item,
.ui.form .field > label.flex-text-block, /* override fomantic "block" style */
diff --git a/web_src/css/devtest.css b/web_src/css/devtest.css
index a7b00e1e56..c344d99058 100644
--- a/web_src/css/devtest.css
+++ b/web_src/css/devtest.css
@@ -1,3 +1,8 @@
+h1, h2 {
+ margin: 0;
+ padding: 10px 0;
+}
+
.button-sample-groups {
margin: 0; padding: 0;
}
@@ -10,7 +15,6 @@
margin-bottom: 5px;
}
-h1, h2 {
- margin: 0;
- padding: 10px 0;
+.fetch-action-demo-forms .form-fetch-action {
+ border: 1px red dashed; /* show the border for demo purpose */
}