##// END OF EJS Templates
Autodetect only a white list of mimetypes
neko259 -
r1372:cab9c599 default
parent child Browse files
Show More
@@ -1,387 +1,404 b''
1 1 import hashlib
2 2 import re
3 3 import time
4 import logging
5 import pytz
4 6
5 import pytz
6 7 from django import forms
7 8 from django.core.files.uploadedfile import SimpleUploadedFile
8 9 from django.core.exceptions import ObjectDoesNotExist
9 10 from django.forms.util import ErrorList
10 11 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
11 12
12 13 from boards.mdx_neboard import formatters
13 14 from boards.models.attachment.downloaders import Downloader
14 15 from boards.models.post import TITLE_MAX_LENGTH
15 16 from boards.models import Tag, Post
16 17 from boards.utils import validate_file_size, get_file_mimetype, \
17 18 FILE_EXTENSION_DELIMITER
18 19 from neboard import settings
19 20 import boards.settings as board_settings
20 21 import neboard
21 22
22 23 REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE)
23 24
24 25 VETERAN_POSTING_DELAY = 5
25 26
26 27 ATTRIBUTE_PLACEHOLDER = 'placeholder'
27 28 ATTRIBUTE_ROWS = 'rows'
28 29
29 30 LAST_POST_TIME = 'last_post_time'
30 31 LAST_LOGIN_TIME = 'last_login_time'
31 32 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
32 33 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
33 34
34 35 LABEL_TITLE = _('Title')
35 36 LABEL_TEXT = _('Text')
36 37 LABEL_TAG = _('Tag')
37 38 LABEL_SEARCH = _('Search')
38 39
39 40 ERROR_SPEED = 'Please wait %(delay)d second before sending message'
40 41 ERROR_SPEED_PLURAL = 'Please wait %(delay)d seconds before sending message'
41 42
42 43 TAG_MAX_LENGTH = 20
43 44
44 45 TEXTAREA_ROWS = 4
45 46
46 47 TRIPCODE_DELIM = '#'
47 48
49 # TODO Maybe this may be converted into the database table?
50 MIMETYPE_EXTENSIONS = {
51 'image/jpeg': 'jpeg',
52 'image/png': 'png',
53 'image/gif': 'gif',
54 'video/webm': 'webm',
55 'application/pdf': 'pdf',
56 'x-diff': 'diff',
57 }
58
48 59
49 60 def get_timezones():
50 61 timezones = []
51 62 for tz in pytz.common_timezones:
52 63 timezones.append((tz, tz),)
53 64 return timezones
54 65
55 66
56 67 class FormatPanel(forms.Textarea):
57 68 """
58 69 Panel for text formatting. Consists of buttons to add different tags to the
59 70 form text area.
60 71 """
61 72
62 73 def render(self, name, value, attrs=None):
63 74 output = '<div id="mark-panel">'
64 75 for formatter in formatters:
65 76 output += '<span class="mark_btn"' + \
66 77 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
67 78 '\', \'' + formatter.format_right + '\')">' + \
68 79 formatter.preview_left + formatter.name + \
69 80 formatter.preview_right + '</span>'
70 81
71 82 output += '</div>'
72 83 output += super(FormatPanel, self).render(name, value, attrs=None)
73 84
74 85 return output
75 86
76 87
77 88 class PlainErrorList(ErrorList):
78 89 def __unicode__(self):
79 90 return self.as_text()
80 91
81 92 def as_text(self):
82 93 return ''.join(['(!) %s ' % e for e in self])
83 94
84 95
85 96 class NeboardForm(forms.Form):
86 97 """
87 98 Form with neboard-specific formatting.
88 99 """
89 100
90 101 def as_div(self):
91 102 """
92 103 Returns this form rendered as HTML <as_div>s.
93 104 """
94 105
95 106 return self._html_output(
96 107 # TODO Do not show hidden rows in the list here
97 108 normal_row='<div class="form-row">'
98 109 '<div class="form-label">'
99 110 '%(label)s'
100 111 '</div>'
101 112 '<div class="form-input">'
102 113 '%(field)s'
103 114 '</div>'
104 115 '</div>'
105 116 '<div class="form-row">'
106 117 '%(help_text)s'
107 118 '</div>',
108 119 error_row='<div class="form-row">'
109 120 '<div class="form-label"></div>'
110 121 '<div class="form-errors">%s</div>'
111 122 '</div>',
112 123 row_ender='</div>',
113 124 help_text_html='%s',
114 125 errors_on_separate_row=True)
115 126
116 127 def as_json_errors(self):
117 128 errors = []
118 129
119 130 for name, field in list(self.fields.items()):
120 131 if self[name].errors:
121 132 errors.append({
122 133 'field': name,
123 134 'errors': self[name].errors.as_text(),
124 135 })
125 136
126 137 return errors
127 138
128 139
129 140 class PostForm(NeboardForm):
130 141
131 142 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
132 143 label=LABEL_TITLE,
133 144 widget=forms.TextInput(
134 145 attrs={ATTRIBUTE_PLACEHOLDER:
135 146 'test#tripcode'}))
136 147 text = forms.CharField(
137 148 widget=FormatPanel(attrs={
138 149 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
139 150 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
140 151 }),
141 152 required=False, label=LABEL_TEXT)
142 153 file = forms.FileField(required=False, label=_('File'),
143 154 widget=forms.ClearableFileInput(
144 155 attrs={'accept': 'file/*'}))
145 156 file_url = forms.CharField(required=False, label=_('File URL'),
146 157 widget=forms.TextInput(
147 158 attrs={ATTRIBUTE_PLACEHOLDER:
148 159 'http://example.com/image.png'}))
149 160
150 161 # This field is for spam prevention only
151 162 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
152 163 widget=forms.TextInput(attrs={
153 164 'class': 'form-email'}))
154 165 threads = forms.CharField(required=False, label=_('Additional threads'),
155 166 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER:
156 167 '123 456 789'}))
157 168
158 169 session = None
159 170 need_to_ban = False
160 171
161 172 def _update_file_extension(self, file):
162 173 if file:
163 extension = get_file_mimetype(file)
164 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
165 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
174 mimetype = get_file_mimetype(file)
175 extension = MIMETYPE_EXTENSIONS.get(mimetype)
176 if extension:
177 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
178 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
166 179
167 file.name = new_filename
180 file.name = new_filename
181 else:
182 logger = logging.getLogger('boards.forms.extension')
183
184 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
168 185
169 186 def clean_title(self):
170 187 title = self.cleaned_data['title']
171 188 if title:
172 189 if len(title) > TITLE_MAX_LENGTH:
173 190 raise forms.ValidationError(_('Title must have less than %s '
174 191 'characters') %
175 192 str(TITLE_MAX_LENGTH))
176 193 return title
177 194
178 195 def clean_text(self):
179 196 text = self.cleaned_data['text'].strip()
180 197 if text:
181 198 max_length = board_settings.get_int('Forms', 'MaxTextLength')
182 199 if len(text) > max_length:
183 200 raise forms.ValidationError(_('Text must have less than %s '
184 201 'characters') % str(max_length))
185 202 return text
186 203
187 204 def clean_file(self):
188 205 file = self.cleaned_data['file']
189 206
190 207 if file:
191 208 validate_file_size(file.size)
192 209 self._update_file_extension(file)
193 210
194 211 return file
195 212
196 213 def clean_file_url(self):
197 214 url = self.cleaned_data['file_url']
198 215
199 216 file = None
200 217 if url:
201 218 file = self._get_file_from_url(url)
202 219
203 220 if not file:
204 221 raise forms.ValidationError(_('Invalid URL'))
205 222 else:
206 223 validate_file_size(file.size)
207 224 self._update_file_extension(file)
208 225
209 226 return file
210 227
211 228 def clean_threads(self):
212 229 threads_str = self.cleaned_data['threads']
213 230
214 231 if len(threads_str) > 0:
215 232 threads_id_list = threads_str.split(' ')
216 233
217 234 threads = list()
218 235
219 236 for thread_id in threads_id_list:
220 237 try:
221 238 thread = Post.objects.get(id=int(thread_id))
222 239 if not thread.is_opening() or thread.get_thread().archived:
223 240 raise ObjectDoesNotExist()
224 241 threads.append(thread)
225 242 except (ObjectDoesNotExist, ValueError):
226 243 raise forms.ValidationError(_('Invalid additional thread list'))
227 244
228 245 return threads
229 246
230 247 def clean(self):
231 248 cleaned_data = super(PostForm, self).clean()
232 249
233 250 if cleaned_data['email']:
234 251 self.need_to_ban = True
235 252 raise forms.ValidationError('A human cannot enter a hidden field')
236 253
237 254 if not self.errors:
238 255 self._clean_text_file()
239 256
240 257 if not self.errors and self.session:
241 258 self._validate_posting_speed()
242 259
243 260 return cleaned_data
244 261
245 262 def get_file(self):
246 263 """
247 264 Gets file from form or URL.
248 265 """
249 266
250 267 file = self.cleaned_data['file']
251 268 return file or self.cleaned_data['file_url']
252 269
253 270 def get_tripcode(self):
254 271 title = self.cleaned_data['title']
255 272 if title is not None and TRIPCODE_DELIM in title:
256 273 code = title.split(TRIPCODE_DELIM, maxsplit=1)[1] + neboard.settings.SECRET_KEY
257 274 tripcode = hashlib.md5(code.encode()).hexdigest()
258 275 else:
259 276 tripcode = ''
260 277 return tripcode
261 278
262 279 def get_title(self):
263 280 title = self.cleaned_data['title']
264 281 if title is not None and TRIPCODE_DELIM in title:
265 282 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
266 283 else:
267 284 return title
268 285
269 286 def _clean_text_file(self):
270 287 text = self.cleaned_data.get('text')
271 288 file = self.get_file()
272 289
273 290 if (not text) and (not file):
274 291 error_message = _('Either text or file must be entered.')
275 292 self._errors['text'] = self.error_class([error_message])
276 293
277 294 def _validate_posting_speed(self):
278 295 can_post = True
279 296
280 297 posting_delay = settings.POSTING_DELAY
281 298
282 299 if board_settings.get_bool('Forms', 'LimitPostingSpeed'):
283 300 now = time.time()
284 301
285 302 current_delay = 0
286 303
287 304 if LAST_POST_TIME not in self.session:
288 305 self.session[LAST_POST_TIME] = now
289 306
290 307 need_delay = True
291 308 else:
292 309 last_post_time = self.session.get(LAST_POST_TIME)
293 310 current_delay = int(now - last_post_time)
294 311
295 312 need_delay = current_delay < posting_delay
296 313
297 314 if need_delay:
298 315 delay = posting_delay - current_delay
299 316 error_message = ungettext_lazy(ERROR_SPEED, ERROR_SPEED_PLURAL,
300 317 delay) % {'delay': delay}
301 318 self._errors['text'] = self.error_class([error_message])
302 319
303 320 can_post = False
304 321
305 322 if can_post:
306 323 self.session[LAST_POST_TIME] = now
307 324
308 325 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
309 326 """
310 327 Gets an file file from URL.
311 328 """
312 329
313 330 img_temp = None
314 331
315 332 try:
316 333 for downloader in Downloader.__subclasses__():
317 334 if downloader.handles(url):
318 335 return downloader.download(url)
319 336 # If nobody of the specific downloaders handles this, use generic
320 337 # one
321 338 return Downloader.download(url)
322 339 except forms.ValidationError as e:
323 340 raise e
324 341 except Exception as e:
325 342 # Just return no file
326 343 pass
327 344
328 345
329 346 class ThreadForm(PostForm):
330 347
331 348 tags = forms.CharField(
332 349 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
333 350 max_length=100, label=_('Tags'), required=True)
334 351
335 352 def clean_tags(self):
336 353 tags = self.cleaned_data['tags'].strip()
337 354
338 355 if not tags or not REGEX_TAGS.match(tags):
339 356 raise forms.ValidationError(
340 357 _('Inappropriate characters in tags.'))
341 358
342 359 required_tag_exists = False
343 360 tag_set = set()
344 361 for tag_string in tags.split():
345 362 tag, created = Tag.objects.get_or_create(name=tag_string.strip().lower())
346 363 tag_set.add(tag)
347 364
348 365 # If this is a new tag, don't check for its parents because nobody
349 366 # added them yet
350 367 if not created:
351 368 tag_set |= set(tag.get_all_parents())
352 369
353 370 for tag in tag_set:
354 371 if tag.required:
355 372 required_tag_exists = True
356 373 break
357 374
358 375 if not required_tag_exists:
359 376 raise forms.ValidationError(
360 377 _('Need at least one section.'))
361 378
362 379 return tag_set
363 380
364 381 def clean(self):
365 382 cleaned_data = super(ThreadForm, self).clean()
366 383
367 384 return cleaned_data
368 385
369 386
370 387 class SettingsForm(NeboardForm):
371 388
372 389 theme = forms.ChoiceField(choices=settings.THEMES, label=_('Theme'))
373 390 image_viewer = forms.ChoiceField(choices=settings.IMAGE_VIEWERS, label=_('Image view mode'))
374 391 username = forms.CharField(label=_('User name'), required=False)
375 392 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
376 393
377 394 def clean_username(self):
378 395 username = self.cleaned_data['username']
379 396
380 397 if username and not REGEX_TAGS.match(username):
381 398 raise forms.ValidationError(_('Inappropriate characters.'))
382 399
383 400 return username
384 401
385 402
386 403 class SearchForm(NeboardForm):
387 404 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
@@ -1,40 +1,41 b''
1 1 from django.db import models
2 2
3 3 from boards import utils
4 4 from boards.models.attachment.viewers import get_viewers, AbstractViewer
5 5 from boards.utils import get_upload_filename, get_file_mimetype
6 6
7 7
8 8 class AttachmentManager(models.Manager):
9 9 def create_with_hash(self, file):
10 10 file_hash = utils.get_file_hash(file)
11 11 existing = self.filter(hash=file_hash)
12 12 if len(existing) > 0:
13 13 attachment = existing[0]
14 14 else:
15 file_type = get_file_mimetype(file)
15 # FIXME Use full mimetype here, need to modify viewers too
16 file_type = get_file_mimetype(file).split('/')[-1]
16 17 attachment = self.create(file=file, mimetype=file_type,
17 18 hash=file_hash)
18 19
19 20 return attachment
20 21
21 22
22 23 class Attachment(models.Model):
23 24 objects = AttachmentManager()
24 25
25 26 file = models.FileField(upload_to=get_upload_filename)
26 27 mimetype = models.CharField(max_length=50)
27 28 hash = models.CharField(max_length=36)
28 29
29 30 def get_view(self):
30 31 file_viewer = None
31 32 for viewer in get_viewers():
32 33 if viewer.supports(self.mimetype):
33 34 file_viewer = viewer
34 35 break
35 36 if file_viewer is None:
36 37 file_viewer = AbstractViewer
37 38
38 39 return file_viewer(self.file, self.mimetype).get_view()
39 40
40 41
@@ -1,181 +1,181 b''
1 1 /*
2 2 @licstart The following is the entire license notice for the
3 3 JavaScript code in this page.
4 4
5 5
6 6 Copyright (C) 2013 neko259
7 7
8 8 The JavaScript code in this page is free software: you can
9 9 redistribute it and/or modify it under the terms of the GNU
10 10 General Public License (GNU GPL) as published by the Free Software
11 11 Foundation, either version 3 of the License, or (at your option)
12 12 any later version. The code is distributed WITHOUT ANY WARRANTY;
13 13 without even the implied warranty of MERCHANTABILITY or FITNESS
14 14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
15 15
16 16 As additional permission under GNU GPL version 3 section 7, you
17 17 may distribute non-source (e.g., minimized or compacted) forms of
18 18 that code without the copy of the GNU GPL normally required by
19 19 section 4, provided you include this license notice and a URL
20 20 through which recipients can access the Corresponding Source.
21 21
22 22 @licend The above is the entire license notice
23 23 for the JavaScript code in this page.
24 24 */
25 25
26 26 var IMAGE_POPUP_MARGIN = 10;
27 27
28 28
29 29 var IMAGE_VIEWERS = [
30 30 ['simple', new SimpleImageViewer()],
31 31 ['popup', new PopupImageViewer()]
32 32 ];
33 33
34 34 var FULL_IMG_CLASS = 'post-image-full';
35 35
36 36 var ATTR_SCALE = 'scale';
37 37
38 38
39 39 function ImageViewer() {}
40 40 ImageViewer.prototype.view = function (post) {};
41 41
42 42 function SimpleImageViewer() {}
43 43 SimpleImageViewer.prototype.view = function (post) {
44 44 var images = post.find('img');
45 45 images.toggle();
46 46
47 47 // When we first enlarge an image, a full image needs to be created
48 48 if (images.length == 1) {
49 49 var thumb = images.first();
50 50
51 51 var width = thumb.attr('data-width');
52 52 var height = thumb.attr('data-height');
53 53
54 54 if (width == null || height == null) {
55 55 width = '100%';
56 56 height = '100%';
57 57 }
58 58
59 59 var parent = images.first().parent();
60 60 var link = parent.attr('href');
61 61
62 62 var fullImg = $('<img />')
63 63 .addClass(FULL_IMG_CLASS)
64 64 .attr('src', link)
65 65 .attr('width', width)
66 66 .attr('height', height);
67 67
68 68 parent.append(fullImg);
69 69 }
70 70 };
71 71
72 72 function PopupImageViewer() {}
73 73 PopupImageViewer.prototype.view = function (post) {
74 74 var el = post;
75 75 var thumb_id = 'full' + el.find('img').attr('alt');
76 76
77 77 var existingPopups = $('#' + thumb_id);
78 78 if (!existingPopups.length) {
79 79 var imgElement= el.find('img');
80 80
81 81 var full_img_w = imgElement.attr('data-width');
82 82 var full_img_h = imgElement.attr('data-height');
83 83
84 84 var win = $(window);
85 85
86 86 var win_w = win.width();
87 87 var win_h = win.height();
88 88
89 89 // New image size
90 90 var w_scale = 1;
91 91 var h_scale = 1;
92 92
93 93 var freeWidth = win_w - 2 * IMAGE_POPUP_MARGIN;
94 94 var freeHeight = win_h - 2 * IMAGE_POPUP_MARGIN;
95 95
96 96 if (full_img_w > freeWidth) {
97 97 w_scale = full_img_w / freeWidth;
98 98 }
99 99 if (full_img_h > freeHeight) {
100 100 h_scale = full_img_h / freeHeight;
101 101 }
102 102
103 103 var scale = Math.max(w_scale, h_scale)
104 104 var img_w = full_img_w / scale;
105 105 var img_h = full_img_h / scale;
106 106
107 107 var postNode = $(el);
108 108
109 109 var img_pv = new Image();
110 110 var newImage = $(img_pv);
111 111 newImage.addClass('img-full')
112 112 .attr('id', thumb_id)
113 113 .attr('src', postNode.attr('href'))
114 114 .attr(ATTR_SCALE, scale)
115 .appendTo(postNode)
116 115 .css({
117 116 'width': img_w,
118 117 'height': img_h,
119 118 'left': (win_w - img_w) / 2,
120 119 'top': ((win_h - img_h) / 2)
121 120 })
121 .appendTo(postNode)
122 122 //scaling preview
123 123 .mousewheel(function(event, delta) {
124 124 var cx = event.originalEvent.clientX;
125 125 var cy = event.originalEvent.clientY;
126 126
127 127 var scale = newImage.attr(ATTR_SCALE) / (delta > 0 ? 1.25 : 0.8);
128 128
129 129 var oldWidth = newImage.width();
130 130 var oldHeight = newImage.height();
131 131
132 132 var newIW = full_img_w / scale;
133 133 var newIH = full_img_h / scale;
134 134
135 135 newImage.width(newIW);
136 136 newImage.height(newIH);
137 137 newImage.attr(ATTR_SCALE, scale);
138 138
139 139 // Set position
140 140 var oldPosition = newImage.position();
141 141 newImage.css({
142 142 left: parseInt(cx - (newIW/oldWidth) * (cx - parseInt(oldPosition.left, 10)), 10),
143 143 top: parseInt(cy - (newIH/oldHeight) * (cy - parseInt(oldPosition.top, 10)), 10)
144 144 });
145 145
146 146 return false;
147 147 }
148 148 )
149 149 .draggable({
150 150 addClasses: false,
151 151 stack: '.img-full'
152 152 });
153 153 } else {
154 154 existingPopups.remove();
155 155 }
156 156 };
157 157
158 158 function addImgPreview() {
159 159 var viewerName = $('body').attr('data-image-viewer');
160 160 var viewer = ImageViewer();
161 161 for (var i = 0; i < IMAGE_VIEWERS.length; i++) {
162 162 var item = IMAGE_VIEWERS[i];
163 163 if (item[0] === viewerName) {
164 164 viewer = item[1];
165 165 break;
166 166 }
167 167 }
168 168
169 169 //keybind
170 170 $(document).on('keyup.removepic', function(e) {
171 171 if(e.which === 27) {
172 172 $('.img-full').remove();
173 173 }
174 174 });
175 175
176 176 $('body').on('click', '.thumb', function() {
177 177 viewer.view($(this));
178 178
179 179 return false;
180 180 });
181 181 }
@@ -1,148 +1,144 b''
1 1 """
2 2 This module contains helper functions and helper classes.
3 3 """
4 4 import hashlib
5 5 from random import random
6 6 import time
7 7 import hmac
8 8
9 9 from django.core.cache import cache
10 10 from django.db.models import Model
11 11 from django import forms
12 12 from django.utils import timezone
13 13 from django.utils.translation import ugettext_lazy as _
14 14 import magic
15 15 from portage import os
16 16
17 17 import boards
18 18 from boards.settings import get_bool
19 19 from neboard import settings
20 20
21 21 CACHE_KEY_DELIMITER = '_'
22 22 PERMISSION_MODERATE = 'moderation'
23 23
24 24 HTTP_FORWARDED = 'HTTP_X_FORWARDED_FOR'
25 25 META_REMOTE_ADDR = 'REMOTE_ADDR'
26 26
27 27 SETTING_MESSAGES = 'Messages'
28 28 SETTING_ANON_MODE = 'AnonymousMode'
29 29
30 30 ANON_IP = '127.0.0.1'
31 31
32 32 UPLOAD_DIRS ={
33 33 'PostImage': 'images/',
34 34 'Attachment': 'files/',
35 35 }
36 36 FILE_EXTENSION_DELIMITER = '.'
37 37
38 38
39 39 def is_anonymous_mode():
40 40 return get_bool(SETTING_MESSAGES, SETTING_ANON_MODE)
41 41
42 42
43 43 def get_client_ip(request):
44 44 if is_anonymous_mode():
45 45 ip = ANON_IP
46 46 else:
47 47 x_forwarded_for = request.META.get(HTTP_FORWARDED)
48 48 if x_forwarded_for:
49 49 ip = x_forwarded_for.split(',')[-1].strip()
50 50 else:
51 51 ip = request.META.get(META_REMOTE_ADDR)
52 52 return ip
53 53
54 54
55 55 # TODO The output format is not epoch because it includes microseconds
56 56 def datetime_to_epoch(datetime):
57 57 return int(time.mktime(timezone.localtime(
58 58 datetime,timezone.get_current_timezone()).timetuple())
59 59 * 1000000 + datetime.microsecond)
60 60
61 61
62 62 def get_websocket_token(user_id='', timestamp=''):
63 63 """
64 64 Create token to validate information provided by new connection.
65 65 """
66 66
67 67 sign = hmac.new(settings.CENTRIFUGE_PROJECT_SECRET.encode())
68 68 sign.update(settings.CENTRIFUGE_PROJECT_ID.encode())
69 69 sign.update(user_id.encode())
70 70 sign.update(timestamp.encode())
71 71 token = sign.hexdigest()
72 72
73 73 return token
74 74
75 75
76 76 def cached_result(key_method=None):
77 77 """
78 78 Caches method result in the Django's cache system, persisted by object name,
79 79 object name and model id if object is a Django model.
80 80 """
81 81 def _cached_result(function):
82 82 def inner_func(obj, *args, **kwargs):
83 83 # TODO Include method arguments to the cache key
84 84 cache_key_params = [obj.__class__.__name__, function.__name__]
85 85 if isinstance(obj, Model):
86 86 cache_key_params.append(str(obj.id))
87 87
88 88 if key_method is not None:
89 89 cache_key_params += [str(arg) for arg in key_method(obj)]
90 90
91 91 cache_key = CACHE_KEY_DELIMITER.join(cache_key_params)
92 92
93 93 persisted_result = cache.get(cache_key)
94 94 if persisted_result is not None:
95 95 result = persisted_result
96 96 else:
97 97 result = function(obj, *args, **kwargs)
98 98 cache.set(cache_key, result)
99 99
100 100 return result
101 101
102 102 return inner_func
103 103 return _cached_result
104 104
105 105
106 106 def is_moderator(request):
107 107 try:
108 108 moderate = request.user.has_perm(PERMISSION_MODERATE)
109 109 except AttributeError:
110 110 moderate = False
111 111
112 112 return moderate
113 113
114 114
115 115 def get_file_hash(file) -> str:
116 116 md5 = hashlib.md5()
117 117 for chunk in file.chunks():
118 118 md5.update(chunk)
119 119 return md5.hexdigest()
120 120
121 121
122 122 def validate_file_size(size: int):
123 123 max_size = boards.settings.get_int('Forms', 'MaxFileSize')
124 124 if size > max_size:
125 125 raise forms.ValidationError(
126 126 _('File must be less than %s bytes')
127 127 % str(max_size))
128 128
129 129
130 130 def get_upload_filename(model_instance, old_filename):
131 131 # TODO Use something other than random number in file name
132 if hasattr(model_instance, 'mimetype'):
133 extension = model_instance.mimetype
134 else:
135 extension = old_filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
132 extension = old_filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
136 133 new_name = '{}{}.{}'.format(
137 134 str(int(time.mktime(time.gmtime()))),
138 135 str(int(random() * 1000)),
139 136 extension)
140 137
141 138 directory = UPLOAD_DIRS[type(model_instance).__name__]
142 139
143 140 return os.path.join(directory, new_name)
144 141
145 142
146 143 def get_file_mimetype(file) -> str:
147 return magic.from_buffer(file.chunks().__next__(), mime=True) \
148 .decode().split('/')[-1]
144 return magic.from_buffer(file.chunks().__next__(), mime=True).decode()
General Comments 0
You need to be logged in to leave comments. Login now