fix: stop marking JSON attributes as trusted HTML
This commit is contained in:
@@ -3,7 +3,6 @@ package templates
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"mal/internal/domain"
|
"mal/internal/domain"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@@ -34,12 +33,12 @@ func dict(values ...any) (map[string]any, error) {
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonAttr(v any) (template.HTMLAttr, error) {
|
func jsonAttr(v any) (string, error) {
|
||||||
b, err := json.Marshal(v)
|
b, err := json.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("json marshal: %w", err)
|
return "", fmt.Errorf("json marshal: %w", err)
|
||||||
}
|
}
|
||||||
return template.HTMLAttr(b), nil
|
return string(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func genresParams(genres []int) string {
|
func genresParams(genres []int) string {
|
||||||
|
|||||||
@@ -47,7 +47,13 @@ func TestJSONAttr(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("jsonAttr error: %v", err)
|
t.Fatalf("jsonAttr error: %v", err)
|
||||||
}
|
}
|
||||||
want := template.HTMLAttr(`{"a":1}`)
|
if _, ok := any(got).(template.HTMLAttr); ok {
|
||||||
|
t.Fatal("jsonAttr returned trusted HTML attribute content")
|
||||||
|
}
|
||||||
|
if _, ok := any(got).(string); !ok {
|
||||||
|
t.Fatalf("jsonAttr returned %T, want string", got)
|
||||||
|
}
|
||||||
|
want := `{"a":1}`
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf("expected %q, got %q", want, got)
|
t.Errorf("expected %q, got %q", want, got)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,14 @@ package templates
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"mal/integrations/jikan"
|
||||||
|
"mal/internal/domain"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProvideRendererParsesTemplates(t *testing.T) {
|
func TestProvideRendererParsesTemplates(t *testing.T) {
|
||||||
@@ -102,6 +107,68 @@ func TestRenderWithNonStringFragment(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWatchTemplateEscapesJSONDataAttributes(t *testing.T) {
|
||||||
|
r, err := ProvideRenderer()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
label := `English ' data-injected='yes' <b>&"`
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = r.ExecuteFragment(&buf, "watch.gohtml", "content", domain.WatchPageData{
|
||||||
|
Anime: domain.Anime{Anime: jikan.Anime{MalID: 123, Title: "Example Anime"}},
|
||||||
|
CurrentEpID: "1",
|
||||||
|
Episodes: []domain.CanonicalEpisode{
|
||||||
|
{Number: 1, Title: "Episode 1", HasSub: true},
|
||||||
|
},
|
||||||
|
WatchData: domain.WatchData{
|
||||||
|
MalID: 123,
|
||||||
|
Title: "Example Anime",
|
||||||
|
CurrentEpisode: "1",
|
||||||
|
ModeSources: map[string]domain.ModeSource{
|
||||||
|
"sub": {
|
||||||
|
Token: "stream-token",
|
||||||
|
Subtitles: []domain.SubtitleItem{
|
||||||
|
{Lang: label, Token: "subtitle-token"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AvailableModes: []string{"sub"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExecuteFragment error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, err := goquery.NewDocumentFromReader(strings.NewReader(buf.String()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse rendered html: %v", err)
|
||||||
|
}
|
||||||
|
player := doc.Find("[data-video-player]")
|
||||||
|
if got := player.Length(); got != 1 {
|
||||||
|
t.Fatalf("data-video-player count = %d, want 1", got)
|
||||||
|
}
|
||||||
|
if injected, ok := player.Attr("data-injected"); ok {
|
||||||
|
t.Fatalf("json data created injected attribute %q in html:\n%s", injected, buf.String())
|
||||||
|
}
|
||||||
|
if got := player.Find("b").Length(); got != 0 {
|
||||||
|
t.Fatalf("json data created %d nested b tags in html:\n%s", got, buf.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, ok := player.Attr("data-mode-sources")
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("missing data-mode-sources in html:\n%s", buf.String())
|
||||||
|
}
|
||||||
|
var sources map[string]domain.ModeSource
|
||||||
|
if err := json.Unmarshal([]byte(raw), &sources); err != nil {
|
||||||
|
t.Fatalf("data-mode-sources is not recoverable json: %v\nraw: %s", err, raw)
|
||||||
|
}
|
||||||
|
got := sources["sub"].Subtitles[0].Lang
|
||||||
|
if got != label {
|
||||||
|
t.Fatalf("subtitle label mismatch\nwant: %q\ngot: %q", label, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExecuteFragmentValid(t *testing.T) {
|
func TestExecuteFragmentValid(t *testing.T) {
|
||||||
r, err := ProvideRenderer()
|
r, err := ProvideRenderer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user