Skip to content

Commit dfcf5ab

Browse files
Corrupted files + ZIP bomb protection (#80)
1 parent 26767d1 commit dfcf5ab

4 files changed

Lines changed: 31 additions & 0 deletions

File tree

calendar_backend/methods/utils.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import asyncio
12
import datetime
23
import os
34
import random
45
import string
6+
from concurrent.futures import ThreadPoolExecutor
7+
from functools import partial
58
from typing import Final
69

710
import aiofiles
@@ -17,6 +20,9 @@
1720
ApproveStatuses,
1821
)
1922
from calendar_backend.settings import get_settings
23+
from PIL import Image
24+
from io import BytesIO
25+
2026

2127
settings = get_settings()
2228

@@ -80,6 +86,7 @@ async def upload_lecturer_photo(lecturer_id: int, session: Session, file: Upload
8086
path = os.path.join(settings.STATIC_PATH, "photo", "lecturer", f"{random_string}.{ext}")
8187
async with aiofiles.open(path, 'wb') as out_file:
8288
content = await file.read()
89+
await async_image_process(content)
8390
await out_file.write(content)
8491
approve_status = ApproveStatuses.APPROVED if not settings.REQUIRE_REVIEW_PHOTOS else ApproveStatuses.PENDING
8592
photo = Photo(
@@ -92,3 +99,19 @@ async def upload_lecturer_photo(lecturer_id: int, session: Session, file: Upload
9299
lecturer.avatar_id = lecturer.last_photo.id if lecturer.last_photo else lecturer.avatar_id
93100
session.flush()
94101
return photo
102+
103+
104+
def process_image(image_bytes: bytes) -> None:
105+
with Image.open(BytesIO(image_bytes)) as image:
106+
try:
107+
image.verify()
108+
except SyntaxError:
109+
raise HTTPException(status_code=422, detail="Corrupted file")
110+
111+
112+
thread_pool = ThreadPoolExecutor()
113+
114+
115+
async def async_image_process(image_bytes: bytes) -> None:
116+
loop = asyncio.get_event_loop()
117+
await loop.run_in_executor(thread_pool, partial(process_image, image_bytes))

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ SQLAlchemy
1515
gunicorn
1616
python-multipart
1717
httpx
18+
Pillow

tests/lecturer/broken_photo.png

21.7 KB
Loading

tests/lecturer/photos.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,10 @@ def test_unsupported_format(lecturer_path: str, client_auth: TestClient):
3535
response = client_auth.post(RESOURCE, files={"photo": f})
3636
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
3737

38+
39+
def test_corrupted_file(lecturer_path: str, client_auth: TestClient):
40+
RESOURCE = f"{lecturer_path}/photo"
41+
with open(os.path.dirname(__file__) + "/broken_photo.png", "rb") as f:
42+
response = client_auth.post(RESOURCE, files={"photo": f})
43+
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
44+

0 commit comments

Comments
 (0)