Sanitize your inputs

This commit is contained in:
2025-02-27 11:34:58 -06:00
parent 7c2a8e667f
commit ea905066ed

View File

@ -9,16 +9,19 @@ const objectsToCSV = (data: RSVPItem[]): string => {
data.forEach((entry) => { data.forEach((entry) => {
const row = headers.map((header) => { const row = headers.map((header) => {
let field = entry[header as keyof RSVPItem]; let value = entry[header as keyof RSVPItem];
// Convert boolean to string for CSV
if (typeof field === 'boolean') { // Ensure proper value formatting
field = field.toString(); if (header === "attending") {
value = Boolean(value).toString();
} else if (value === null || value === undefined) {
value = "";
} }
// Handle null/undefined
if (field === null || field === undefined) { // Escape special characters
field = ''; const escaped = String(value)
} .replace(/"/g, '""')
const escaped = String(field).replace(/"/g, '""'); .replace(/\n/g, ' ');
return `"${escaped}"`; return `"${escaped}"`;
}); });
csvRows.push(row.join(",")); csvRows.push(row.join(","));
@ -30,30 +33,59 @@ const objectsToCSV = (data: RSVPItem[]): string => {
const csvToObjects = (csv: string): RSVPItem[] => { const csvToObjects = (csv: string): RSVPItem[] => {
const lines = csv.split("\n"); const lines = csv.split("\n");
const headers = lines[0].split(",").map((h) => h.trim()); const headers = lines[0].split(",").map((h) => h.trim());
const hasAttendingColumn = headers.includes("attending");
return lines return lines
.slice(1) .slice(1)
.filter((line) => line.trim()) .filter((line) => line.trim())
.map((line) => { .map((line) => {
const values = line.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || []; // More robust CSV parsing
const entry: Partial<RSVPItem> = {}; const values: string[] = [];
let inQuote = false;
let currentValue = '';
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"') {
if (inQuote && line[i + 1] === '"') {
// Handle escaped quotes
currentValue += '"';
i++;
} else {
inQuote = !inQuote;
}
} else if (char === ',' && !inQuote) {
values.push(currentValue.trim());
currentValue = '';
} else {
currentValue += char;
}
}
values.push(currentValue.trim());
// Create the entry object
const entry: Partial<RSVPItem> = {};
headers.forEach((header, index) => { headers.forEach((header, index) => {
let value = values[index] || ""; let value = values[index] || "";
value = value.replace(/^"(.*)"$/, "$1").replace(/""/g, '"'); value = value.replace(/^"(.*)"$/, "$1").replace(/""/g, '"');
entry[header as keyof RSVPItem] = value;
if (header === "attending") {
// Explicitly convert to boolean
entry.attending = value.toLowerCase() === "true";
} else {
entry[header as keyof RSVPItem] = value;
}
}); });
// If the attending column doesn't exist in the CSV, assume they're attending if they provided dietary restrictions
if (!hasAttendingColumn) {
entry.attending = Boolean(entry.dietaryRestrictions);
} else if (typeof entry.attending === 'string') {
entry.attending = entry.attending.toLowerCase() === 'true';
}
return entry as RSVPItem; return entry as RSVPItem;
}); })
.filter(entry =>
// Filter out malformed entries
entry.name &&
typeof entry.attending === 'boolean' &&
typeof entry.timestamp === 'string' &&
entry.timestamp.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/)
);
}; };
// GET: Retrieve all RSVPs (requires admin role) // GET: Retrieve all RSVPs (requires admin role)