diff --git a/packages/app/src/utils/session-export.test.ts b/packages/app/src/utils/session-export.test.ts index ff18a16b7fa3..c8a7b8b4f080 100644 --- a/packages/app/src/utils/session-export.test.ts +++ b/packages/app/src/utils/session-export.test.ts @@ -16,6 +16,23 @@ describe("sessionExportFilename", () => { test("falls back to id when title and slug are empty", () => { expect(sessionExportFilename({ id: "ses_123" })).toBe("ses_123.json") }) + + test("avoids Windows reserved device names", () => { + const reserved = [ + "CON", + "PRN", + "AUX", + "NUL", + ...Array.from({ length: 9 }, (_, index) => `COM${index + 1}`), + ...Array.from({ length: 9 }, (_, index) => `LPT${index + 1}`), + ] + + reserved.forEach((title) => + expect(sessionExportFilename({ id: "ses_123", title })).toBe(`${title.toLowerCase()}-session.json`), + ) + expect(sessionExportFilename({ id: "ses_123", title: "COM0" })).toBe("com0.json") + expect(sessionExportFilename({ id: "ses_123", title: "COM10" })).toBe("com10.json") + }) }) describe("fetchSessionExport", () => { diff --git a/packages/app/src/utils/session-export.ts b/packages/app/src/utils/session-export.ts index 6eb9f9ab6a7c..1889c9335bca 100644 --- a/packages/app/src/utils/session-export.ts +++ b/packages/app/src/utils/session-export.ts @@ -16,6 +16,8 @@ export type SessionExportClient = { } } +const WINDOWS_RESERVED_NAME = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i + export async function fetchSessionExport(input: { sessionID: string client: SessionExportClient @@ -44,7 +46,8 @@ export function sessionExportFilename(session: { id: string; title?: string; slu .toLowerCase() .replace(/[^a-z0-9_-]+/gi, "-") .replace(/^-+|-+$/g, "") - return `${clean || session.id}.json` + const basename = clean || session.id + return `${WINDOWS_RESERVED_NAME.test(basename) ? `${basename}-session` : basename}.json` } export function downloadSessionExport(filename: string, data: unknown) {