hello' 31 | ), 32 | ('[code]hello :test:[/code]', '
hello :test:'),
33 | )
34 |
35 | def create_smilies(self):
36 | self.parser = get_parser()
37 | self.parser_loader = BBCodeParserLoader(parser=self.parser)
38 | # Set up an image used for doing smilies tests
39 | f = open(settings.MEDIA_ROOT + '/icon_e_wink.gif', 'rb')
40 | image_file = File(f)
41 | self.image = image_file
42 | # Set up a smiley tag
43 | smiley = SmileyTag()
44 | smiley.code = ':test:'
45 | smiley.image.save('icon_e_wink.gif', self.image)
46 | smiley.save()
47 | self.parser_loader.init_bbcode_smilies()
48 |
49 | def teardown_method(self, method):
50 | self.image.close()
51 | shutil.rmtree(settings.MEDIA_ROOT + '/precise_bbcode')
52 |
53 | def test_can_render_valid_smilies(self):
54 | # Setup
55 | self.create_smilies()
56 | # Run & check
57 | for bbcodes_text, expected_html_text in self.SMILIES_TESTS:
58 | result = self.parser.render(bbcodes_text)
59 | assert result == expected_html_text
60 |
--------------------------------------------------------------------------------
/example_project/test_messages/bbcode_tags.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from precise_bbcode.bbcode.tag import BBCodeTag
4 | from precise_bbcode.tag_pool import tag_pool
5 |
6 |
7 | color_re = re.compile(r'^([a-z]+|#[0-9abcdefABCDEF]{3,6})$')
8 |
9 |
10 | class SubTag(BBCodeTag):
11 | name = 'sub'
12 |
13 | def render(self, value, option=None, parent=None):
14 | return '%s' % value
15 |
16 |
17 | class PreTag(BBCodeTag):
18 | name = 'pre'
19 | render_embedded = False
20 |
21 | def render(self, value, option=None, parent=None):
22 | return '%s' % value 23 | 24 | 25 | class SizeTag(BBCodeTag): 26 | name = 'size' 27 | definition_string = '[size={RANGE=4,7}]{TEXT}[/size]' 28 | format_string = '{TEXT}' 29 | 30 | 31 | class FruitTag(BBCodeTag): 32 | name = 'fruit' 33 | definition_string = '[fruit]{CHOICE=tomato,orange,apple}[/fruit]' 34 | format_string = '
{TEXT}' 72 | 73 | class Options: 74 | strip = True 75 | 76 | 77 | class CodeBBCodeTag(BBCodeTag): 78 | name = 'code' 79 | definition_string = '[code]{TEXT}[/code]' 80 | format_string = '
{TEXT}'
81 |
82 | class Options:
83 | render_embedded = False
84 |
85 |
86 | class CenterBBCodeTag(BBCodeTag):
87 | name = 'center'
88 | definition_string = '[center]{TEXT}[/center]'
89 | format_string = '
'
65 | ),
66 | (
67 | '[img]fake.png" onerror="alert(String.fromCharCode(88,83,83))[/img]',
68 | '[img]fake.png" onerror"alert(String.fromCharCode(88,83,83))[/img]'
69 | ),
70 | (
71 | '[img]http://foo.com/fake.png [img] '
72 | 'onerror=javascript:alert(String.fromCharCode(88,83,83)) [/img] [/img]',
73 | '[img]http://foo.com/fake.png [img] '
74 | 'onerrorjavascript:alert(String.fromCharCode(88,83,83)) [/img] [/img]'
75 | ),
76 | ('[quote] \r\nhello\nworld! [/quote]', 'hello'), 77 | ('[code][b]hello world![/b][/code]', '
world!
[b]hello world![/b]'),
78 | (
79 | '[color=green]goto [url=google.com]google website[/url][/color]',
80 | 'goto google website'
81 | ),
82 | ('[color=#FFFFFF]white[/color]', 'white'),
83 | (
84 | '[color=]xss[/color]',
85 | '[color=<script></script>]xss[/color]'
86 | ),
87 | ('[COLOR=blue]hello world![/color]', 'hello world!'),
88 | (
89 | '[color=#ff0000;font-size:100px;]XSS[/color]',
90 | '[color=#ff0000;font-size:100px;]XSS[/color]'
91 | ),
92 | (
93 | '[color=#ff0000;xss:expression(alert(String.fromCharCode(88,83,83)));]XSS[/color]',
94 | '[color=#ff0000;xss:expression(alert(String.fromCharCode(88,83,83)));]XSS[/color]'
95 | ),
96 | ('[', '['),
97 | # BBCodes with syntactic errors
98 | ('[b]z sdf s s', '[b]z sdf s s'),
99 | ('[b][i]hello world![/b][/i]', '[i]hello world![/i]'),
100 | ('[b]hello [i]world![/i]', '[b]hello world!'),
101 | ('[color]test[/color]', '[color]test[/color]'),
102 | ('[/abcdef][/i]', '[/abcdef][/i]'),
103 | ('[b\n hello [i]the[/i] world![/b]', '[b{}'.format(value)
31 |
32 |
33 | class FooTagAlt(ParserBBCodeTag):
34 | name = 'fooalt'
35 |
36 | class Options:
37 | render_embedded = False
38 |
39 | def render(self, value, option=None, parent=None):
40 | return '{}'.format(value)
41 |
42 |
43 | class FooTagSub(ParserBBCodeTag):
44 | name = 'foo2'
45 |
46 | class Options:
47 | render_embedded = False
48 |
49 |
50 | class BarTag(ParserBBCodeTag):
51 | name = 'bar'
52 |
53 | def render(self, value, option=None, parent=None):
54 | if not option:
55 | return ''.format(value)
56 | return ''.format(option, value)
57 |
58 |
59 | @pytest.mark.django_db
60 | class TestBbcodeTagPool(object):
61 | TAGS_TESTS = (
62 | ('[fooalt]hello world![/fooalt]', 'hello world!'), 63 | ('[bar]hello world![/bar]', ''), 64 | ('[fooalt]hello [bar]world![/bar][/fooalt]', '
hello [bar]world![/bar]'), 65 | ( 66 | '[bar]hello [fooalt]world![/fooalt][/bar]', 67 | '' 68 | ), 69 | ('[bar]안녕하세요![/bar]', ''), 70 | ) 71 | 72 | def setup_method(self, method): 73 | self.parser = get_parser() 74 | 75 | def test_should_raise_if_a_tag_is_registered_twice(self): 76 | # Setup 77 | number_of_tags_before = len(tag_pool.get_tags()) 78 | tag_pool.register_tag(FooTag) 79 | # Run & check 80 | # Let's add it a second time. We should catch an exception 81 | with pytest.raises(TagAlreadyRegistered): 82 | tag_pool.register_tag(FooTag) 83 | # Let's make sure we have the same number of tags as before 84 | tag_pool.unregister_tag(FooTag) 85 | number_of_tags_after = len(tag_pool.get_tags()) 86 | assert number_of_tags_before == number_of_tags_after 87 | 88 | def test_cannot_register_tags_with_incorrect_parent_classes(self): 89 | # Setup 90 | number_of_tags_before = len(tag_pool.get_tags()) 91 | # Run & check 92 | with pytest.raises(ImproperlyConfigured): 93 | class ErrnoneousTag4: 94 | pass 95 | tag_pool.register_tag(ErrnoneousTag4) 96 | number_of_tags_after = len(tag_pool.get_tags()) 97 | assert number_of_tags_before == number_of_tags_after 98 | 99 | def test_cannot_register_tags_that_are_already_stored_in_the_database(self): 100 | # Setup 101 | BBCodeTag.objects.create( 102 | tag_definition='[tt]{TEXT}[/tt]', html_replacement='{TEXT}') 103 | # Run 104 | with pytest.raises(TagAlreadyCreated): 105 | class ErrnoneousTag9(ParserBBCodeTag): 106 | name = 'tt' 107 | definition_string = '[tt]{TEXT}[/tt]' 108 | format_string = '{TEXT}' 109 | tag_pool.register_tag(ErrnoneousTag9) 110 | 111 | def test_cannot_unregister_a_non_registered_tag(self): 112 | # Setup 113 | number_of_tags_before = len(tag_pool.get_tags()) 114 | # Run & check 115 | with pytest.raises(TagNotRegistered): 116 | tag_pool.unregister_tag(FooTagSub) 117 | number_of_tags_after = len(tag_pool.get_tags()) 118 | assert number_of_tags_before == number_of_tags_after 119 | 120 | def test_tags_can_be_rendered(self): 121 | # Setup 122 | parser_loader = BBCodeParserLoader(parser=self.parser) 123 | tag_pool.register_tag(FooTagAlt) 124 | tag_pool.register_tag(BarTag) 125 | parser_loader.init_bbcode_tags() 126 | # Run & check 127 | for bbcodes_text, expected_html_text in self.TAGS_TESTS: 128 | result = self.parser.render(bbcodes_text) 129 | assert result == expected_html_text 130 | 131 | def test_can_disable_builtin_tags(self): 132 | # Setup 133 | bbcode_settings.BBCODE_DISABLE_BUILTIN_TAGS = True 134 | parser_loader = BBCodeParserLoader(parser=BBCodeParser()) 135 | # Run & check 136 | parser_loader.load_parser() 137 | import precise_bbcode.bbcode.defaults.tag 138 | for tag_klass in get_subclasses(precise_bbcode.bbcode.defaults.tag, ParserBBCodeTag): 139 | assert tag_klass.name not in parser_loader.parser.bbcodes 140 | bbcode_settings.BBCODE_DISABLE_BUILTIN_TAGS = False 141 | 142 | 143 | @pytest.mark.django_db 144 | class TestBbcodeTag(object): 145 | def setup_method(self, method): 146 | self.parser = get_parser() 147 | 148 | def test_that_are_invalid_should_raise_at_runtime(self): 149 | # Run & check 150 | with pytest.raises(InvalidBBCodeTag): 151 | class ErrnoneousTag1(ParserBBCodeTag): 152 | pass 153 | with pytest.raises(InvalidBBCodeTag): 154 | class ErrnoneousTag2(ParserBBCodeTag): 155 | delattr(ParserBBCodeTag, 'name') 156 | with pytest.raises(InvalidBBCodeTag): 157 | class ErrnoneousTag3(ParserBBCodeTag): 158 | name = 'it\'s a bad tag name' 159 | with pytest.raises(InvalidBBCodeTag): 160 | class ErrnoneousTag4(ParserBBCodeTag): 161 | name = 'ooo' 162 | definition_string = '[ooo]{TEXT}[/ooo]' 163 | with pytest.raises(InvalidBBCodeTag): 164 | class ErrnoneousTag5(ParserBBCodeTag): 165 | name = 'ooo' 166 | definition_string = 'bad definition' 167 | format_string = 'bad format string' 168 | with pytest.raises(InvalidBBCodeTag): 169 | class ErrnoneousTag6(ParserBBCodeTag): 170 | name = 'ooo' 171 | definition_string = '[ooo]{TEXT}[/aaa]' 172 | format_string = 'bad format string' 173 | with pytest.raises(InvalidBBCodeTag): 174 | class ErrnoneousTag7(ParserBBCodeTag): 175 | name = 'ooo' 176 | definition_string = '[ooo]{TEXT}[/ooo]' 177 | format_string = '' 178 | with pytest.raises(InvalidBBCodeTag): 179 | class ErrnoneousTag8(ParserBBCodeTag): 180 | name = 'ooo' 181 | definition_string = '[ooo={TEXT}]{TEXT}[/ooo]' 182 | format_string = '{TEXT}' 183 | 184 | def test_containing_invalid_placeholders_should_raise_during_rendering(self): 185 | # Setup 186 | class TagWithInvalidPlaceholders(ParserBBCodeTag): 187 | name = 'bad' 188 | definition_string = '[bad]{FOOD}[/bad]' 189 | format_string = '{FOOD}' 190 | self.parser.add_bbcode_tag(TagWithInvalidPlaceholders) 191 | # Run 192 | with pytest.raises(InvalidBBCodePlaholder): 193 | self.parser.render('[bad]apple[/bad]') 194 | 195 | 196 | @pytest.mark.django_db 197 | class TestDbBbcodeTag(object): 198 | ERRONEOUS_TAGS_TESTS = ( 199 | {'tag_definition': '[tag]', 'html_replacement': ''}, 200 | {'tag_definition': 'it\'s not a tag', 'html_replacement': ''}, 201 | {'tag_definition': '[first]{TEXT1}[/end]', 'html_replacement': '
{TEXT1}
'}, 202 | {'tag_definition': '[t2y={TEXT1}]{TEXT1}[/t2y]', 'html_replacement': '{TEXT1}'}, 203 | { 204 | 'tag_definition': '[tag2]{TEXT1}[/tag2]', 205 | 'html_replacement': '{TEXT1}
', 206 | 'standalone': True 207 | }, 208 | {'tag_definition': '[start]{TEXT1}[/end]', 'html_replacement': '{TEXT1}
'}, 209 | {'tag_definition': '[start]{TEXT1}[/end]', 'html_replacement': '{TEXT1}
'}, 210 | {'tag_definition': '[start]{TEXT1}[/end]', 'html_replacement': '{TEXT2}
'}, 211 | { 212 | 'tag_definition': '[start={TEXT1}]{TEXT1}[/end]', 213 | 'html_replacement': '{TEXT1}
' 214 | }, 215 | { 216 | 'tag_definition': '[justify]{TEXT1}[/justify]', 217 | 'html_replacement': '' 218 | }, 219 | { 220 | 'tag_definition': '[center][/center]', 221 | 'html_replacement': '{TEXT}'},
247 | {
248 | 'tag_definition': '[pre2={COLOR}]{TEXT1}[/pre2]',
249 | 'html_replacement': '{TEXT1}'
250 | },
251 | {'tag_definition': '[hrcustom]', 'html_replacement': '{TEXT}',
270 | 'newline_closes': True
271 | },
272 | {
273 | 'tag_definition': '[pre4]{TEXT}[/pre4]',
274 | 'html_replacement': '{TEXT}',
275 | 'same_tag_closes': True
276 | },
277 | {
278 | 'tag_definition': '[troll]{TEXT}[/troll]',
279 | 'html_replacement': '{TEXT}'}
354 | tag = BBCodeTag(**tag_dict)
355 | tag.save()
356 | # Run
357 | tag.html_replacement = '{TEXT}'
358 | # Check
359 | try:
360 | tag.clean()
361 | except ValidationError:
362 | self.fail('The following BBCode failed to validate: {}'.format(tag_dict))
363 |
364 | def test_should_allow_tag_creation_after_the_deletion_of_another_tag_with_the_same_name(self):
365 | # Setup
366 | tag_dict = {'tag_definition': '[pr]{TEXT}[/pr]', 'html_replacement': '{TEXT}'}
367 | tag = BBCodeTag(**tag_dict)
368 | tag.save()
369 | tag.delete()
370 | new_tag = BBCodeTag(**tag_dict)
371 | # Run & check
372 | try:
373 | new_tag.clean()
374 | except ValidationError:
375 | self.fail('The following BBCode failed to validate: {}'.format(tag_dict))
376 |
377 | def test_should_allow_tag_creation_after_the_bulk_deletion_of_another_tag_with_the_same_name_in_the_admin(self): # noqa
378 | # Setup
379 | tag_dict = {'tag_definition': '[pr2]{TEXT}[/pr2]', 'html_replacement': '{TEXT}'}
380 | tag = BBCodeTag(**tag_dict)
381 | tag.save()
382 |
383 | admin_user = User.objects.create_user('admin', 'admin@admin.io', 'adminpass')
384 | admin_user.is_staff = True
385 | admin_user.is_superuser = True
386 | admin_user.save()
387 | client = Client()
388 | client.login(username='admin', password='adminpass')
389 | url = reverse('admin:precise_bbcode_bbcodetag_changelist')
390 | client.post(url, data={
391 | 'action': 'delete_selected', '_selected_action': [tag.pk, ], 'post': 'yes'})
392 |
393 | new_tag = BBCodeTag(**tag_dict)
394 | # Run & check
395 | try:
396 | new_tag.clean()
397 | except ValidationError:
398 | self.fail('The following BBCode failed to validate: {}'.format(tag_dict))
399 |
400 | def test_should_save_default_bbcode_tags_rewrites(self):
401 | # Setup
402 | tag = BBCodeTag(tag_definition='[b]{TEXT1}[/b]', html_replacement='{TEXT1}')
403 | # Run & check
404 | try:
405 | tag.clean()
406 | except ValidationError:
407 | self.fail('The following BBCode failed to validate: {}'.format(tag))
408 |
409 | def test_should_provide_the_required_parser_bbcode_tag_class(self):
410 | # Setup
411 | tag = BBCodeTag(
412 | **{'tag_definition': '[io]{TEXT}[/io]', 'html_replacement': '{TEXT}'}
413 | )
414 | tag.save()
415 | # Run & check
416 | parser_tag_klass = tag.parser_tag_klass
417 | assert issubclass(parser_tag_klass, ParserBBCodeTag)
418 | assert parser_tag_klass.name == 'io'
419 | assert parser_tag_klass.definition_string == '[io]{TEXT}[/io]'
420 | assert parser_tag_klass.format_string == '{TEXT}'
421 |
422 | def test_can_be_rendered_by_the_bbcode_parser(self):
423 | # Setup
424 | parser_loader = BBCodeParserLoader(parser=self.parser)
425 | tag = BBCodeTag(
426 | **{
427 | 'tag_definition': '[mail]{EMAIL}[/mail]',
428 | 'html_replacement': '{EMAIL}',
429 | 'swallow_trailing_newline': True
430 | }
431 | )
432 | tag.save()
433 | parser_loader.init_custom_bbcode_tags()
434 | # Run & check
435 | assert (
436 | self.parser.render('[mail]xyz@xyz.com[/mail]') ==
437 | 'xyz@xyz.com'
438 | )
439 |
--------------------------------------------------------------------------------