Merge pull request #1897 from bookwyrm-social/verify-image-types

Check image extensions before saving
This commit is contained in:
Mouse Reeve 2022-02-02 11:04:52 -08:00 committed by GitHub
commit b5baf1620f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 55 additions and 51 deletions

View file

@ -1,7 +1,9 @@
""" functionality outline for a book data connector """ """ functionality outline for a book data connector """
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import imghdr
import logging import logging
from django.core.files.base import ContentFile
from django.db import transaction from django.db import transaction
import requests import requests
from requests.exceptions import RequestException from requests.exceptions import RequestException
@ -290,10 +292,18 @@ def get_image(url, timeout=10):
) )
except RequestException as err: except RequestException as err:
logger.exception(err) logger.exception(err)
return None return None, None
if not resp.ok: if not resp.ok:
return None return None, None
return resp
image_content = ContentFile(resp.content)
extension = imghdr.what(None, image_content.read())
if not extension:
logger.exception("File requested was not an image: %s", url)
return None, None
return image_content, extension
class Mapping: class Mapping:

View file

@ -1,6 +1,5 @@
""" activitypub-aware django model fields """ """ activitypub-aware django model fields """
from dataclasses import MISSING from dataclasses import MISSING
import imghdr
import re import re
from uuid import uuid4 from uuid import uuid4
from urllib.parse import urljoin from urllib.parse import urljoin
@ -9,7 +8,6 @@ import dateutil.parser
from dateutil.parser import ParserError from dateutil.parser import ParserError
from django.contrib.postgres.fields import ArrayField as DjangoArrayField from django.contrib.postgres.fields import ArrayField as DjangoArrayField
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile
from django.db import models from django.db import models
from django.forms import ClearableFileInput, ImageField as DjangoImageField from django.forms import ClearableFileInput, ImageField as DjangoImageField
from django.utils import timezone from django.utils import timezone
@ -443,12 +441,10 @@ class ImageField(ActivitypubFieldMixin, models.ImageField):
except ValidationError: except ValidationError:
return None return None
response = get_image(url) image_content, extension = get_image(url)
if not response: if not image_content:
return None return None
image_content = ContentFile(response.content)
extension = imghdr.what(None, image_content.read()) or ""
image_name = f"{uuid4()}.{extension}" image_name = f"{uuid4()}.{extension}"
return [image_name, image_content] return [image_name, image_content]

View file

@ -443,18 +443,17 @@ class ModelFields(TestCase):
image_file = pathlib.Path(__file__).parent.joinpath( image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/default_avi.jpg" "../../static/images/default_avi.jpg"
) )
image = Image.open(image_file)
output = BytesIO()
image.save(output, format=image.format)
instance = fields.ImageField() instance = fields.ImageField()
responses.add( with open(image_file, "rb") as image_data:
responses.GET, responses.add(
"http://www.example.com/image.jpg", responses.GET,
body=image.tobytes(), "http://www.example.com/image.jpg",
status=200, body=image_data.read(),
) status=200,
content_type="image/jpeg",
stream=True,
)
loaded_image = instance.field_from_activity("http://www.example.com/image.jpg") loaded_image = instance.field_from_activity("http://www.example.com/image.jpg")
self.assertIsInstance(loaded_image, list) self.assertIsInstance(loaded_image, list)
self.assertIsInstance(loaded_image[1], ContentFile) self.assertIsInstance(loaded_image[1], ContentFile)
@ -465,18 +464,18 @@ class ModelFields(TestCase):
image_file = pathlib.Path(__file__).parent.joinpath( image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/default_avi.jpg" "../../static/images/default_avi.jpg"
) )
image = Image.open(image_file)
output = BytesIO()
image.save(output, format=image.format)
instance = fields.ImageField(activitypub_field="cover", name="cover") instance = fields.ImageField(activitypub_field="cover", name="cover")
responses.add( with open(image_file, "rb") as image_data:
responses.GET, responses.add(
"http://www.example.com/image.jpg", responses.GET,
body=image.tobytes(), "http://www.example.com/image.jpg",
status=200, body=image_data.read(),
) content_type="image/jpeg",
status=200,
stream=True,
)
book = Edition.objects.create(title="hello") book = Edition.objects.create(title="hello")
MockActivity = namedtuple("MockActivity", ("cover")) MockActivity = namedtuple("MockActivity", ("cover"))
@ -491,18 +490,18 @@ class ModelFields(TestCase):
image_file = pathlib.Path(__file__).parent.joinpath( image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/default_avi.jpg" "../../static/images/default_avi.jpg"
) )
image = Image.open(image_file)
output = BytesIO()
image.save(output, format=image.format)
instance = fields.ImageField(activitypub_field="cover", name="cover") instance = fields.ImageField(activitypub_field="cover", name="cover")
responses.add( with open(image_file, "rb") as image_data:
responses.GET, responses.add(
"http://www.example.com/image.jpg", responses.GET,
body=image.tobytes(), "http://www.example.com/image.jpg",
status=200, body=image_data.read(),
) status=200,
content_type="image/jpeg",
stream=True,
)
book = Edition.objects.create(title="hello") book = Edition.objects.create(title="hello")
MockActivity = namedtuple("MockActivity", ("cover")) MockActivity = namedtuple("MockActivity", ("cover"))
@ -565,18 +564,18 @@ class ModelFields(TestCase):
another_image_file = pathlib.Path(__file__).parent.joinpath( another_image_file = pathlib.Path(__file__).parent.joinpath(
"../../static/images/logo.png" "../../static/images/logo.png"
) )
another_image = Image.open(another_image_file)
another_output = BytesIO()
another_image.save(another_output, format=another_image.format)
instance = fields.ImageField(activitypub_field="cover", name="cover") instance = fields.ImageField(activitypub_field="cover", name="cover")
responses.add( with open(another_image_file, "rb") as another_image:
responses.GET, responses.add(
"http://www.example.com/image.jpg", responses.GET,
body=another_image.tobytes(), "http://www.example.com/image.jpg",
status=200, body=another_image.read(),
) status=200,
content_type="image/jpeg",
stream=True,
)
MockActivity = namedtuple("MockActivity", ("cover")) MockActivity = namedtuple("MockActivity", ("cover"))
mock_activity = MockActivity("http://www.example.com/image.jpg") mock_activity = MockActivity("http://www.example.com/image.jpg")

View file

@ -2,7 +2,6 @@
from uuid import uuid4 from uuid import uuid4
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.core.files.base import ContentFile
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.db.models import Avg, Q from django.db.models import Avg, Q
from django.http import Http404 from django.http import Http404
@ -144,13 +143,12 @@ def upload_cover(request, book_id):
def set_cover_from_url(url): def set_cover_from_url(url):
"""load it from a url""" """load it from a url"""
try: try:
image_file = get_image(url) image_content, extension = get_image(url)
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
return None return None
if not image_file: if not image_content:
return None return None
image_name = str(uuid4()) + "." + url.split(".")[-1] image_name = str(uuid4()) + "." + extension
image_content = ContentFile(image_file.content)
return [image_name, image_content] return [image_name, image_content]

1
bw-dev
View file

@ -209,6 +209,7 @@ case "$CMD" in
echo " build" echo " build"
echo " clean" echo " clean"
echo " black" echo " black"
echo " prettier"
echo " populate_streams [--stream=<stream name>]" echo " populate_streams [--stream=<stream name>]"
echo " populate_suggestions" echo " populate_suggestions"
echo " generate_thumbnails" echo " generate_thumbnails"