Compare commits

...

1 Commits

Author SHA1 Message Date
Tom Moor e4de5a151d chore: Improve CSV output sanitization 2025-03-12 19:59:15 -04:00
3 changed files with 65 additions and 1 deletions
+2 -1
View File
@@ -13,6 +13,7 @@ import {
deleteColumn,
} from "prosemirror-tables";
import { ProsemirrorHelper } from "../../utils/ProsemirrorHelper";
import { CSVHelper } from "../../utils/csv";
import { chainTransactions } from "../lib/chainTransactions";
import { getCellsInColumn, isHeaderEnabled } from "../queries/table";
import { TableLayout } from "../types";
@@ -137,7 +138,7 @@ export function exportTable({
}
// Avoid cell content being interpreted as formulas by adding a leading single quote
value = value.trimStart().replace(/^([+\-=@])/, "'$1");
value = CSVHelper.sanitizeValue(value);
return `"${value}"`;
})
+32
View File
@@ -0,0 +1,32 @@
import { CSVHelper } from "./csv";
describe("CSVHelper", () => {
describe("sanitizeValue", () => {
it("should leave a value unchanged", () => {
const value = "Hello, World!";
const sanitizedValue = CSVHelper.sanitizeValue(value);
expect(sanitizedValue).toBe(value);
});
it("should escape formula trigger character", () => {
expect(CSVHelper.sanitizeValue("@1x2")).toBe(`'@1x2`);
expect(CSVHelper.sanitizeValue("=1x2")).toBe(`'=1x2`);
expect(CSVHelper.sanitizeValue("1x2")).toBe(`'1x2`);
expect(CSVHelper.sanitizeValue("≠1x2")).toBe(`'≠1x2`);
expect(CSVHelper.sanitizeValue("+1x2")).toBe(`'+1x2`);
expect(CSVHelper.sanitizeValue("∑1x2")).toBe(`'∑1x2`);
});
it("should remove control characters", () => {
expect(CSVHelper.sanitizeValue("\t1x2")).toBe(`1x2`);
});
it("should remove zero-width characters", () => {
expect(CSVHelper.sanitizeValue("\u200B1x2")).toBe(`1x2`);
});
it("should remove whitespace characters", () => {
expect(CSVHelper.sanitizeValue("\u200B1x2")).toBe(`1x2`);
});
});
});
+31
View File
@@ -0,0 +1,31 @@
/* eslint-disable no-control-regex */
/**
* Helper class for CSV operations.
*/
export class CSVHelper {
/**
* Sanitizes a value for CSV output.
*
* @param value The value to sanitize.
* @returns The sanitized value.
*/
public static sanitizeValue(value: string): string {
if (!value) {
return "";
}
return (
value
.toString()
// Formula triggers
.replace(/^([+\-=@<>±÷×])/u, "'$1")
// Control characters
.replace(/[\u0000-\u001F\u007F-\u009F]/gu, "")
// Zero-width spaces
.replace(/[\u200B-\u200D\uFEFF]/g, "")
// Bidirectional control
.replace(/[\u202A-\u202E\u2066-\u2069]/g, "")
);
}
}