Skip to content

Commit aef49f5

Browse files
committed
pillow image validation
1 parent 72e9821 commit aef49f5

File tree

3 files changed

+64
-17
lines changed

3 files changed

+64
-17
lines changed

csm_web/frontend/src/components/ImageUploader.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1-
import Cookies from "js-cookie";
21
import React, { useState } from "react";
2+
import { fetchWithMethod, HTTP_METHODS } from "../utils/api";
3+
4+
const MAX_FILE_SIZE_BYTES = 3 * 150 * 150;
35

46
const ImageUploader = () => {
57
const [file, setFile] = useState<File | null>(null);
68
const [status, setStatus] = useState<string>("");
79

810
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
911
if (e.target.files && e.target.files[0]) {
10-
setFile(e.target.files[0]);
12+
const selectedFile = e.target.files[0];
13+
14+
if (selectedFile.size > MAX_FILE_SIZE_BYTES) {
15+
setStatus(`File size exceeds max limit of ${MAX_FILE_SIZE_BYTES}`);
16+
}
17+
setFile(selectedFile);
18+
setStatus("");
1119
}
1220
};
1321

@@ -20,14 +28,7 @@ const ImageUploader = () => {
2028
const formData = new FormData();
2129
formData.append("file", file);
2230

23-
const response = await fetch(`/profile/user/upload_image/`, {
24-
method: "POST",
25-
credentials: "same-origin",
26-
headers: {
27-
"X-CSRFToken": Cookies.get("csrftoken") ?? ""
28-
},
29-
body: formData
30-
});
31+
const response = await fetchWithMethod(`user/upload_image/`, HTTP_METHODS.POST, formData, true);
3132

3233
if (!response.ok) {
3334
throw new Error("Failed to upload file");
@@ -41,7 +42,6 @@ const ImageUploader = () => {
4142
};
4243
return (
4344
<div>
44-
<h1>Image Upload Tester</h1>
4545
<input type="file" accept="image/*" onChange={handleFileChange} />
4646
<button onClick={handleUpload}>Upload</button>
4747
{status && <p>{status}</p>}

csm_web/frontend/src/utils/api.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export function fetchWithMethod(
3535
const normalizedEndpoint = endpointWithQueryParams(normalizeEndpoint(endpoint), queryParams);
3636

3737
if (isFormData) {
38+
console.log("test");
39+
// print("test")
3840
return fetch(normalizedEndpoint, {
3941
method: method,
4042
credentials: "same-origin",

csm_web/scheduler/views/user.py

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from PIL import Image, UnidentifiedImageError
12
from rest_framework import status
23
from rest_framework.decorators import api_view
34
from rest_framework.exceptions import PermissionDenied
@@ -151,12 +152,56 @@ def upload_image(request):
151152
return Response(
152153
{"error": "No file was uploaded"}, status=status.HTTP_400_BAD_REQUEST
153154
)
154-
image = request.FILES["file"]
155+
image_file = request.FILES["file"] # InMemoryUploadedFile
156+
allowed_types = ["JPEG", "PNG"] # Define allowed image types
157+
max_width = 150
158+
max_height = 150
155159

156-
user = request.user
160+
try:
161+
# Open and validate the image
162+
with Image.open(image_file) as img:
163+
img.verify() # Check for corrupt files
164+
img = Image.open(image_file) # Reopen for further use
157165

158-
user.profile_image.save(image.name, image)
166+
# Validate type and size
167+
validate_image_type(img, allowed_types)
168+
validate_image_size(img, max_width, max_height)
159169

160-
return Response(
161-
{"message": "File uploaded successfully"}, status=status.HTTP_200_OK
162-
)
170+
# Save the image to the user's profile
171+
user = request.user
172+
user.profile_image.save(image_file.name, image_file)
173+
174+
return Response(
175+
{"message": "File uploaded successfully"}, status=status.HTTP_200_OK
176+
)
177+
178+
except UnidentifiedImageError:
179+
return Response(
180+
{"error": "Uploaded file is not a valid image."},
181+
status=status.HTTP_400_BAD_REQUEST,
182+
)
183+
except ValueError as e:
184+
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
185+
186+
187+
# Validation Helper Functions
188+
def validate_image_type(image, allowed_types):
189+
"""
190+
Validates the image type
191+
"""
192+
if image.format not in allowed_types:
193+
raise ValueError(
194+
f"Invalid image type: {image.format}. Allowed types are: {allowed_types}."
195+
)
196+
197+
198+
def validate_image_size(image, max_width, max_height):
199+
"""
200+
Validates the image size
201+
"""
202+
width, height = image.size
203+
if width > max_width or height > max_height:
204+
raise ValueError(
205+
f"Image size {width}x{height} exceeds maximum allowed size of"
206+
f" {max_width}x{max_height}."
207+
)

0 commit comments

Comments
 (0)