Only accept a certain file type in FileField, server-side

前端 未结 10 593
日久生厌
日久生厌 2020-11-27 02:56

How can I restrict FileField to only accept a certain type of file (video, audio, pdf, etc.) in an elegant way, server-side?

相关标签:
10条回答
  • 2020-11-27 03:14

    First. Create a file named formatChecker.py inside the app where the you have the model that has the FileField that you want to accept a certain file type.

    This is your formatChecker.py:

    from django.db.models import FileField
    from django.forms import forms
    from django.template.defaultfilters import filesizeformat
    from django.utils.translation import ugettext_lazy as _
    
    class ContentTypeRestrictedFileField(FileField):
        """
        Same as FileField, but you can specify:
            * content_types - list containing allowed content_types. Example: ['application/pdf', 'image/jpeg']
            * max_upload_size - a number indicating the maximum file size allowed for upload.
                2.5MB - 2621440
                5MB - 5242880
                10MB - 10485760
                20MB - 20971520
                50MB - 5242880
                100MB 104857600
                250MB - 214958080
                500MB - 429916160
    """
    def __init__(self, *args, **kwargs):
        self.content_types = kwargs.pop("content_types")
        self.max_upload_size = kwargs.pop("max_upload_size")
    
        super(ContentTypeRestrictedFileField, self).__init__(*args, **kwargs)
    
    def clean(self, *args, **kwargs):        
        data = super(ContentTypeRestrictedFileField, self).clean(*args, **kwargs)
    
        file = data.file
        try:
            content_type = file.content_type
            if content_type in self.content_types:
                if file._size > self.max_upload_size:
                    raise forms.ValidationError(_('Please keep filesize under %s. Current filesize %s') % (filesizeformat(self.max_upload_size), filesizeformat(file._size)))
            else:
                raise forms.ValidationError(_('Filetype not supported.'))
        except AttributeError:
            pass        
    
        return data
    

    Second. In your models.py, add this:

    from formatChecker import ContentTypeRestrictedFileField
    

    Then instead of using 'FileField', use this 'ContentTypeRestrictedFileField'.

    Example:

    class Stuff(models.Model):
        title = models.CharField(max_length=245)
        handout = ContentTypeRestrictedFileField(upload_to='uploads/', content_types=['video/x-msvideo', 'application/pdf', 'video/mp4', 'audio/mpeg', ],max_upload_size=5242880,blank=True, null=True)
    

    Those are the things you have to when you want to only accept a certain file type in FileField.

    0 讨论(0)
  • 2020-11-27 03:14

    I think you would be best suited using the ExtFileField that Dominic Rodger specified in his answer and python-magic that Daniel Quinn mentioned is the best way to go. If someone is smart enough to change the extension at least you will catch them with the headers.

    0 讨论(0)
  • 2020-11-27 03:20

    You can define a list of accepted mime types in settings and then define a validator which uses python-magic to detect the mime-type and raises ValidationError if the mime-type is not accepted. Set that validator on the file form field.

    The only problem is that sometimes the mime type is application/octet-stream, which could correspond to different file formats. Did someone of you overcome this issue?

    0 讨论(0)
  • 2020-11-27 03:22

    There's a Django snippet that does this:

    import os
    
    from django import forms
    
    class ExtFileField(forms.FileField):
        """
        Same as forms.FileField, but you can specify a file extension whitelist.
    
        >>> from django.core.files.uploadedfile import SimpleUploadedFile
        >>>
        >>> t = ExtFileField(ext_whitelist=(".pdf", ".txt"))
        >>>
        >>> t.clean(SimpleUploadedFile('filename.pdf', 'Some File Content'))
        >>> t.clean(SimpleUploadedFile('filename.txt', 'Some File Content'))
        >>>
        >>> t.clean(SimpleUploadedFile('filename.exe', 'Some File Content'))
        Traceback (most recent call last):
        ...
        ValidationError: [u'Not allowed filetype!']
        """
        def __init__(self, *args, **kwargs):
            ext_whitelist = kwargs.pop("ext_whitelist")
            self.ext_whitelist = [i.lower() for i in ext_whitelist]
    
            super(ExtFileField, self).__init__(*args, **kwargs)
    
        def clean(self, *args, **kwargs):
            data = super(ExtFileField, self).clean(*args, **kwargs)
            filename = data.name
            ext = os.path.splitext(filename)[1]
            ext = ext.lower()
            if ext not in self.ext_whitelist:
                raise forms.ValidationError("Not allowed filetype!")
    
    #-------------------------------------------------------------------------
    
    if __name__ == "__main__":
        import doctest, datetime
        doctest.testmod()
    
    0 讨论(0)
  • 2020-11-27 03:23

    Django in version 1.11 has a newly added FileExtensionValidator for model fields, the docs is here: https://docs.djangoproject.com/en/dev/ref/validators/#fileextensionvalidator.

    An example of how to validate a file extension:

    from django.core.validators import FileExtensionValidator
    from django.db import models
    
    class MyModel(models.Model):
        pdf_file = models.FileField(upload_to='foo/',
                                    validators=[FileExtensionValidator(allowed_extensions=['pdf'])])
    

    Note that this method is not safe. Citation from Django docs:

    Don’t rely on validation of the file extension to determine a file’s type. Files can be renamed to have any extension no matter what data they contain.

    There is also new validate_image_file_extension (https://docs.djangoproject.com/en/dev/ref/validators/#validate-image-file-extension) for validating image extensions (using Pillow).

    0 讨论(0)
  • 2020-11-27 03:30

    One very easy way is to use a custom validator.

    In your app's validators.py:

    def validate_file_extension(value):
        import os
        from django.core.exceptions import ValidationError
        ext = os.path.splitext(value.name)[1]  # [0] returns path+filename
        valid_extensions = ['.pdf', '.doc', '.docx', '.jpg', '.png', '.xlsx', '.xls']
        if not ext.lower() in valid_extensions:
            raise ValidationError('Unsupported file extension.')
    

    Then in your models.py:

    from .validators import validate_file_extension
    

    ... and use the validator for your form field:

    class Document(models.Model):
        file = models.FileField(upload_to="documents/%Y/%m/%d", validators=[validate_file_extension])
    

    See also: How to limit file types on file uploads for ModelForms with FileFields?.

    Warning

    For securing your code execution environment from malicious media files

    1. Use Exif libraries to properly validate the media files.
    2. Separate your media files from your application code execution environment
    3. If possible use solutions like S3, GCS, Minio or anything similar
    4. When loading media files on client side, use client native methods (for example if you are loading the media files non securely in a browser, it may cause execution of "crafted" JavaScript code)
    0 讨论(0)
提交回复
热议问题