Files
summercms/modules/seaglass/seaglass_smoke_test.go
Jakub Zych 71fa597830 feat(17-01): richeditor field type on the seaglass sanitizer
- seaglass (new module): bluemonday v1.0.27 policy ported from Journal
  without the Blog Editor rules, byte-stable token comparison,
  SanitizeReport, Sanitize, Check, Removals
- cabana: type richeditor (common keys plus size); every create and update
  value sanitized before Fill, non-string values are a 422, removals become
  meta.notices; settings and relation forms refuse the type
- backend lang richeditor.removed_* and markup_changed (en, pl plurals)
- TestSeaglassSmoke, TestCompanionsRichEditorSmoke, README and docs
2026-10-12 15:15:38 +02:00

54 lines
2.2 KiB
Go

package seaglass_test
import (
"slices"
"strings"
"testing"
"git.golem15.com/golem15/summercms/modules/seaglass"
)
// TestSeaglassSmoke checks the two promises of the sanitizer: clean HTML
// comes back byte for byte with an empty report, and unsafe constructs are
// removed and named.
func TestSeaglassSmoke(t *testing.T) {
clean := "<p>Mushrooms <strong>can</strong> help:</p>\n<ul><li>focus</li><li><a href=\"https://example.com/a?b=1&amp;c=2\">sleep</a></li></ul>\n<p>Call <a href=\"tel:+48123\">us</a> or <a href=\"/kontakt\">write</a>.</p>"
got, report := seaglass.SanitizeReport(clean)
if got != clean || report.Changed || report.ElementCount+report.AttributeCount+report.Comments != 0 {
t.Fatalf("clean HTML changed: %q %+v", got, report)
}
if err := seaglass.Check(clean); err != nil {
t.Fatalf("Check(clean) = %v", err)
}
// Re-escaping alone is not a change: bluemonday writes &#39; here.
quoted := `<p>It's "fine"</p>`
if got := seaglass.Sanitize(quoted); got != quoted {
t.Fatalf("re-escaped text changed: %q", got)
}
dirty := `<p onclick="steal()">Hi <a href="javascript:alert(1)">there</a></p><script>alert(1)</script><!-- note -->`
got, report = seaglass.SanitizeReport(dirty)
for _, bad := range []string{"onclick", "javascript", "<script", "alert", "<!--"} {
if strings.Contains(got, bad) {
t.Fatalf("%q survived: %q", bad, got)
}
}
if !strings.Contains(got, "Hi ") || !report.Changed {
t.Fatalf("sanitized = %q %+v", got, report)
}
// A link whose only href is refused loses its tag (its text stays), so
// it is reported as a removed a element.
if !slices.Contains(report.Elements, "script") || !slices.Contains(report.Elements, "a") || !slices.Contains(report.Attributes, "onclick") || report.Comments != 1 {
t.Fatalf("report = %+v", report)
}
if err := seaglass.Check(dirty); err == nil {
t.Fatal("Check(dirty) = nil")
}
// Bypasses of regex gates are removed too.
for _, payload := range []string{`<img src="x"onerror="alert(1)">`, `<a href="javascript&#58;alert(1)">x</a>`, `<svg/onload=alert(1)>`, `<a href="https://a" href="javascript:alert(1)">x</a>`} {
if err := seaglass.Check(payload); err == nil {
t.Fatalf("payload passed: %s", payload)
}
}
}