diff --git a/apps/readest-app/src/utils/txt.ts b/apps/readest-app/src/utils/txt.ts index 1b3a0be8..389fbcef 100644 --- a/apps/readest-app/src/utils/txt.ts +++ b/apps/readest-app/src/utils/txt.ts @@ -35,6 +35,16 @@ const zipWriteOptions = { lastModDate: new Date(0), }; +const escapeXml = (str: string) => { + if (!str) return ''; + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +}; + export class TxtToEpubConverter { public async convert(options: Txt2EpubOptions): Promise { const { file: txtFile, author: providedAuthor, language: providedLanguage } = options; @@ -105,6 +115,7 @@ export class TxtToEpubConverter { } const formatSegment = (segment: string): string => { + segment = escapeXml(segment); return segment .replace(/-{4,}|_{4,}/g, '\n') .split(/\n+/) @@ -153,7 +164,7 @@ export class TxtToEpubConverter { const headTitle = isVolume ? `

${title}

` : `

${title}

`; const formattedSegment = formatSegment(content); segmentChapters.push({ - title, + title: escapeXml(title), content: `${headTitle}

${formattedSegment}

`, }); } @@ -166,7 +177,7 @@ export class TxtToEpubConverter { initialContent.slice(0, 16); const formattedSegment = formatSegment(initialContent); segmentChapters.unshift({ - title: segmentTitle, + title: escapeXml(segmentTitle), content: `

${formattedSegment}

`, }); } @@ -221,10 +232,10 @@ export class TxtToEpubConverter { - ${bookTitle} + ${escapeXml(bookTitle)} - ${author} + ${escapeXml(author)} ${navPoints} @@ -285,9 +296,9 @@ export class TxtToEpubConverter { const contentOpf = ` - ${bookTitle} + ${escapeXml(bookTitle)} ${language} - ${author} + ${escapeXml(author)} ${identifier}