diff --git a/modules/htmlutil/html.go b/modules/htmlutil/html.go
index 8dbfe0c22e..5db1d6404a 100644
--- a/modules/htmlutil/html.go
+++ b/modules/htmlutil/html.go
@@ -83,3 +83,34 @@ func HTMLPrintTag(w io.Writer, tag template.HTML, attrs map[string]string) (writ
written += n
return written, err
}
+
+func EscapeString(s string) template.HTML {
+ return template.HTML(template.HTMLEscapeString(s))
+}
+
+type HTMLBuilder struct {
+ sb strings.Builder
+}
+
+func (b *HTMLBuilder) WriteString(s string) *HTMLBuilder {
+ b.sb.WriteString(template.HTMLEscapeString(s))
+ return b
+}
+
+func (b *HTMLBuilder) WriteHTML(s template.HTML) *HTMLBuilder {
+ b.sb.WriteString(string(s))
+ return b
+}
+
+func (b *HTMLBuilder) WriteFormat(fmt template.HTML, args ...any) *HTMLBuilder {
+ _, _ = HTMLPrintf(&b.sb, fmt, args...)
+ return b
+}
+
+func (b *HTMLBuilder) HTMLString() template.HTML {
+ return template.HTML(b.sb.String())
+}
+
+func (b *HTMLBuilder) String() string {
+ return b.sb.String()
+}
diff --git a/modules/htmlutil/html_test.go b/modules/htmlutil/html_test.go
index 22258ce59d..a1ab0a6a49 100644
--- a/modules/htmlutil/html_test.go
+++ b/modules/htmlutil/html_test.go
@@ -22,3 +22,10 @@ func TestHTMLFormat(t *testing.T) {
assert.Equal(t, template.HTML("<>"), HTMLFormat("%s", template.URL("<>")))
assert.Equal(t, template.HTML("&StringMethod &StringMethod"), HTMLFormat("%s %s", testStringer{}, &testStringer{}))
}
+
+func TestHTMLBuilder(t *testing.T) {
+ b := &HTMLBuilder{}
+ b.WriteString("<").WriteHTML("
").WriteFormat("%s%s", ">", EscapeString(">"))
+ assert.Equal(t, "<
>>", b.String())
+ assert.Equal(t, template.HTML("<
>>"), b.HTMLString())
+}
diff --git a/modules/markup/internal/renderinternal.go b/modules/markup/internal/renderinternal.go
index 9fd9a1c0e8..370e642454 100644
--- a/modules/markup/internal/renderinternal.go
+++ b/modules/markup/internal/renderinternal.go
@@ -77,6 +77,7 @@ func (r *RenderInternal) ProtectSafeAttrs(content template.HTML) template.HTML {
}
func (r *RenderInternal) FormatWithSafeAttrs(w io.Writer, fmt template.HTML, a ...any) error {
- _, err := w.Write([]byte(r.ProtectSafeAttrs(htmlutil.HTMLFormat(fmt, a...))))
+ htmlStr := r.ProtectSafeAttrs(htmlutil.HTMLFormat(fmt, a...))
+ _, err := io.WriteString(w, string(htmlStr))
return err
}
diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go
index fd3071645a..ea331672bf 100644
--- a/modules/markup/orgmode/orgmode.go
+++ b/modules/markup/orgmode/orgmode.go
@@ -106,31 +106,27 @@ func (r *orgWriter) resolveLink(link string) string {
// WriteRegularLink renders images, links or videos
func (r *orgWriter) WriteRegularLink(l org.RegularLink) {
link := r.resolveLink(l.URL)
-
- printHTML := func(html template.HTML, a ...any) {
- _, _ = fmt.Fprint(r, htmlutil.HTMLFormat(html, a...))
- }
// Inspired by https://github.com/niklasfasching/go-org/blob/6eb20dbda93cb88c3503f7508dc78cbbc639378f/org/html_writer.go#L406-L427
switch l.Kind() {
case "image":
if l.Description == nil {
- printHTML(`
`, link, link)
+ _, _ = htmlutil.HTMLPrintf(r, `
`, link, link)
} else {
imageSrc := r.resolveLink(org.String(l.Description...))
- printHTML(`
`, link, imageSrc, imageSrc)
+ _, _ = htmlutil.HTMLPrintf(r, `
`, link, imageSrc, imageSrc)
}
case "video":
if l.Description == nil {
- printHTML(``, link, link)
+ _, _ = htmlutil.HTMLPrintf(r, ``, link, link)
} else {
videoSrc := r.resolveLink(org.String(l.Description...))
- printHTML(``, link, videoSrc, videoSrc)
+ _, _ = htmlutil.HTMLPrintf(r, ``, link, videoSrc, videoSrc)
}
default:
var description any = link
if l.Description != nil {
description = template.HTML(r.WriteNodesAsString(l.Description...)) // orgmode HTMLWriter outputs HTML content
}
- printHTML(`%s`, link, description)
+ _, _ = htmlutil.HTMLPrintf(r, `%s`, link, description)
}
}
diff --git a/modules/web/router.go b/modules/web/router.go
index f4575399b9..30f25deb4e 100644
--- a/modules/web/router.go
+++ b/modules/web/router.go
@@ -260,7 +260,7 @@ func (r *Router) normalizeRequestPath(resp http.ResponseWriter, req *http.Reques
// do not respond to other requests, to simulate a real sub-path environment
resp.Header().Add("Content-Type", "text/html; charset=utf-8")
resp.WriteHeader(http.StatusNotFound)
- _, _ = resp.Write([]byte(htmlutil.HTMLFormat(`404 page not found, sub-path is: %s`, setting.AppSubURL, setting.AppSubURL)))
+ _, _ = htmlutil.HTMLPrintf(resp, `404 page not found, sub-path is: %s`, setting.AppSubURL, setting.AppSubURL)
return
}
normalized = true
diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go
index cbdc65b6c0..036ddce6b1 100644
--- a/routers/web/repo/view_home.go
+++ b/routers/web/repo/view_home.go
@@ -276,7 +276,7 @@ func handleRepoViewSubmodule(ctx *context.Context, commitSubmoduleFile *git.Comm
redirectLink := submoduleWebLink.CommitWebLink
if isViewHomeOnlyContent(ctx) {
ctx.Resp.Header().Set("Content-Type", "text/html; charset=utf-8")
- _, _ = ctx.Resp.Write([]byte(htmlutil.HTMLFormat(`%s`, redirectLink, redirectLink)))
+ _, _ = htmlutil.HTMLPrintf(ctx.Resp, `%s`, redirectLink, redirectLink)
} else if !httplib.IsCurrentGiteaSiteURL(ctx, redirectLink) {
// don't auto-redirect to external URL, to avoid open redirect or phishing
ctx.Data["NotFoundPrompt"] = redirectLink