From e8f5f5d1d7c194d6442a5181c6ba9ac5b3e25914 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Mon, 12 Apr 2021 22:17:28 +0200
Subject: [PATCH] misc: add a |strip_metadata filter (#51483)

---
 tests/test_workflows.py           | 15 +++++++++++++++
 wcs/qommon/evalutils.py           | 26 ++------------------------
 wcs/qommon/templatetags/qommon.py |  7 +++++++
 wcs/qommon/upload_storage.py      | 21 ++++++++++++++++++++-
 4 files changed, 44 insertions(+), 25 deletions(-)

diff --git a/tests/test_workflows.py b/tests/test_workflows.py
index d35b5e7fa..4603e393b 100644
--- a/tests/test_workflows.py
+++ b/tests/test_workflows.py
@@ -4408,6 +4408,21 @@ def test_set_backoffice_field_file(http_requests, two_pubs):
         == open(os.path.join(os.path.dirname(__file__), 'image-with-gps-data.jpeg'), 'rb').read()
     )
 
+    # check |strip_metadata filter
+    formdata = formdef.data_class()()
+    formdata.data = {'00': upload}
+    formdata.just_created()
+    formdata.store()
+
+    two_pubs.substitutions.feed(formdata)
+    item.fields = [{'field_id': 'bo1', 'value': '{{form_var_file|strip_metadata}}'}]
+    item.perform(formdata)
+
+    assert formdata.data['bo1'].base_filename == 'test.jpeg'
+    assert formdata.data['bo1'].content_type == 'image/jpeg'
+    assert b'JFIF' in formdata.data['bo1'].get_content()
+    assert b'<exif:XResolution>' not in formdata.data['bo1'].get_content()
+
     # check with a template string, into a string field
     two_pubs.substitutions.feed(formdata)
     item.fields = [{'field_id': 'bo2', 'value': '{{form_var_file}}'}]
diff --git a/wcs/qommon/evalutils.py b/wcs/qommon/evalutils.py
index d54b6f72c..d1785cef0 100644
--- a/wcs/qommon/evalutils.py
+++ b/wcs/qommon/evalutils.py
@@ -21,7 +21,6 @@ get_global_eval_dict.
 """
 import base64
 import datetime
-import io
 import time
 
 from django.utils.encoding import force_bytes
@@ -31,12 +30,6 @@ from wcs.qommon.upload_storage import UploadStorage
 
 from .misc import get_as_datetime
 
-try:
-    from PIL import Image
-except ImportError:
-    Image = None
-
-
 today = datetime.date.today
 now = datetime.datetime.now
 
@@ -154,23 +147,8 @@ def attachment(content, filename='', content_type=None, strip_metadata=False):
     upload = FileField.convert_value_from_anything(content)
     UploadStorage().save(upload)
 
-    if strip_metadata and Image:
-        try:
-            image = Image.open(io.BytesIO(upload.get_content()))
-        except OSError:
-            pass
-        else:
-            image_without_exif = Image.new(image.mode, image.size)
-            image_without_exif.putdata(image.getdata())
-            content = io.BytesIO()
-            image_without_exif.save(content, image.format)
-            upload = FileField.convert_value_from_anything(
-                {
-                    'filename': upload.base_filename,
-                    'content_type': upload.content_type,
-                    'content': content.getvalue(),
-                }
-            )
+    if strip_metadata:
+        upload = upload.strip_metadata()
     if filename:
         upload.base_filename = filename
     if content_type:
diff --git a/wcs/qommon/templatetags/qommon.py b/wcs/qommon/templatetags/qommon.py
index 97e3d6ec6..85b31deb9 100644
--- a/wcs/qommon/templatetags/qommon.py
+++ b/wcs/qommon/templatetags/qommon.py
@@ -772,3 +772,10 @@ def is_empty(value):
     if isinstance(value, (LazyFormDefObjectsManager, LazyList)):
         return not list(value)
     return value is None
+
+
+@register.filter
+def strip_metadata(value):
+    if hasattr(value, 'get_value'):
+        value = value.get_value()  # unlazy
+    return value.strip_metadata()
diff --git a/wcs/qommon/upload_storage.py b/wcs/qommon/upload_storage.py
index c2524bc1c..0eeba3924 100644
--- a/wcs/qommon/upload_storage.py
+++ b/wcs/qommon/upload_storage.py
@@ -24,7 +24,7 @@ from quixote import get_publisher
 from quixote.http_request import Upload
 
 from .errors import ConnectionError
-from .misc import can_thumbnail, file_digest, json_loads
+from .misc import Image, can_thumbnail, file_digest, json_loads
 from .storage import atomic_write
 
 
@@ -72,6 +72,9 @@ class PicklableUpload(Upload):
             filename = os.path.join(get_publisher().app_dir, 'uploads', self.qfilename)
             with open(filename, 'rb') as fd:
                 return fd.read()
+        if self.fp:
+            get_storage_object(getattr(self, 'storage', None)).save(self)
+            return self.get_content()
         return None
 
     def get_base64_content(self):
@@ -96,6 +99,22 @@ class PicklableUpload(Upload):
             self, backoffice=backoffice
         )
 
+    def strip_metadata(self):
+        if Image is None:
+            return self
+        try:
+            image = Image.open(io.BytesIO(self.get_content()))
+        except OSError:
+            return self
+
+        image_without_exif = Image.new(image.mode, image.size)
+        image_without_exif.putdata(image.getdata())
+        content = io.BytesIO()
+        image_without_exif.save(content, image.format)
+        new_file = PicklableUpload(self.base_filename, self.content_type)
+        new_file.receive([content.getvalue()])
+        return new_file
+
 
 class UploadStorageError(Exception):
     pass
-- 
2.31.0

