From f7485a52f71e7b1d7eba5d69daebd1d3749e7441 Mon Sep 17 00:00:00 2001
From: Thomas NOEL <tnoel@entrouvert.com>
Date: Wed, 5 Feb 2020 14:54:32 +0100
Subject: [PATCH] upload_storage: add can_thumbnails method on PicklableUpload
 (#39517)

---
 wcs/fields.py                |  4 ++--
 wcs/forms/common.py          | 39 ++++++++++++++++++++----------------
 wcs/qommon/upload_storage.py | 11 +++++++++-
 3 files changed, 34 insertions(+), 20 deletions(-)

diff --git a/wcs/fields.py b/wcs/fields.py
index f5e7de2b..50583408 100644
--- a/wcs/fields.py
+++ b/wcs/fields.py
@@ -38,7 +38,7 @@ from django.utils.six.moves.html_parser import HTMLParser
 from .qommon import _, force_str
 from .qommon import evalutils
 from .qommon.form import *
-from .qommon.misc import localstrftime, strftime, date_format, ellipsize, can_thumbnail, xml_node_text
+from .qommon.misc import localstrftime, strftime, date_format, ellipsize, xml_node_text
 from .qommon.template import Template, TemplateError
 from .qommon import get_cfg, get_logger
 
@@ -1041,7 +1041,7 @@ class FileField(WidgetField):
         t = TemplateIO(html=True)
         t += htmltext('<div class="file-field">')
         t += htmltext('<a download="%s" href="[download]?f=%s">') % (value.base_filename, self.id)
-        if include_image_thumbnail and can_thumbnail(value.content_type) and not hasattr(value, 'storage_attrs'):
+        if include_image_thumbnail and hasattr(value, 'can_thumbnail') and value.can_thumbnail():
             t += htmltext('<img alt="" src="[download]?f=%s&thumbnail=1"/>') % self.id
         t += htmltext('<span>%s</span>') % value
         t += htmltext('</a></div>')
diff --git a/wcs/forms/common.py b/wcs/forms/common.py
index c28a9c88..af4486c3 100644
--- a/wcs/forms/common.py
+++ b/wcs/forms/common.py
@@ -67,15 +67,26 @@ class FileDirectory(Directory):
         if component and component != file.base_filename:
             raise errors.TraversalError()
 
-        if hasattr(file, 'storage_attrs'):  # remote storage
-            if self.thumbnails:
+        response = get_response()
+
+        if self.thumbnails:
+            if hasattr(file, 'can_thumbnail') and file.can_thumbnail():
+                try:
+                    thumbnail = misc.get_thumbnail(file.get_filename(),
+                            content_type=file.content_type)
+                    response.set_content_type('image/png')
+                    return thumbnail
+                except misc.ThumbnailError:
+                    raise errors.TraversalError()
+            else:
                 raise errors.TraversalError()
+
+        if hasattr(file, 'storage_attrs'):  # remote storage
             if not file.storage_attrs.get('redirect_url'):
                 raise errors.TraversalError()
             redirect_url = sign_url_auto_orig(file.storage_attrs['redirect_url'])
             return redirect(redirect_url)
 
-        response = get_response()
         if file.content_type:
             response.set_content_type(file.content_type)
         else:
@@ -91,15 +102,6 @@ class FileDirectory(Directory):
             response.set_header('content-disposition',
                     '%s; filename="%s"' % (content_disposition, file.base_filename))
 
-        if self.thumbnails and misc.can_thumbnail(file.content_type):
-            try:
-                thumbnail = misc.get_thumbnail(file.get_filename(),
-                        content_type=file.content_type)
-                response.set_content_type('image/png')
-                return thumbnail
-            except misc.ThumbnailError:
-                pass
-
         return file.get_file_pointer().read()
 
 
@@ -681,17 +683,20 @@ class FormStatusPage(Directory, FormTemplateMixin):
         if not hasattr(file, 'content_type'):
             raise errors.TraversalError()
 
-        if hasattr(file, 'storage_attrs'):  # remote storage
-            if get_request().form.get('thumbnail') == '1':
+        file_url = 'files/%s/' % fn
+
+        if get_request().form.get('thumbnail') == '1':
+            if hasattr(file, 'can_thumbnail') and file.can_thumbnail():
+                file_url += 'thumbnail/'
+            else:
                 raise errors.TraversalError()
+
+        if hasattr(file, 'storage_attrs'):  # remote storage
             if not file.storage_attrs.get('redirect_url'):
                 raise errors.TraversalError()
             redirect_url = sign_url_auto_orig(file.storage_attrs['redirect_url'])
             return redirect(redirect_url)
 
-        file_url = 'files/%s/' % fn
-        if get_request().form.get('thumbnail') == '1':
-            file_url += 'thumbnail/'
         if getattr(file, 'base_filename'):
             file_url += file.base_filename
         return redirect(file_url)
diff --git a/wcs/qommon/upload_storage.py b/wcs/qommon/upload_storage.py
index c08d136a..b0a268a2 100644
--- a/wcs/qommon/upload_storage.py
+++ b/wcs/qommon/upload_storage.py
@@ -24,7 +24,7 @@ from django.utils.module_loading import import_string
 from django.utils.six import StringIO
 
 from .errors import ConnectionError
-from .misc import json_loads, file_digest
+from .misc import json_loads, file_digest, can_thumbnail
 from .storage import atomic_write
 
 
@@ -77,6 +77,9 @@ class PicklableUpload(Upload):
             return base64.encodestring(content)
         return b''
 
+    def can_thumbnail(self):
+        return get_storage_object(getattr(self, 'storage', None)).can_thumbnail(self)
+
 
 class UploadStorageError(Exception):
     pass
@@ -119,6 +122,9 @@ class UploadStorage(object):
             upload.fp.seek(0)
             atomic_write(filepath, upload.fp, async_op=False)
 
+    def can_thumbnail(self, upload):
+        return can_thumbnail(upload.content_type)
+
 
 class RemoteOpaqueUploadStorage(object):
     def __init__(self, ws, **kwargs):
@@ -181,6 +187,9 @@ class RemoteOpaqueUploadStorage(object):
     def save(self, upload):
         pass
 
+    def can_thumbnail(self, upload):
+        return False
+
 
 def get_storage_object(storage):
     if not storage or storage == 'default':
-- 
2.24.1

