Skip to content

Commit

Permalink
Merge pull request frappe#12014 from abhishekbalam/image_metadata_strip
Browse files Browse the repository at this point in the history
feat(File): Option to strip EXIF data from image files before uploading
  • Loading branch information
abhishekbalam authored Dec 1, 2020
2 parents e760c39 + e306f56 commit 9e5c1a9
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 5 deletions.
16 changes: 13 additions & 3 deletions frappe/core/doctype/file/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from frappe import _, conf
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, cstr, encode, get_files_path, get_hook_method, random_string, strip

from frappe.utils.image import strip_exif_data

class MaxFileSizeReachedError(frappe.ValidationError):
pass
Expand Down Expand Up @@ -456,6 +456,7 @@ def get_uploaded_content(self):
def save_file(self, content=None, decode=False, ignore_existing_file_check=False):
file_exists = False
self.content = content

if decode:
if isinstance(content, text_type):
self.content = content.encode("utf-8")
Expand All @@ -466,10 +467,19 @@ def save_file(self, content=None, decode=False, ignore_existing_file_check=False

if not self.is_private:
self.is_private = 0
self.file_size = self.check_max_file_size()
self.content_hash = get_content_hash(self.content)

self.content_type = mimetypes.guess_type(self.file_name)[0]

self.file_size = self.check_max_file_size()

if (
self.content_type and "image" in self.content_type
and frappe.get_system_settings("strip_exif_metadata_from_uploaded_images")
):
self.content = strip_exif_data(self.content, self.content_type)

self.content_hash = get_content_hash(self.content)

duplicate_file = None

# check if a file exists with the same content hash and is also in the same folder (public or private)
Expand Down
9 changes: 8 additions & 1 deletion frappe/core/doctype/system_settings/system_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"allow_login_using_mobile_number",
"allow_login_using_user_name",
"allow_error_traceback",
"strip_exif_metadata_from_uploaded_images",
"password_settings",
"logout_on_password_reset",
"force_user_to_reset_password",
Expand Down Expand Up @@ -460,12 +461,18 @@
"fieldname": "prepared_report_section",
"fieldtype": "Section Break",
"label": "Prepared Report"
},
{
"default": "1",
"fieldname": "strip_exif_metadata_from_uploaded_images",
"fieldtype": "Check",
"label": "Strip EXIF tags from uploaded images"
}
],
"icon": "fa fa-cog",
"issingle": 1,
"links": [],
"modified": "2020-08-12 14:35:45.214327",
"modified": "2020-11-30 18:52:22.161391",
"modified_by": "Administrator",
"module": "Core",
"name": "System Settings",
Expand Down
Binary file added frappe/tests/data/exif_sample_image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions frappe/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
from frappe.utils import evaluate_filters, money_in_words, scrub_urls, get_url
from frappe.utils import ceil, floor

from PIL import Image
from frappe.utils.image import strip_exif_data
import io

class TestFilters(unittest.TestCase):
def test_simple_dict(self):
self.assertTrue(evaluate_filters({'doctype': 'User', 'status': 'Open'}, {'status': 'Open'}))
Expand Down Expand Up @@ -122,3 +126,14 @@ def test_clean_email_html(self):
clean = clean_email_html(sample)
self.assertTrue('<h1>Hello</h1>' in clean)
self.assertTrue('<a href="http://test.com">text</a>' in clean)

class TestImage(unittest.TestCase):
def test_strip_exif_data(self):
original_image = Image.open("../apps/frappe/frappe/tests/data/exif_sample_image.jpg")
original_image_content = io.open("../apps/frappe/frappe/tests/data/exif_sample_image.jpg", mode='rb').read()

new_image_content = strip_exif_data(original_image_content, "image/jpeg")
new_image = Image.open(io.BytesIO(new_image_content))

self.assertEqual(new_image._getexif(), None)
self.assertNotEqual(original_image._getexif(), new_image._getexif())
26 changes: 25 additions & 1 deletion frappe/utils/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import os

def resize_images(path, maxdim=700):
import Image
from PIL import Image
size = (maxdim, maxdim)
for basepath, folders, files in os.walk(path):
for fname in files:
Expand All @@ -17,3 +17,27 @@ def resize_images(path, maxdim=700):
im.save(os.path.join(basepath, fname))

print("resized {0}".format(os.path.join(basepath, fname)))

def strip_exif_data(content, content_type):
""" Strips EXIF from image files which support it.
Works by creating a new Image object which ignores exif by
default and then extracts the binary data back into content.
Returns:
Bytes: Stripped image content
"""

from PIL import Image
import io

original_image = Image.open(io.BytesIO(content))
output = io.BytesIO()

new_image = Image.new(original_image.mode, original_image.size)
new_image.putdata(list(original_image.getdata()))
new_image.save(output, format=content_type.split('/')[1])

content = output.getvalue()

return content

0 comments on commit 9e5c1a9

Please sign in to comment.