Files
dhkatzandClaude Opus 5.5 9bdc3ff455 Add pack, cfl and safehtml
pack loads local product packs (format 1): t5de-product.json and the product's files, as product.chkn or
files/, with index.xml's rating, type, gender and parent product. cfl writes and reads IMVU's product
container, uncompressed. safehtml cleans product descriptions by an allowlist (bluemonday).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 02:52:59 -07:00

115 lines
5.6 KiB
Go

package safehtml
import (
"strings"
"testing"
)
func TestDescription(t *testing.T) {
const rel = ` rel="nofollow ugc noopener noreferrer" target="_blank"`
for _, c := range []struct{ in, want string }{
// Kept.
{`<font color="#ff0000" size="4"><b>Hi</b></font><br><center>Made by me</center>`,
`<font color="#ff0000" size="4"><b>Hi</b></font><br><center>Made by me</center>`},
{`<font color="ff0000" size="+2">x</font>`, `<font color="ff0000" size="+2">x</font>`},
{`<p style="color: red; text-align: center; font-weight: bold">x</p>`,
`<p style="color: red; text-align: center; font-weight: bold">x</p>`},
{`<span style="background-color: rgb(1, 2, 3); font-size: 12px; font-style: italic; text-decoration: underline">x</span>`,
`<span style="background-color: rgb(1, 2, 3); font-size: 12px; font-style: italic; text-decoration: underline">x</span>`},
{`<a href="https://example.com/a?b=c&amp;d=e">x</a>`, `<a href="https://example.com/a?b=c&amp;d=e"` + rel + `>x</a>`},
{`<a href="http://example.com/">x</a>`, `<a href="http://example.com/"` + rel + `>x</a>`},
{`<img src="https://example.com/a.png" alt="A" width="100">`, `<img src="https://example.com/a.png" alt="A" width="100">`},
{`<table><tbody><tr><td colspan="2">x</td></tr></tbody></table>`, `<table><tbody><tr><td colspan="2">x</td></tr></tbody></table>`},
{`<ul><li>a</li></ul><h1>b</h1><blockquote>c</blockquote><big>d</big><hr>`,
`<ul><li>a</li></ul><h1>b</h1><blockquote>c</blockquote><big>d</big><hr>`},
{`5 < 6 & "quotes"`, `5 &lt; 6 &amp; &#34;quotes&#34;`},
// Removed.
{`<script>alert(1)</script>x`, `x`},
{`<SCRIPT SRC=https://e.com/x.js></SCRIPT>x`, `x`},
{`<scr<script>ipt>alert(1)</script>`, `ipt&gt;alert(1)`},
{`<img src=x onerror=alert(1)>`, ``},
{`<img src="https://e.com/a.png" onerror="alert(1)">`, `<img src="https://e.com/a.png">`},
{`<b onclick="alert(1)" onmouseover=alert(1)>x</b>`, `<b>x</b>`},
{`<a href="javascript:alert(1)">x</a>`, `x`},
{`<a href=" javascript:alert(1)">x</a>`, `x`},
{`<a href="JaVaScRiPt:alert(1)">x</a>`, `x`},
{`<a href="&#106;avascript:alert(1)">x</a>`, `x`},
{`<a href="&#x6A;avascript&#58;alert(1)">x</a>`, `x`},
{`<a href="java&#x09;script:alert(1)">x</a>`, `x`},
{`<a href="vbscript:msgbox(1)">x</a>`, `x`},
{`<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==">x</a>`, `x`},
{`<a href="chrome://browser/content/browser.xul">x</a>`, `x`},
{`<a href="/relative">x</a>`, `x`},
{`<a href="https://e.com/" target="_self" rel="opener">x</a>`, `<a href="https://e.com/"` + rel + `>x</a>`},
{`<img src="javascript:alert(1)">`, ``},
{`<img src="http://e.com/a.png">`, ``},
{`<img src="data:image/png;base64,iVBORw0KGgo=">`, ``},
{`<img alt="no src">`, ``},
{`<p style="background-color: url(https://e.com/x.png)">x</p>`, `<p>x</p>`},
{`<p style="background: url(https://e.com/x.png)">x</p>`, `<p>x</p>`},
{`<p style="color: expression(alert(1))">x</p>`, `<p>x</p>`},
{`<p style="color: red; position: fixed; top: 0; width: 100%">x</p>`, `<p style="color: red">x</p>`},
{`<p style="font-size: 99999px">x</p>`, `<p>x</p>`},
{`<font color="red" face="Arial" style="color: blue">x</font>`, `<font color="red" style="color: blue">x</font>`},
{`<style>body{display:none}</style>x`, `x`},
{`<link rel="stylesheet" href="https://e.com/x.css">x`, `x`},
{`<iframe src="https://e.com/"></iframe>x`, `x`},
{`<object data="x.swf"></object><embed src="x.swf">x`, `x`},
{`<form action="https://e.com/"><input name="p"></form>x`, `x`},
{`<!-- a comment -->x<!--[if IE]><script>alert(1)</script><![endif]-->`, `x`},
{`<svg onload="alert(1)"><script>alert(1)</script></svg>x`, `x`},
{`<math><mi xlink:href="javascript:alert(1)">x</mi></math>`, `x`},
{`<div><p><b>unclosed`, `<div><p><b>unclosed`},
{`<b><i>misnested</b></i>`, `<b><i>misnested</b></i>`},
{`<a href="https://e.com/"<script>alert(1)</script>>x</a>`, `<a href="https://e.com/"` + rel + `>alert(1)&gt;x</a>`},
{`<img src="https://e.com/a.png" width="100%" height="-1">`, `<img src="https://e.com/a.png">`},
{`<td rowspan="0">x</td>`, `<td>x</td>`},
} {
if got := Description(c.in); got != c.want {
t.Errorf("Description(%q)\n got %q\nwant %q", c.in, got, c.want)
}
}
}
func TestNoScriptAnywhere(t *testing.T) {
for _, in := range []string{
`<<script>script>alert(1)<</script>/script>`,
`<a href="https://e.com/" style="x:expression(alert(1))">x</a>`,
`<img src="https://e.com/a.png"/onerror=alert(1)>`,
`<IMG SRC="jav&#x0A;ascript:alert('XSS');">`,
`<a href="https://e.com/" onclick=alert(1)//>x</a>`,
`<base href="javascript:alert(1)//">`,
`<meta http-equiv="refresh" content="0;url=javascript:alert(1)">`,
} {
got := strings.ToLower(Description(in))
for _, bad := range []string{"<script", "javascript:", "onerror", "onclick", "expression", "<base", "<meta"} {
if strings.Contains(got, bad) {
t.Errorf("Description(%q) = %q, contains %q", in, got, bad)
}
}
}
}
func TestDescriptionWithImages(t *testing.T) {
rewrite := func(src string) (string, bool) {
if strings.Contains(src, "drop") {
return "", false
}
if strings.Contains(src, "evil") {
return "javascript:alert(1)", true
}
return "https://images.t5de.dev/?u=" + src, true
}
for _, c := range []struct{ in, want string }{
{`<img src="https://e.com/a.png">`, `<img src="https://images.t5de.dev/?u=https://e.com/a.png">`},
{`<img src="https://e.com/drop.png">x`, `x`},
{`<img src="https://e.com/evil.png">x`, `x`},
{`<img src="http://e.com/a.png">x`, `x`},
} {
if got := DescriptionWithImages(c.in, rewrite); got != c.want {
t.Errorf("DescriptionWithImages(%q)\n got %q\nwant %q", c.in, got, c.want)
}
}
}