Sanitize your inputs
This commit is contained in:
@ -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)
|
||||||
|
Reference in New Issue
Block a user