File opensuse-buildresults.patch of Package gitea
commit 9ae48b4769c076ef9aeb65b0f8bec066ce871c53
Author: Adam Majer <amajer@suse.com>
Date: Sun Aug 24 23:55:12 2025 +0200
object links to br.opensuse.org
diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go
index 5fdbf43f7c..1a9a2d8b01 100644
--- a/modules/markup/html_test.go
+++ b/modules/markup/html_test.go
@@ -181,6 +181,9 @@ func TestRender_links(t *testing.T) {
test(
`[link](javascript:xss)`,
`<p>link</p>`)
+ test(
+ ``,
+ `<p><object data="https://br.opensuse.org/status/openSUSE:Tumbleweed/nodejs22/standard" type="image/svg+xml" aria-label="status in TW"></object></p>`)
// Test that should *not* be turned into URL
test(
diff --git a/modules/markup/markdown/buildresult_object_renderer.go b/modules/markup/markdown/buildresult_object_renderer.go
new file mode 100644
index 0000000000..e51ae273ab
--- /dev/null
+++ b/modules/markup/markdown/buildresult_object_renderer.go
@@ -0,0 +1,65 @@
+package markdown
+
+import (
+ "bytes"
+ "net/url"
+
+ "github.com/yuin/goldmark/ast"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/util"
+)
+
+// buildresultsObjectRenderer renders images from br.opensuse.org as <object>.
+type buildresultsObjectRenderer struct {
+ defaultFuncs map[ast.NodeKind]renderer.NodeRendererFunc
+}
+
+// adapter so we can capture funcs into a map
+type funcRegisterer struct {
+ funcs map[ast.NodeKind]renderer.NodeRendererFunc
+}
+
+func (r *funcRegisterer) Register(kind ast.NodeKind, v renderer.NodeRendererFunc) {
+ r.funcs[kind] = v
+}
+
+func newObjectImageRenderer() *buildresultsObjectRenderer {
+ tmp := html.NewRenderer()
+ reg := &funcRegisterer{funcs: make(map[ast.NodeKind]renderer.NodeRendererFunc)}
+ tmp.RegisterFuncs(reg)
+ return &buildresultsObjectRenderer{defaultFuncs: reg.funcs}
+}
+
+func (r *buildresultsObjectRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(ast.KindImage, r.renderImage)
+}
+
+func (r *buildresultsObjectRenderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+ img := node.(*ast.Image)
+ if u, err := url.Parse(string(img.Destination)); err == nil && (u.Host == "br.opensuse.org" || u.Host == "buildresults.opensuse.org") {
+ // collect alt text
+ var alt bytes.Buffer
+ for c := img.FirstChild(); c != nil; c = c.NextSibling() {
+ if t, ok := c.(*ast.Text); ok {
+ alt.Write(t.Segment.Value(source))
+ }
+ }
+ // <object data="..."> (let sanitizer control which attrs are kept)
+ w.WriteString(`<object data="`)
+ w.Write(util.EscapeHTML([]byte(u.String())))
+ w.WriteString(`" type="image/svg+xml" aria-label="`)
+ w.Write(util.EscapeHTML(alt.Bytes()))
+ w.WriteString(`"></object>`)
+ return ast.WalkSkipChildren, nil
+ }
+
+ // Delegate to stock html image renderer for all other cases
+ if f := r.defaultFuncs[ast.KindImage]; f != nil {
+ return f(w, source, node, entering)
+ }
+ return ast.WalkContinue, nil
+}
diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
index 79df547c2c..e1463193d5 100644
--- a/modules/markup/markdown/markdown.go
+++ b/modules/markup/markdown/markdown.go
@@ -134,7 +134,10 @@ func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender {
parser.WithAutoHeadingID(),
parser.WithASTTransformers(util.Prioritized(NewASTTransformer(&ctx.RenderInternal), 10000)),
),
- goldmark.WithRendererOptions(html.WithUnsafe()),
+ goldmark.WithRendererOptions(
+ html.WithUnsafe(),
+ renderer.WithNodeRenderers(util.Prioritized(newObjectImageRenderer(), 500)),
+ ),
)
// Override the original Tasklist renderer!
diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go
index 391ddad46c..90b833ee1b 100644
--- a/modules/markup/sanitizer.go
+++ b/modules/markup/sanitizer.go
@@ -42,6 +42,14 @@ func GetDefaultSanitizer() *Sanitizer {
defaultSanitizer.defaultPolicy = defaultSanitizer.createDefaultPolicy()
defaultSanitizer.descriptionPolicy = defaultSanitizer.createRepoDescriptionPolicy()
})
+
+ // Allow <object> for br.opensuse.org image replacements.
+ p := defaultSanitizer.defaultPolicy
+ p.AllowElements("object")
+ p.AllowAttrs("data").Matching(regexp.MustCompile(`^https://(br|buildresults)\.opensuse\.org/.*$`)).OnElements("object")
+ p.AllowAttrs("type").Matching(regexp.MustCompile(`^image/svg\+xml$`)).OnElements("object")
+ p.AllowAttrs("aria-label").OnElements("object")
+
return defaultSanitizer
}