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(
+ ``,
+ `<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
}