package markup
import (
"strings"
"testing"
)
// Every renderer sends an external image through the proxy, hands the
// proxy the author's URL unescaped, and leaves relative images alone.
func TestExternalImagesGoThroughTheProxy(t *testing.T) {
var seen []string
SetImageProxy(func(src string) (string, int, int) {
seen = append(seen, src)
if strings.HasPrefix(src, "https://skip.example/") {
return "", 0, 0
}
if strings.Contains(src, "sized") {
return "/img/sig/" + src, 120, 20
}
return "/img/sig/" + src, 0, 0
})
t.Cleanup(func() { SetImageProxy(nil) })
md, err := Render([]byte("  "), nil)
if err != nil {
t.Fatal(err)
}
got := string(md)
if !strings.Contains(got, `src="/img/sig/https://ci.example/badge.svg?branch=main&x=1"`) {
t.Errorf("markdown image not proxied: %s", got)
}
if !strings.Contains(got, `src="img/shot.png"`) || !strings.Contains(got, `src="https://skip.example/a.png"`) {
t.Errorf("a relative or refused image was rewritten: %s", got)
}
if len(seen) == 0 || seen[0] != "https://ci.example/badge.svg?branch=main&x=1" {
t.Errorf("the proxy was handed %q, not the author's URL", seen)
}
wiki, err := RenderFossilWiki([]byte(`<img src="https://ci.example/w.png">`), nil)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(wiki), `src="/img/sig/https://ci.example/w.png"`) {
t.Errorf("fossil wiki image not proxied: %s", wiki)
}
if html := Sanitize(`<img src="http://ci.example/h.png">`); !strings.Contains(html, `src="/img/sig/http://ci.example/h.png"`) {
t.Errorf("html document image not proxied: %s", html)
}
}
// Off, nothing is rewritten.
func TestNoProxyLeavesImagesAlone(t *testing.T) {
SetImageProxy(nil)
out, err := Render([]byte(""), nil)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(out), `src="https://ci.example/badge.svg"`) {
t.Errorf("rewritten with no proxy: %s", out)
}
}
// What the proxy knows goes into the tag: the size it measured, so the
// page does not reflow, and lazy loading for every image after the first
// — the first is usually what the reader came to see.
func TestProxiedImagesCarrySizeAndLazyLoading(t *testing.T) {
SetImageProxy(func(src string) (string, int, int) {
if strings.Contains(src, "unsized") {
return "/img/sig/" + src, 0, 0
}
return "/img/sig/" + src, 120, 20
})
t.Cleanup(func() { SetImageProxy(nil) })
out, err := Render([]byte(
"\n\n\n\n"), nil)
if err != nil {
t.Fatal(err)
}
got := string(out)
imgs := strings.Split(got, "<img ")[1:]
if len(imgs) != 3 {
t.Fatalf("want 3 images, got %d: %s", len(imgs), got)
}
if !strings.Contains(imgs[0], `width="120" height="20"`) {
t.Errorf("the first image has no measured size: %s", imgs[0])
}
if strings.Contains(imgs[0], "loading=") || strings.Contains(imgs[0], "decoding=") {
t.Errorf("the first image was made lazy: %s", imgs[0])
}
for i, img := range imgs[1:] {
if !strings.Contains(img, `loading="lazy"`) || !strings.Contains(img, `decoding="async"`) {
t.Errorf("image %d is not lazy: %s", i+2, img)
}
}
if strings.Contains(imgs[2], "width=") {
t.Errorf("an unmeasured image was given a size: %s", imgs[2])
}
}
// An author who wrote their own width, height or loading keeps it.
func TestAuthorAttributesAreNotOverwritten(t *testing.T) {
SetImageProxy(func(src string) (string, int, int) { return "/img/sig/" + src, 120, 20 })
t.Cleanup(func() { SetImageProxy(nil) })
out := Sanitize(`<img src="https://ci.example/a.png" width="40" height="10"><img src="https://ci.example/b.png" loading="lazy">`)
imgs := strings.Split(out, "<img ")[1:]
if len(imgs) != 2 {
t.Fatalf("want 2 images, got %d: %s", len(imgs), out)
}
// Sized by its author: the measured size is not added on top.
if !strings.Contains(imgs[0], `width="40" height="10"`) || strings.Contains(imgs[0], `width="120"`) {
t.Errorf("the author's size was replaced: %s", imgs[0])
}
// Lazy by its author, and unsized: it keeps the one and is given the
// other, each exactly once.
if strings.Count(imgs[1], `loading="lazy"`) != 1 {
t.Errorf("loading was written twice: %s", imgs[1])
}
if !strings.Contains(imgs[1], `width="120" height="20"`) {
t.Errorf("an unsized image was not measured: %s", imgs[1])
}
}