'Method not allowed']); exit; } if (!isset($_FILES['photo']) || $_FILES['photo']['error'] !== UPLOAD_ERR_OK) { $upload_errors = [ UPLOAD_ERR_INI_SIZE => 'File exceeds server upload limit', UPLOAD_ERR_FORM_SIZE => 'File exceeds form size limit', UPLOAD_ERR_PARTIAL => 'File was only partially uploaded', UPLOAD_ERR_NO_FILE => 'No file was uploaded', UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder', UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk', UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the upload', ]; $err_code = $_FILES['photo']['error'] ?? UPLOAD_ERR_NO_FILE; $err_msg = $upload_errors[$err_code] ?? 'Unknown upload error'; http_response_code(400); echo json_encode(['error' => $err_msg]); exit; } $file = $_FILES['photo']; $max_size = 5 * 1024 * 1024; // 5 MB // Validate file size if ($file['size'] > $max_size) { http_response_code(400); echo json_encode(['error' => 'File is too large (max 5 MB)']); exit; } // Validate MIME type using finfo (not just extension) $finfo = new finfo(FILEINFO_MIME_TYPE); $mime = $finfo->file($file['tmp_name']); $allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; if (!in_array($mime, $allowed, true)) { http_response_code(400); echo json_encode(['error' => 'Invalid file type. Allowed: JPEG, PNG, GIF, WebP']); exit; } $ext_map = [ 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp', ]; $ext = $ext_map[$mime]; $filename = bin2hex(random_bytes(16)) . '.' . $ext; $dest = UPLOADS_DIR . $filename; if (!is_dir(UPLOADS_DIR)) { mkdir(UPLOADS_DIR, 0755, true); } if (!move_uploaded_file($file['tmp_name'], $dest)) { http_response_code(500); echo json_encode(['error' => 'Failed to save file']); exit; } echo json_encode(['path' => UPLOADS_URL . $filename]);