From a7d775654569555f8a848deb5b6248cad38f2b77 Mon Sep 17 00:00:00 2001 From: Eliot Berriot Date: Wed, 2 Jan 2019 12:39:00 +0100 Subject: [PATCH] Fix #374: Strip EXIF metadata from uploaded avatars to avoid leaking private data --- api/funkwhale_api/common/serializers.py | 26 ++++++++++++++++++++++++ api/funkwhale_api/users/serializers.py | 10 +++++++-- api/tests/common/exif.jpg | Bin 0 -> 4278 bytes api/tests/common/test_serializers.py | 19 +++++++++++++++++ changes/changelog.d/374.enhancement | 1 + 5 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 api/tests/common/exif.jpg create mode 100644 changes/changelog.d/374.enhancement diff --git a/api/funkwhale_api/common/serializers.py b/api/funkwhale_api/common/serializers.py index e253906de..e7bbf8f1f 100644 --- a/api/funkwhale_api/common/serializers.py +++ b/api/funkwhale_api/common/serializers.py @@ -1,8 +1,12 @@ import collections +import io +import PIL +import os from rest_framework import serializers from django.core.exceptions import ObjectDoesNotExist +from django.core.files.uploadedfile import SimpleUploadedFile from django.utils.encoding import smart_text from django.utils.translation import ugettext_lazy as _ @@ -190,3 +194,25 @@ def track_fields_for_update(*fields): return serializer_class return decorator + + +class StripExifImageField(serializers.ImageField): + def to_internal_value(self, data): + file_obj = super().to_internal_value(data) + + image = PIL.Image.open(file_obj) + data = list(image.getdata()) + image_without_exif = PIL.Image.new(image.mode, image.size) + image_without_exif.putdata(data) + + with io.BytesIO() as output: + image_without_exif.save( + output, + format=PIL.Image.EXTENSION[os.path.splitext(file_obj.name)[-1]], + quality=100, + ) + content = output.getvalue() + + return SimpleUploadedFile( + file_obj.name, content, content_type=file_obj.content_type + ) diff --git a/api/funkwhale_api/users/serializers.py b/api/funkwhale_api/users/serializers.py index bcacc3144..c75604f6e 100644 --- a/api/funkwhale_api/users/serializers.py +++ b/api/funkwhale_api/users/serializers.py @@ -11,7 +11,7 @@ from rest_framework import serializers from versatileimagefield.serializers import VersatileImageFieldSerializer from funkwhale_api.activity import serializers as activity_serializers - +from funkwhale_api.common import serializers as common_serializers from . import models @@ -66,7 +66,13 @@ class UserActivitySerializer(activity_serializers.ModelSerializer): return "Person" -avatar_field = VersatileImageFieldSerializer(allow_null=True, sizes="square") +class AvatarField( + common_serializers.StripExifImageField, VersatileImageFieldSerializer +): + pass + + +avatar_field = AvatarField(allow_null=True, sizes="square") class UserBasicSerializer(serializers.ModelSerializer): diff --git a/api/tests/common/exif.jpg b/api/tests/common/exif.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c13141aff10c549e994468090649702c5c13c086 GIT binary patch literal 4278 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