Skip to content

Commit 1c826d4

Browse files
eschoellerEric Schoeller
andauthored
Fix markdown-to-storage rendering on Confluence Cloud (#75)
* Fix markdown-to-storage rendering issues on Confluence Cloud - Use smart links (data-card-appearance="inline") for Cloud instances instead of ac:link/ri:url which Cloud no longer renders. Server/DC instances continue using the ac:link format. - Decode HTML entities (&quot; &amp; &lt; &gt;) inside code blocks before wrapping in CDATA, so code renders with literal characters instead of escaped entities. - Trim trailing newline that markdown-it appends to code block content, which caused an extra blank line in rendered Confluence code macros. - Remove global HTML entity decode (&lt; &gt; &amp;) that was stripping angle-bracket placeholders (e.g. <tenant>) from inline text. Code blocks now handle their own entity decoding before CDATA insertion. * Add CDATA injection defense and fix entity decode order - Escape ]]> in code block content to prevent premature CDATA termination when user code contains XML/CDATA snippets - Move &amp; decode last to avoid double-decoding entities - Add test for CDATA terminator escaping in code blocks Addresses review feedback from pchuri on PR #75. * Remove duplicate isCloud() method introduced by rebase The rebase onto main (v1.27.3) introduced a duplicate isCloud() at line 95. The upstream version at line 86 is preferred as it also handles scoped tokens via isScopedToken(). Removes the duplicate to fix the ESLint no-dupe-class-members error. --------- Co-authored-by: Eric Schoeller <schoelle@colorado.edu>
1 parent 9bd6896 commit 1c826d4

2 files changed

Lines changed: 14 additions & 3 deletions

File tree

lib/confluence-client.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,10 +1073,11 @@ class ConfluenceClient {
10731073
// so they appear as literal characters in the CDATA output
10741074
const decodedCode = code.replace(/\n$/, '')
10751075
.replace(/&quot;/g, '"')
1076-
.replace(/&amp;/g, '&')
10771076
.replace(/&lt;/g, '<')
1078-
.replace(/&gt;/g, '>');
1079-
return `<ac:structured-macro ac:name="code"><ac:parameter ac:name="language">${language}</ac:parameter><ac:plain-text-body><![CDATA[${decodedCode}]]></ac:plain-text-body></ac:structured-macro>`;
1077+
.replace(/&gt;/g, '>')
1078+
.replace(/&amp;/g, '&'); // & last to avoid double-decoding
1079+
const safeCode = decodedCode.replace(/]]>/g, ']]]]><![CDATA[>');
1080+
return `<ac:structured-macro ac:name="code"><ac:parameter ac:name="language">${language}</ac:parameter><ac:plain-text-body><![CDATA[${safeCode}]]></ac:plain-text-body></ac:structured-macro>`;
10801081
});
10811082

10821083
// Convert inline code

tests/confluence-client.test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,16 @@ describe('ConfluenceClient', () => {
327327
expect(result).toContain('<td><p>Cell 1</p></td>');
328328
});
329329

330+
test('should escape CDATA terminators in code blocks', () => {
331+
const markdown = '```xml\n<![CDATA[some data]]>\n```';
332+
const result = client.markdownToStorage(markdown);
333+
334+
expect(result).toContain('<![CDATA[');
335+
expect(result).toContain(']]]]><![CDATA[>');
336+
// The literal ]]> from user code should not appear unescaped
337+
expect(result).not.toContain('some data]]>');
338+
});
339+
330340
test('should convert links to smart link format on Cloud instances', () => {
331341
const markdown = '[Example Link](https://example.com)';
332342
const result = client.markdownToStorage(markdown);

0 commit comments

Comments
 (0)