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