diff --git a/apps/readest-app/src/utils/book.ts b/apps/readest-app/src/utils/book.ts index 9d340454..8f897fd6 100644 --- a/apps/readest-app/src/utils/book.ts +++ b/apps/readest-app/src/utils/book.ts @@ -1,5 +1,6 @@ import { EXTS } from '@/libs/document'; import { Book, BookConfig } from '@/types/book'; +import { makeSafeFilename } from './misc'; export const getDir = (book: Book) => { return `${book.hash}`; @@ -8,7 +9,7 @@ export const getLibraryFilename = () => { return 'library.json'; }; export const getFilename = (book: Book) => { - return `${book.hash}/${book.title}.${EXTS[book.format]}`; + return `${book.hash}/${makeSafeFilename(book.title)}.${EXTS[book.format]}`; }; export const getCoverFilename = (book: Book) => { return `${book.hash}/cover.png`; diff --git a/apps/readest-app/src/utils/misc.ts b/apps/readest-app/src/utils/misc.ts index 57392730..57fab126 100644 --- a/apps/readest-app/src/utils/misc.ts +++ b/apps/readest-app/src/utils/misc.ts @@ -3,3 +3,22 @@ import { md5 } from 'js-md5'; export const uniqueId = () => Math.random().toString(36).substring(2, 9); export const getContentMd5 = (content: unknown) => md5(JSON.stringify(content)); + +export const makeSafeFilename = (filename: string, replacement = '_') => { + // Windows restricted characters + control characters and reserved names + const unsafeCharacters = /[<>:"\/\\|?*\x00-\x1F]/g; + const reservedFilenames = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i; + const maxFilenameLength = 255; + + let safeName = filename.replace(unsafeCharacters, replacement); + + if (reservedFilenames.test(safeName)) { + safeName = `${safeName}${replacement}`; + } + + if (safeName.length > maxFilenameLength) { + safeName = safeName.substring(0, maxFilenameLength); + } + + return safeName.trim(); +};