2

Why isn't Django's ImageField throwing a validation error here?

    # field in model
    image_mobile = ImageField(
        upload_to='static/images/',
        blank=True,
        null=True
    )

    # in test
    from django.core.files.uploadedfile import SimpleUploadedFile

    attachment = SimpleUploadedFile("file.mp4", b"file_content", content_type="text/plain")
    obj.image_mobile = attachment
    obj.save()
    self.assertEqual(obj.image_mobile, '')

Outputs this:

AssertionError: <ImageFieldFile: static/images/file_7wanB5P.mp4> != ''

From the documentation:

Inherits all attributes and methods from FileField, but also validates that the uploaded object is a valid image.

dan-klasson
  • 13,734
  • 14
  • 63
  • 101

2 Answers2

0

Looks like it's up to PIL library. Django uses it to get image dimensions and it seems to be the only validation for ImageField. It gets first 1024 bytes of the file and reads metadata, and likely mp4 and jpeg have similar data there. So it is not the most reliable way to check

 try:
    # Most of the time Pillow only needs a small chunk to parse the image
    # and get the dimensions, but with some TIFF files Pillow needs to
    # parse the whole file.
    chunk_size = 1024
    while 1:
        data = file.read(chunk_size)
        if not data:
            break
        try:
            p.feed(data)
        except zlib.error as e:
            # ignore zlib complaining on truncated stream, just feed more
            # data to parser (ticket #19457).
            if e.args[0].startswith("Error -5"):
                pass
            else:
                raise
        except struct.error:
            # Ignore PIL failing on a too short buffer when reads return
            # less bytes than expected. Skip and feed more data to the
            # parser (ticket #24544).
            pass
        except RuntimeError:
            # e.g. "RuntimeError: could not create decoder object" for
            # WebP files. A different chunk_size may work.
            pass
        if p.image:
            return p.image.size
        chunk_size *= 2
    return (None, None)
finally:
    if close:
        file.close()
    else:
        file.seek(file_pos)
0
def to_python(self, data):
        """
        Check that the file-upload field data contains a valid image (GIF, JPG,
        PNG, etc. -- whatever Pillow supports).
        """
        f = super().to_python(data)
        if f is None:
            return None

        from PIL import Image

        # We need to get a file object for Pillow. We might have a path or we might
        # have to read the data into memory.
        if hasattr(data, 'temporary_file_path'):
            file = data.temporary_file_path()
        else:
            if hasattr(data, 'read'):
                file = BytesIO(data.read())
            else:
                file = BytesIO(data['content'])

        try:
            # load() could spot a truncated JPEG, but it loads the entire
            # image in memory, which is a DoS vector. See #3848 and #18520.
            image = Image.open(file)
            # verify() must be called immediately after the constructor.
            image.verify()

            # Annotating so subclasses can reuse it for their own validation
            f.image = image
            # Pillow doesn't detect the MIME type of all formats. In those
            # cases, content_type will be None.
            f.content_type = Image.MIME.get(image.format)
        except Exception as exc:
            # Pillow doesn't recognize it as an image.
            raise ValidationError(
                self.error_messages['invalid_image'],
                code='invalid_image',
            ) from exc
        if hasattr(f, 'seek') and callable(f.seek):
            f.seek(0)
        return f

This is code that is run, if you see the documentation

https://pillow.readthedocs.io/en/5.1.x/reference/Image.html?highlight=verify#PIL.Image.Image.verify

Image.verify()

Verifies the contents of a file. For data read from a file, this method attempts to determine if the file is broken, without actually decoding the image data. If this method finds any problems, it raises suitable exceptions. If you need to load the image after using this method, you must reopen the image file.

So basically it is trying to determine if the file is broken. And may be it is validating your mp4 file, that is why no exception. If you want more stricter validations you need to use magic or some other library for determining the file type. See below SO threads for same

How does one use magic to verify file type in a Django form clean method?

Django: file field validation in model using python-magic

Django: Validate file type of uploaded file

https://timmyomahony.com/blog/upload-and-validate-image-from-url-in-django/

Community
  • 1
  • 1
Tarun Lalwani
  • 142,312
  • 9
  • 204
  • 265