feat: publish the Sandwich Hime source preview
Signed-off-by: Cole Speelman <gamertan@noreply.localhost>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
// SPDX-License-Identifier: 0BSD
|
||||
|
||||
package views
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"html/template"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gamertan.com/sandwich-hime/sando"
|
||||
)
|
||||
|
||||
func TestBrowseResultsEscapesUntrustedValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
component := BrowseResults(BrowseView{
|
||||
Query: `"><script>alert(1)</script>`,
|
||||
Records: []RecordView{{
|
||||
URL: `/item?q=" onclick="alert(1)`,
|
||||
Title: `<img src=x onerror=alert(1)>`,
|
||||
Kind: `" aria-label="injected`,
|
||||
}},
|
||||
})
|
||||
|
||||
var output bytes.Buffer
|
||||
if err := sando.Render(context.Background(), &output, component); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := output.String()
|
||||
if strings.Contains(got, "<script>") || strings.Contains(got, "<img") || strings.Contains(got, `" onclick=`) {
|
||||
t.Fatalf("untrusted markup survived escaping: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBrowseResultsRejectsDangerousURL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
component := BrowseResults(BrowseView{Records: []RecordView{{
|
||||
URL: " JaVaScRiPt:\talert(1)",
|
||||
Title: "unsafe",
|
||||
}}})
|
||||
var output bytes.Buffer
|
||||
err := sando.Render(context.Background(), &output, component)
|
||||
if !errors.Is(err, sando.ErrUnsafeURL) {
|
||||
t.Fatalf("expected unsafe URL error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratedComponentPropagatesWriterFailures(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
component := Badge("proof")
|
||||
if err := sando.Render(context.Background(), shortWriter{}, component); !errors.Is(err, io.ErrShortWrite) {
|
||||
t.Fatalf("short writer error = %v, want io.ErrShortWrite", err)
|
||||
}
|
||||
|
||||
want := errors.New("connection closed")
|
||||
if err := sando.Render(context.Background(), errorWriter{err: want}, component); !errors.Is(err, want) {
|
||||
t.Fatalf("writer error = %v, want %v", err, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextEscapingAgainstHTMLTemplateBaseline(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
baseline := template.Must(template.New("probe").Parse(`<a href="{{ .URL }}" title="{{ .Title }}">{{ .Text }}</a>`))
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
title string
|
||||
text string
|
||||
}{
|
||||
{name: "entities", url: "/items?q=one&kind=book", title: `say "hello" & goodbye`, text: `<strong>not markup</strong>`},
|
||||
{name: "unicode", url: "/items/cole", title: "Café & 雪", text: "Cole's archive ♥"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
data := struct{ URL, Title, Text string }{test.url, test.title, test.text}
|
||||
var legacy bytes.Buffer
|
||||
if err := baseline.Execute(&legacy, data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var generated bytes.Buffer
|
||||
if err := sando.Render(context.Background(), &generated, SecurityProbe(test.url, test.title, test.text)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := strings.TrimSpace(generated.String()), legacy.String(); got != want {
|
||||
t.Fatalf("generated escaping differs from html/template baseline\n got: %s\nwant: %s", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDangerousURLIsBlockedAtLeastAsStrictlyAsHTMLTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const dangerous = " JaVaScRiPt:\talert(1)"
|
||||
baseline := template.Must(template.New("probe").Parse(`<a href="{{ . }}">link</a>`))
|
||||
var legacy bytes.Buffer
|
||||
if err := baseline.Execute(&legacy, dangerous); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(legacy.String(), "#ZgotmplZ") {
|
||||
t.Fatalf("html/template baseline unexpectedly accepted dangerous URL: %s", legacy.String())
|
||||
}
|
||||
|
||||
var generated bytes.Buffer
|
||||
err := sando.Render(context.Background(), &generated, SecurityProbe(dangerous, "title", "link"))
|
||||
if !errors.Is(err, sando.ErrUnsafeURL) {
|
||||
t.Fatalf("generated renderer error = %v, want ErrUnsafeURL", err)
|
||||
}
|
||||
}
|
||||
|
||||
type shortWriter struct{}
|
||||
|
||||
func (shortWriter) Write(value []byte) (int, error) {
|
||||
if len(value) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return len(value) - 1, nil
|
||||
}
|
||||
|
||||
type errorWriter struct{ err error }
|
||||
|
||||
func (writer errorWriter) Write([]byte) (int, error) { return 0, writer.err }
|
||||
Reference in New Issue
Block a user