File br.objects.patch of Package gitea

commit f7e56fad9c18bd8dccda0ce464fa43989b01a536
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(
+		`![status in TW](https://br.opensuse.org/status/openSUSE:Tumbleweed/nodejs22/standard)`,
+		`<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..ead0716f1a
--- /dev/null
+++ b/modules/markup/markdown/buildresult_object_renderer.go
@@ -0,0 +1,43 @@
+package markdown
+
+import (
+	"bytes"
+	"net/url"
+
+	"github.com/yuin/goldmark/ast"
+	"github.com/yuin/goldmark/renderer"
+	"github.com/yuin/goldmark/util"
+)
+
+// buildresultsObjectRenderer renders images from br.opensuse.org as <object>.
+type buildresultsObjectRenderer struct{}
+
+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
+	}
+
+	// default renderer for other images
+	return ast.WalkContinue, nil
+}
diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
index 79df547c2c..b03f6de448 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(&buildresultsObjectRenderer{}, 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
 }
 
openSUSE Build Service is sponsored by