~widelands-dev/widelands-website/trunk

« back to all changes in this revision

Viewing changes to pybb/util.py

  • Committer: kaputtnik
  • Date: 2019-09-03 06:16:23 UTC
  • mfrom: (544.2.25 pybb_attachments)
  • Revision ID: kaputtnik-20190903061623-xu4kvqpabnzmuskw
Allow file uploads in the forum; added some basic file checks to validate files

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
import traceback
4
4
import json
5
5
import re
 
6
import subprocess
6
7
 
7
8
from bs4 import BeautifulSoup, NavigableString
8
9
from datetime import datetime
14
15
from django import forms
15
16
from django.core.paginator import Paginator, EmptyPage, InvalidPage
16
17
from django.conf import settings
 
18
from django.core.exceptions import ValidationError
17
19
from pybb import settings as pybb_settings
 
20
import magic
 
21
import zipfile
 
22
import configparser
 
23
from PIL import Image
 
24
 
 
25
 
18
26
 
19
27
 
20
28
def render_to(template_path):
31
39
            output = func(request, *args, **kwargs)
32
40
            if not isinstance(output, dict):
33
41
                return output
34
 
            
35
42
 
36
43
            # TODO(Franku): 'MIME_TYPE' is never in output as i can see for now.
37
44
            # But if, this should maybe 'content_type' instead
173
180
    text = text.replace('"', '"')
174
181
    text = text.replace(''', '\'')
175
182
    return text
 
183
 
 
184
 
 
185
def validate_file(attachment):
 
186
 
 
187
    tmp_file_path = attachment.temporary_file_path()
 
188
 
 
189
    # Helper functions
 
190
    def _split_mime(mime_type):
 
191
        main, sub = mime_type.split('/', maxsplit=1)
 
192
        return {'maintype': main, 'subtype': sub}
 
193
 
 
194
    def _is_image():
 
195
        # Use PIL to determine if it is a valid image file
 
196
        # works not for corrupted jpg
 
197
        try:
 
198
            with Image.open(tmp_file_path) as im:
 
199
                im.verify()
 
200
        except:
 
201
            return False
 
202
        return True
 
203
 
 
204
    def _is_zip():
 
205
        try:
 
206
            zip_obj = zipfile.ZipFile(tmp_file_path)
 
207
        except zipfile.BadZipfile:
 
208
            return None
 
209
        return zip_obj
 
210
 
 
211
    def _zip_contains(zip_parts):
 
212
        # Check if each entry in zip_parts is inside the attachment
 
213
        zip_obj = _is_zip()
 
214
        if zip_obj:
 
215
            try:
 
216
                for obj in zip_parts:
 
217
                    zip_obj.getinfo(obj)
 
218
            except KeyError:
 
219
                return False
 
220
        return True
 
221
 
 
222
 
 
223
    # Main part of file checks
 
224
    # File size
 
225
    if attachment.size > pybb_settings.ATTACHMENT_SIZE_LIMIT:
 
226
        raise ValidationError(
 
227
            'Attachment is too big. We allow max %(size)s MiB',
 
228
            params = {
 
229
                'size': pybb_settings.ATTACHMENT_SIZE_LIMIT/1024/1024,
 
230
            }
 
231
            )
 
232
 
 
233
    # Checks by file extension
 
234
    splitted_fn = attachment.name.rsplit('.', maxsplit=2)
 
235
    if len(splitted_fn) == 1:
 
236
        raise ValidationError(
 
237
            'We do not allow uploading files without an extension.'
 
238
            )
 
239
 
 
240
    ext = splitted_fn[-1]
 
241
    if not ext in settings.ALLOWED_EXTENSIONS:
 
242
        raise ValidationError(
 
243
            'This type of file is not allowed.'
 
244
            )
 
245
 
 
246
    # Widelands map file
 
247
    if ext == 'wmf':
 
248
        raise ValidationError(
 
249
            'This seems to be a widelands map file. Please upload \
 
250
            it at our maps section.'
 
251
        )
 
252
 
 
253
    # Widelands savegame (*.wgf) and widelands replay (*.wrpl.wgf)
 
254
    # are not the same.
 
255
    if ext == 'wgf' and not splitted_fn[-2] == 'wrpl':
 
256
        if not _zip_contains(settings.WGF_CONTENT_CHECK):
 
257
            raise ValidationError(
 
258
                'This is not a valid widelands savegame.'
 
259
            )
 
260
 
 
261
    # Widelands replay
 
262
    if ext == 'wrpl':
 
263
        raise ValidationError(
 
264
            'This file is part of a replay. Please zip it together with \
 
265
            the corresponding .wrpl.wgf file and upload again.'
 
266
        )
 
267
 
 
268
    if ext == 'zip':
 
269
        if _is_zip() == None:
 
270
            raise ValidationError(
 
271
                'This is not a valid zip file.'
 
272
            )
 
273
 
 
274
    # Widelands AI configuration
 
275
    if ext == 'wai':
 
276
        wai = configparser.ConfigParser()
 
277
        try:
 
278
            wai.read(tmp_file_path)
 
279
            wai_sections = wai.sections()
 
280
            if len(settings.ALLOWED_WAI_SECTIONS) == len(wai_sections):
 
281
                for section in settings.ALLOWED_WAI_SECTIONS:
 
282
                    if section not in wai_sections:
 
283
                        raise
 
284
            else:
 
285
                raise
 
286
        except:
 
287
            raise ValidationError(
 
288
                'This not a valid wai file.'
 
289
                )
 
290
 
 
291
    # Checks by MimeType
 
292
    # Get MIME-Type from python-magic
 
293
    magic_mime = magic.from_file(tmp_file_path, mime=True)
 
294
    magic_mime = _split_mime(magic_mime)
 
295
    send_mime = _split_mime(attachment.content_type)
 
296
 
 
297
    # Check for valid image file. Use te mime-type provided by python-magic,
 
298
    # because for a renamed image the wrong mime-type is send by the browser.
 
299
    if magic_mime['maintype'] == 'image':
 
300
        if not _is_image():
 
301
            raise ValidationError(
 
302
                'This is not a valid image: %(file)s',
 
303
                params={'file': attachment.name}
 
304
            )
 
305
 
 
306
    # Compare Mime type send by browser and Mime type from python-magic.
 
307
    # We only compare the main type (the first part) because the second
 
308
    # part may not be recoginzed correctly. E.g. for .lua the submitted
 
309
    # type is 'text/x-lua' but 'x-lua' is not official at all. See:
 
310
    # https://www.iana.org/assignments/media-types/media-types.xhtml
 
311
    # Unrecoginzed extension are always send with mime type
 
312
    # 'application/octet-stream'. Skip if we know them. 
 
313
    if not ext in settings.SKIP_MIME_EXTENSIONS:
 
314
        if not magic_mime['maintype'] == send_mime['maintype']:
 
315
            raise ValidationError(
 
316
                'The file %(file)s looks like %(send_mime)s, \
 
317
                but we think it is %(magic_mime)s',
 
318
                params={
 
319
                    'file': attachment.name,
 
320
                    'send_mime': send_mime['maintype'],
 
321
                    'magic_mime': magic_mime['maintype'],
 
322
                    },
 
323
            )