@@ -301,6 +301,73 @@ def test_blue_user_cannot_upload_when_not_waiting(
301301 assert response .status_code == 403
302302
303303
304+ _PNG_BYTES = b"\x89 PNG\r \n \x1a \n " + b"\x00 " * 16
305+
306+
307+ def test_upload_polyglot_keeps_safe_extension (
308+ client : TestClient ,
309+ session : Session ,
310+ test_assessment : Assessment ,
311+ test_activity : Activity ,
312+ auth_headers_regular : dict [str , str ],
313+ test_acl_red : Acl ,
314+ ):
315+ """A PNG-magic file named shell.php is stored with a .png extension, not .php."""
316+ response = client .post (
317+ f"/api/v1/assessments/{ test_assessment .id } /activity/{ test_activity .id } /upload" ,
318+ files = {"file" : ("shell.php" , _PNG_BYTES , "image/png" )},
319+ headers = auth_headers_regular ,
320+ )
321+ assert response .status_code == 200
322+
323+ file_id = uuid .UUID (response .json ()["file_id" ])
324+ stored = session .get (File , file_id )
325+ assert stored .filename == "shell.png"
326+ assert ".php" not in stored .filename
327+ assert stored .content_type == FileType .PNG
328+
329+
330+ def test_upload_text_normalized_to_txt (
331+ client : TestClient ,
332+ session : Session ,
333+ test_assessment : Assessment ,
334+ test_activity : Activity ,
335+ auth_headers_regular : dict [str , str ],
336+ test_acl_red : Acl ,
337+ ):
338+ """UTF-8 content with a non-txt extension is stored as .txt."""
339+ response = client .post (
340+ f"/api/v1/assessments/{ test_assessment .id } /activity/{ test_activity .id } /upload" ,
341+ files = {"file" : ("notes.md" , b"just some notes" , "text/plain" )},
342+ headers = auth_headers_regular ,
343+ )
344+ assert response .status_code == 200
345+
346+ stored = session .get (File , uuid .UUID (response .json ()["file_id" ]))
347+ assert stored .filename == "notes.txt"
348+ assert stored .content_type == FileType .TXT
349+
350+
351+ def test_upload_matching_extension_unchanged (
352+ client : TestClient ,
353+ session : Session ,
354+ test_assessment : Assessment ,
355+ test_activity : Activity ,
356+ auth_headers_regular : dict [str , str ],
357+ test_acl_red : Acl ,
358+ ):
359+ """A real png keeps its name (no double extension)."""
360+ response = client .post (
361+ f"/api/v1/assessments/{ test_assessment .id } /activity/{ test_activity .id } /upload" ,
362+ files = {"file" : ("photo.png" , _PNG_BYTES , "image/png" )},
363+ headers = auth_headers_regular ,
364+ )
365+ assert response .status_code == 200
366+
367+ stored = session .get (File , uuid .UUID (response .json ()["file_id" ]))
368+ assert stored .filename == "photo.png"
369+
370+
304371def test_blue_user_fail_hidden (
305372 client : TestClient ,
306373 session : Session ,
0 commit comments