82 lines
3.4 KiB
Python
82 lines
3.4 KiB
Python
# /****************************************************************************
|
|
# * <Novell-copyright>
|
|
# * Copyright (c) 2001 Novell, Inc. All Rights Reserved.
|
|
# *
|
|
# * This program is free software; you can redistribute it and/or
|
|
# * modify it under the terms of version 2 of the GNU General Public License
|
|
# * as published by the Free Software Foundation.
|
|
# *
|
|
# * This program is distributed in the hope that it will be useful,
|
|
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# * GNU General Public License for more details.
|
|
# *
|
|
# * You should have received a copy of the GNU General Public License
|
|
# * along with this program; if not, contact Novell, Inc.
|
|
# *
|
|
# * To contact Novell about this file by physical or electronic mail, you
|
|
# * may find current contact information at www.novell.com.
|
|
# * </Novell-copyright>
|
|
# ****************************************************************************/
|
|
|
|
import unittest
|
|
|
|
from bongo_web.html_mail import sanitize_html
|
|
|
|
|
|
def exercising_cleaner(html, **options):
|
|
"""Exercise the URL callback without duplicating nh3's HTML parser."""
|
|
callback = options["attribute_filter"]
|
|
values = [
|
|
callback("img", "src", "cid:logo@example"),
|
|
callback("img", "src", "https://tracker.example/pixel.gif"),
|
|
callback("img", "src", "javascript:alert(1)"),
|
|
callback("a", "href", "https://example.test/"),
|
|
callback("a", "href", "javascript:alert(1)"),
|
|
]
|
|
return "|".join(value or "REMOVED" for value in values)
|
|
|
|
|
|
class HtmlMailTests(unittest.TestCase):
|
|
def test_keeps_inline_and_marks_remote_images(self):
|
|
result = sanitize_html("<p>mail</p>", exercising_cleaner)
|
|
self.assertIn("cid:logo@example", result["html"])
|
|
self.assertIn("bongo-remote:0", result["html"])
|
|
self.assertIn("REMOVED", result["html"])
|
|
self.assertEqual(result["remote_images"],
|
|
["https://tracker.example/pixel.gif"])
|
|
|
|
def test_rejects_oversized_html(self):
|
|
with self.assertRaises(ValueError):
|
|
sanitize_html("x" * (4 * 1024 * 1024 + 1), exercising_cleaner)
|
|
|
|
def test_real_sanitizer_removes_active_content_and_unsafe_css(self):
|
|
result = sanitize_html(
|
|
'<p style="color:red;position:fixed;background-image:url(x)">ok'
|
|
'<script>alert(1)</script><img src="cid:logo">'
|
|
'<img src="https://tracker.example/pixel">'
|
|
'<a href="javascript:alert(2)">bad</a></p>')
|
|
output = result["html"]
|
|
self.assertNotIn("alert", output)
|
|
self.assertNotIn("position", output)
|
|
self.assertNotIn("background-image", output)
|
|
self.assertIn('style="color:red"', output)
|
|
self.assertIn('src="cid:logo"', output)
|
|
self.assertIn('src="bongo-remote:0"', output)
|
|
self.assertEqual(result["remote_images"],
|
|
["https://tracker.example/pixel"])
|
|
|
|
def test_style_filter_rejects_obfuscated_and_active_values(self):
|
|
result = sanitize_html(
|
|
'<p style="color:e\\78pression(alert(1));'
|
|
'background-color:url(https://tracker.example/pixel);'
|
|
'font-weight:bold">safe</p>')
|
|
output = result["html"]
|
|
self.assertNotIn("expression", output)
|
|
self.assertNotIn("url", output)
|
|
self.assertIn("font-weight:bold", output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|