Akzeptieren Sie nur einen bestimmten Dateityp in FileField, serverseitig

74

Wie kann ich einschränken FileField, dass nur ein bestimmter Dateityp (Video, Audio, PDF usw.) auf elegante Weise serverseitig akzeptiert wird?

Maroxe
quelle
1
Informationen zum Öffnen des Dialogfelds zum Einschränken von Dateien auf bestimmte Typen clientseitig finden Sie in dieser Frage .
Flimm

Antworten:

104

Eine sehr einfache Möglichkeit ist die Verwendung eines benutzerdefinierten Validators.

In Ihrer App 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.')

Dann in Ihrem models.py:

from .validators import validate_file_extension

... und verwenden Sie den Validator für Ihr Formularfeld:

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

Siehe auch: Wie kann ich Dateitypen beim Hochladen von Dateien für ModelForms mit FileFields einschränken? .

Warnung

Zum Sichern Ihrer Codeausführungsumgebung vor schädlichen Mediendateien

  1. Verwenden Sie Exif- Bibliotheken , um die Mediendateien ordnungsgemäß zu überprüfen.
  2. Trennen Sie Ihre Mediendateien von Ihrer Anwendungscode-Ausführungsumgebung
  3. Verwenden Sie nach Möglichkeit Lösungen wie S3, GCS, Minio oder ähnliches
  4. Verwenden Sie beim Laden von Mediendateien auf der Clientseite native Client-Methoden (wenn Sie beispielsweise die Mediendateien nicht sicher in einem Browser laden, kann dies zur Ausführung von "gestaltetem" JavaScript-Code führen).
SaeX
quelle
11
@dabad, das nur die Erweiterung verwendet, ist nicht gut für die Validierung von Dateien. Diese Art von Antworten führt zu Sicherheitslücken. Bitte Leute, die diese Antworten sehen, überprüfen Sie auch CVE in Bezug auf verschiedene Formate und Validatoren, nehmen Sie den Fall von Kissen / PIL zum Beispiel: O
Renjith Thankachan
1
@RenjithThankachan Manchmal können Bilder von Exif-Daten mit beliebigem Code geändert werden, aber dieser Trick wurde in der Vergangenheit verwendet, um mit PHP-Apps zu arbeiten.
Ammad Khalid
1
Eine Sache, die wir tun können, ist, Lösungen wie S3, Minio zu verwenden, um Mediendateien von Benutzern von der Anwendungscode-Ausführungsumgebung @AmmadKhalid
Renjith Thankachan
83

Django in der Version 1.11hat einen neu hinzugefügten FileExtensionValidatorfür Modellfelder. Die Dokumentation finden Sie hier: https://docs.djangoproject.com/en/dev/ref/validators/#fileextensionvalidator .

Ein Beispiel für die Überprüfung einer Dateierweiterung:

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'])])

Beachten Sie, dass diese Methode nicht sicher ist . Zitat aus Django-Dokumenten:

Verlassen Sie sich nicht auf die Überprüfung der Dateierweiterung, um den Dateityp zu bestimmen. Dateien können umbenannt werden, um eine beliebige Erweiterung zu haben, unabhängig davon, welche Daten sie enthalten.

Es gibt auch neue validate_image_file_extension( https://docs.djangoproject.com/de/dev/ref/validators/#validate-image-file-extension ) zum Überprüfen von Bilderweiterungen (mit Pillow).

illagrenan
quelle
3
Die Datei wurde hochgeladen, obwohl sie allowed_extensionsaus irgendeinem Grund nicht hinzugefügt wurde . Verwenden des Django Rest Framework.
Vineeth Sai
10

Es gibt einen Django-Ausschnitt , der dies tut:

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()
Dominic Rodger
quelle
2
Dies ist ein Filter, der auf einer Erweiterung basiert und überhaupt nicht zuverlässig ist. Ich habe darüber nachgedacht, die Datei nach dem Hochladen zu analysieren.
Maroxe
@maroxe Hast du eine Lösung dafür gefunden? Ich habe nur das gleiche Problem mit der Feststellung, ob Dateien Audiodateien sind oder nicht.
März
3
Versuchen Sie diesen Link: nemesisdesign.net/blog/coding/…
PhoebeB
1
@ user126795 - das bestimmt immer noch nicht, ob es der richtige Typ ist - es scheint sich nur auf den angegebenen Inhaltstyp zu verlassen - was möglicherweise nicht zuverlässig ist.
Dominic Rodger
2
Sie können Python-Magic (Wrapper für libmagic) verwenden, um den Mimetyp abzurufen und dann basierend darauf zu akzeptieren / abzulehnen.
Daniel Quinn
8

Zuerst. Erstellen Sie eine Datei mit dem Namen formatChecker.py in der App, in der Sie das Modell mit dem FileField haben, das Sie für einen bestimmten Dateityp akzeptieren möchten.

Dies ist Ihr 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

Zweite. Fügen Sie in Ihrer models.py Folgendes hinzu:

from formatChecker import ContentTypeRestrictedFileField

Verwenden Sie dann anstelle von 'FileField' dieses 'ContentTypeRestrictedFileField'.

Beispiel:

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)

Dies sind die Dinge, die Sie tun müssen, wenn Sie nur einen bestimmten Dateityp in FileField akzeptieren möchten.

Erstaunlicher Angelo
quelle
Inhaltstyp ist leicht betrogen
Trinh Hoang Nhu
3
Durch Hinzufügen dieser Zeile vor dem Superaufruf: self.widget = ClearableFileInput (attrs = {'accept': ','. Join (self.content_types)}) werden nur akzeptierte Inhaltstypen im modalen Fenster ausgewählt.
Laffuste
@laffuste Nachdem Sie Ihren Kommentar bewertet haben, scheint dies auf dem Mac nicht zu funktionieren (dh alle Dateien können ausgewählt werden). Habe aber nicht unter Windows getestet.
Erve1879
1
Funktioniert in Chrom, FF gibt keinen ****. Ich fand heraus, dass es sowieso besser ist, nach Erweiterung als nach Inhaltstyp zu filtern. Letzteres wird von Ihrem Browser + os fast willkürlich eingestellt.
Laffuste
8

Einige Leute haben vorgeschlagen, Python-Magic zu verwenden, um zu überprüfen, ob die Datei tatsächlich von dem Typ ist, den Sie erwarten. Dies kann in die validatorin der akzeptierten Antwort vorgeschlagene Antwort aufgenommen werden:

import os
import magic
from django.core.exceptions import ValidationError

def validate_is_pdf(file):
    valid_mime_types = ['application/pdf']
    file_mime_type = magic.from_buffer(file.read(1024), mime=True)
    if file_mime_type not in valid_mime_types:
        raise ValidationError('Unsupported file type.')
    valid_file_extensions = ['.pdf']
    ext = os.path.splitext(file.name)[1]
    if ext.lower() not in valid_file_extensions:
        raise ValidationError('Unacceptable file extension.')

In diesem Beispiel wird nur ein PDF überprüft, es können jedoch beliebig viele MIME-Typen und Dateierweiterungen zu den Arrays hinzugefügt werden.

Angenommen, Sie haben das oben Gesagte gespeichert validators.py, können Sie dies wie folgt in Ihr Modell integrieren:

from myapp.validators import validate_is_pdf

class PdfFile(models.Model):
    file = models.FileField(upload_to='pdfs/', validators=(validate_is_pdf,))
Das ist wichtig
quelle
2
Auf jeden Fall die sicherste Antwort auf der Seite!
Rune Kaagaard
7

Sie können das Folgende verwenden, um Dateitypen in Ihrem Formular einzuschränken

file = forms.FileField(widget=forms.FileInput(attrs={'accept':'application/pdf'}))
savp
quelle
2

Ich denke, Sie wären am besten geeignet, wenn Sie das ExtFileField verwenden würden , das Dominic Rodger in seiner Antwort angegeben hat, und die von Daniel Quinn erwähnte Python-Magie ist der beste Weg. Wenn jemand klug genug ist, um zumindest die Erweiterung zu ändern, werden Sie ihn mit den Headern abfangen.

man2xxl
quelle
3
Da Sie festgestellt haben, dass dies die beste Methode ist, können Sie Code veröffentlichen, um ihn dem Rest von uns zu demonstrieren.
Thismatters
2

Sie können eine Liste der akzeptierten MIME-Typen in den Einstellungen definieren und dann einen Validator definieren, der Python-Magic verwendet, um den MIME-Typ zu erkennen, und ValidationError auslöst, wenn der MIME-Typ nicht akzeptiert wird. Setzen Sie diesen Validator in das Dateiformatfeld.

Das einzige Problem ist, dass der MIME-Typ manchmal Application / Octet-Stream ist, was verschiedenen Dateiformaten entsprechen kann. Hat jemand von Ihnen dieses Problem gelöst?

Sabrina
quelle
1

Zusätzlich werde ich diese Klasse mit etwas zusätzlichem Verhalten erweitern.

class ContentTypeRestrictedFileField(forms.FileField):
    ...
    widget = None
    ...
    def __init__(self, *args, **kwargs):
        ...
        self.widget = forms.ClearableFileInput(attrs={'accept':kwargs.pop('accept', None)})
        super(ContentTypeRestrictedFileField, self).__init__(*args, **kwargs)

Wenn wir eine Instanz mit param accept = ".pdf, .txt" erstellen, werden im Popup mit der Dateistruktur als Standard standardmäßig Dateien mit übergebener Erweiterung angezeigt.

gauee
quelle
1

Nachdem ich die akzeptierte Antwort überprüft hatte, beschloss ich, einen Tipp basierend auf der Django-Dokumentation zu teilen. Es gibt bereits einen Validator zur Validierung der Dateierweiterung. Sie müssen Ihre eigene benutzerdefinierte Funktion nicht neu schreiben, um zu überprüfen, ob Ihre Dateierweiterung zulässig ist oder nicht.

https://docs.djangoproject.com/de/3.0/ref/validators/#fileextensionvalidator

Warnung

Verlassen Sie sich nicht auf die Überprüfung der Dateierweiterung, um den Dateityp zu bestimmen. Dateien können umbenannt werden, um eine beliebige Erweiterung zu haben, unabhängig davon, welche Daten sie enthalten.

Oğuzhan
quelle