48 lines
1.2 KiB
TypeScript

import Papa from "papaparse";
export type CsvValue = string | number | boolean | null | undefined;
export function buildCsv(headers: string[], rows: CsvValue[][]): string {
const normalizedRows = rows.map((row) =>
row.map((value) => sanitizeCsvValue(value ?? "")),
);
return Papa.unparse(
{
fields: headers,
data: normalizedRows,
},
{
quotes: true,
newline: "\n",
},
);
}
// Prevent CSV/TSV injection (formula injection) by prefixing dangerous
// characters with a single quote. See OWASP guidance:
// https://owasp.org/www-community/attacks/CSV_Injection
export function sanitizeCsvValue(
value: string | number | boolean,
): string | number | boolean {
if (typeof value !== "string" || value.length === 0) {
return value;
}
if (["=", "+", "-", "@", "\t", "\r", "\n"].includes(value[0])) {
return `'${value}`;
}
return value;
}
export function downloadCsv(filename: string, content: string): void {
const blob = new Blob([content], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
link.click();
URL.revokeObjectURL(url);
}