web-dev-qa-db-fra.com

Django: Valider le type de fichier du fichier téléchargé

J'ai une application qui permet aux gens de télécharger des fichiers, représentés par UploadedFiles. Cependant, je veux m'assurer que les utilisateurs téléchargent uniquement des fichiers xml. Je sais que je peux le faire en utilisant magic, mais je ne sais pas où placer cette vérification - je ne peux pas le mettre dans la fonction clean car le fichier n'est pas encore téléchargé quand clean s'exécute, pour autant que je sache.

Voici le modèle UploadedFile:

class UploadedFile(models.Model):
    """This represents a file that has been uploaded to the server."""
    STATE_UPLOADED = 0
    STATE_ANNOTATED = 1
    STATE_PROCESSING = 2
    STATE_PROCESSED = 4
    STATES = (
        (STATE_UPLOADED, "Uploaded"),
        (STATE_ANNOTATED, "Annotated"),
        (STATE_PROCESSING, "Processing"),
        (STATE_PROCESSED, "Processed"),
    )

    status = models.SmallIntegerField(choices=STATES,
        default=0, blank=True, null=True) 
    file = models.FileField(upload_to=settings.XML_ROOT)
    project = models.ForeignKey(Project)

    def __unicode__(self):
        return self.file.name

    def name(self):
        return os.path.basename(self.file.name)

    def save(self, *args, **kwargs):
        if not self.status:
            self.status = self.STATE_UPLOADED
        super(UploadedFile, self).save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        os.remove(self.file.path)
        self.file.delete(False)
        super(UploadedFile, self).delete(*args, **kwargs)

    def get_absolute_url(self):
        return u'/upload/projects/%d' % self.id

    def clean(self):
        if not "XML" in magic.from_file(self.file.url):
            raise ValidationError(u'Not an xml file.')

class UploadedFileForm(forms.ModelForm):
    class Meta:                
        model = UploadedFile
        exclude = ('project',)
31
Plasma

Pour la postérité: la solution est d'utiliser la méthode read et de la transmettre à magic.from_buffer.

class UploadedFileForm(ModelForm):
    def clean_file(self):
        file = self.cleaned_data.get("file", False)
        filetype = magic.from_buffer(file.read())
        if not "XML" in filetype:
            raise ValidationError("File is not XML.")
        return file

    class Meta:
        model = models.UploadedFile
        exclude = ('project',)
15
Plasma

Valider les fichiers est un défi commun, donc je voudrais utiliser un validateur:

import magic

from Django.utils.deconstruct import deconstructible
from Django.template.defaultfilters import filesizeformat


@deconstructible
class FileValidator(object):
    error_messages = {
     'max_size': ("Ensure this file size is not greater than %(max_size)s."
                  " Your file size is %(size)s."),
     'min_size': ("Ensure this file size is not less than %(min_size)s. "
                  "Your file size is %(size)s."),
     'content_type': "Files of type %(content_type)s are not supported.",
    }

    def __init__(self, max_size=None, min_size=None, content_types=()):
        self.max_size = max_size
        self.min_size = min_size
        self.content_types = content_types

    def __call__(self, data):
        if self.max_size is not None and data.size > self.max_size:
            params = {
                'max_size': filesizeformat(self.max_size), 
                'size': filesizeformat(data.size),
            }
            raise ValidationError(self.error_messages['max_size'],
                                   'max_size', params)

        if self.min_size is not None and data.size < self.min_size:
            params = {
                'min_size': filesizeformat(self.mix_size),
                'size': filesizeformat(data.size)
            }
            raise ValidationError(self.error_messages['min_size'], 
                                   'min_size', params)

        if self.content_types:
            content_type = magic.from_buffer(data.read(), mime=True)
            data.seek(0)

            if content_type not in self.content_types:
                params = { 'content_type': content_type }
                raise ValidationError(self.error_messages['content_type'],
                                   'content_type', params)

    def __eq__(self, other):
        return (
            isinstance(other, FileValidator) and
            self.max_size == other.max_size and
            self.min_size == other.min_size and
            self.content_types == other.content_types
        )

Ensuite, vous pouvez utiliser FileValidator dans votre model.FileField ou forms.FileField comme suit:

validate_file = FileValidator(max_size=1024 * 100, 
                             content_types=('application/xml',))
file = models.FileField(upload_to=settings.XML_ROOT, 
                        validators=[validate_file])
23
Sultan Alotaibi

Depuis Django 1.11, vous pouvez également utiliser FileExtensionValidator.

from Django.core.validators import FileExtensionValidator
class UploadedFile(models.Model):
    file = models.FileField(upload_to=settings.XML_ROOT, 
        validators=[FileExtensionValidator(allowed_extensions=['xml'])])

Notez que cela doit être utilisé sur un FileField et ne fonctionnera pas sur un CharField (par exemple), car le validateur valide sur value.name.

réf: https://docs.djangoproject.com/en/dev/ref/validators/#fileextensionvalidator

14
rbennell

Je pense que ce que vous voulez faire est de nettoyer le fichier téléchargé dans les méthodes Form.clean_your_field_name_here() de Django - les données sont alors disponibles sur votre système si elles ont été soumises en HTTP normal POST demande.

De plus, si vous considérez cela comme inefficace, explorez les options des différents backends de téléchargement de fichiers Django et comment faire un traitement en streaming.

Si vous devez tenir compte de la sécurité du système lorsque vous traitez des téléchargements

  • Assurez-vous que le fichier téléchargé a l'extension correcte

  • Assurez-vous que le mimetype correspond à l'extension de fichier

Dans le cas où vous craignez que l'utilisateur télécharge des fichiers d'exploitation (pour attaquer contre votre site)

  • Réécrivez tout le contenu du fichier lors de la sauvegarde pour vous débarrasser de la charge utile supplémentaire (exploit) (vous ne pouvez donc pas incorporer du HTML dans XML que le navigateur interpréterait comme un fichier HTML d'origine du site lors du téléchargement)

  • Assurez-vous d'utiliser l'en-tête de disposition de contenu lors du téléchargement

Quelques informations supplémentaires ici: http://opensourcehacker.com/2013/07/31/secure-user-uploads-and-exploiting-served-user-content/

Voici mon exemple de la façon dont je désinfecte les images téléchargées:

class Example(models.Model):
    image = models.ImageField(upload_to=filename_gen("participant-images/"), blank=True, null=True)


class Example(forms.ModelForm):
    def clean_image(self):
        """ Clean the uploaded image attachemnt.
        """
        image = self.cleaned_data.get('image', False)
        utils.ensure_safe_user_image(image)
        return image


def ensure_safe_user_image(image):
    """ Perform various checks to sanitize user uploaded image data.

    Checks that image was valid header, then

    :param: InMemoryUploadedFile instance (Django form field value)

    :raise: ValidationError in the case the image content has issues
    """

    if not image:
        return

    assert isinstance(image, InMemoryUploadedFile), "Image rewrite has been only tested on in-memory upload backend"

    # Make sure the image is not too big, so that PIL trashes the server
    if image:
        if image._size > 4*1024*1024:
            raise ValidationError("Image file too large - the limit is 4 megabytes")

    # Then do header peak what the image claims
    image.file.seek(0)
    mime = magic.from_buffer(image.file.getvalue(), mime=True)
    if mime not in ("image/png", "image/jpeg"):
        raise ValidationError("Image is not valid. Please upload a JPEG or PNG image.")

    doc_type = mime.split("/")[-1].upper()

    # Read data from cStringIO instance
    image.file.seek(0)
    pil_image = Image.open(image.file)

    # Rewrite the image contents in the memory
    # (bails out with exception on bad data)
    buf = StringIO()
    pil_image.thumbnail((2048, 2048), Image.ANTIALIAS)
    pil_image.save(buf, doc_type)
    image.file = buf

    # Make sure the image has valid extension (can't upload .htm image)
    extension = unicode(doc_type.lower())
    if not image.name.endswith(u".%s" % extension):
        image.name = image.name + u"." + extension
4
Mikko Ohtamaa