Updated locations that process a cover file upload to strip EXIF data with a utility function

This commit is contained in:
Tim Rogers 2025-03-21 14:07:50 -05:00
parent 12f94a8821
commit 7a50943ea5
5 changed files with 55 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -1,9 +1,12 @@
""" test searching for books """
from PIL import Image
import re
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.test import TestCase
from bookwyrm.settings import BASE_URL
from bookwyrm.utils import regex
from bookwyrm.utils.images import remove_uploaded_image_exif
from bookwyrm.utils.validate import validate_url_domain
@ -24,3 +27,18 @@ class TestUtils(TestCase):
self.assertIsNone(
validate_url_domain("https://up-to-no-good.tld/bad-actor.exe")
)
def test_remove_uploaded_image_exif(self):
"""Check that EXIF data is removed from image"""
image_path = "bookwyrm/tests/data/default_avi_exif.jpg"
with open(image_path, "rb") as image_file:
source = InMemoryUploadedFile(
image_file,
"cover",
"default_avi_exif.jpg",
"image/jpeg",
os.fstat(image_file.fileno()).st_size,
None,
)
sanitized_image = Image.open(remove_uploaded_image_exif(source).open())
self.assertNotIn("exif", sanitized_image.info)

27
bookwyrm/utils/images.py Normal file
View file

@ -0,0 +1,27 @@
""" Image utilities """
from io import BytesIO
from PIL import Image
from django.core.files.uploadedfile import InMemoryUploadedFile
def remove_uploaded_image_exif(source: InMemoryUploadedFile):
"""Removes EXIF data from provided image and returns a sanitized copy"""
io = BytesIO()
with Image.open(source) as image:
if "exif" in image.info:
del image.info["exif"]
if image.format == "JPEG":
image.save(io, format=image.format, quality="keep")
else:
image.save(io, format=image.format)
return InMemoryUploadedFile(
io,
source.field_name,
source.name,
source.content_type,
len(io.getvalue()),
source.charset,
)

View file

@ -17,6 +17,7 @@ from bookwyrm.activitypub import ActivitypubResponse
from bookwyrm.connectors import connector_manager, ConnectorException
from bookwyrm.connectors.abstract_connector import get_image
from bookwyrm.settings import PAGE_LENGTH
from bookwyrm.utils.images import remove_uploaded_image_exif
from bookwyrm.views.helpers import (
is_api_request,
maybe_redirect_local_path,
@ -158,7 +159,7 @@ def upload_cover(request, book_id):
if not form.is_valid() or not form.files.get("cover"):
return redirect(book.local_path)
book.cover = form.files["cover"]
book.cover = remove_uploaded_image_exif(form.files["cover"])
book.save()
return redirect(book.local_path)

View file

@ -1,6 +1,7 @@
""" the good stuff! the books! """
from re import sub, findall
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.postgres.search import SearchRank, SearchVector
from django.db import transaction
@ -12,6 +13,7 @@ from django.views.decorators.http import require_POST
from django.views import View
from bookwyrm import book_search, forms, models
from bookwyrm.utils.images import remove_uploaded_image_exif
# from bookwyrm.activitypub.base_activity import ActivityObject
from bookwyrm.utils.isni import (
@ -71,6 +73,8 @@ class EditBook(View):
image = set_cover_from_url(url)
if image:
book.cover.save(*image, save=False)
elif "cover" in form.files:
book.cover = remove_uploaded_image_exif(form.files["cover"])
book.save()
return redirect(f"/book/{book.id}")
@ -142,6 +146,8 @@ class CreateBook(View):
image = set_cover_from_url(url)
if image:
book.cover.save(*image, save=False)
elif "cover" in form.files:
book.cover = remove_uploaded_image_exif(form.files["cover"])
book.save()
return redirect(f"/book/{book.id}")
@ -311,6 +317,8 @@ class ConfirmEditBook(View):
image = set_cover_from_url(url)
if image:
book.cover.save(*image, save=False)
elif "cover" in form.files:
book.cover = remove_uploaded_image_exif(form.files["cover"])
# we don't tell the world when creating a book
book.save(broadcast=False)