django-storage 文件上传SuspiciousOperation 和joined path is located outside the base path component error

django-storage file upload SuspiciousOperation and the joined path is located outside of the base path component error

我正在使用最新版本的 django 和 django-storage。我正在尝试保存上传的图像和本地生成的调整大小的图像。当我尝试保存图像时出现以下错误:

回溯:

File "/home/..proj/.env/lib/python3.6/site-packages/storages/backends/s3boto3.py" in _normalize_name
  431.             return safe_join(self.location, name)

File "/home/prism/Desktop/code/viper/.env/lib/python3.6/site-packages/storages/utils.py" in safe_join
  75.         raise ValueError('the joined path is located outside of the base path'

During handling of the above exception (the joined path is located outside of the base path component), another exception occurred:

File "/home/..proj/.env/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner
  34.             response = get_response(request)

File "/home/..proj/.env/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  115.                 response = self.process_exception_by_middleware(e, request)

File "/home/..proj/.env/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  113.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/home/..proj/.env/lib/python3.6/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
  21.                 return view_func(request, *args, **kwargs)

File "/home/..proj/admin/views.py" in product_update
  578.                                 image_model.image_thumbnail_index.save(a.name, File(a))

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/fields/files.py" in save
  87.         self.name = self.storage.save(name, content, max_length=self.field.max_length)

File "/home/..proj/.env/lib/python3.6/site-packages/django/core/files/storage.py" in save
  51.         name = self.get_available_name(name, max_length=max_length)

File "/home/..proj/.env/lib/python3.6/site-packages/storages/backends/s3boto3.py" in get_available_name
  633.         return super(S3Boto3Storage, self).get_available_name(name, max_length)

File "/home/..proj/.env/lib/python3.6/site-packages/django/core/files/storage.py" in get_available_name
  75.         while self.exists(name) or (max_length and len(name) > max_length):
File "/home/..proj/.env/lib/python3.6/site-packages/storages/backends/s3boto3.py" in exists
  528.         name = self._normalize_name(self._clean_name(name))

File "/home/..proj/.env/lib/python3.6/site-packages/storages/backends/s3boto3.py" in _normalize_name
  434.                                       name)

Exception Type: SuspiciousOperation at /admin/product/update/7/
Exception Value: Attempted access to '/home/..proj/convert/mqedpqL4tepvF4bT7wySMm/jo_308x412.webp' denied.

图片模型:

class ProductImage(models.Model):
    image = models.ImageField(storage=ProductMediaStorage())
    image_thumbnail_index = models.ImageField(storage=ProductMediaStorage())
    image_quickview = models.ImageField(storage=MediaStorage())

自定义 S3 存储:

class ProductMediaStorage(S3Boto3Storage):
    location = settings.AWS_PRODUCT_LOCATION
    default_acl = 'public-read'
    file_overwrite = False

class MediaStorage(S3Boto3Storage):
    location = settings.AWS_MEDIA_LOCATION
    default_acl = 'public-read'
    file_overwrite = False

设置:

AWS_ACCESS_KEY_ID = JSON_DATA['aws_access_key']
AWS_SECRET_ACCESS_KEY = JSON_DATA['aws_secret_key']
AWS_STORAGE_BUCKET_NAME = JSON_DATA['aws_bucket']
AWS_DEFAULT_ACL = None
AWS_IS_GZIPPED = True
AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}

AWS_PRODUCT_LOCATION = 'product'
AWS_MEDIA_LOCATION = 'media'


if not DEBUG:
    STATIC_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{AWS_STATIC_LOCATION}/'
    MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/media/'
    STATICFILES_STORAGE = 'hiren.storage.StaticStorage'
    DEFAULT_FILE_STORAGE = 'hiren.storage.DefaultStorage'
else:
    MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/media/'
    STATIC_URL = '/static/'
    STATIC_ROOT = os.path.join(BASE_DIR, 'collect_static')
    STATICFILES_DIRS = (
        os.path.join(BASE_DIR, "static"),
    )
    DEFAULT_FILE_STORAGE = 'hiren.storage.DefaultStorage'

# uploaded file settings

FILE_UPLOAD_MAX_MEMORY_SIZE = 0

Views.py:

def product_update(request):
   product_file = request.FILES.getlist('image', None)

   if product_file:
      for pro in product_file:
           uuid = shortuuid.uuid()
           directory = BASE_DIR + '/convert/' + uuid
           os.mkdir(directory)
           fs = FileSystemStorage(location=directory)
           filename = fs.save(pro.name, pro)
           image_sizes = ["308x412", "400x400" ]
           files = resize_convert(filename, image_sizes, uuid)
           # print(files)  -->  {"308x412": "/home/.../proj/img location"}
           image_model = ProductImage(image=pro)
           with open(files["308x412"], 'rb') as a, open(files["400x400"], 'rb') as b:
               image_model.image_thumbnail_index.save(a.name, File(a))
               image_model.image_quickview.save(b.name, File(b))

根据另一个 Whosebug 的回答,我已将此解决方案添加到所有客户存储中:

class MediaStorage(S3Boto3Storage):
    location = settings.AWS_MEDIA_LOCATION
    default_acl = 'public-read'
    file_overwrite = False

    def _clean_name(self, name):
        return name

    def _normalize_name(self, name):
        if not name.endswith('/'):
            name += "/"

        name += self.location
        return name

然后我得到这个错误:

File "/home/..proj/.env/lib/python3.6/site-packages/django/core/handlers/exception.py" in inner
  34.             response = get_response(request)

File "/home/..proj/.env/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  115.                 response = self.process_exception_by_middleware(e, request)

File "/home..proj/.env/lib/python3.6/site-packages/django/core/handlers/base.py" in _get_response
  113.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/home/..proj/.env/lib/python3.6/site-packages/django/contrib/auth/decorators.py" in _wrapped_view
  21.                 return view_func(request, *args, **kwargs)

File "/home/..proj/admin/views.py" in product_update
  578.                                 image_model.image_thumbnail_index.save(a.name, File(a))

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/fields/files.py" in save
  93.             self.instance.save()

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/base.py" in save
  741.                        force_update=force_update, update_fields=update_fields)

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/base.py" in save_base
  779.                 force_update, using, update_fields,

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/base.py" in _save_table
  870.             result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/base.py" in _do_insert
  908.                                using=using, raw=raw)

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/manager.py" in manager_method
  82.                 return getattr(self.get_queryset(), name)(*args, **kwargs)

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/query.py" in _insert
  1186.         return query.get_compiler(using=using).execute_sql(return_id)

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/sql/compiler.py" in execute_sql
  1334.             for sql, params in self.as_sql():

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/sql/compiler.py" in as_sql
  1278.                 for obj in self.query.objs

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/sql/compiler.py" in <listcomp>
  1278.                 for obj in self.query.objs

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/sql/compiler.py" in <listcomp>
  1277.                 [self.prepare_value(field, self.pre_save_val(field, obj)) for field in fields]

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/sql/compiler.py" in pre_save_val
  1228.         return field.pre_save(obj, add=True)

File "/home/..proj/.env/lib/python3.6/site-packages/django/db/models/fields/files.py" in pre_save
  288.             file.save(file.name, file.file, save=False)

File "/home..proj/.env/lib/python3.6/site-packages/django/db/models/fields/files.py" in save
  87.         self.name = self.storage.save(name, content, max_length=self.field.max_length)

File "/home/..proj/.env/lib/python3.6/site-packages/django/core/files/storage.py" in save
  52.         return self._save(name, content)

File "/home/..proj/.env/lib/python3.6/site-packages/storages/backends/s3boto3.py" in _save
  506.         self._save_content(obj, content, parameters=parameters)

File "/home/..proj/.env/lib/python3.6/site-packages/storages/backends/s3boto3.py" in _save_content
  521.         obj.upload_fileobj(content, ExtraArgs=put_parameters)

File "/home/..proj/.env/lib/python3.6/site-packages/boto3/s3/inject.py" in object_upload_fileobj
  621.         ExtraArgs=ExtraArgs, Callback=Callback, Config=Config)

File "/home/..proj/.env/lib/python3.6/site-packages/boto3/s3/inject.py" in upload_fileobj
  539.         return future.result()

File "/home/..proj/.env/lib/python3.6/site-packages/s3transfer/futures.py" in result
  106.             return self._coordinator.result()

File "/home/..proj/.env/lib/python3.6/site-packages/s3transfer/futures.py" in result
  265.             raise self._exception

File "/home/..proj/.env/lib/python3.6/site-packages/s3transfer/tasks.py" in __call__
  126.                 return self._execute_main(kwargs)

File "/home/..proj/.env/lib/python3.6/site-packages/s3transfer/tasks.py" in _execute_main
  150.         return_value = self._main(**kwargs)

File "/home/..proj/.env/lib/python3.6/site-packages/s3transfer/upload.py" in _main
  692.             client.put_object(Bucket=bucket, Key=key, Body=body, **extra_args)

File "/home/..proj/.env/lib/python3.6/site-packages/s3transfer/utils.py" in __exit__
  525.         self.close()

File "/home/..proj/.env/lib/python3.6/site-packages/s3transfer/utils.py" in close
  508.         self._fileobj.close()

File "/home/..proj/.env/lib/python3.6/site-packages/s3transfer/upload.py" in close
  95.         self._fileobj.close()

File "/home/..proj/.env/lib/python3.6/tempfile.py" in close
  650.         self._closer.close()

File "/home/..proj/.env/lib/python3.6/tempfile.py" in close
  587.                         unlink(self.name)

Exception Type: FileNotFoundError at /admin/product/update/7/
Exception Value: [Errno 2] No such file or directory: '/tmp/tmpfnqmmwq8.upload.jpeg'

修复:

class ProductMediaStorage(S3Boto3Storage):
    location = settings.AWS_PRODUCT_LOCATION
    default_acl = 'public-read'
    file_overwrite = False

    def _save_content(self, obj, content, parameters):
        """
        We create a clone of the content file as when this is passed to boto3 it wrongly closes
        the file upon upload where as the storage backend expects it to still be open
        """
        # Seek our content back to the start
        content.seek(0, os.SEEK_SET)

        # Create a temporary file that will write to disk after a specified size
        content_autoclose = SpooledTemporaryFile()

        # Write our original content into our copy that will be closed by boto3
        content_autoclose.write(content.read())

        # Upload the object which will auto close the content_autoclose instance
        super(ProductMediaStorage, self)._save_content(obj, content_autoclose, parameters)

        # Cleanup if this is fixed upstream our duplicate should always close
        if not content_autoclose.closed:
            content_autoclose.close()

    def _normalize_name(self, name):
        # if not name.endswith('/'):
        #     name += "/"
        #
        # name += self.location
        return name