##// END OF EJS Templates
Extend PoW lifetime to 5 mins
neko259 -
r1447:237dee1d default
parent child Browse files
Show More
@@ -1,443 +1,443 b''
1 import hashlib
1 import hashlib
2 import re
2 import re
3 import time
3 import time
4 import logging
4 import logging
5
5
6 import pytz
6 import pytz
7
7
8 from django import forms
8 from django import forms
9 from django.core.files.uploadedfile import SimpleUploadedFile
9 from django.core.files.uploadedfile import SimpleUploadedFile
10 from django.core.exceptions import ObjectDoesNotExist
10 from django.core.exceptions import ObjectDoesNotExist
11 from django.forms.util import ErrorList
11 from django.forms.util import ErrorList
12 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
12 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
13 from django.utils import timezone
13 from django.utils import timezone
14
14
15 from boards.mdx_neboard import formatters
15 from boards.mdx_neboard import formatters
16 from boards.models.attachment.downloaders import Downloader
16 from boards.models.attachment.downloaders import Downloader
17 from boards.models.post import TITLE_MAX_LENGTH
17 from boards.models.post import TITLE_MAX_LENGTH
18 from boards.models import Tag, Post
18 from boards.models import Tag, Post
19 from boards.utils import validate_file_size, get_file_mimetype, \
19 from boards.utils import validate_file_size, get_file_mimetype, \
20 FILE_EXTENSION_DELIMITER
20 FILE_EXTENSION_DELIMITER
21 from neboard import settings
21 from neboard import settings
22 import boards.settings as board_settings
22 import boards.settings as board_settings
23 import neboard
23 import neboard
24
24
25 POW_HASH_LENGTH = 16
25 POW_HASH_LENGTH = 16
26 POW_LIFE_MINUTES = 1
26 POW_LIFE_MINUTES = 5
27
27
28 REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE)
28 REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE)
29 REGEX_USERNAMES = re.compile(r'^[\w\s\d,]+$', re.UNICODE)
29 REGEX_USERNAMES = re.compile(r'^[\w\s\d,]+$', re.UNICODE)
30
30
31 VETERAN_POSTING_DELAY = 5
31 VETERAN_POSTING_DELAY = 5
32
32
33 ATTRIBUTE_PLACEHOLDER = 'placeholder'
33 ATTRIBUTE_PLACEHOLDER = 'placeholder'
34 ATTRIBUTE_ROWS = 'rows'
34 ATTRIBUTE_ROWS = 'rows'
35
35
36 LAST_POST_TIME = 'last_post_time'
36 LAST_POST_TIME = 'last_post_time'
37 LAST_LOGIN_TIME = 'last_login_time'
37 LAST_LOGIN_TIME = 'last_login_time'
38 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
38 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
39 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
39 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
40
40
41 LABEL_TITLE = _('Title')
41 LABEL_TITLE = _('Title')
42 LABEL_TEXT = _('Text')
42 LABEL_TEXT = _('Text')
43 LABEL_TAG = _('Tag')
43 LABEL_TAG = _('Tag')
44 LABEL_SEARCH = _('Search')
44 LABEL_SEARCH = _('Search')
45
45
46 ERROR_SPEED = 'Please wait %(delay)d second before sending message'
46 ERROR_SPEED = 'Please wait %(delay)d second before sending message'
47 ERROR_SPEED_PLURAL = 'Please wait %(delay)d seconds before sending message'
47 ERROR_SPEED_PLURAL = 'Please wait %(delay)d seconds before sending message'
48
48
49 TAG_MAX_LENGTH = 20
49 TAG_MAX_LENGTH = 20
50
50
51 TEXTAREA_ROWS = 4
51 TEXTAREA_ROWS = 4
52
52
53 TRIPCODE_DELIM = '#'
53 TRIPCODE_DELIM = '#'
54
54
55 # TODO Maybe this may be converted into the database table?
55 # TODO Maybe this may be converted into the database table?
56 MIMETYPE_EXTENSIONS = {
56 MIMETYPE_EXTENSIONS = {
57 'image/jpeg': 'jpeg',
57 'image/jpeg': 'jpeg',
58 'image/png': 'png',
58 'image/png': 'png',
59 'image/gif': 'gif',
59 'image/gif': 'gif',
60 'video/webm': 'webm',
60 'video/webm': 'webm',
61 'application/pdf': 'pdf',
61 'application/pdf': 'pdf',
62 'x-diff': 'diff',
62 'x-diff': 'diff',
63 'image/svg+xml': 'svg',
63 'image/svg+xml': 'svg',
64 'application/x-shockwave-flash': 'swf',
64 'application/x-shockwave-flash': 'swf',
65 }
65 }
66
66
67
67
68 def get_timezones():
68 def get_timezones():
69 timezones = []
69 timezones = []
70 for tz in pytz.common_timezones:
70 for tz in pytz.common_timezones:
71 timezones.append((tz, tz),)
71 timezones.append((tz, tz),)
72 return timezones
72 return timezones
73
73
74
74
75 class FormatPanel(forms.Textarea):
75 class FormatPanel(forms.Textarea):
76 """
76 """
77 Panel for text formatting. Consists of buttons to add different tags to the
77 Panel for text formatting. Consists of buttons to add different tags to the
78 form text area.
78 form text area.
79 """
79 """
80
80
81 def render(self, name, value, attrs=None):
81 def render(self, name, value, attrs=None):
82 output = '<div id="mark-panel">'
82 output = '<div id="mark-panel">'
83 for formatter in formatters:
83 for formatter in formatters:
84 output += '<span class="mark_btn"' + \
84 output += '<span class="mark_btn"' + \
85 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
85 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
86 '\', \'' + formatter.format_right + '\')">' + \
86 '\', \'' + formatter.format_right + '\')">' + \
87 formatter.preview_left + formatter.name + \
87 formatter.preview_left + formatter.name + \
88 formatter.preview_right + '</span>'
88 formatter.preview_right + '</span>'
89
89
90 output += '</div>'
90 output += '</div>'
91 output += super(FormatPanel, self).render(name, value, attrs=attrs)
91 output += super(FormatPanel, self).render(name, value, attrs=attrs)
92
92
93 return output
93 return output
94
94
95
95
96 class PlainErrorList(ErrorList):
96 class PlainErrorList(ErrorList):
97 def __unicode__(self):
97 def __unicode__(self):
98 return self.as_text()
98 return self.as_text()
99
99
100 def as_text(self):
100 def as_text(self):
101 return ''.join(['(!) %s ' % e for e in self])
101 return ''.join(['(!) %s ' % e for e in self])
102
102
103
103
104 class NeboardForm(forms.Form):
104 class NeboardForm(forms.Form):
105 """
105 """
106 Form with neboard-specific formatting.
106 Form with neboard-specific formatting.
107 """
107 """
108
108
109 def as_div(self):
109 def as_div(self):
110 """
110 """
111 Returns this form rendered as HTML <as_div>s.
111 Returns this form rendered as HTML <as_div>s.
112 """
112 """
113
113
114 return self._html_output(
114 return self._html_output(
115 # TODO Do not show hidden rows in the list here
115 # TODO Do not show hidden rows in the list here
116 normal_row='<div class="form-row">'
116 normal_row='<div class="form-row">'
117 '<div class="form-label">'
117 '<div class="form-label">'
118 '%(label)s'
118 '%(label)s'
119 '</div>'
119 '</div>'
120 '<div class="form-input">'
120 '<div class="form-input">'
121 '%(field)s'
121 '%(field)s'
122 '</div>'
122 '</div>'
123 '</div>'
123 '</div>'
124 '<div class="form-row">'
124 '<div class="form-row">'
125 '%(help_text)s'
125 '%(help_text)s'
126 '</div>',
126 '</div>',
127 error_row='<div class="form-row">'
127 error_row='<div class="form-row">'
128 '<div class="form-label"></div>'
128 '<div class="form-label"></div>'
129 '<div class="form-errors">%s</div>'
129 '<div class="form-errors">%s</div>'
130 '</div>',
130 '</div>',
131 row_ender='</div>',
131 row_ender='</div>',
132 help_text_html='%s',
132 help_text_html='%s',
133 errors_on_separate_row=True)
133 errors_on_separate_row=True)
134
134
135 def as_json_errors(self):
135 def as_json_errors(self):
136 errors = []
136 errors = []
137
137
138 for name, field in list(self.fields.items()):
138 for name, field in list(self.fields.items()):
139 if self[name].errors:
139 if self[name].errors:
140 errors.append({
140 errors.append({
141 'field': name,
141 'field': name,
142 'errors': self[name].errors.as_text(),
142 'errors': self[name].errors.as_text(),
143 })
143 })
144
144
145 return errors
145 return errors
146
146
147
147
148 class PostForm(NeboardForm):
148 class PostForm(NeboardForm):
149
149
150 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
150 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
151 label=LABEL_TITLE,
151 label=LABEL_TITLE,
152 widget=forms.TextInput(
152 widget=forms.TextInput(
153 attrs={ATTRIBUTE_PLACEHOLDER:
153 attrs={ATTRIBUTE_PLACEHOLDER:
154 'test#tripcode'}))
154 'test#tripcode'}))
155 text = forms.CharField(
155 text = forms.CharField(
156 widget=FormatPanel(attrs={
156 widget=FormatPanel(attrs={
157 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
157 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
158 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
158 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
159 }),
159 }),
160 required=False, label=LABEL_TEXT)
160 required=False, label=LABEL_TEXT)
161 file = forms.FileField(required=False, label=_('File'),
161 file = forms.FileField(required=False, label=_('File'),
162 widget=forms.ClearableFileInput(
162 widget=forms.ClearableFileInput(
163 attrs={'accept': 'file/*'}))
163 attrs={'accept': 'file/*'}))
164 file_url = forms.CharField(required=False, label=_('File URL'),
164 file_url = forms.CharField(required=False, label=_('File URL'),
165 widget=forms.TextInput(
165 widget=forms.TextInput(
166 attrs={ATTRIBUTE_PLACEHOLDER:
166 attrs={ATTRIBUTE_PLACEHOLDER:
167 'http://example.com/image.png'}))
167 'http://example.com/image.png'}))
168
168
169 # This field is for spam prevention only
169 # This field is for spam prevention only
170 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
170 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
171 widget=forms.TextInput(attrs={
171 widget=forms.TextInput(attrs={
172 'class': 'form-email'}))
172 'class': 'form-email'}))
173 threads = forms.CharField(required=False, label=_('Additional threads'),
173 threads = forms.CharField(required=False, label=_('Additional threads'),
174 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER:
174 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER:
175 '123 456 789'}))
175 '123 456 789'}))
176
176
177 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
177 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
178 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
178 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
179 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
179 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
180
180
181 session = None
181 session = None
182 need_to_ban = False
182 need_to_ban = False
183
183
184 def _update_file_extension(self, file):
184 def _update_file_extension(self, file):
185 if file:
185 if file:
186 mimetype = get_file_mimetype(file)
186 mimetype = get_file_mimetype(file)
187 extension = MIMETYPE_EXTENSIONS.get(mimetype)
187 extension = MIMETYPE_EXTENSIONS.get(mimetype)
188 if extension:
188 if extension:
189 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
189 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
190 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
190 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
191
191
192 file.name = new_filename
192 file.name = new_filename
193 else:
193 else:
194 logger = logging.getLogger('boards.forms.extension')
194 logger = logging.getLogger('boards.forms.extension')
195
195
196 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
196 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
197
197
198 def clean_title(self):
198 def clean_title(self):
199 title = self.cleaned_data['title']
199 title = self.cleaned_data['title']
200 if title:
200 if title:
201 if len(title) > TITLE_MAX_LENGTH:
201 if len(title) > TITLE_MAX_LENGTH:
202 raise forms.ValidationError(_('Title must have less than %s '
202 raise forms.ValidationError(_('Title must have less than %s '
203 'characters') %
203 'characters') %
204 str(TITLE_MAX_LENGTH))
204 str(TITLE_MAX_LENGTH))
205 return title
205 return title
206
206
207 def clean_text(self):
207 def clean_text(self):
208 text = self.cleaned_data['text'].strip()
208 text = self.cleaned_data['text'].strip()
209 if text:
209 if text:
210 max_length = board_settings.get_int('Forms', 'MaxTextLength')
210 max_length = board_settings.get_int('Forms', 'MaxTextLength')
211 if len(text) > max_length:
211 if len(text) > max_length:
212 raise forms.ValidationError(_('Text must have less than %s '
212 raise forms.ValidationError(_('Text must have less than %s '
213 'characters') % str(max_length))
213 'characters') % str(max_length))
214 return text
214 return text
215
215
216 def clean_file(self):
216 def clean_file(self):
217 file = self.cleaned_data['file']
217 file = self.cleaned_data['file']
218
218
219 if file:
219 if file:
220 validate_file_size(file.size)
220 validate_file_size(file.size)
221 self._update_file_extension(file)
221 self._update_file_extension(file)
222
222
223 return file
223 return file
224
224
225 def clean_file_url(self):
225 def clean_file_url(self):
226 url = self.cleaned_data['file_url']
226 url = self.cleaned_data['file_url']
227
227
228 file = None
228 file = None
229 if url:
229 if url:
230 file = self._get_file_from_url(url)
230 file = self._get_file_from_url(url)
231
231
232 if not file:
232 if not file:
233 raise forms.ValidationError(_('Invalid URL'))
233 raise forms.ValidationError(_('Invalid URL'))
234 else:
234 else:
235 validate_file_size(file.size)
235 validate_file_size(file.size)
236 self._update_file_extension(file)
236 self._update_file_extension(file)
237
237
238 return file
238 return file
239
239
240 def clean_threads(self):
240 def clean_threads(self):
241 threads_str = self.cleaned_data['threads']
241 threads_str = self.cleaned_data['threads']
242
242
243 if len(threads_str) > 0:
243 if len(threads_str) > 0:
244 threads_id_list = threads_str.split(' ')
244 threads_id_list = threads_str.split(' ')
245
245
246 threads = list()
246 threads = list()
247
247
248 for thread_id in threads_id_list:
248 for thread_id in threads_id_list:
249 try:
249 try:
250 thread = Post.objects.get(id=int(thread_id))
250 thread = Post.objects.get(id=int(thread_id))
251 if not thread.is_opening() or thread.get_thread().is_archived():
251 if not thread.is_opening() or thread.get_thread().is_archived():
252 raise ObjectDoesNotExist()
252 raise ObjectDoesNotExist()
253 threads.append(thread)
253 threads.append(thread)
254 except (ObjectDoesNotExist, ValueError):
254 except (ObjectDoesNotExist, ValueError):
255 raise forms.ValidationError(_('Invalid additional thread list'))
255 raise forms.ValidationError(_('Invalid additional thread list'))
256
256
257 return threads
257 return threads
258
258
259 def clean(self):
259 def clean(self):
260 cleaned_data = super(PostForm, self).clean()
260 cleaned_data = super(PostForm, self).clean()
261
261
262 if cleaned_data['email']:
262 if cleaned_data['email']:
263 self.need_to_ban = True
263 self.need_to_ban = True
264 raise forms.ValidationError('A human cannot enter a hidden field')
264 raise forms.ValidationError('A human cannot enter a hidden field')
265
265
266 if not self.errors:
266 if not self.errors:
267 self._clean_text_file()
267 self._clean_text_file()
268
268
269 limit_speed = board_settings.get_bool('Forms', 'LimitPostingSpeed')
269 limit_speed = board_settings.get_bool('Forms', 'LimitPostingSpeed')
270 if not self.errors and limit_speed:
270 if not self.errors and limit_speed:
271 pow_difficulty = board_settings.get_int('Forms', 'PowDifficulty')
271 pow_difficulty = board_settings.get_int('Forms', 'PowDifficulty')
272 if pow_difficulty > 0 and cleaned_data['timestamp'] and cleaned_data['iteration'] and cleaned_data['guess']:
272 if pow_difficulty > 0 and cleaned_data['timestamp'] and cleaned_data['iteration'] and cleaned_data['guess']:
273 self._validate_hash(cleaned_data['timestamp'], cleaned_data['iteration'], cleaned_data['guess'], cleaned_data['text'])
273 self._validate_hash(cleaned_data['timestamp'], cleaned_data['iteration'], cleaned_data['guess'], cleaned_data['text'])
274 else:
274 else:
275 self._validate_posting_speed()
275 self._validate_posting_speed()
276
276
277 return cleaned_data
277 return cleaned_data
278
278
279 def get_file(self):
279 def get_file(self):
280 """
280 """
281 Gets file from form or URL.
281 Gets file from form or URL.
282 """
282 """
283
283
284 file = self.cleaned_data['file']
284 file = self.cleaned_data['file']
285 return file or self.cleaned_data['file_url']
285 return file or self.cleaned_data['file_url']
286
286
287 def get_tripcode(self):
287 def get_tripcode(self):
288 title = self.cleaned_data['title']
288 title = self.cleaned_data['title']
289 if title is not None and TRIPCODE_DELIM in title:
289 if title is not None and TRIPCODE_DELIM in title:
290 code = title.split(TRIPCODE_DELIM, maxsplit=1)[1] + neboard.settings.SECRET_KEY
290 code = title.split(TRIPCODE_DELIM, maxsplit=1)[1] + neboard.settings.SECRET_KEY
291 tripcode = hashlib.md5(code.encode()).hexdigest()
291 tripcode = hashlib.md5(code.encode()).hexdigest()
292 else:
292 else:
293 tripcode = ''
293 tripcode = ''
294 return tripcode
294 return tripcode
295
295
296 def get_title(self):
296 def get_title(self):
297 title = self.cleaned_data['title']
297 title = self.cleaned_data['title']
298 if title is not None and TRIPCODE_DELIM in title:
298 if title is not None and TRIPCODE_DELIM in title:
299 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
299 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
300 else:
300 else:
301 return title
301 return title
302
302
303 def _clean_text_file(self):
303 def _clean_text_file(self):
304 text = self.cleaned_data.get('text')
304 text = self.cleaned_data.get('text')
305 file = self.get_file()
305 file = self.get_file()
306
306
307 if (not text) and (not file):
307 if (not text) and (not file):
308 error_message = _('Either text or file must be entered.')
308 error_message = _('Either text or file must be entered.')
309 self._errors['text'] = self.error_class([error_message])
309 self._errors['text'] = self.error_class([error_message])
310
310
311 def _validate_posting_speed(self):
311 def _validate_posting_speed(self):
312 can_post = True
312 can_post = True
313
313
314 posting_delay = settings.POSTING_DELAY
314 posting_delay = settings.POSTING_DELAY
315
315
316 if board_settings.get_bool('Forms', 'LimitPostingSpeed'):
316 if board_settings.get_bool('Forms', 'LimitPostingSpeed'):
317 now = time.time()
317 now = time.time()
318
318
319 current_delay = 0
319 current_delay = 0
320
320
321 if LAST_POST_TIME not in self.session:
321 if LAST_POST_TIME not in self.session:
322 self.session[LAST_POST_TIME] = now
322 self.session[LAST_POST_TIME] = now
323
323
324 need_delay = True
324 need_delay = True
325 else:
325 else:
326 last_post_time = self.session.get(LAST_POST_TIME)
326 last_post_time = self.session.get(LAST_POST_TIME)
327 current_delay = int(now - last_post_time)
327 current_delay = int(now - last_post_time)
328
328
329 need_delay = current_delay < posting_delay
329 need_delay = current_delay < posting_delay
330
330
331 if need_delay:
331 if need_delay:
332 delay = posting_delay - current_delay
332 delay = posting_delay - current_delay
333 error_message = ungettext_lazy(ERROR_SPEED, ERROR_SPEED_PLURAL,
333 error_message = ungettext_lazy(ERROR_SPEED, ERROR_SPEED_PLURAL,
334 delay) % {'delay': delay}
334 delay) % {'delay': delay}
335 self._errors['text'] = self.error_class([error_message])
335 self._errors['text'] = self.error_class([error_message])
336
336
337 can_post = False
337 can_post = False
338
338
339 if can_post:
339 if can_post:
340 self.session[LAST_POST_TIME] = now
340 self.session[LAST_POST_TIME] = now
341
341
342 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
342 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
343 """
343 """
344 Gets an file file from URL.
344 Gets an file file from URL.
345 """
345 """
346
346
347 img_temp = None
347 img_temp = None
348
348
349 try:
349 try:
350 for downloader in Downloader.__subclasses__():
350 for downloader in Downloader.__subclasses__():
351 if downloader.handles(url):
351 if downloader.handles(url):
352 return downloader.download(url)
352 return downloader.download(url)
353 # If nobody of the specific downloaders handles this, use generic
353 # If nobody of the specific downloaders handles this, use generic
354 # one
354 # one
355 return Downloader.download(url)
355 return Downloader.download(url)
356 except forms.ValidationError as e:
356 except forms.ValidationError as e:
357 raise e
357 raise e
358 except Exception as e:
358 except Exception as e:
359 raise forms.ValidationError(e)
359 raise forms.ValidationError(e)
360
360
361 def _validate_hash(self, timestamp: str, iteration: str, guess: str, message: str):
361 def _validate_hash(self, timestamp: str, iteration: str, guess: str, message: str):
362 post_time = timezone.datetime.fromtimestamp(
362 post_time = timezone.datetime.fromtimestamp(
363 int(timestamp[:-3]), tz=timezone.get_current_timezone())
363 int(timestamp[:-3]), tz=timezone.get_current_timezone())
364 timedelta = (timezone.now() - post_time).seconds / 60
364 timedelta = (timezone.now() - post_time).seconds / 60
365 if timedelta > POW_LIFE_MINUTES:
365 if timedelta > POW_LIFE_MINUTES:
366 self._errors['text'] = self.error_class([_('Stale PoW.')])
366 self._errors['text'] = self.error_class([_('Stale PoW.')])
367
367
368 payload = timestamp + message.replace('\r\n', '\n')
368 payload = timestamp + message.replace('\r\n', '\n')
369 difficulty = board_settings.get_int('Forms', 'PowDifficulty')
369 difficulty = board_settings.get_int('Forms', 'PowDifficulty')
370 target = str(int(2 ** (POW_HASH_LENGTH * 3) / difficulty))
370 target = str(int(2 ** (POW_HASH_LENGTH * 3) / difficulty))
371 if len(target) < POW_HASH_LENGTH:
371 if len(target) < POW_HASH_LENGTH:
372 target = '0' * (POW_HASH_LENGTH - len(target)) + target
372 target = '0' * (POW_HASH_LENGTH - len(target)) + target
373
373
374 computed_guess = hashlib.sha256((payload + iteration).encode())\
374 computed_guess = hashlib.sha256((payload + iteration).encode())\
375 .hexdigest()[0:POW_HASH_LENGTH]
375 .hexdigest()[0:POW_HASH_LENGTH]
376 if guess != computed_guess or guess > target:
376 if guess != computed_guess or guess > target:
377 self._errors['text'] = self.error_class(
377 self._errors['text'] = self.error_class(
378 [_('Invalid PoW.')])
378 [_('Invalid PoW.')])
379
379
380
380
381 class ThreadForm(PostForm):
381 class ThreadForm(PostForm):
382
382
383 tags = forms.CharField(
383 tags = forms.CharField(
384 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
384 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
385 max_length=100, label=_('Tags'), required=True)
385 max_length=100, label=_('Tags'), required=True)
386 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
386 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
387
387
388 def clean_tags(self):
388 def clean_tags(self):
389 tags = self.cleaned_data['tags'].strip()
389 tags = self.cleaned_data['tags'].strip()
390
390
391 if not tags or not REGEX_TAGS.match(tags):
391 if not tags or not REGEX_TAGS.match(tags):
392 raise forms.ValidationError(
392 raise forms.ValidationError(
393 _('Inappropriate characters in tags.'))
393 _('Inappropriate characters in tags.'))
394
394
395 required_tag_exists = False
395 required_tag_exists = False
396 tag_set = set()
396 tag_set = set()
397 for tag_string in tags.split():
397 for tag_string in tags.split():
398 tag, created = Tag.objects.get_or_create(name=tag_string.strip().lower())
398 tag, created = Tag.objects.get_or_create(name=tag_string.strip().lower())
399 tag_set.add(tag)
399 tag_set.add(tag)
400
400
401 # If this is a new tag, don't check for its parents because nobody
401 # If this is a new tag, don't check for its parents because nobody
402 # added them yet
402 # added them yet
403 if not created:
403 if not created:
404 tag_set |= set(tag.get_all_parents())
404 tag_set |= set(tag.get_all_parents())
405
405
406 for tag in tag_set:
406 for tag in tag_set:
407 if tag.required:
407 if tag.required:
408 required_tag_exists = True
408 required_tag_exists = True
409 break
409 break
410
410
411 if not required_tag_exists:
411 if not required_tag_exists:
412 raise forms.ValidationError(
412 raise forms.ValidationError(
413 _('Need at least one section.'))
413 _('Need at least one section.'))
414
414
415 return tag_set
415 return tag_set
416
416
417 def clean(self):
417 def clean(self):
418 cleaned_data = super(ThreadForm, self).clean()
418 cleaned_data = super(ThreadForm, self).clean()
419
419
420 return cleaned_data
420 return cleaned_data
421
421
422 def is_monochrome(self):
422 def is_monochrome(self):
423 return self.cleaned_data['monochrome']
423 return self.cleaned_data['monochrome']
424
424
425
425
426 class SettingsForm(NeboardForm):
426 class SettingsForm(NeboardForm):
427
427
428 theme = forms.ChoiceField(choices=settings.THEMES, label=_('Theme'))
428 theme = forms.ChoiceField(choices=settings.THEMES, label=_('Theme'))
429 image_viewer = forms.ChoiceField(choices=settings.IMAGE_VIEWERS, label=_('Image view mode'))
429 image_viewer = forms.ChoiceField(choices=settings.IMAGE_VIEWERS, label=_('Image view mode'))
430 username = forms.CharField(label=_('User name'), required=False)
430 username = forms.CharField(label=_('User name'), required=False)
431 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
431 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
432
432
433 def clean_username(self):
433 def clean_username(self):
434 username = self.cleaned_data['username']
434 username = self.cleaned_data['username']
435
435
436 if username and not REGEX_USERNAMES.match(username):
436 if username and not REGEX_USERNAMES.match(username):
437 raise forms.ValidationError(_('Inappropriate characters.'))
437 raise forms.ValidationError(_('Inappropriate characters.'))
438
438
439 return username
439 return username
440
440
441
441
442 class SearchForm(NeboardForm):
442 class SearchForm(NeboardForm):
443 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
443 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
General Comments 0
You need to be logged in to leave comments. Login now