From c5bff2d84ae76a3e58bbf1b55e7e5630c398f853 Mon Sep 17 00:00:00 2001 From: agent_coder Date: Sun, 5 Jul 2026 05:38:07 +0300 Subject: [PATCH] fix(#345): normalize CRLF before front-matter strip (review round 3) F9 [WARNING] The line-anchored front-matter regex from round 2 requires a bare LF after the opening `---`, so a Windows/CRLF foreign file (`---\r\n...`) slips past the strip and leaks its front-matter into the body (where `title: Foo` renders as a setext heading that title extraction hijacks). The canonical parser whose regex shape this copied (page-file.ts) normalizes CRLF -> LF BEFORE its FRONTMATTER_RE; the import path copied the regex but missed the normalization. normalizeForeignMarkdown now replaces CRLF with LF first (which also makes convertReferenceFootnotes' split('\n') consistent). Adds a CRLF fixture. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../import/utils/foreign-markdown.spec.ts | 12 ++++++++++++ .../import/utils/foreign-markdown.ts | 16 +++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/apps/server/src/integrations/import/utils/foreign-markdown.spec.ts b/apps/server/src/integrations/import/utils/foreign-markdown.spec.ts index c0d8dac4..8a43fa20 100644 --- a/apps/server/src/integrations/import/utils/foreign-markdown.spec.ts +++ b/apps/server/src/integrations/import/utils/foreign-markdown.spec.ts @@ -144,6 +144,18 @@ describe('normalizeForeignMarkdown — GFM reference footnotes', () => { expect(out).toContain(`^[body ${N}]`); }); + it('strips a CRLF (Windows) front-matter block, not just LF', () => { + // F9: the line-anchored regex needs LF after the opening `---`, so a Windows + // file (`---\r\n…`) would slip past the strip and leak the front-matter into + // the body. normalizeForeignMarkdown normalizes CRLF -> LF first. + const out = normalizeForeignMarkdown( + '---\r\ntitle: Foo\r\ntags: [a]\r\n---\r\n\r\n# Heading\r\n\r\nBody.', + ); + expect(out).toBe('# Heading\n\nBody.'); + expect(out).not.toContain('title: Foo'); + expect(out).not.toContain('---'); + }); + it('strips front-matter whose value contains a triple-dash (line-anchored)', () => { // F8: the block must close only on a `\n---` LINE, not the first inline // `---`. A value like `title: Q1 --- Q2` must not truncate the front-matter diff --git a/apps/server/src/integrations/import/utils/foreign-markdown.ts b/apps/server/src/integrations/import/utils/foreign-markdown.ts index ef253a61..2173bfdc 100644 --- a/apps/server/src/integrations/import/utils/foreign-markdown.ts +++ b/apps/server/src/integrations/import/utils/foreign-markdown.ts @@ -247,13 +247,19 @@ const YAML_FRONT_MATTER_RE = /^\uFEFF?---\n[\s\S]*?\n---\n?/; /** * Normalize a foreign markdown string into Docmost's canonical markdown surface - * so the strict canonical parser accepts it losslessly: strip a leading YAML - * front-matter block, then rewrite GFM reference footnotes into inline - * footnotes. Add further fixture-driven foreign-surface cases here as they are - * found. + * so the strict canonical parser accepts it losslessly: normalize line endings, + * strip a leading YAML front-matter block, then rewrite GFM reference footnotes + * into inline footnotes. Add further fixture-driven foreign-surface cases here as + * they are found. */ export function normalizeForeignMarkdown(markdown: string): string { if (!markdown) return markdown; - const withoutFrontMatter = markdown.replace(YAML_FRONT_MATTER_RE, '').trimStart(); + // Normalize CRLF -> LF FIRST. The line-anchored front-matter regex requires a + // bare `\n` after the opening `---`, and convertReferenceFootnotes splits on + // `\n`; a Windows/CRLF foreign file (`---\r\n…`) would otherwise slip past the + // front-matter strip and leak into the body. The canonical parser + // (page-file.ts parsePageFile) normalizes the same way before its FRONTMATTER_RE. + const src = markdown.replace(/\r\n/g, '\n'); + const withoutFrontMatter = src.replace(YAML_FRONT_MATTER_RE, '').trimStart(); return convertReferenceFootnotes(withoutFrontMatter); }