Getting error "SuspiciousFileOperation" after Django Version Update Django: 3.1.9 from Django: 3.1.8

Viewed 1782

I am getting this error after updating to Django: 3.1.9, on Django: 3.1.8 it works fine.

I have a Files model with a FileField as below:

class JobFiles(BaseModel):
    category = models.CharField(max_length=50, choices=FILE_CATEGORY)
    job_file = models.FileField('JobFile', upload_to=user_directory_path)

I specified the upload_to option so that it gets uploaded differently per Category:

def user_directory_path(instance, filename):
    import uuid
    if not instance.job_file_name:
        instance.job_file_name = filename
    if instance.category == 'Job Card':
        return f'job_card/{uuid.uuid4()}'
    if instance.category == 'Photos':
        return f'job_card/photos/{uuid.uuid4()}'
    if instance.category == 'Other':
        return f'job_card/other/{uuid.uuid4()}'
    return f'job_card/other/{uuid.uuid4()}'

The code generating the error (double checked the file exists and it is wrapped in a file object):

from django.core.files import File

def test()
    job_files = job_card.job_card_file or JobFiles(category='JobCard')
    # Get path to a temp file that was processed (existence and other checks already done)
    doc = '/tmp/mergedfile.pdf'
    with open(doc, mode='rb') as f:
        job_files.job_file = File(f)
        job_files.save()

The error I am getting:

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/django/core/handlers/exception.py", line 47, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.9/site-packages/django/core/handlers/base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.9/site-packages/django/contrib/auth/decorators.py", line 21, in _wrapped_view
    return view_func(request, *args, **kwargs)
  File "/code/job_cards/views.py", line 311, in job_card_maintenance_create
    job_files.save()
  File "/usr/local/lib/python3.9/site-packages/django/db/models/base.py", line 753, in save
    self.save_base(using=using, force_insert=force_insert,
  File "/usr/local/lib/python3.9/site-packages/django/db/models/base.py", line 790, in save_base
    updated = self._save_table(
  File "/usr/local/lib/python3.9/site-packages/django/db/models/base.py", line 895, in _save_table
    results = self._do_insert(cls._base_manager, using, fields, returning_fields, raw)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/base.py", line 933, in _do_insert
    return manager._insert(
  File "/usr/local/lib/python3.9/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/query.py", line 1254, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1396, in execute_sql
    for sql, params in self.as_sql():
  File "/usr/local/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1339, in as_sql
    value_rows = [
  File "/usr/local/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1340, in <listcomp>
    [self.prepare_value(field, self.pre_save_val(field, obj)) for field in fields]
  File "/usr/local/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1340, in <listcomp>
    [self.prepare_value(field, self.pre_save_val(field, obj)) for field in fields]
  File "/usr/local/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1291, in pre_save_val
    return field.pre_save(obj, add=True)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/fields/files.py", line 308, in pre_save
    file.save(file.name, file.file, save=False)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/fields/files.py", line 87, in save
    name = self.field.generate_filename(self.instance, name)
  File "/usr/local/lib/python3.9/site-packages/django/db/models/fields/files.py", line 322, in generate_filename
    filename = validate_file_name(filename)
  File "/usr/local/lib/python3.9/site-packages/django/core/files/utils.py", line 8, in validate_file_name
    raise SuspiciousFileOperation("File name '%s' includes path elements" % name)

Exception Type: SuspiciousFileOperation at /job_cards/create/
Exception Value: File name '/tmp/a1134065293b47c1a054c8fc1481ec93.pdf' includes path elements

What I tried: Downgrading to 3.1.8 (works) Passing a string to the FileField works, but then it does not execute the upload_to. Tried to save the file in the "Base" folder and the "Media" folder (still getting the same error)

4 Answers

I found that django's new mitigation code doesn't agree with the project's own File and ImageFile default behavior, which is to copy the exact file name from the object you gave the constructor.

applying a change like this to your code should fix it, like it fixed mine:

     doc = '/tmp/mergedfile.pdf'
     with open(doc, mode='rb') as f:
-         job_files.job_file = File(f)
+         job_files.job_file = File(f, name=os.path.basename(doc))
          job_files.save()

edit: The bug is now fixed in the django releases made today, May 13th, 2.23, versions 2.2.23, 3.1.11, 3.2.3

I also ran into this but needed to be able to programatically specify the upload directory. This change makes it impossible to put files in directories when passing a filename. It is still, however, possible to work around this new restriction by specifying a custom upload_to function.

Here is an example commit which works around this change:

+def get_invoice_dir(instance, filename):
+    return "dpnk-{}/{}".format(instance.campaign.pk, filename)
+
+
 @with_author
 class Invoice(StaleSyncMixin, AbstractOrder):
     """Faktura"""
@@ -103,14 +107,14 @@ class Invoice(StaleSyncMixin, AbstractOrder):
     )
     invoice_pdf = models.FileField(
         verbose_name=_(u"PDF faktury"),
-        upload_to=u"invoices",
+        upload_to=get_invoice_dir,
         max_length=512,
         blank=True,
         null=True,
     )
     invoice_xml = models.FileField(
         verbose_name=_("XML faktury"),
-        upload_to="invoices",
+        upload_to=get_invoice_dir,
         max_length=512,
         blank=True,
         null=True,
@@ -340,16 +344,13 @@ def create_and_send_invoice_files(sender, instance, created, **kwargs):
     if not instance.invoice_pdf or not instance.invoice_xml:
         invoice_data = invoice_gen.generate_invoice(instance)
         instance.total_amount = invoice_data.price_tax
-        filename = "dpnk-%s/%s" % (
-            instance.campaign.pk,
-            slugify(
-                "invoice_%s_%s_%s_%s"
-                % (
-                    instance.sequence_number,
-                    instance.company.name[0:40],
-                    instance.exposure_date.strftime("%Y-%m-%d"),
-                    uuid.uuid4(),
-                ),
+        filename = slugify(
+            "invoice_%s_%s_%s_%s"
+            % (
+                instance.sequence_number,
+                instance.company.name[0:40],
+                instance.exposure_date.strftime("%Y-%m-%d"),
+                uuid.uuid4(),
             ),
         )
Related