Show More
@@ -0,0 +1,83 b'' | |||||
|
1 | import os | |||
|
2 | import re | |||
|
3 | ||||
|
4 | from django.core.files.uploadedfile import SimpleUploadedFile | |||
|
5 | from pytube import YouTube | |||
|
6 | import requests | |||
|
7 | ||||
|
8 | from boards.utils import validate_file_size | |||
|
9 | ||||
|
10 | YOUTUBE_VIDEO_FORMAT = 'webm' | |||
|
11 | ||||
|
12 | HTTP_RESULT_OK = 200 | |||
|
13 | ||||
|
14 | HEADER_CONTENT_LENGTH = 'content-length' | |||
|
15 | HEADER_CONTENT_TYPE = 'content-type' | |||
|
16 | ||||
|
17 | FILE_DOWNLOAD_CHUNK_BYTES = 100000 | |||
|
18 | ||||
|
19 | YOUTUBE_URL = re.compile(r'https?://www\.youtube\.com/watch\?v=\w+') | |||
|
20 | ||||
|
21 | ||||
|
22 | class Downloader: | |||
|
23 | @staticmethod | |||
|
24 | def handles(url: str) -> bool: | |||
|
25 | return False | |||
|
26 | ||||
|
27 | @staticmethod | |||
|
28 | def download(url: str): | |||
|
29 | # Verify content headers | |||
|
30 | response_head = requests.head(url, verify=False) | |||
|
31 | content_type = response_head.headers[HEADER_CONTENT_TYPE].split(';')[0] | |||
|
32 | length_header = response_head.headers.get(HEADER_CONTENT_LENGTH) | |||
|
33 | if length_header: | |||
|
34 | length = int(length_header) | |||
|
35 | validate_file_size(length) | |||
|
36 | # Get the actual content into memory | |||
|
37 | response = requests.get(url, verify=False, stream=True) | |||
|
38 | ||||
|
39 | # Download file, stop if the size exceeds limit | |||
|
40 | size = 0 | |||
|
41 | content = b'' | |||
|
42 | for chunk in response.iter_content(FILE_DOWNLOAD_CHUNK_BYTES): | |||
|
43 | size += len(chunk) | |||
|
44 | validate_file_size(size) | |||
|
45 | content += chunk | |||
|
46 | ||||
|
47 | if response.status_code == HTTP_RESULT_OK and content: | |||
|
48 | # Set a dummy file name that will be replaced | |||
|
49 | # anyway, just keep the valid extension | |||
|
50 | filename = 'file.' + content_type.split('/')[1] | |||
|
51 | return SimpleUploadedFile(filename, content, content_type) | |||
|
52 | ||||
|
53 | ||||
|
54 | class YouTubeDownloader(Downloader): | |||
|
55 | @staticmethod | |||
|
56 | def download(url: str): | |||
|
57 | yt = YouTube() | |||
|
58 | yt.from_url(url) | |||
|
59 | videos = yt.filter(YOUTUBE_VIDEO_FORMAT) | |||
|
60 | if len(videos) > 0: | |||
|
61 | video = videos[0] | |||
|
62 | filename = '{}.{}'.format(video.filename, video.extension) | |||
|
63 | try: | |||
|
64 | video.download(on_progress=YouTubeDownloader.on_progress) | |||
|
65 | ||||
|
66 | file = open(filename, 'rb') | |||
|
67 | content = file.read() | |||
|
68 | file.close() | |||
|
69 | ||||
|
70 | os.remove(filename) | |||
|
71 | return SimpleUploadedFile(filename, content, video.extension) | |||
|
72 | except Exception as e: | |||
|
73 | if os.path.isfile(filename): | |||
|
74 | os.remove(filename) | |||
|
75 | raise e | |||
|
76 | ||||
|
77 | @staticmethod | |||
|
78 | def handles(url: str) -> bool: | |||
|
79 | return YOUTUBE_URL.match(url) | |||
|
80 | ||||
|
81 | @staticmethod | |||
|
82 | def on_progress(bytes, file_size, start): | |||
|
83 | validate_file_size(file_size) |
@@ -8,18 +8,16 b' from django.core.files.uploadedfile impo' | |||||
8 | from django.core.exceptions import ObjectDoesNotExist |
|
8 | from django.core.exceptions import ObjectDoesNotExist | |
9 | from django.forms.util import ErrorList |
|
9 | from django.forms.util import ErrorList | |
10 | from django.utils.translation import ugettext_lazy as _ |
|
10 | from django.utils.translation import ugettext_lazy as _ | |
11 | import requests |
|
|||
12 |
|
11 | |||
13 | from boards.mdx_neboard import formatters |
|
12 | from boards.mdx_neboard import formatters | |
|
13 | from boards.models.attachment.downloaders import Downloader | |||
14 | from boards.models.post import TITLE_MAX_LENGTH |
|
14 | from boards.models.post import TITLE_MAX_LENGTH | |
15 | from boards.models import Tag, Post |
|
15 | from boards.models import Tag, Post | |
|
16 | from boards.utils import validate_file_size | |||
16 | from neboard import settings |
|
17 | from neboard import settings | |
17 | import boards.settings as board_settings |
|
18 | import boards.settings as board_settings | |
18 | import neboard |
|
19 | import neboard | |
19 |
|
20 | |||
20 | HEADER_CONTENT_LENGTH = 'content-length' |
|
|||
21 | HEADER_CONTENT_TYPE = 'content-type' |
|
|||
22 |
|
||||
23 | REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE) |
|
21 | REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE) | |
24 |
|
22 | |||
25 | VETERAN_POSTING_DELAY = 5 |
|
23 | VETERAN_POSTING_DELAY = 5 | |
@@ -41,10 +39,6 b" ERROR_SPEED = _('Please wait %s seconds " | |||||
41 |
|
39 | |||
42 | TAG_MAX_LENGTH = 20 |
|
40 | TAG_MAX_LENGTH = 20 | |
43 |
|
41 | |||
44 | FILE_DOWNLOAD_CHUNK_BYTES = 100000 |
|
|||
45 |
|
||||
46 | HTTP_RESULT_OK = 200 |
|
|||
47 |
|
||||
48 | TEXTAREA_ROWS = 4 |
|
42 | TEXTAREA_ROWS = 4 | |
49 |
|
43 | |||
50 |
|
44 | |||
@@ -182,7 +176,7 b' class PostForm(NeboardForm):' | |||||
182 | file = self.cleaned_data['file'] |
|
176 | file = self.cleaned_data['file'] | |
183 |
|
177 | |||
184 | if file: |
|
178 | if file: | |
185 |
|
|
179 | validate_file_size(file.size) | |
186 |
|
180 | |||
187 | return file |
|
181 | return file | |
188 |
|
182 | |||
@@ -196,7 +190,7 b' class PostForm(NeboardForm):' | |||||
196 | if not file: |
|
190 | if not file: | |
197 | raise forms.ValidationError(_('Invalid URL')) |
|
191 | raise forms.ValidationError(_('Invalid URL')) | |
198 | else: |
|
192 | else: | |
199 |
|
|
193 | validate_file_size(file.size) | |
200 |
|
194 | |||
201 | return file |
|
195 | return file | |
202 |
|
196 | |||
@@ -294,13 +288,6 b' class PostForm(NeboardForm):' | |||||
294 | if can_post: |
|
288 | if can_post: | |
295 | self.session[LAST_POST_TIME] = now |
|
289 | self.session[LAST_POST_TIME] = now | |
296 |
|
290 | |||
297 | def validate_file_size(self, size: int): |
|
|||
298 | max_size = board_settings.get_int('Forms', 'MaxFileSize') |
|
|||
299 | if size > max_size: |
|
|||
300 | raise forms.ValidationError( |
|
|||
301 | _('File must be less than %s bytes') |
|
|||
302 | % str(max_size)) |
|
|||
303 |
|
||||
304 | def _get_file_from_url(self, url: str) -> SimpleUploadedFile: |
|
291 | def _get_file_from_url(self, url: str) -> SimpleUploadedFile: | |
305 | """ |
|
292 | """ | |
306 | Gets an file file from URL. |
|
293 | Gets an file file from URL. | |
@@ -309,36 +296,18 b' class PostForm(NeboardForm):' | |||||
309 | img_temp = None |
|
296 | img_temp = None | |
310 |
|
297 | |||
311 | try: |
|
298 | try: | |
312 | # Verify content headers |
|
299 | for downloader in Downloader.__subclasses__(): | |
313 | response_head = requests.head(url, verify=False) |
|
300 | if downloader.handles(url): | |
314 | content_type = response_head.headers[HEADER_CONTENT_TYPE].split(';')[0] |
|
301 | return downloader.download(url) | |
315 | length_header = response_head.headers.get(HEADER_CONTENT_LENGTH) |
|
302 | # If nobody of the specific downloaders handles this, use generic | |
316 |
|
|
303 | # one | |
317 | length = int(length_header) |
|
304 | return Downloader.download(url) | |
318 | self.validate_file_size(length) |
|
305 | except forms.ValidationError as e: | |
319 | # Get the actual content into memory |
|
306 | raise e | |
320 | response = requests.get(url, verify=False, stream=True) |
|
|||
321 |
|
||||
322 | # Download file, stop if the size exceeds limit |
|
|||
323 | size = 0 |
|
|||
324 | content = b'' |
|
|||
325 | for chunk in response.iter_content(FILE_DOWNLOAD_CHUNK_BYTES): |
|
|||
326 | size += len(chunk) |
|
|||
327 | self.validate_file_size(size) |
|
|||
328 | content += chunk |
|
|||
329 |
|
||||
330 | if response.status_code == HTTP_RESULT_OK and content: |
|
|||
331 | # Set a dummy file name that will be replaced |
|
|||
332 | # anyway, just keep the valid extension |
|
|||
333 | filename = 'file.' + content_type.split('/')[1] |
|
|||
334 | img_temp = SimpleUploadedFile(filename, content, |
|
|||
335 | content_type) |
|
|||
336 | except Exception as e: |
|
307 | except Exception as e: | |
337 | # Just return no file |
|
308 | # Just return no file | |
338 | pass |
|
309 | pass | |
339 |
|
310 | |||
340 | return img_temp |
|
|||
341 |
|
||||
342 |
|
311 | |||
343 | class ThreadForm(PostForm): |
|
312 | class ThreadForm(PostForm): | |
344 |
|
313 |
@@ -7,8 +7,11 b' import hmac' | |||||
7 |
|
7 | |||
8 | from django.core.cache import cache |
|
8 | from django.core.cache import cache | |
9 | from django.db.models import Model |
|
9 | from django.db.models import Model | |
|
10 | from django import forms | |||
10 |
|
11 | |||
11 | from django.utils import timezone |
|
12 | from django.utils import timezone | |
|
13 | from django.utils.translation import ugettext_lazy as _ | |||
|
14 | import boards | |||
12 |
|
15 | |||
13 | from neboard import settings |
|
16 | from neboard import settings | |
14 |
|
17 | |||
@@ -90,3 +93,11 b' def get_file_hash(file) -> str:' | |||||
90 | for chunk in file.chunks(): |
|
93 | for chunk in file.chunks(): | |
91 | md5.update(chunk) |
|
94 | md5.update(chunk) | |
92 | return md5.hexdigest() |
|
95 | return md5.hexdigest() | |
|
96 | ||||
|
97 | ||||
|
98 | def validate_file_size(size: int): | |||
|
99 | max_size = boards.settings.get_int('Forms', 'MaxFileSize') | |||
|
100 | if size > max_size: | |||
|
101 | raise forms.ValidationError( | |||
|
102 | _('File must be less than %s bytes') | |||
|
103 | % str(max_size)) |
General Comments 0
You need to be logged in to leave comments.
Login now