Package trac :: Package util :: Package tests :: Module html

Source Code for Module trac.util.tests.html

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (C) 2007-2023 Edgewall Software 
  4  # All rights reserved. 
  5  # 
  6  # This software is licensed as described in the file COPYING, which 
  7  # you should have received as part of this distribution. The terms 
  8  # are also available at https://trac.edgewall.org/wiki/TracLicense. 
  9  # 
 10  # This software consists of voluntary contributions made by many 
 11  # individuals. For the exact contribution history, see the revision 
 12  # history and logs, available at https://trac.edgewall.org/log/. 
 13   
 14  from __future__ import unicode_literals 
 15   
 16  import doctest 
 17  import io 
 18  import sys 
 19  import unittest 
 20   
 21  try: 
 22      from babel.support import LazyProxy 
 23  except ImportError: 
 24      LazyProxy = None 
 25   
 26  from trac.core import TracError 
 27  from trac.util import html 
 28  from trac.util.html import ( 
 29      Element, FormTokenInjector, Fragment, HTML, Markup, TracHTMLSanitizer, 
 30      escape, find_element, genshi, html_attribute, is_safe_origin, plaintext, 
 31      tag, to_fragment, xml 
 32  ) 
 33  from trac.util.translation import gettext, tgettext 
34 35 36 -class EscapeFragmentTestCase(unittest.TestCase):
37
38 - def test_escape_element(self):
39 self.assertEqual(Markup('<b class="em&#34;ph&#34;">"1 &lt; 2"</b>'), 40 escape(tag.b('"1 < 2"', class_='em"ph"'))) 41 self.assertEqual(Markup('<b class="em&#34;ph&#34;">"1 &lt; 2"</b>'), 42 escape(tag.b('"1 < 2"', class_='em"ph"'), 43 quotes=False))
44
45 - def test_escape_fragment(self):
46 self.assertEqual(Markup('<b class="em&#34;ph&#34;">"1 &lt; 2"</b>'), 47 escape(tag(tag.b('"1 < 2"', class_='em"ph"')))) 48 self.assertEqual(Markup('<b class="em&#34;ph&#34;">"1 &lt; 2"</b>'), 49 escape(tag(tag.b('"1 < 2"', class_='em"ph"')), 50 quotes=False))
51 52 @unittest.skipUnless(LazyProxy, 'Babel unavailable')
54 lazyproxy = gettext('Back to %(parent)s', parent='a&b<c>d"e\'f') 55 self.assertEqual(Markup('Back to a&amp;b&lt;c&gt;d&#34;e\'f'), 56 escape(lazyproxy)) 57 self.assertEqual(Markup('Back to a&amp;b&lt;c&gt;d"e\'f'), 58 escape(lazyproxy, quotes=False))
59 60 @unittest.skipUnless(LazyProxy, 'Babel unavailable')
62 lazyproxy = tgettext('Back to %(parent)s', 63 parent=tag.a('a&b<c>d"e\'f', 64 href='/a&b<c>d"e\'f')) 65 self.assertEqual(Markup('Back to <a ' 66 'href="/a&amp;b&lt;c&gt;d&#34;e\'f">' 67 'a&amp;b&lt;c&gt;d"e\'f</a>'), 68 escape(lazyproxy)) 69 self.assertEqual(Markup('Back to <a ' 70 'href="/a&amp;b&lt;c&gt;d&#34;e\'f">' 71 'a&amp;b&lt;c&gt;d"e\'f</a>'), 72 escape(lazyproxy, quotes=False))
73
74 75 -class HtmlAttributeTestCase(unittest.TestCase):
76
78 self.assertEqual('async', html_attribute('async', True)) 79 self.assertEqual(None, html_attribute('async', False)) 80 self.assertEqual(None, html_attribute('async', None))
81
83 self.assertEqual('yes', html_attribute('translate', True)) 84 self.assertEqual('no', html_attribute('translate', False)) 85 self.assertEqual('no', html_attribute('translate', None))
86
88 self.assertEqual('on', html_attribute('autocomplete', True)) 89 self.assertEqual('off', html_attribute('autocomplete', False)) 90 self.assertEqual('off', html_attribute('autocomplete', None))
91
93 self.assertEqual('true', html_attribute('spellcheck', True)) 94 self.assertEqual('false', html_attribute('spellcheck', False)) 95 self.assertEqual('false', html_attribute('spellcheck', None))
96
98 self.assertEqual('https://trac.edgewall.org', 99 html_attribute('src', 'https://trac.edgewall.org')) 100 self.assertEqual(None, html_attribute('src', None))
101
102 -class FragmentTestCase(unittest.TestCase):
103
104 - def test_zeros(self):
105 self.assertEqual(Markup('0<b>0</b> and <b>0</b>'), 106 Markup(tag(0, tag.b(0), ' and ', tag.b(0.0))))
107
108 - def test_unicode(self):
109 self.assertEqual('<b>M</b>essäge', 110 unicode(tag(tag.b('M'), 'essäge')))
111
112 - def test_str(self):
113 self.assertEqual(b'<b>M</b>ess\xc3\xa4ge', 114 str(tag(tag.b('M'), 'essäge')))
115
116 - def test_call(self):
117 t = tag() 118 self.assertEqual(b'<b>M</b>', 119 str(t(tag.b('M')))) 120 t = tag() 121 self.assertEqual(b'<b>M</b>ess\xc3\xa4ge', 122 str(t(tag.b('M'), 'essäge')))
123
124 125 -class XMLElementTestCase(unittest.TestCase):
126
127 - def test_xml(self):
128 self.assertEqual(Markup('0<a>0</a> and <b>0</b> and <c/> and' 129 ' <d class="[\'a\', \'\', \'b\']"' 130 ' more_="[\'a\']"/>'), 131 Markup(xml(0, xml.a(0), ' and ', xml.b(0.0), 132 ' and ', xml.c(None), ' and ', 133 xml.d('', class_=[b'a', b'', b'b'], 134 more__=[b'a']))))
135
136 137 -class ElementTestCase(unittest.TestCase):
138
139 - def test_tag(self):
140 self.assertEqual(Markup('0<a>0</a> and <b>0</b> and <c></c>' 141 ' and <d class="a b" more_="[\'a\']"></d>'), 142 Markup(tag(0, tag.a(0, href=''), b' and ', tag.b(0.0), 143 ' and ', tag.c(None), ' and ', 144 tag.d('', class_=['a', '', 'b'], 145 more__=[b'a']))))
146
147 - def test_unicode(self):
148 self.assertEqual('<b>M<em>essäge</em></b>', 149 unicode(tag.b('M', tag.em('essäge'))))
150
151 - def test_str(self):
152 self.assertEqual(b'<b>M<em>ess\xc3\xa4ge</em></b>', 153 str(tag.b('M', tag.em('essäge'))))
154
155 156 -class FormTokenInjectorTestCase(unittest.TestCase):
157
158 - def test_no_form(self):
159 html = '<div><img src="trac.png"/></div>' 160 injector = FormTokenInjector('123123', io.StringIO()) 161 injector.feed(html) 162 injector.close() 163 self.assertEqual(html, injector.out.getvalue())
164
165 - def test_form_get(self):
166 html = '<form method="get"><input name="age" value=""/></form>' 167 injector = FormTokenInjector('123123', io.StringIO()) 168 injector.feed(html) 169 injector.close() 170 self.assertEqual(html, injector.out.getvalue())
171
172 - def test_form_post(self):
173 html = '<form method="POST">%s<input name="age" value=""/></form>' 174 injector = FormTokenInjector('123123', io.StringIO()) 175 injector.feed(html % '') 176 injector.close() 177 html %= ('<input type="hidden" name="__FORM_TOKEN" value="%s"/>' 178 % injector.token) 179 self.assertEqual(html, injector.out.getvalue())
180
181 182 -class TracHTMLSanitizerTestCaseBase(unittest.TestCase):
183 184 safe_schemes = ('http', 'data') 185 safe_origins = ('data:', 'http://example.net', 'https://example.org/') 186
187 - def sanitize(self, html):
188 sanitizer = TracHTMLSanitizer(safe_schemes=self.safe_schemes, 189 safe_origins=self.safe_origins) 190 return unicode(sanitizer.sanitize(html))
191
192 - def test_input_type_password(self):
193 html = '<input type="password" />' 194 self.assertEqual('', self.sanitize(html))
195
196 - def test_empty_attribute(self):
197 html = '<option value="1236" selected>Family B</option>' 198 self.assertEqual( 199 '<option selected="selected" value="1236">Family B</option>', 200 self.sanitize(html))
201
202 - def test_expression(self):
203 html = '<div style="top:expression(alert())">XSS</div>' 204 self.assertEqual('<div>XSS</div>', self.sanitize(html))
205
206 - def test_capital_expression(self):
207 html = '<div style="top:EXPRESSION(alert())">XSS</div>' 208 self.assertEqual('<div>XSS</div>', self.sanitize(html))
209
211 html = r'<div style="top:exp/**/ression(alert())">XSS</div>' 212 self.assertEqual('<div style="top:exp ression(alert())">XSS</div>', 213 self.sanitize(html)) 214 html = r'<div style="top:exp//**/**/ression(alert())">XSS</div>' 215 self.assertEqual( 216 '<div style="top:exp/ **/ression(alert())">XSS</div>', 217 self.sanitize(html)) 218 html = r'<div style="top:ex/*p*/ression(alert())">XSS</div>' 219 self.assertEqual('<div style="top:ex ression(alert())">XSS</div>', 220 self.sanitize(html))
221
222 - def test_url_with_javascript(self):
223 html = ( 224 '<div style="background-image:url(javascript:alert())">XSS</div>' 225 ) 226 self.assertEqual('<div>XSS</div>', self.sanitize(html))
227
229 html = ( 230 '<div style="background-image:URL(javascript:alert())">XSS</div>' 231 ) 232 self.assertEqual('<div>XSS</div>', self.sanitize(html))
233
234 - def test_unicode_escapes(self):
235 html = r'<div style="top:exp\72 ess\000069 on(alert())">XSS</div>' 236 self.assertEqual('<div>XSS</div>', self.sanitize(html)) 237 # escaped backslash 238 html = r'<div style="top:exp\5c ression(alert())">XSS</div>' 239 self.assertEqual(r'<div style="top:exp\\ression(alert())">XSS</div>', 240 self.sanitize(html)) 241 html = r'<div style="top:exp\5c 72 ession(alert())">XSS</div>' 242 self.assertEqual(r'<div style="top:exp\\72 ession(alert())">XSS</div>', 243 self.sanitize(html)) 244 # escaped control characters 245 html = r'<div style="top:exp\000000res\1f sion(alert())">XSS</div>' 246 self.assertEqual('<div style="top:exp res sion(alert())">XSS</div>', 247 self.sanitize(html))
248
250 html = r'<div style="top:e\xp\ression(alert())">XSS</div>' 251 self.assertEqual('<div>XSS</div>', self.sanitize(html)) 252 html = r'<div style="top:e\\xp\\ression(alert())">XSS</div>' 253 self.assertEqual(r'<div style="top:e\\xp\\ression(alert())">XSS</div>', 254 self.sanitize(html))
255
256 - def test_unsafe_props(self):
257 html = '<div style="POSITION:RELATIVE">XSS</div>' 258 self.assertEqual('<div>XSS</div>', self.sanitize(html)) 259 html = '<div style="position:STATIC">safe</div>' 260 self.assertEqual('<div style="position:STATIC">safe</div>', 261 self.sanitize(html)) 262 html = '<div style="behavior:url(test.htc)">XSS</div>' 263 self.assertEqual('<div>XSS</div>', self.sanitize(html)) 264 html = '<div style="-ms-behavior:url(test.htc) url(#obj)">XSS</div>' 265 self.assertEqual('<div>XSS</div>', self.sanitize(html)) 266 html = ("""<div style="-o-link:'javascript:alert(1)';""" 267 """-o-link-source:current">XSS</div>""") 268 self.assertEqual('<div>XSS</div>', self.sanitize(html)) 269 html = """<div style="-moz-binding:url(xss.xbl)">XSS</div>""" 270 self.assertEqual('<div>XSS</div>', self.sanitize(html))
271
272 - def test_nagative_margin(self):
273 html = '<div style="margin-top:-9999px">XSS</div>' 274 self.assertEqual('<div>XSS</div>', self.sanitize(html)) 275 html = '<div style="margin:0 -9999px">XSS</div>' 276 self.assertEqual('<div>XSS</div>', self.sanitize(html))
277
278 - def test_css_hack(self):
279 html = '<div style="*position:static">XSS</div>' 280 self.assertEqual('<div>XSS</div>', self.sanitize(html)) 281 html = '<div style="_margin:-10px">XSS</div>' 282 self.assertEqual('<div>XSS</div>', self.sanitize(html))
283
284 - def test_property_name(self):
285 html = ('<div style="display:none;border-left-color:red;' 286 'user_defined:1;-moz-user-selct:-moz-all">prop</div>') 287 self.assertEqual('<div style="display:none; border-left-color:red' 288 '">prop</div>', 289 self.sanitize(html))
290
291 - def test_unicode_expression(self):
292 # Fullwidth small letters 293 html = '<div style="top:expression(alert())">XSS</div>' 294 self.assertEqual('<div>XSS</div>', self.sanitize(html)) 295 # Fullwidth capital letters 296 html = '<div style="top:EXPRESSION(alert())">XSS</div>' 297 self.assertEqual('<div>XSS</div>', self.sanitize(html)) 298 # IPA extensions 299 html = '<div style="top:expʀessɪoɴ(alert())">XSS</div>' 300 self.assertEqual('<div>XSS</div>', self.sanitize(html))
301
302 - def test_unicode_url(self):
303 # IPA extensions 304 html = ( 305 '<div style="background-image:uʀʟ(javascript:alert())">XSS</div>' 306 ) 307 self.assertEqual('<div>XSS</div>', self.sanitize(html))
308
309 - def test_cross_origin(self):
310 test = self._assert_sanitize 311 312 test('<img src="data:image/png,...."/>', 313 '<img src="data:image/png,...."/>') 314 test('<img src="http://example.org/login" crossorigin="anonymous"/>', 315 '<img src="http://example.org/login"/>') 316 test('<img src="http://example.org/login" crossorigin="anonymous"/>', 317 '<img src="http://example.org/login"' 318 ' crossorigin="use-credentials"/>') 319 test('<img src="http://example.net/bar.png"/>', 320 '<img src="http://example.net/bar.png"/>') 321 test('<img src="http://example.net:443/qux.png"' 322 ' crossorigin="anonymous"/>', 323 '<img src="http://example.net:443/qux.png"/>') 324 test('<img src="/path/foo.png"/>', '<img src="/path/foo.png"/>') 325 test('<img src="../../bar.png"/>', '<img src="../../bar.png"/>') 326 test('<img src="qux.png"/>', '<img src="qux.png"/>') 327 328 test('<div>x</div>', 329 '<div style="background:url(http://example.org/login)">x</div>') 330 test('<div style="background:url(http://example.net/1.png)">x</div>', 331 '<div style="background:url(http://example.net/1.png)">x</div>') 332 test('<div>x</div>', 333 '<div style="background:url(http://example.net:443/1.png)">' 334 'x</div>') 335 test('<div style="background:url(data:image/png,...)">x</div>', 336 '<div style="background:url(data:image/png,...)">x</div>') 337 test('<div>x</div>', 338 '<div style="background:url(//example.net/foo.png)">x</div>') 339 test('<div style="background:url(/path/to/foo.png)">safe</div>', 340 '<div style="background:url(/path/to/foo.png)">safe</div>') 341 test('<div style="background:url(../../bar.png)">safe</div>', 342 '<div style="background:url(../../bar.png)">safe</div>') 343 test('<div style="background:url(qux.png)">safe</div>', 344 '<div style="background:url(qux.png)">safe</div>')
345
347 test = self._assert_sanitize 348 test('<p>&amp;hellip;</p>', '<p>&amp;hellip;</p>') 349 test('<p>&amp;</p>', '<p>&amp;</p>') 350 test('<p>&amp;</p>', '<p>&</p>') 351 test('<p>&amp;&lt;&gt;</p>', '<p>&<></p>') 352 test('<p>&amp;&amp;</p>', '<p>&amp;&amp;</p>') 353 test('<p>&amp;\u2026</p>', '<p>&amp;&hellip;</p>') 354 test("<p>&amp;unknown;</p>", '<p>&unknown;</p>') 355 test("<p>\U0010ffff</p>", '<p>&#1114111;</p>') 356 test("<p>\U0010ffff</p>", '<p>&#x10ffff;</p>') 357 test("<p>\U0010ffff</p>", '<p>&#X10FFFF;</p>') 358 test("<p>&amp;#1114112;</p>", '<p>&#1114112;</p>') 359 test("<p>&amp;#x110000;</p>", '<p>&#x110000;</p>') 360 test("<p>&amp;#X110000;</p>", '<p>&#X110000;</p>') 361 test("<p>&amp;#abcd;</p>", '<p>&#abcd;</p>') 362 test('<p>&amp;#%d;</p>' % (sys.maxint + 1), 363 '<p>&#%d;</p>' % (sys.maxint + 1))
364
366 self._assert_sanitize('<img title="&amp;"/>', '<img title="&amp;"/>') 367 self._assert_sanitize('''<img title="&amp;&lt;&gt;&#34;'"/>''', 368 '''<img title="&amp;&lt;&gt;&quot;&apos;"/>''') 369 self._assert_sanitize('''<img title="&amp;&lt;&gt;&#34;'"/>''', 370 '''<img title="&#38;&#60;&#62;&#34;&#39;"/>''') 371 self._assert_sanitize("""<img title="&amp;&lt;&gt;'"/>""", 372 """<img title="&<>'"/>""") 373 self._assert_sanitize('<img title="&amp;&amp;"/>', 374 '<img title="&amp;&amp;"/>') 375 self._assert_sanitize('<img title="&amp;\u2026"/>', 376 '<img title="&amp;&hellip;"/>') 377 self._assert_sanitize('<img title="&amp;hellip;"/>', 378 '<img title="&amp;hellip;"/>') 379 self._assert_sanitize('<img title="&amp;unknown;"/>', 380 '<img title="&unknown;"/>') 381 self._assert_sanitize('<img title="\U0010ffff"/>', 382 '<img title="&#1114111;"/>') 383 self._assert_sanitize('<img title="\U0010ffff"/>', 384 '<img title="&#x10ffff;"/>') 385 self._assert_sanitize('<img title="\U0010ffff"/>', 386 '<img title="&#X10FFFF;"/>') 387 self._assert_sanitize('<img title="&amp;#1114112;"/>', 388 '<img title="&#1114112;"/>') 389 self._assert_sanitize('<img title="&amp;#x110000;"/>', 390 '<img title="&#x110000;"/>') 391 self._assert_sanitize('<img title="&amp;#X110000;"/>', 392 '<img title="&#X110000;"/>') 393 self._assert_sanitize('<img title="&amp;#abcd;"/>', 394 '<img title="&#abcd;"/>') 395 self._assert_sanitize('<img title="&amp;#%d;"/>' % (sys.maxint + 1), 396 '<img title="&#%d;"/>' % (sys.maxint + 1))
397
398 - def _assert_sanitize(self, expected, content):
399 self.assertEqual(expected, self.sanitize(content))
400
401 402 -class TracHTMLSanitizerTestCase(TracHTMLSanitizerTestCaseBase):
403
405 test = self._assert_sanitize 406 test("<p>&amp;&lt;&gt;&#34;'</p>", 407 '<p>&amp;&lt;&gt;&quot;&apos;</p>') 408 test("<p>&amp;&lt;&gt;&#34;'</p>", 409 '<p>&#38;&#60;&#62;&#34;&#39;</p>')
410 411 412 if genshi:
413 - class TracHTMLSanitizerLegacyGenshiTestCase(TracHTMLSanitizerTestCaseBase):
414 - def sanitize(self, html):
415 sanitizer = TracHTMLSanitizer(safe_schemes=self.safe_schemes, 416 safe_origins=self.safe_origins) 417 return unicode(HTML(html, encoding='utf-8') | sanitizer)
418
420 test = self._assert_sanitize 421 test('''<p>&amp;&lt;&gt;"'</p>''', 422 '<p>&amp;&lt;&gt;&quot;&apos;</p>') 423 test('''<p>&amp;&lt;&gt;"'</p>''', 424 '<p>&#38;&#60;&#62;&#34;&#39;</p>')
425
426 427 -class FindElementTestCase(unittest.TestCase):
428
430 frag = tag(tag.p('Paragraph with a ', 431 tag.a('link', href='http://www.edgewall.org'), 432 ' and some ', tag.strong('strong text'))) 433 self.assertIsNotNone(find_element(frag, tag='p')) 434 result = find_element(frag, tag='a') 435 self.assertIsNotNone(result) 436 self.assertEqual('<a href="http://www.edgewall.org">link</a>', 437 str(result)) 438 result = find_element(frag, tag='strong') 439 self.assertIsNotNone(result) 440 self.assertEqual('<strong>strong text</strong>', str(result)) 441 self.assertIsNone(find_element(frag, tag='input')) 442 self.assertIsNone(find_element(frag, tag='textarea'))
443 444 @unittest.skipUnless(LazyProxy, 'Babel unavailable')
446 lazyproxy = tgettext('Text with a %(a)s and some %(b)s', 447 a=tag.a('link', href='http://www.edgewall.org'), 448 b=tag.strong('strong text')) 449 self.assertIsNotNone(find_element(lazyproxy, tag='a')) 450 self.assertEqual('<a href="http://www.edgewall.org">link</a>', 451 str(find_element(lazyproxy, tag='a'))) 452 result = find_element(lazyproxy, tag='strong') 453 self.assertIsNotNone(result) 454 self.assertEqual('<strong>strong text</strong>', str(result)) 455 self.assertIsNone(find_element(lazyproxy, tag='input')) 456 self.assertIsNone(find_element(lazyproxy, tag='textarea'))
457
458 459 -class IsSafeOriginTestCase(unittest.TestCase):
460
461 - def test_schemes(self):
462 uris = ['data:', 'https:'] 463 self.assertTrue(is_safe_origin(uris, 'data:text/plain,blah')) 464 self.assertFalse(is_safe_origin(uris, 'http://127.0.0.1/')) 465 self.assertTrue(is_safe_origin(uris, 'https://127.0.0.1/')) 466 self.assertFalse(is_safe_origin(uris, 'blob:')) 467 self.assertTrue(is_safe_origin(uris, '/path/to')) 468 self.assertTrue(is_safe_origin(uris, 'file.txt'))
469
470 - def test_wild_card(self):
471 uris = ['*'] 472 self.assertTrue(is_safe_origin(uris, 'data:text/plain,blah')) 473 self.assertTrue(is_safe_origin(uris, 'http://127.0.0.1/')) 474 self.assertTrue(is_safe_origin(uris, 'https://127.0.0.1/')) 475 self.assertTrue(is_safe_origin(uris, 'blob:')) 476 self.assertTrue(is_safe_origin(uris, '/path/to')) 477 self.assertTrue(is_safe_origin(uris, 'file.txt'))
478
479 - def test_hostname(self):
480 uris = ['https://example.org/', 'http://example.net'] 481 self.assertFalse(is_safe_origin(uris, 'data:text/plain,blah')) 482 self.assertTrue(is_safe_origin(uris, 'https://example.org')) 483 self.assertTrue(is_safe_origin(uris, 'https://example.org/')) 484 self.assertTrue(is_safe_origin(uris, 'https://example.org/path/')) 485 self.assertTrue(is_safe_origin(uris, 'http://example.net')) 486 self.assertTrue(is_safe_origin(uris, 'http://example.net/')) 487 self.assertTrue(is_safe_origin(uris, 'http://example.net/path')) 488 self.assertFalse(is_safe_origin(uris, 'https://example.com')) 489 self.assertFalse(is_safe_origin(uris, 'blob:')) 490 self.assertTrue(is_safe_origin(uris, '/path/to')) 491 self.assertTrue(is_safe_origin(uris, 'file.txt'))
492
493 - def test_path(self):
494 uris = ['https://example.org/path/to', 'http://example.net/path/to/'] 495 self.assertFalse(is_safe_origin(uris, 'https://example.org')) 496 self.assertFalse(is_safe_origin(uris, 'https://example.org/')) 497 self.assertFalse(is_safe_origin(uris, 'https://example.org/path')) 498 self.assertFalse(is_safe_origin(uris, 'https://example.org/path/')) 499 self.assertTrue(is_safe_origin(uris, 'https://example.org/path/to')) 500 self.assertTrue(is_safe_origin(uris, 'https://example.org/path/to/')) 501 self.assertTrue(is_safe_origin( 502 uris, 'https://example.org/path/to/image.png')) 503 self.assertFalse(is_safe_origin(uris, 'http://example.net')) 504 self.assertFalse(is_safe_origin(uris, 'http://example.net/')) 505 self.assertFalse(is_safe_origin(uris, 'http://example.net/path')) 506 self.assertFalse(is_safe_origin(uris, 'http://example.net/path/')) 507 self.assertFalse(is_safe_origin(uris, 'http://example.net/path/to')) 508 self.assertTrue(is_safe_origin(uris, 'http://example.net/path/to/')) 509 self.assertTrue(is_safe_origin( 510 uris, 'http://example.net/path/to/image.png')) 511 self.assertFalse(is_safe_origin(uris, 'blob:')) 512 self.assertTrue(is_safe_origin(uris, '/path/to')) 513 self.assertTrue(is_safe_origin(uris, 'file.txt'))
514
515 516 -class PlaintextTestCase(unittest.TestCase):
517
518 - def test_plaintext_string(self):
519 self.assertEqual('Back to &<>"\'', 520 plaintext('Back to &amp;&lt;&gt;&#34;&#39;'))
521
522 - def test_plaintext_fragment(self):
523 fragment = tag('Back to ', tag.span('&amp;&lt;&gt;&#34;&#39;')) 524 self.assertEqual('Back to &amp;&lt;&gt;&#34;&#39;', 525 plaintext(fragment))
526 527 @unittest.skipUnless(LazyProxy, 'Babel unavailable')
529 lazyproxy = gettext('Back to %(parent)s', 530 parent='&amp;&lt;&gt;&#34;&#39;') 531 self.assertEqual('Back to &<>"\'', plaintext(lazyproxy))
532 533 @unittest.skipUnless(LazyProxy, 'Babel unavailable')
535 lazyproxy = tgettext('Back to %(parent)s', 536 parent=tag.span('&amp;&lt;&gt;&#34;&#39;')) 537 self.assertEqual('Back to &amp;&lt;&gt;&#34;&#39;', 538 plaintext(lazyproxy))
539
540 541 -class ToFragmentTestCase(unittest.TestCase):
542
543 - def test_unicode(self):
544 rv = to_fragment('blah') 545 self.assertEqual(Fragment, type(rv)) 546 self.assertEqual('blah', unicode(rv))
547
548 - def test_fragment(self):
549 rv = to_fragment(tag('blah')) 550 self.assertEqual(Fragment, type(rv)) 551 self.assertEqual('blah', unicode(rv))
552
553 - def test_element(self):
554 rv = to_fragment(tag.p('blah')) 555 self.assertEqual(Element, type(rv)) 556 self.assertEqual('<p>blah</p>', unicode(rv))
557
558 - def test_tracerror(self):
559 rv = to_fragment(TracError('blah')) 560 self.assertEqual(Fragment, type(rv)) 561 self.assertEqual('blah', unicode(rv))
562
564 message = tag('Powered by ', 565 tag.a('Trac', href='https://trac.edgewall.org/')) 566 rv = to_fragment(TracError(message)) 567 self.assertEqual(Fragment, type(rv)) 568 self.assertEqual('Powered by <a href="https://trac.edgewall.org/">Trac' 569 '</a>', unicode(rv))
570
572 message = tag.p('Powered by ', 573 tag.a('Trac', href='https://trac.edgewall.org/')) 574 rv = to_fragment(TracError(message)) 575 self.assertEqual(Element, type(rv)) 576 self.assertEqual('<p>Powered by <a href="https://trac.edgewall.org/">' 577 'Trac</a></p>', unicode(rv))
578
580 message = tag('Powered by ', 581 tag.a('Trac', href='https://trac.edgewall.org/')) 582 rv = to_fragment(TracError(TracError(message))) 583 self.assertEqual(Fragment, type(rv)) 584 self.assertEqual('Powered by <a href="https://trac.edgewall.org/">Trac' 585 '</a>', unicode(rv))
586
588 message = tag.p('Powered by ', 589 tag.a('Trac', href='https://trac.edgewall.org/')) 590 rv = to_fragment(TracError(TracError(message))) 591 self.assertEqual(Element, type(rv)) 592 self.assertEqual('<p>Powered by <a href="https://trac.edgewall.org/">' 593 'Trac</a></p>', unicode(rv))
594
595 - def test_error(self):
596 rv = to_fragment(ValueError('invalid literal for int(): blah')) 597 self.assertEqual(Fragment, type(rv)) 598 self.assertEqual('invalid literal for int(): blah', unicode(rv))
599
600 - def test_error_with_fragment(self):
601 rv = to_fragment(ValueError(tag('invalid literal for int(): ', 602 tag.b('blah')))) 603 self.assertEqual(Fragment, type(rv)) 604 self.assertEqual('invalid literal for int(): <b>blah</b>', unicode(rv))
605
607 v1 = ValueError(tag('invalid literal for int(): ', tag.b('blah'))) 608 rv = to_fragment(ValueError(v1)) 609 self.assertEqual(Fragment, type(rv)) 610 self.assertEqual('invalid literal for int(): <b>blah</b>', unicode(rv))
611
612 - def test_gettext(self):
613 rv = to_fragment(gettext('%(size)s bytes', size=0)) 614 self.assertEqual(Fragment, type(rv)) 615 self.assertEqual('0 bytes', unicode(rv))
616
617 - def test_tgettext(self):
618 rv = to_fragment(tgettext('Back to %(parent)s', 619 parent=tag.a('WikiStart', 620 href='http://localhost/'))) 621 self.assertEqual(Fragment, type(rv)) 622 self.assertEqual('Back to <a href="http://localhost/">WikiStart</a>', 623 unicode(rv))
624
626 e = TracError(gettext('%(size)s bytes', size=0)) 627 rv = to_fragment(e) 628 self.assertEqual(Fragment, type(rv)) 629 self.assertEqual('0 bytes', unicode(rv))
630
632 e = TracError(tgettext('Back to %(parent)s', 633 parent=tag.a('WikiStart', 634 href='http://localhost/'))) 635 rv = to_fragment(e) 636 self.assertEqual(Fragment, type(rv)) 637 self.assertEqual('Back to <a href="http://localhost/">WikiStart</a>', 638 unicode(rv))
639
640 - def _ioerror(self, filename):
641 try: 642 open(filename) 643 except IOError as e: 644 return e 645 else: 646 self.fail('IOError not raised')
647
648 - def test_ioerror(self):
649 rv = to_fragment(self._ioerror(b'./notfound')) 650 self.assertEqual(Fragment, type(rv)) 651 self.assertEqual("[Errno 2] No such file or directory: './notfound'", 652 unicode(rv))
653
654 - def test_error_with_ioerror(self):
655 e = self._ioerror(b'./notfound') 656 rv = to_fragment(ValueError(e)) 657 self.assertEqual(Fragment, type(rv)) 658 self.assertEqual("[Errno 2] No such file or directory: './notfound'", 659 unicode(rv))
660
661 662 -def test_suite():
663 suite = unittest.TestSuite() 664 suite.addTest(doctest.DocTestSuite(html)) 665 suite.addTest(unittest.makeSuite(EscapeFragmentTestCase)) 666 suite.addTest(unittest.makeSuite(HtmlAttributeTestCase)) 667 suite.addTest(unittest.makeSuite(FragmentTestCase)) 668 suite.addTest(unittest.makeSuite(XMLElementTestCase)) 669 suite.addTest(unittest.makeSuite(ElementTestCase)) 670 suite.addTest(unittest.makeSuite(FormTokenInjectorTestCase)) 671 suite.addTest(unittest.makeSuite(TracHTMLSanitizerTestCase)) 672 if genshi: 673 suite.addTest(unittest.makeSuite(TracHTMLSanitizerLegacyGenshiTestCase)) 674 suite.addTest(unittest.makeSuite(FindElementTestCase)) 675 suite.addTest(unittest.makeSuite(IsSafeOriginTestCase)) 676 suite.addTest(unittest.makeSuite(PlaintextTestCase)) 677 suite.addTest(unittest.makeSuite(ToFragmentTestCase)) 678 return suite
679 680 681 if __name__ == '__main__': 682 unittest.main(defaultTest='test_suite') 683