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(`%s`, link, link) + _, _ = htmlutil.HTMLPrintf(r, `%s`, link, link) } else { imageSrc := r.resolveLink(org.String(l.Description...)) - printHTML(`%s`, link, imageSrc, imageSrc) + _, _ = htmlutil.HTMLPrintf(r, `%s`, 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