import io import zipfile from django.core.files.uploadedfile import SimpleUploadedFile from django.test import Client, TestCase, override_settings from django.urls import reverse from PIL import Image from apps.pages.contact_uploads import ( MAX_ATTACHMENTS, MAX_FILE_SIZE, validate_contact_attachments, ) from apps.pages.models import ContactSubmission, ContactSubmissionAttachment def _png_file(name="screenshot.png"): buffer = io.BytesIO() Image.new("RGB", (8, 8), color="red").save(buffer, format="PNG") buffer.seek(0) return SimpleUploadedFile(name, buffer.read(), content_type="image/png") def _zip_file(name="logs.zip", entries=None): buffer = io.BytesIO() with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as archive: for entry_name, content in (entries or {"app.log": "line one\n"}).items(): archive.writestr(entry_name, content) buffer.seek(0) return SimpleUploadedFile(name, buffer.read(), content_type="application/zip") class ContactUploadValidationTest(TestCase): def test_accepts_valid_png(self): validated = validate_contact_attachments([_png_file()]) self.assertEqual(len(validated), 1) self.assertEqual(validated[0][1], "screenshot.png") def test_accepts_valid_zip(self): validated = validate_contact_attachments([_zip_file()]) self.assertEqual(len(validated), 1) def test_accepts_valid_text_log(self): uploaded = SimpleUploadedFile( "error.log", b"2026-06-07 ERROR something failed\n", content_type="text/plain", ) validated = validate_contact_attachments([uploaded]) self.assertEqual(validated[0][1], "error.log") def test_rejects_executable_extension(self): uploaded = SimpleUploadedFile( "malware.exe", b"MZfake", content_type="application/octet-stream", ) with self.assertRaises(Exception): validate_contact_attachments([uploaded]) def test_rejects_php_disguised_as_png(self): uploaded = SimpleUploadedFile( "image.png", b"", content_type="image/png", ) with self.assertRaises(Exception): validate_contact_attachments([uploaded]) def test_rejects_oversized_file(self): uploaded = SimpleUploadedFile( "big.log", b"x" * (MAX_FILE_SIZE + 1), content_type="text/plain", ) with self.assertRaises(Exception): validate_contact_attachments([uploaded]) def test_rejects_too_many_files(self): files = [_png_file(f"shot-{index}.png") for index in range(MAX_ATTACHMENTS + 1)] with self.assertRaises(Exception): validate_contact_attachments(files) def test_rejects_zip_with_path_traversal(self): buffer = io.BytesIO() with zipfile.ZipFile(buffer, "w") as archive: archive.writestr("../escape.txt", "bad") buffer.seek(0) uploaded = SimpleUploadedFile( "bad.zip", buffer.read(), content_type="application/zip", ) with self.assertRaises(Exception): validate_contact_attachments([uploaded]) @override_settings( CONTACT_UPLOAD_ROOT=__import__("pathlib").Path(__file__).resolve().parents[3] / "test_private_uploads" ) class ContactViewUploadTest(TestCase): def setUp(self): self.client = Client(enforce_csrf_checks=True) self.url = reverse("pages:contact") def _start_session(self): response = self.client.get(self.url) self.assertEqual(response.status_code, 200) self.csrf_token = response.cookies["csrftoken"].value captcha_question = response.context["captcha_question"] left, right = captcha_question.split(" + ") return int(left) + int(right) def _post_contact(self, captcha_answer, attachments=None, extra=None): payload = { "name": "Test User", "title": "Upload test", "description": "Testing attachments", "email": "test@example.com", "captcha_answer": captcha_answer, } if attachments is not None: payload["attachments"] = attachments if extra: payload.update(extra) return self.client.post( self.url, data=payload, HTTP_X_CSRFTOKEN=self.csrf_token, ) def test_contact_submission_with_png_attachment(self): captcha_answer = self._start_session() response = self._post_contact(captcha_answer, attachments=_png_file()) self.assertEqual(response.status_code, 200) self.assertTrue(response.json()["success"]) submission = ContactSubmission.objects.get(title="Upload test") self.assertEqual(submission.attachments.count(), 1) attachment = submission.attachments.first() self.assertEqual(attachment.original_filename, "screenshot.png") self.assertTrue(attachment.file.storage.exists(attachment.file.name)) def test_contact_submission_rejects_invalid_attachment(self): captcha_answer = self._start_session() response = self._post_contact( captcha_answer, attachments=SimpleUploadedFile( "bad.exe", b"MZ", content_type="application/octet-stream", ), ) self.assertEqual(response.status_code, 400) self.assertFalse(response.json()["success"]) self.assertIn("attachments", response.json()["errors"]) self.assertEqual(ContactSubmission.objects.count(), 0) self.assertEqual(ContactSubmissionAttachment.objects.count(), 0)