package parsemail
import (
"bytes"
"io"
"strings"
"testing"
"github.com/emersion/go-message"
)
// XSS Test Cases
func TestSanitizeHTML(t *testing.T) {
testCases := []struct {
name string
input string
expected string
}{
{
name: "Simple script tag",
input: ``,
expected: ``,
},
{
name: "Image with onerror - should remove dangerous attributes",
input: `
`,
expected: `
`, // 保留图片元素并移除事件处理属性
},
{
name: "SVG with onload",
input: ``,
expected: ``,
},
{
name: "Iframe with javascript",
input: ``,
expected: ``,
},
{
name: "Safe HTML content",
input: `
Hello World!
`,
expected: `Hello World!
`,
},
{
name: "Link with javascript protocol - removes dangerous href",
input: `Click me`,
expected: `Click me`, // bluemonday removes entire link when href is dangerous
},
{
name: "Style tag with javascript",
input: ``,
expected: ``,
},
{
name: "Object tag",
input: ``,
expected: ``,
},
{
name: "Form tag",
input: ``,
expected: ``,
},
{
name: "Mixed content with XSS - keeps safe div tags",
input: `Safe content
More safe content
`,
expected: `Safe content
More safe content
`, // div is now allowed, script removed
},
{
name: "Event handlers in various tags - removes handlers but keeps tag",
input: `Click me
`,
expected: `Click me
`, // div is kept, onclick handler removed
},
{
name: "Data URLs with javascript - removes dangerous link",
input: `Link`,
expected: `Link`, // dangerous data URL removes the link
},
{
name: "Meta refresh with javascript",
input: ``,
expected: ``,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := SanitizeHTML(tc.input)
if result != tc.expected {
t.Errorf("SanitizeHTML(%q) = %q, want %q", tc.input, result, tc.expected)
}
})
}
}
func TestSanitizeText(t *testing.T) {
testCases := []struct {
name string
input string
expected string
}{
{
name: "Script tag in text",
input: ``,
expected: ``,
},
{
name: "HTML tags in text",
input: `Hello World!
`,
expected: `Hello World!`,
},
{
name: "Plain text",
input: `Hello World! This is plain text.`,
expected: `Hello World! This is plain text.`,
},
{
name: "Text with special characters",
input: `Hello & goodbye < > " '`,
expected: `Hello & goodbye < > " '`,
},
{
name: "Image tag in text",
input: `
`,
expected: ``,
},
{
name: "Mixed HTML and text",
input: `Normal text more text`,
expected: `Normal text more text`,
},
{
name: "Unicode characters",
input: `Hello 世界 🌍`,
expected: `Hello 世界 🌍`,
},
{
name: "Multiple script tags",
input: `Text`,
expected: `Text`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := SanitizeText(tc.input)
if result != tc.expected {
t.Errorf("SanitizeText(%q) = %q, want %q", tc.input, result, tc.expected)
}
})
}
}
func TestBuildUser_XSS(t *testing.T) {
testCases := []struct {
name string
input string
expectedEmail string
expectedName string
shouldBeNil bool
}{
{
name: "Normal user",
input: `John Doe `,
expectedEmail: "john@example.com",
expectedName: "John Doe",
shouldBeNil: false,
},
{
name: "Simple email only",
input: `test@example.com`,
expectedEmail: "test@example.com",
expectedName: "",
shouldBeNil: false,
},
{
name: "User with quotes",
input: `"John O'Connor" `,
expectedEmail: "john@example.com",
expectedName: "John O'Connor",
shouldBeNil: false,
},
{
name: "JavaScript in name - should be filtered",
input: `javascript:alert('xss') `,
expectedEmail: "js@example.com",
expectedName: "javascript:alert('xss')",
shouldBeNil: false,
},
{
name: "Event handler in name - quotes removed in sanitization",
input: `onclick="alert('xss')" `,
expectedEmail: "click@example.com",
expectedName: "onclick= alert('xss')",
shouldBeNil: false,
},
{
name: "HTML in email address - should be filtered",
input: ``,
expectedEmail: "script>test@example.com`)
h.Set("To", "to@example.com")
h.Set("Subject", `Test Subject`)
h.Set("Date", "Mon, 02 Jan 2006 15:04:05 -0700")
h.SetContentType("multipart/alternative", nil)
w, err := message.CreateWriter(&b, h)
if err != nil {
t.Fatal(err)
}
var htmlHeader message.Header
htmlHeader.SetContentType("text/html", nil)
htmlPart, err := w.CreatePart(htmlHeader)
if err != nil {
t.Fatal(err)
}
io.WriteString(htmlPart, `Hello
Safe content
`)
htmlPart.Close()
var textHeader message.Header
textHeader.SetContentType("text/plain", nil)
textPart, err := w.CreatePart(textHeader)
if err != nil {
t.Fatal(err)
}
io.WriteString(textPart, `Hello World`)
textPart.Close()
w.Close()
email := mustParseEmail(t, []string{"to@example.com"}, &b, b.Len())
if email.From.EmailAddress != "from@example.com" {
t.Errorf("Expected From.EmailAddress to be 'from@example.com', got: %q", email.From.EmailAddress)
}
if email.From.Name != "Safe Name" {
t.Errorf("Expected From.Name to be 'Safe Name', got: %q", email.From.Name)
}
expectedSubject := "Test Subject"
if email.Subject != expectedSubject {
t.Errorf("Expected Subject to be %q, got: %q", expectedSubject, email.Subject)
}
htmlContent := string(email.HTML)
if strings.Contains(htmlContent, "malicious.txt`
sanitizedFilename := SanitizeText(dangerousFilename)
if strings.Contains(sanitizedFilename, "`
sanitizedContentType := SanitizeText(dangerousContentType)
if strings.Contains(sanitizedContentType, "`,
testType: "html",
},
{
name: "Base64 encoded script",
input: `
`,
testType: "html",
},
{
name: "CSS expression - should be removed",
input: `Test
`,
testType: "html",
},
{
name: "HTML entities script",
input: `<script>alert('xss')</script>`,
testType: "html",
},
{
name: "Multiple attack vectors",
input: `