chore: introduce HTMLBuilder (#37688)

This commit is contained in:
wxiaoguang
2026-05-14 01:06:53 +08:00
committed by GitHub
parent 701908a945
commit 523822090c
6 changed files with 47 additions and 12 deletions

View File

@@ -83,3 +83,34 @@ func HTMLPrintTag(w io.Writer, tag template.HTML, attrs map[string]string) (writ
written += n written += n
return written, err 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()
}

View File

@@ -22,3 +22,10 @@ func TestHTMLFormat(t *testing.T) {
assert.Equal(t, template.HTML("&lt;&gt;"), HTMLFormat("%s", template.URL("<>"))) assert.Equal(t, template.HTML("&lt;&gt;"), HTMLFormat("%s", template.URL("<>")))
assert.Equal(t, template.HTML("&amp;StringMethod &amp;StringMethod"), HTMLFormat("%s %s", testStringer{}, &testStringer{})) assert.Equal(t, template.HTML("&amp;StringMethod &amp;StringMethod"), HTMLFormat("%s %s", testStringer{}, &testStringer{}))
} }
func TestHTMLBuilder(t *testing.T) {
b := &HTMLBuilder{}
b.WriteString("<").WriteHTML("<hr>").WriteFormat("<span>%s%s</span>", ">", EscapeString(">"))
assert.Equal(t, "&lt;<hr><span>&gt;&gt;</span>", b.String())
assert.Equal(t, template.HTML("&lt;<hr><span>&gt;&gt;</span>"), b.HTMLString())
}

View File

@@ -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 { 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 return err
} }

View File

@@ -106,31 +106,27 @@ func (r *orgWriter) resolveLink(link string) string {
// WriteRegularLink renders images, links or videos // WriteRegularLink renders images, links or videos
func (r *orgWriter) WriteRegularLink(l org.RegularLink) { func (r *orgWriter) WriteRegularLink(l org.RegularLink) {
link := r.resolveLink(l.URL) 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 // Inspired by https://github.com/niklasfasching/go-org/blob/6eb20dbda93cb88c3503f7508dc78cbbc639378f/org/html_writer.go#L406-L427
switch l.Kind() { switch l.Kind() {
case "image": case "image":
if l.Description == nil { if l.Description == nil {
printHTML(`<img src="%s" alt="%s">`, link, link) _, _ = htmlutil.HTMLPrintf(r, `<img src="%s" alt="%s">`, link, link)
} else { } else {
imageSrc := r.resolveLink(org.String(l.Description...)) imageSrc := r.resolveLink(org.String(l.Description...))
printHTML(`<a href="%s"><img src="%s" alt="%s"></a>`, link, imageSrc, imageSrc) _, _ = htmlutil.HTMLPrintf(r, `<a href="%s"><img src="%s" alt="%s"></a>`, link, imageSrc, imageSrc)
} }
case "video": case "video":
if l.Description == nil { if l.Description == nil {
printHTML(`<video src="%s">%s</video>`, link, link) _, _ = htmlutil.HTMLPrintf(r, `<video src="%s">%s</video>`, link, link)
} else { } else {
videoSrc := r.resolveLink(org.String(l.Description...)) videoSrc := r.resolveLink(org.String(l.Description...))
printHTML(`<a href="%s"><video src="%s">%s</video></a>`, link, videoSrc, videoSrc) _, _ = htmlutil.HTMLPrintf(r, `<a href="%s"><video src="%s">%s</video></a>`, link, videoSrc, videoSrc)
} }
default: default:
var description any = link var description any = link
if l.Description != nil { if l.Description != nil {
description = template.HTML(r.WriteNodesAsString(l.Description...)) // orgmode HTMLWriter outputs HTML content description = template.HTML(r.WriteNodesAsString(l.Description...)) // orgmode HTMLWriter outputs HTML content
} }
printHTML(`<a href="%s">%s</a>`, link, description) _, _ = htmlutil.HTMLPrintf(r, `<a href="%s">%s</a>`, link, description)
} }
} }

View File

@@ -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 // do not respond to other requests, to simulate a real sub-path environment
resp.Header().Add("Content-Type", "text/html; charset=utf-8") resp.Header().Add("Content-Type", "text/html; charset=utf-8")
resp.WriteHeader(http.StatusNotFound) resp.WriteHeader(http.StatusNotFound)
_, _ = resp.Write([]byte(htmlutil.HTMLFormat(`404 page not found, sub-path is: <a href="%s">%s</a>`, setting.AppSubURL, setting.AppSubURL))) _, _ = htmlutil.HTMLPrintf(resp, `404 page not found, sub-path is: <a href="%s">%s</a>`, setting.AppSubURL, setting.AppSubURL)
return return
} }
normalized = true normalized = true

View File

@@ -276,7 +276,7 @@ func handleRepoViewSubmodule(ctx *context.Context, commitSubmoduleFile *git.Comm
redirectLink := submoduleWebLink.CommitWebLink redirectLink := submoduleWebLink.CommitWebLink
if isViewHomeOnlyContent(ctx) { if isViewHomeOnlyContent(ctx) {
ctx.Resp.Header().Set("Content-Type", "text/html; charset=utf-8") ctx.Resp.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = ctx.Resp.Write([]byte(htmlutil.HTMLFormat(`<a href="%s">%s</a>`, redirectLink, redirectLink))) _, _ = htmlutil.HTMLPrintf(ctx.Resp, `<a href="%s">%s</a>`, redirectLink, redirectLink)
} else if !httplib.IsCurrentGiteaSiteURL(ctx, redirectLink) { } else if !httplib.IsCurrentGiteaSiteURL(ctx, redirectLink) {
// don't auto-redirect to external URL, to avoid open redirect or phishing // don't auto-redirect to external URL, to avoid open redirect or phishing
ctx.Data["NotFoundPrompt"] = redirectLink ctx.Data["NotFoundPrompt"] = redirectLink