diff --git a/docs/content/administration/mail-templates.en-us.md b/docs/content/administration/mail-templates.en-us.md
index 05c41a6a02f0..b642ff4aa7f6 100644
--- a/docs/content/administration/mail-templates.en-us.md
+++ b/docs/content/administration/mail-templates.en-us.md
@@ -266,7 +266,7 @@ the messages. Here's a list of some of them:
| `AppDomain` | - | Any | Gitea's host name |
| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed |
| `Str2html` | string | Body only | Sanitizes text by removing any HTML tags from it. |
-| `Safe` | string | Body only | Takes the input as HTML; can be used for `.ReviewComments.RenderedContent`. |
+| `SafeHTML` | string | Body only | Takes the input as HTML; can be used for `.ReviewComments.RenderedContent`. |
These are _functions_, not metadata, so they have to be used:
diff --git a/docs/content/administration/mail-templates.zh-cn.md b/docs/content/administration/mail-templates.zh-cn.md
index 4846f6f398b2..fd455ef3a860 100644
--- a/docs/content/administration/mail-templates.zh-cn.md
+++ b/docs/content/administration/mail-templates.zh-cn.md
@@ -242,14 +242,14 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
模板系统包含一些函数,可用于进一步处理和格式化消息。以下是其中一些函数的列表:
-| 函数名 | 参数 | 可用于 | 用法 |
-| ----------------- | ----------- | ------------ | --------------------------------------------------------------------------------- |
-| `AppUrl` | - | 任何地方 | Gitea 的 URL |
-| `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" |
-| `AppDomain` | - | 任何地方 | Gitea 的主机名 |
-| `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 |
-| `Str2html` | string | 仅正文部分 | 通过删除其中的 HTML 标签对文本进行清理 |
-| `Safe` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于 `.ReviewComments.RenderedContent` 等字段 |
+| 函数名 | 参数 | 可用于 | 用法 |
+|------------------| ----------- | ------------ | --------------------------------------------------------------------------------- |
+| `AppUrl` | - | 任何地方 | Gitea 的 URL |
+| `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" |
+| `AppDomain` | - | 任何地方 | Gitea 的主机名 |
+| `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 |
+| `Str2html` | string | 仅正文部分 | 通过删除其中的 HTML 标签对文本进行清理 |
+| `SafeHTML` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于 `.ReviewComments.RenderedContent` 等字段 |
这些都是 _函数_,而不是元数据,因此必须按以下方式使用:
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 691f7547488d..567948749811 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -9,6 +9,7 @@ import (
"html"
"html/template"
"net/url"
+ "slices"
"strings"
"time"
@@ -34,7 +35,8 @@ func NewFuncMap() 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.
"Eval": Eval,
- "Safe": Safe,
+ "SafeHTML": SafeHTML,
+ "HTMLFormat": HTMLFormat,
"Escape": Escape,
"QueryEscape": url.QueryEscape,
"JSEscape": JSEscapeSafe,
@@ -177,8 +179,25 @@ func NewFuncMap() template.FuncMap {
}
}
-// Safe render raw as HTML
-func Safe(s any) template.HTML {
+func HTMLFormat(s string, rawArgs ...any) template.HTML {
+ args := slices.Clone(rawArgs)
+ for i, v := range args {
+ switch v := v.(type) {
+ case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
+ // for most basic types (including template.HTML which is safe), just do nothing and use it
+ case string:
+ args[i] = template.HTMLEscapeString(v)
+ case fmt.Stringer:
+ args[i] = template.HTMLEscapeString(v.String())
+ default:
+ args[i] = template.HTMLEscapeString(fmt.Sprint(v))
+ }
+ }
+ return template.HTML(fmt.Sprintf(s, args...))
+}
+
+// SafeHTML render raw as HTML
+func SafeHTML(s any) template.HTML {
switch v := s.(type) {
case string:
return template.HTML(v)
diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go
index 739a92f34f93..8f5d633d4f80 100644
--- a/modules/templates/helper_test.go
+++ b/modules/templates/helper_test.go
@@ -4,6 +4,7 @@
package templates
import (
+ "html/template"
"testing"
"github.com/stretchr/testify/assert"
@@ -56,3 +57,7 @@ func TestSubjectBodySeparator(t *testing.T) {
func TestJSEscapeSafe(t *testing.T) {
assert.EqualValues(t, `\u0026\u003C\u003E\'\"`, JSEscapeSafe(`&<>'"`))
}
+
+func TestHTMLFormat(t *testing.T) {
+ assert.Equal(t, template.HTML("< < 1 "), HTMLFormat("%s %s %d ", "<", template.HTML("<"), 1))
+}
diff --git a/templates/admin/packages/list.tmpl b/templates/admin/packages/list.tmpl
index 04f76748d081..cf860dab2ab1 100644
--- a/templates/admin/packages/list.tmpl
+++ b/templates/admin/packages/list.tmpl
@@ -88,7 +88,7 @@
{{ctx.Locale.Tr "packages.settings.delete"}}
- {{ctx.Locale.Tr "packages.settings.delete.notice" (` `|Safe) (` `|Safe)}}
+ {{ctx.Locale.Tr "packages.settings.delete.notice" (` `|SafeHTML) (` `|SafeHTML)}}
{{template "base/modal_actions_confirm" .}}
diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl
index c7a6ec7e4e9f..e11247aed499 100644
--- a/templates/admin/repo/list.tmpl
+++ b/templates/admin/repo/list.tmpl
@@ -101,7 +101,7 @@
{{ctx.Locale.Tr "repo.settings.delete_desc"}}
- {{ctx.Locale.Tr "repo.settings.delete_notices_2" (`
`|Safe)}}
+ {{ctx.Locale.Tr "repo.settings.delete_notices_2" (`
`|SafeHTML)}}
{{ctx.Locale.Tr "repo.settings.delete_notices_fork_1"}}
{{template "base/modal_actions_confirm" .}}
diff --git a/templates/admin/stacktrace.tmpl b/templates/admin/stacktrace.tmpl
index aa5e810cd780..42944615c38b 100644
--- a/templates/admin/stacktrace.tmpl
+++ b/templates/admin/stacktrace.tmpl
@@ -39,7 +39,7 @@
{{ctx.Locale.Tr "admin.monitor.process.cancel"}}
-
{{ctx.Locale.Tr "admin.monitor.process.cancel_notices" (` `|Safe)}}
+
{{ctx.Locale.Tr "admin.monitor.process.cancel_notices" (` `|SafeHTML)}}
{{ctx.Locale.Tr "admin.monitor.process.cancel_desc"}}
{{template "base/modal_actions_confirm" .}}
diff --git a/templates/mail/issue/assigned.tmpl b/templates/mail/issue/assigned.tmpl
index e80bd2fc31c1..5720319ee845 100644
--- a/templates/mail/issue/assigned.tmpl
+++ b/templates/mail/issue/assigned.tmpl
@@ -8,14 +8,14 @@
{{.Subject}}
-{{$repo_url := printf "%s " (Escape .Issue.Repo.HTMLURL) (Escape .Issue.Repo.FullName)}}
-{{$link := printf "#%d " (Escape .Link) .Issue.Index}}
+{{$repo_url := HTMLFormat "%s " .Issue.Repo.HTMLURL .Issue.Repo.FullName}}
+{{$link := HTMLFormat "#%d " .Link .Issue.Index}}
{{if .IsPull}}
- {{.locale.Tr "mail.issue_assigned.pull" .Doer.Name ($link|Safe) ($repo_url|Safe)}}
+ {{.locale.Tr "mail.issue_assigned.pull" .Doer.Name $link $repo_url}}
{{else}}
- {{.locale.Tr "mail.issue_assigned.issue" .Doer.Name ($link|Safe) ($repo_url|Safe)}}
+ {{.locale.Tr "mail.issue_assigned.issue" .Doer.Name $link $repo_url}}
{{end}}
-
{{ctx.Locale.Tr "org.members.leave.detail" (` `|Safe)}}
+
{{ctx.Locale.Tr "org.members.leave.detail" (` `|SafeHTML)}}
{{template "base/modal_actions_confirm" .}}
@@ -82,7 +82,7 @@
{{ctx.Locale.Tr "org.members.remove"}}
-
{{ctx.Locale.Tr "org.members.remove.detail" (` `|Safe) (` `|Safe)}}
+
{{ctx.Locale.Tr "org.members.remove.detail" (` `|SafeHTML) (` `|SafeHTML)}}
{{template "base/modal_actions_confirm" .}}
diff --git a/templates/org/team/members.tmpl b/templates/org/team/members.tmpl
index dd4ece14335a..adaf83ae155e 100644
--- a/templates/org/team/members.tmpl
+++ b/templates/org/team/members.tmpl
@@ -81,7 +81,7 @@
{{ctx.Locale.Tr "org.members.remove"}}
-
{{ctx.Locale.Tr "org.members.remove.detail" (` `|Safe) (` `|Safe)}}
+
{{ctx.Locale.Tr "org.members.remove.detail" (` `|SafeHTML) (` `|SafeHTML)}}
{{template "base/modal_actions_confirm" .}}
diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl
index 440fa11dc950..9311a46e38f7 100644
--- a/templates/org/team/sidebar.tmpl
+++ b/templates/org/team/sidebar.tmpl
@@ -88,7 +88,7 @@
{{ctx.Locale.Tr "org.teams.leave"}}
-
{{ctx.Locale.Tr "org.teams.leave.detail" (` `|Safe)}}
+
{{ctx.Locale.Tr "org.teams.leave.detail" (` `|SafeHTML)}}
{{template "base/modal_actions_confirm" .}}
diff --git a/templates/org/team/teams.tmpl b/templates/org/team/teams.tmpl
index b518d7d9d738..53c909ee9c81 100644
--- a/templates/org/team/teams.tmpl
+++ b/templates/org/team/teams.tmpl
@@ -49,7 +49,7 @@
{{ctx.Locale.Tr "org.teams.leave"}}
-
{{ctx.Locale.Tr "org.teams.leave.detail" (` `|Safe)}}
+
{{ctx.Locale.Tr "org.teams.leave.detail" (` `|SafeHTML)}}
{{template "base/modal_actions_confirm" .}}
diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl
index fbfaa19411bf..115ee9295510 100644
--- a/templates/repo/commit_page.tmpl
+++ b/templates/repo/commit_page.tmpl
@@ -88,7 +88,7 @@
{{.CsrfTokenHtml}}
- {{ctx.Locale.Tr "repo.branch.new_branch_from" (` `|Safe)}}
+ {{ctx.Locale.Tr "repo.branch.new_branch_from" (` `|SafeHTML)}}
@@ -113,7 +113,7 @@
- {{ctx.Locale.Tr "repo.tag.create_tag_from" (` `|Safe)}}
+ {{ctx.Locale.Tr "repo.tag.create_tag_from" (` `|SafeHTML)}}
diff --git a/templates/repo/editor/cherry_pick.tmpl b/templates/repo/editor/cherry_pick.tmpl
index b65c3a30337f..f9c9eef5aab6 100644
--- a/templates/repo/editor/cherry_pick.tmpl
+++ b/templates/repo/editor/cherry_pick.tmpl
@@ -11,11 +11,11 @@
diff --git a/templates/repo/issue/view_content/pull.tmpl b/templates/repo/issue/view_content/pull.tmpl
index 13d49b61b76c..371c9db6f052 100644
--- a/templates/repo/issue/view_content/pull.tmpl
+++ b/templates/repo/issue/view_content/pull.tmpl
@@ -39,7 +39,7 @@
{{ctx.Locale.Tr "repo.pulls.merged_success"}}
- {{ctx.Locale.Tr "repo.pulls.merged_info_text" (printf "%s
" (.HeadTarget | Escape) | Safe)}}
+ {{ctx.Locale.Tr "repo.pulls.merged_info_text" (HTMLFormat "%s
" .HeadTarget)}}
diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl
index 9b4657b63475..37cad26c9b39 100644
--- a/templates/repo/issue/view_title.tmpl
+++ b/templates/repo/issue/view_title.tmpl
@@ -43,31 +43,31 @@
{{end}}
{{if .Issue.IsPull}}
- {{$headHref := .HeadTarget|Escape}}
+ {{$headHref := .HeadTarget}}
{{if .HeadBranchLink}}
- {{$headHref = printf `
%s ` (.HeadBranchLink | Escape) $headHref}}
+ {{$headHref = HTMLFormat `
%s ` .HeadBranchLink $headHref}}
{{end}}
- {{$headHref = printf `%s
%s ` $headHref (ctx.Locale.Tr "copy_branch") (.HeadTarget | Escape) (svg "octicon-copy" 14)}}
- {{$baseHref := .BaseTarget|Escape}}
+ {{$headHref = HTMLFormat `%s
%s ` $headHref (ctx.Locale.Tr "copy_branch") .HeadTarget (svg "octicon-copy" 14)}}
+ {{$baseHref := .BaseTarget}}
{{if .BaseBranchLink}}
- {{$baseHref = printf `
%s ` (.BaseBranchLink | Escape) $baseHref}}
+ {{$baseHref = HTMLFormat `
%s ` .BaseBranchLink $baseHref}}
{{end}}
{{if .Issue.PullRequest.HasMerged}}
{{$mergedStr:= TimeSinceUnix .Issue.PullRequest.MergedUnix ctx.Locale}}
{{if .Issue.OriginalAuthor}}
{{.Issue.OriginalAuthor}}
-
{{ctx.Locale.Tr "repo.pulls.merged_title_desc" .NumCommits ($headHref|Safe) ($baseHref|Safe) $mergedStr}}
+
{{ctx.Locale.Tr "repo.pulls.merged_title_desc" .NumCommits $headHref $baseHref $mergedStr}}
{{else}}
{{.Issue.PullRequest.Merger.GetDisplayName}}
-
{{ctx.Locale.Tr "repo.pulls.merged_title_desc" .NumCommits ($headHref|Safe) ($baseHref|Safe) $mergedStr}}
+
{{ctx.Locale.Tr "repo.pulls.merged_title_desc" .NumCommits $headHref $baseHref $mergedStr}}
{{end}}
{{else}}
{{if .Issue.OriginalAuthor}}
-
{{.Issue.OriginalAuthor}} {{ctx.Locale.Tr "repo.pulls.title_desc" .NumCommits ($headHref|Safe) ($baseHref|Safe)}}
+
{{.Issue.OriginalAuthor}} {{ctx.Locale.Tr "repo.pulls.title_desc" .NumCommits $headHref $baseHref}}
{{else}}
{{.Issue.Poster.GetDisplayName}}
- {{ctx.Locale.Tr "repo.pulls.title_desc" .NumCommits ($headHref|Safe) ($baseHref|Safe)}}
+ {{ctx.Locale.Tr "repo.pulls.title_desc" .NumCommits $headHref $baseHref}}
{{end}}
diff --git a/templates/repo/migrate/migrate.tmpl b/templates/repo/migrate/migrate.tmpl
index c686f0b832b1..d1abb153748a 100644
--- a/templates/repo/migrate/migrate.tmpl
+++ b/templates/repo/migrate/migrate.tmpl
@@ -20,7 +20,7 @@
{{.Title}}
- {{(printf "repo.migrate.%s.description" .Name) | ctx.Locale.Tr}}
+ {{ctx.Locale.Tr (printf "repo.migrate.%s.description" .Name)}}
diff --git a/templates/repo/settings/lfs_file.tmpl b/templates/repo/settings/lfs_file.tmpl
index 0aeb2af178b2..7f1d07e46fc3 100644
--- a/templates/repo/settings/lfs_file.tmpl
+++ b/templates/repo/settings/lfs_file.tmpl
@@ -15,9 +15,9 @@
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}}
{{if .IsMarkup}}
- {{if .FileContent}}{{.FileContent | Safe}}{{end}}
+ {{if .FileContent}}{{.FileContent | SafeHTML}}{{end}}
{{else if .IsPlainText}}
-
{{if .FileContent}}{{.FileContent | Safe}}{{end}}
+
{{if .FileContent}}{{.FileContent | SafeHTML}}{{end}}
{{else if not .IsTextFile}}
{{if .IsImageFile}}
diff --git a/templates/repo/settings/webhook/settings.tmpl b/templates/repo/settings/webhook/settings.tmpl
index f636108b379c..3ef8894444a4 100644
--- a/templates/repo/settings/webhook/settings.tmpl
+++ b/templates/repo/settings/webhook/settings.tmpl
@@ -263,7 +263,7 @@
{{ctx.Locale.Tr "repo.settings.authorization_header"}}
{{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}}
- {{ctx.Locale.Tr "repo.settings.authorization_header_desc" ("Bearer token123456
, Basic YWxhZGRpbjpvcGVuc2VzYW1l
" | Safe)}}
+ {{ctx.Locale.Tr "repo.settings.authorization_header_desc" ("Bearer token123456
, Basic YWxhZGRpbjpvcGVuc2VzYW1l
" | SafeHTML)}}
{{end}}
diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl
index 5b296dc2af4e..f3b6be97cf2f 100644
--- a/templates/repo/wiki/view.tmpl
+++ b/templates/repo/wiki/view.tmpl
@@ -67,13 +67,13 @@
{{if .sidebarTocContent}}
{{end}}
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}}
- {{.content | Safe}}
+ {{.content | SafeHTML}}
{{if .sidebarPresent}}
@@ -82,7 +82,7 @@
{{svg "octicon-pencil"}}
{{end}}
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .sidebarEscapeStatus "root" $}}
- {{.sidebarContent | Safe}}
+ {{.sidebarContent | SafeHTML}}
{{end}}
@@ -94,7 +94,7 @@
{{svg "octicon-pencil"}}
{{end}}
{{template "repo/unicode_escape_prompt" dict "footerEscapeStatus" .sidebarEscapeStatus "root" $}}
- {{.footerContent | Safe}}
+ {{.footerContent | SafeHTML}}
{{end}}
diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl
index 8cf76d80a577..7ce9a4b70fb2 100644
--- a/templates/user/settings/applications.tmpl
+++ b/templates/user/settings/applications.tmpl
@@ -75,7 +75,7 @@
{{ctx.Locale.Tr "settings.select_permissions"}}
- {{ctx.Locale.Tr "settings.access_token_desc" (printf `href="/api/swagger" target="_blank"`) (printf `href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`)}}
+ {{ctx.Locale.Tr "settings.access_token_desc" (`href="/api/swagger" target="_blank"`|SafeHTML) (`href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`|SafeHTML)}}
-
{{ctx.Locale.Tr "org.members.leave.detail" (` `|Safe)}}
+
{{ctx.Locale.Tr "org.members.leave.detail" (` `|SafeHTML)}}
{{template "base/modal_actions_confirm" .}}