##// END OF EJS Templates
Escape html inside [code]
neko259 -
r1621:c6184d71 default
parent child Browse files
Show More
@@ -1,256 +1,256
1 # coding=utf-8
1 # coding=utf-8
2
2
3 import re
3 import re
4 import random
4 import random
5 import bbcode
5 import bbcode
6
6
7 from urllib.parse import unquote
7 from urllib.parse import unquote
8
8
9 from django.core.exceptions import ObjectDoesNotExist
9 from django.core.exceptions import ObjectDoesNotExist
10 from django.core.urlresolvers import reverse
10 from django.core.urlresolvers import reverse
11
11
12 import boards
12 import boards
13
13
14
14
15 __author__ = 'neko259'
15 __author__ = 'neko259'
16
16
17
17
18 REFLINK_PATTERN = re.compile(r'^\d+$')
18 REFLINK_PATTERN = re.compile(r'^\d+$')
19 GLOBAL_REFLINK_PATTERN = re.compile(r'(\w+)::([^:]+)::(\d+)')
19 GLOBAL_REFLINK_PATTERN = re.compile(r'(\w+)::([^:]+)::(\d+)')
20 MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
20 MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
21 ONE_NEWLINE = '\n'
21 ONE_NEWLINE = '\n'
22 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
22 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
23 LINE_BREAK_HTML = '<div class="br"></div>'
23 LINE_BREAK_HTML = '<div class="br"></div>'
24 SPOILER_SPACE = '&nbsp;'
24 SPOILER_SPACE = '&nbsp;'
25
25
26 MAX_SPOILER_MULTIPLIER = 2
26 MAX_SPOILER_MULTIPLIER = 2
27
27
28
28
29 class TextFormatter():
29 class TextFormatter():
30 """
30 """
31 An interface for formatter that can be used in the text format panel
31 An interface for formatter that can be used in the text format panel
32 """
32 """
33
33
34 def __init__(self):
34 def __init__(self):
35 pass
35 pass
36
36
37 name = ''
37 name = ''
38
38
39 # Left and right tags for the button preview
39 # Left and right tags for the button preview
40 preview_left = ''
40 preview_left = ''
41 preview_right = ''
41 preview_right = ''
42
42
43 # Left and right characters for the textarea input
43 # Left and right characters for the textarea input
44 format_left = ''
44 format_left = ''
45 format_right = ''
45 format_right = ''
46
46
47
47
48 class AutolinkPattern():
48 class AutolinkPattern():
49 def handleMatch(self, m):
49 def handleMatch(self, m):
50 link_element = etree.Element('a')
50 link_element = etree.Element('a')
51 href = m.group(2)
51 href = m.group(2)
52 link_element.set('href', href)
52 link_element.set('href', href)
53 link_element.text = href
53 link_element.text = href
54
54
55 return link_element
55 return link_element
56
56
57
57
58 class QuotePattern(TextFormatter):
58 class QuotePattern(TextFormatter):
59 name = '>q'
59 name = '>q'
60 preview_left = '<span class="quote">'
60 preview_left = '<span class="quote">'
61 preview_right = '</span>'
61 preview_right = '</span>'
62
62
63 format_left = '[quote]'
63 format_left = '[quote]'
64 format_right = '[/quote]'
64 format_right = '[/quote]'
65
65
66
66
67 class SpoilerPattern(TextFormatter):
67 class SpoilerPattern(TextFormatter):
68 name = 'spoiler'
68 name = 'spoiler'
69 preview_left = '<span class="spoiler">'
69 preview_left = '<span class="spoiler">'
70 preview_right = '</span>'
70 preview_right = '</span>'
71
71
72 format_left = '[spoiler]'
72 format_left = '[spoiler]'
73 format_right = '[/spoiler]'
73 format_right = '[/spoiler]'
74
74
75
75
76 class CommentPattern(TextFormatter):
76 class CommentPattern(TextFormatter):
77 name = ''
77 name = ''
78 preview_left = '<span class="comment">// '
78 preview_left = '<span class="comment">// '
79 preview_right = '</span>'
79 preview_right = '</span>'
80
80
81 format_left = '[comment]'
81 format_left = '[comment]'
82 format_right = '[/comment]'
82 format_right = '[/comment]'
83
83
84
84
85 # TODO Use <s> tag here
85 # TODO Use <s> tag here
86 class StrikeThroughPattern(TextFormatter):
86 class StrikeThroughPattern(TextFormatter):
87 name = 's'
87 name = 's'
88 preview_left = '<span class="strikethrough">'
88 preview_left = '<span class="strikethrough">'
89 preview_right = '</span>'
89 preview_right = '</span>'
90
90
91 format_left = '[s]'
91 format_left = '[s]'
92 format_right = '[/s]'
92 format_right = '[/s]'
93
93
94
94
95 class ItalicPattern(TextFormatter):
95 class ItalicPattern(TextFormatter):
96 name = 'i'
96 name = 'i'
97 preview_left = '<i>'
97 preview_left = '<i>'
98 preview_right = '</i>'
98 preview_right = '</i>'
99
99
100 format_left = '[i]'
100 format_left = '[i]'
101 format_right = '[/i]'
101 format_right = '[/i]'
102
102
103
103
104 class BoldPattern(TextFormatter):
104 class BoldPattern(TextFormatter):
105 name = 'b'
105 name = 'b'
106 preview_left = '<b>'
106 preview_left = '<b>'
107 preview_right = '</b>'
107 preview_right = '</b>'
108
108
109 format_left = '[b]'
109 format_left = '[b]'
110 format_right = '[/b]'
110 format_right = '[/b]'
111
111
112
112
113 class CodePattern(TextFormatter):
113 class CodePattern(TextFormatter):
114 name = 'code'
114 name = 'code'
115 preview_left = '<code>'
115 preview_left = '<code>'
116 preview_right = '</code>'
116 preview_right = '</code>'
117
117
118 format_left = '[code]'
118 format_left = '[code]'
119 format_right = '[/code]'
119 format_right = '[/code]'
120
120
121
121
122 def render_reflink(tag_name, value, options, parent, context):
122 def render_reflink(tag_name, value, options, parent, context):
123 result = '>>%s' % value
123 result = '>>%s' % value
124
124
125 post = None
125 post = None
126 if REFLINK_PATTERN.match(value):
126 if REFLINK_PATTERN.match(value):
127 post_id = int(value)
127 post_id = int(value)
128
128
129 try:
129 try:
130 post = boards.models.Post.objects.get(id=post_id)
130 post = boards.models.Post.objects.get(id=post_id)
131
131
132 except ObjectDoesNotExist:
132 except ObjectDoesNotExist:
133 pass
133 pass
134 elif GLOBAL_REFLINK_PATTERN.match(value):
134 elif GLOBAL_REFLINK_PATTERN.match(value):
135 match = GLOBAL_REFLINK_PATTERN.search(value)
135 match = GLOBAL_REFLINK_PATTERN.search(value)
136 try:
136 try:
137 global_id = boards.models.GlobalId.objects.get(
137 global_id = boards.models.GlobalId.objects.get(
138 key_type=match.group(1), key=match.group(2),
138 key_type=match.group(1), key=match.group(2),
139 local_id=match.group(3))
139 local_id=match.group(3))
140 post = global_id.post
140 post = global_id.post
141 except ObjectDoesNotExist:
141 except ObjectDoesNotExist:
142 pass
142 pass
143
143
144 if post is not None:
144 if post is not None:
145 result = post.get_link_view()
145 result = post.get_link_view()
146
146
147 return result
147 return result
148
148
149
149
150 def render_quote(tag_name, value, options, parent, context):
150 def render_quote(tag_name, value, options, parent, context):
151 source = ''
151 source = ''
152 if 'source' in options:
152 if 'source' in options:
153 source = options['source']
153 source = options['source']
154 elif 'quote' in options:
154 elif 'quote' in options:
155 source = options['quote']
155 source = options['quote']
156
156
157 if source:
157 if source:
158 result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
158 result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
159 else:
159 else:
160 # Insert a ">" at the start of every line
160 # Insert a ">" at the start of every line
161 result = '<span class="quote">&gt;{}</span>'.format(
161 result = '<span class="quote">&gt;{}</span>'.format(
162 value.replace(LINE_BREAK_HTML,
162 value.replace(LINE_BREAK_HTML,
163 '{}&gt;'.format(LINE_BREAK_HTML)))
163 '{}&gt;'.format(LINE_BREAK_HTML)))
164
164
165 return result
165 return result
166
166
167
167
168 def render_notification(tag_name, value, options, parent, content):
168 def render_notification(tag_name, value, options, parent, content):
169 username = value.lower()
169 username = value.lower()
170
170
171 return '<a href="{}" class="user-cast">@{}</a>'.format(
171 return '<a href="{}" class="user-cast">@{}</a>'.format(
172 reverse('notifications', kwargs={'username': username}), username)
172 reverse('notifications', kwargs={'username': username}), username)
173
173
174
174
175 def render_tag(tag_name, value, options, parent, context):
175 def render_tag(tag_name, value, options, parent, context):
176 tag_name = value.lower()
176 tag_name = value.lower()
177
177
178 try:
178 try:
179 url = boards.models.Tag.objects.get(name=tag_name).get_view()
179 url = boards.models.Tag.objects.get(name=tag_name).get_view()
180 except ObjectDoesNotExist:
180 except ObjectDoesNotExist:
181 url = tag_name
181 url = tag_name
182
182
183 return url
183 return url
184
184
185
185
186 def render_spoiler(tag_name, value, options, parent, context):
186 def render_spoiler(tag_name, value, options, parent, context):
187 text_len = len(value)
187 text_len = len(value)
188 space_count = random.randint(0, text_len * MAX_SPOILER_MULTIPLIER)
188 space_count = random.randint(0, text_len * MAX_SPOILER_MULTIPLIER)
189 side_spaces = SPOILER_SPACE * (space_count // 2)
189 side_spaces = SPOILER_SPACE * (space_count // 2)
190 return '<span class="spoiler">{}{}{}</span>'.format(side_spaces, value,
190 return '<span class="spoiler">{}{}{}</span>'.format(side_spaces, value,
191 side_spaces)
191 side_spaces)
192
192
193
193
194 formatters = [
194 formatters = [
195 QuotePattern,
195 QuotePattern,
196 SpoilerPattern,
196 SpoilerPattern,
197 ItalicPattern,
197 ItalicPattern,
198 BoldPattern,
198 BoldPattern,
199 CommentPattern,
199 CommentPattern,
200 StrikeThroughPattern,
200 StrikeThroughPattern,
201 CodePattern,
201 CodePattern,
202 ]
202 ]
203
203
204
204
205 PREPARSE_PATTERNS = {
205 PREPARSE_PATTERNS = {
206 r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
206 r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
207 r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
207 r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
208 r'^//(.+)': r'[comment]\1[/comment]', # Comment "//text"
208 r'^//(.+)': r'[comment]\1[/comment]', # Comment "//text"
209 r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
209 r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
210 }
210 }
211
211
212
212
213 class Parser:
213 class Parser:
214 def __init__(self):
214 def __init__(self):
215 # The newline hack is added because br's margin does not work in all
215 # The newline hack is added because br's margin does not work in all
216 # browsers except firefox, when the div's does.
216 # browsers except firefox, when the div's does.
217 self.parser = bbcode.Parser(newline=LINE_BREAK_HTML)
217 self.parser = bbcode.Parser(newline=LINE_BREAK_HTML)
218
218
219 self.parser.add_formatter('post', render_reflink, strip=True)
219 self.parser.add_formatter('post', render_reflink, strip=True)
220 self.parser.add_formatter('quote', render_quote, strip=True)
220 self.parser.add_formatter('quote', render_quote, strip=True)
221 self.parser.add_formatter('user', render_notification, strip=True)
221 self.parser.add_formatter('user', render_notification, strip=True)
222 self.parser.add_formatter('tag', render_tag, strip=True)
222 self.parser.add_formatter('tag', render_tag, strip=True)
223 self.parser.add_formatter('spoiler', render_spoiler, strip=True)
223 self.parser.add_formatter('spoiler', render_spoiler, strip=True)
224 self.parser.add_simple_formatter(
224 self.parser.add_simple_formatter(
225 'comment', '<span class="comment">//%(value)s</span>')
225 'comment', '<span class="comment">//%(value)s</span>')
226 self.parser.add_simple_formatter(
226 self.parser.add_simple_formatter(
227 's', '<span class="strikethrough">%(value)s</span>')
227 's', '<span class="strikethrough">%(value)s</span>')
228 self.parser.add_simple_formatter('code',
228 self.parser.add_simple_formatter('code',
229 '<pre><code>%(value)s</pre></code>',
229 '<pre><code>%(value)s</pre></code>',
230 render_embedded=False,
230 render_embedded=False,
231 escape_html=False,
231 escape_html=True,
232 replace_links=False,
232 replace_links=False,
233 replace_cosmetic=False)
233 replace_cosmetic=False)
234
234
235 def preparse(self, text):
235 def preparse(self, text):
236 """
236 """
237 Performs manual parsing before the bbcode parser is used.
237 Performs manual parsing before the bbcode parser is used.
238 Preparsed text is saved as raw and the text before preparsing is lost.
238 Preparsed text is saved as raw and the text before preparsing is lost.
239 """
239 """
240 new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text)
240 new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text)
241
241
242 for key, value in PREPARSE_PATTERNS.items():
242 for key, value in PREPARSE_PATTERNS.items():
243 new_text = re.sub(key, value, new_text, flags=re.MULTILINE)
243 new_text = re.sub(key, value, new_text, flags=re.MULTILINE)
244
244
245 for link in REGEX_URL.findall(text):
245 for link in REGEX_URL.findall(text):
246 new_text = new_text.replace(link, unquote(link))
246 new_text = new_text.replace(link, unquote(link))
247
247
248 return new_text
248 return new_text
249
249
250 def parse(self, text):
250 def parse(self, text):
251 return self.parser.format(text)
251 return self.parser.format(text)
252
252
253
253
254 parser = Parser()
254 parser = Parser()
255 def get_parser():
255 def get_parser():
256 return parser
256 return parser
General Comments 0
You need to be logged in to leave comments. Login now