# -*- coding: utf-8 -*- # # Copyright (C) 2006-2009 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://genshi.edgewall.org/wiki/License. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://genshi.edgewall.org/log/. import unittest import six from genshi.input import HTML, ParseError from genshi.filters.html import HTMLFormFiller, HTMLSanitizer from genshi.template import MarkupTemplate from genshi.tests.test_utils import doctest_suite class HTMLFormFillerTestCase(unittest.TestCase): def test_fill_input_text_no_value(self): html = HTML(u"""

""") | HTMLFormFiller() self.assertEqual("""

""", html.render()) def test_fill_input_text_single_value(self): html = HTML(u"""

""") | HTMLFormFiller(data={'foo': 'bar'}) self.assertEqual("""

""", html.render()) def test_fill_input_text_multi_value(self): html = HTML(u"""

""") | HTMLFormFiller(data={'foo': ['bar']}) self.assertEqual("""

""", html.render()) def test_fill_input_hidden_no_value(self): html = HTML(u"""

""") | HTMLFormFiller() self.assertEqual("""

""", html.render()) def test_fill_input_hidden_single_value(self): html = HTML(u"""

""") | HTMLFormFiller(data={'foo': 'bar'}) self.assertEqual("""

""", html.render()) def test_fill_input_hidden_multi_value(self): html = HTML(u"""

""") | HTMLFormFiller(data={'foo': ['bar']}) self.assertEqual("""

""", html.render()) def test_fill_textarea_no_value(self): html = HTML(u"""

""") | HTMLFormFiller() self.assertEqual("""

""") | HTMLFormFiller(data={'foo': 'bar'}) self.assertEqual("""

""", html.render()) def test_fill_textarea_multi_value(self): html = HTML(u"""

""") | HTMLFormFiller(data={'foo': ['bar']}) self.assertEqual("""

""", html.render()) def test_fill_textarea_multiple(self): # Ensure that the subsequent textarea doesn't get the data from the # first html = HTML(u"""

""") | HTMLFormFiller(data={'foo': 'Some text'}) self.assertEqual("""

""") | HTMLFormFiller(data={'foo': 'Some text'}) self.assertEqual("""

""", html.render()) def test_fill_input_checkbox_single_value_auto_no_value(self): html = HTML(u"""

""") | HTMLFormFiller() self.assertEqual("""

""", html.render()) def test_fill_input_checkbox_single_value_auto(self): html = HTML(u"""

""") self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': ''})).render()) self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': 'on'})).render()) def test_fill_input_checkbox_single_value_defined(self): html = HTML("""

""", encoding='ascii') self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': '1'})).render()) self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': '2'})).render()) def test_fill_input_checkbox_multi_value_auto(self): html = HTML("""

""", encoding='ascii') self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': []})).render()) self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': ['on']})).render()) def test_fill_input_checkbox_multi_value_defined(self): html = HTML(u"""

""") self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': ['1']})).render()) self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': ['2']})).render()) def test_fill_input_radio_no_value(self): html = HTML(u"""

""") | HTMLFormFiller() self.assertEqual("""

""", html.render()) def test_fill_input_radio_single_value(self): html = HTML(u"""

""") self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': '1'})).render()) self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': '2'})).render()) def test_fill_input_radio_multi_value(self): html = HTML(u"""

""") self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': ['1']})).render()) self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': ['2']})).render()) def test_fill_input_radio_empty_string(self): html = HTML(u"""

""") self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': ''})).render()) def test_fill_input_radio_multi_empty_string(self): html = HTML(u"""

""") self.assertEqual("""

""", (html | HTMLFormFiller(data={'foo': ['']})).render()) def test_fill_select_no_value_auto(self): html = HTML(u"""

""") | HTMLFormFiller() self.assertEqual("""

""", html.render()) def test_fill_select_no_value_defined(self): html = HTML(u"""

""") | HTMLFormFiller() self.assertEqual("""

""", html.render()) def test_fill_select_single_value_auto(self): html = HTML(u"""

""") | HTMLFormFiller(data={'foo': '1'}) self.assertEqual("""

""", html.render()) def test_fill_select_single_value_defined(self): html = HTML(u"""

""") | HTMLFormFiller(data={'foo': '1'}) self.assertEqual("""

""", html.render()) def test_fill_select_multi_value_auto(self): html = HTML(u"""

""") | HTMLFormFiller(data={'foo': ['1', '3']}) self.assertEqual("""

""", html.render()) def test_fill_select_multi_value_defined(self): html = HTML(u"""

""") | HTMLFormFiller(data={'foo': ['1', '3']}) self.assertEqual("""

""", html.render()) def test_fill_option_segmented_text(self): html = MarkupTemplate(u"""
""").generate(x=1) | HTMLFormFiller(data={'foo': '1'}) self.assertEqual(u"""
""", html.render()) def test_fill_option_segmented_text_no_value(self): html = MarkupTemplate("""
""").generate(x=1) | HTMLFormFiller(data={'foo': 'foo 1 bar'}) self.assertEqual("""
""", html.render()) def test_fill_option_unicode_value(self): html = HTML(u"""
""") | HTMLFormFiller(data={'foo': u'ö'}) self.assertEqual(u"""
""", html.render(encoding=None)) def test_fill_input_password_disabled(self): html = HTML(u"""

""") | HTMLFormFiller(data={'pass': 'bar'}) self.assertEqual("""

""", html.render()) def test_fill_input_password_enabled(self): html = HTML(u"""

""") | HTMLFormFiller(data={'pass': '1234'}, passwords=True) self.assertEqual("""

""", html.render()) def StyleSanitizer(): safe_attrs = HTMLSanitizer.SAFE_ATTRS | frozenset(['style']) return HTMLSanitizer(safe_attrs=safe_attrs) class HTMLSanitizerTestCase(unittest.TestCase): def assert_parse_error_or_equal(self, expected, exploit, allow_strip=False): try: html = HTML(exploit) except ParseError: return sanitized_html = (html | HTMLSanitizer()).render() if not sanitized_html and allow_strip: return self.assertEqual(expected, sanitized_html) def test_sanitize_unchanged(self): html = HTML(u'fo
o
') self.assertEqual('fo
o
', (html | HTMLSanitizer()).render()) html = HTML(u'foo') self.assertEqual('foo', (html | HTMLSanitizer()).render()) def test_sanitize_escape_text(self): html = HTML(u'fo&') self.assertEqual('fo&', (html | HTMLSanitizer()).render()) html = HTML(u'<foo>') self.assertEqual('<foo>', (html | HTMLSanitizer()).render()) def test_sanitize_entityref_text(self): html = HTML(u'foö') self.assertEqual(u'foö', (html | HTMLSanitizer()).render(encoding=None)) def test_sanitize_escape_attr(self): html = HTML(u'
') self.assertEqual('
', (html | HTMLSanitizer()).render()) def test_sanitize_close_empty_tag(self): html = HTML(u'fo
o
') self.assertEqual('fo
o
', (html | HTMLSanitizer()).render()) def test_sanitize_invalid_entity(self): html = HTML(u'&junk;') self.assertEqual('&junk;', (html | HTMLSanitizer()).render()) def test_sanitize_remove_script_elem(self): html = HTML(u'') self.assertEqual('', (html | HTMLSanitizer()).render()) html = HTML(u'') self.assertEqual('', (html | HTMLSanitizer()).render()) src = u'alert("foo")' self.assert_parse_error_or_equal('<SCR\x00IPT>alert("foo")', src, allow_strip=True) src = u'' self.assert_parse_error_or_equal('<SCRIPT&XYZ; ' 'SRC="http://example.com/">', src, allow_strip=True) def test_sanitize_remove_onclick_attr(self): html = HTML(u'
') self.assertEqual('
', (html | HTMLSanitizer()).render()) def test_sanitize_remove_input_password(self): html = HTML(u'
') self.assertEqual('
', (html | HTMLSanitizer()).render()) def test_sanitize_remove_comments(self): html = HTML(u'''
''') self.assertEqual('
', (html | HTMLSanitizer()).render()) def test_sanitize_remove_style_scripts(self): sanitizer = StyleSanitizer() # Inline style with url() using javascript: scheme html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) # Inline style with url() using javascript: scheme, using control char html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) # Inline style with url() using javascript: scheme, in quotes html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) # IE expressions in CSS not allowed html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) # Inline style with url() using javascript: scheme, using unicode # escapes html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) def test_sanitize_remove_style_phishing(self): sanitizer = StyleSanitizer() # The position property is not allowed html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) # Normal margins get passed through html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) # But not negative margins html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) html = HTML(u'
') self.assertEqual('
', (html | sanitizer).render()) def test_sanitize_remove_src_javascript(self): html = HTML(u'') self.assertEqual('', (html | HTMLSanitizer()).render()) # Case-insensitive protocol matching html = HTML(u'') self.assertEqual('', (html | HTMLSanitizer()).render()) # Grave accents (not parsed) src = u'' self.assert_parse_error_or_equal('', src) # Protocol encoded using UTF-8 numeric entities html = HTML(u'') self.assertEqual('', (html | HTMLSanitizer()).render()) # Protocol encoded using UTF-8 numeric entities without a semicolon # (which is allowed because the max number of digits is used) html = HTML(u'') self.assertEqual('', (html | HTMLSanitizer()).render()) # Protocol encoded using UTF-8 numeric hex entities without a semicolon # (which is allowed because the max number of digits is used) html = HTML(u'') self.assertEqual('', (html | HTMLSanitizer()).render()) # Embedded tab character in protocol html = HTML(u'') self.assertEqual('', (html | HTMLSanitizer()).render()) # Embedded tab character in protocol, but encoded this time html = HTML(u'') self.assertEqual('', (html | HTMLSanitizer()).render()) def test_sanitize_expression(self): html = HTML(u'
XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) def test_capital_expression(self): html = HTML(u'
XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) def test_sanitize_url_with_javascript(self): html = HTML(u'
' u'XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) def test_sanitize_capital_url_with_javascript(self): html = HTML(u'
' u'XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) def test_sanitize_unicode_escapes(self): html = HTML(u'
' u'XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) def test_sanitize_backslash_without_hex(self): html = HTML(u'
XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) input_str = u'
XSS
' html = HTML(input_str) self.assertEqual(input_str, six.text_type(html | StyleSanitizer())) def test_sanitize_unsafe_props(self): html = HTML(u'
XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) html = HTML(u'
XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) html = HTML(u'
' u'XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) html = HTML(u"""
XSS
""") self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) html = HTML(u"""
XSS
""") self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) def test_sanitize_negative_margin(self): html = HTML(u'
XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) html = HTML(u'
XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) def test_sanitize_css_hack(self): html = HTML(u'
XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) html = HTML(u'
XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) def test_sanitize_property_name(self): html = HTML(u'
prop
') self.assertEqual('
prop
', six.text_type(html | StyleSanitizer())) def test_sanitize_unicode_expression(self): # Fullwidth small letters html = HTML(u'
' u'XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) # Fullwidth capital letters html = HTML(u'
' u'XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) # IPA extensions html = HTML(u'
' u'XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) def test_sanitize_unicode_url(self): # IPA extensions html = HTML(u'
' u'XSS
') self.assertEqual('
XSS
', six.text_type(html | StyleSanitizer())) def suite(): suite = unittest.TestSuite() suite.addTest(doctest_suite(HTMLFormFiller.__module__)) suite.addTest(unittest.makeSuite(HTMLFormFillerTestCase, 'test')) suite.addTest(unittest.makeSuite(HTMLSanitizerTestCase, 'test')) return suite if __name__ == '__main__': unittest.main(defaultTest='suite')