Skip to content

Commit

Permalink
[FIX] Nested Markdown blocks not parsed properly (#12998)
Browse files Browse the repository at this point in the history
* Fix markdown tokens not restored properly

* Add tests
  • Loading branch information
Hudell authored and rodrigok committed Dec 20, 2018
1 parent 68a8a75 commit e5c9be6
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 58 deletions.
28 changes: 23 additions & 5 deletions packages/rocketchat-markdown/lib/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,31 @@ class MarkdownClass {
return parsers.original(message);
}

mountTokensBack(message, useHtml = true) {
if (message.tokens && message.tokens.length > 0) {
for (const { token, text, noHtml } of message.tokens) {
message.html = message.html.replace(token, () => (useHtml ? text : noHtml)); // Uses lambda so doesn't need to escape $
mountTokensBackRecursively(message, tokenList, useHtml = true) {
const missingTokens = [];

if (tokenList.length > 0) {
for (const { token, text, noHtml } of tokenList) {
if (message.html.indexOf(token) >= 0) {
message.html = message.html.replace(token, () => (useHtml ? text : noHtml)); // Uses lambda so doesn't need to escape $
} else {
missingTokens.push({ token, text, noHtml });
}
}
}

// If there are tokens that were missing from the string, but the last iteration replaced at least one token, then go again
// this is done because one of the tokens may have been hidden by another one
if (missingTokens.length > 0 && missingTokens.length < tokenList.length) {
this.mountTokensBackRecursively(message, missingTokens, useHtml);
}
}

mountTokensBack(message, useHtml = true) {
if (message.tokens) {
this.mountTokensBackRecursively(message, message.tokens, useHtml);
}

return message;
}

Expand All @@ -60,7 +78,7 @@ class MarkdownClass {
}
}

const Markdown = new MarkdownClass;
export const Markdown = new MarkdownClass;
RocketChat.Markdown = Markdown;

// renderMessage already did html escape
Expand Down
2 changes: 1 addition & 1 deletion packages/rocketchat-markdown/lib/parser/original/code.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import hljs from 'highlight.js';
const inlinecode = (message) =>
// Support `text`
message.html = message.html.replace(/\`([^`\r\n]+)\`([<_*~]|\B|\b|$)/gm, (match, p1, p2) => {
const token = ` =!=${ Random.id() }=!=`;
const token = `=!=${ Random.id() }=!=`;

message.tokens.push({
token,
Expand Down
14 changes: 14 additions & 0 deletions packages/rocketchat-markdown/tests/client.mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ mock('meteor/meteor', {
},
});

mock('meteor/blaze', {
Blaze: {},
});

mock('meteor/rocketchat:lib', {
RocketChat: {
settings: {
get(setting) {
switch (setting) {
case 'Markdown_SupportSchemesForLink':
return 'http,https';
case 'Markdown_Parser':
return 'original';
case 'Markdown_Headers':
// case 'Markdown_Marked_GFM':
// case 'Markdown_Marked_Tables':
Expand All @@ -31,6 +37,14 @@ mock('meteor/rocketchat:lib', {
}
},
},
callbacks: {
add() {

},
priority: {
HIGH: 1,
},
},
},
});

Expand Down
112 changes: 60 additions & 52 deletions packages/rocketchat-markdown/tests/client.tests.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/* eslint-env mocha */
import 'babel-polyfill';
import assert from 'assert';
import s from 'underscore.string';
import './client.mocks.js';
import { original } from '../lib/parser/original/original';
import { Markdown } from '../lib/markdown';
// import {marked} from '../parser/marked/marked';

const wrapper = (text, tag) => `<span class="copyonly">${ tag }</span>${ text }<span class="copyonly">${ tag }</span>`;
Expand All @@ -11,7 +13,7 @@ const italicWrapper = (text) => wrapper(`<em>${ text }</em>`, '_');
const strikeWrapper = (text) => wrapper(`<strike>${ text }</strike>`, '~');
const headerWrapper = (text, level) => `<h${ level }>${ text }</h${ level }>`;
const quoteWrapper = (text) => `<blockquote class="background-transparent-darker-before"><span class="copyonly">&gt;</span>${ text }</blockquote>`;
const linkWrapped = (link, title) => `<a href="${ link }" target="_blank" rel="noopener noreferrer">${ title }</a>`;
const linkWrapped = (link, title) => `<a href="${ s.escapeHTML(link) }" target="_blank" rel="noopener noreferrer">${ s.escapeHTML(title) }</a>`;
const inlinecodeWrapper = (text) => wrapper(`<span><code class="code-colors inline">${ text }</code></span>`, '`');
const codeWrapper = (text, lang) => `<pre><code class='code-colors hljs ${ lang }'><span class='copyonly'>\`\`\`<br></span>${ text }<span class='copyonly'><br>\`\`\`</span></code></pre>`;

Expand Down Expand Up @@ -121,62 +123,62 @@ const headersLevel4 = {
};

const quote = {
'&gt;Hello': quoteWrapper('Hello'),
'&gt;Rocket.Cat': quoteWrapper('Rocket.Cat'),
'&gt;Hi': quoteWrapper('Hi'),
'&gt; Hello this is dog': quoteWrapper(' Hello this is dog'),
'&gt; Rocket cat says Hello': quoteWrapper(' Rocket cat says Hello'),
'&gt; He said Hello to her': quoteWrapper(' He said Hello to her'),
'&gt; He said Hello to her ': quoteWrapper(' He said Hello to her '),
'&lt;Hello': '&lt;Hello',
'&lt;Rocket.Cat&gt;': '&lt;Rocket.Cat&gt;',
' &gt;Hi': ' &gt;Hi',
'Hello &gt; this is dog': 'Hello &gt; this is dog',
'Roc&gt;ket cat says Hello': 'Roc&gt;ket cat says Hello',
'He said Hello to her&gt;': 'He said Hello to her&gt;',
'>Hello': '>Hello',
'>Rocket.Cat': '>Rocket.Cat',
'>Hi': '>Hi',
'> Hello this is dog': '> Hello this is dog',
'> Rocket cat says Hello': '> Rocket cat says Hello',
'> He said Hello to her': '> He said Hello to her',
'<Hello': '<Hello',
'<Rocket.Cat>': '<Rocket.Cat>',
' >Hi': ' >Hi',
'Hello > this is dog': 'Hello > this is dog',
'Roc>ket cat says Hello': 'Roc>ket cat says Hello',
'He said Hello to her>': 'He said Hello to her>',
'&gt;Hello': s.escapeHTML('&gt;Hello'),
'&gt;Rocket.Cat': s.escapeHTML('&gt;Rocket.Cat'),
'&gt;Hi': s.escapeHTML('&gt;Hi'),
'&gt; Hello this is dog': s.escapeHTML('&gt; Hello this is dog'),
'&gt; Rocket cat says Hello': s.escapeHTML('&gt; Rocket cat says Hello'),
'&gt; He said Hello to her': s.escapeHTML('&gt; He said Hello to her'),
'&gt; He said Hello to her ': s.escapeHTML('&gt; He said Hello to her '),
'&lt;Hello': s.escapeHTML('&lt;Hello'),
'&lt;Rocket.Cat&gt;': s.escapeHTML('&lt;Rocket.Cat&gt;'),
' &gt;Hi': s.escapeHTML(' &gt;Hi'),
'Hello &gt; this is dog': s.escapeHTML('Hello &gt; this is dog'),
'Roc&gt;ket cat says Hello': s.escapeHTML('Roc&gt;ket cat says Hello'),
'He said Hello to her&gt;': s.escapeHTML('He said Hello to her&gt;'),
'>Hello': quoteWrapper('Hello'),
'>Rocket.Cat': quoteWrapper('Rocket.Cat'),
'>Hi': quoteWrapper('Hi'),
'> Hello this is dog': quoteWrapper(' Hello this is dog'),
'> Rocket cat says Hello': quoteWrapper(' Rocket cat says Hello'),
'> He said Hello to her': quoteWrapper(' He said Hello to her'),
'<Hello': s.escapeHTML('<Hello'),
'<Rocket.Cat>': s.escapeHTML('<Rocket.Cat>'),
' >Hi': s.escapeHTML(' >Hi'),
'Hello > this is dog': s.escapeHTML('Hello > this is dog'),
'Roc>ket cat says Hello': s.escapeHTML('Roc>ket cat says Hello'),
'He said Hello to her>': s.escapeHTML('He said Hello to her>'),
};

const link = {
'&lt;http://link|Text&gt;': linkWrapped('http://link', 'Text'),
'&lt;https://open.rocket.chat/|Open Site For Rocket.Chat&gt;': linkWrapped('https://open.rocket.chat/', 'Open Site For Rocket.Chat'),
'&lt;https://open.rocket.chat/ | Open Site For Rocket.Chat&gt;': linkWrapped('https://open.rocket.chat/ ', ' Open Site For Rocket.Chat'),
'&lt;https://rocket.chat/|Rocket.Chat Site&gt;': linkWrapped('https://rocket.chat/', 'Rocket.Chat Site'),
'&lt;https://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;': linkWrapped('https://rocket.chat/docs/developer-guides/testing/#testing', 'Testing Entry on Rocket.Chat Docs Site'),
'&lt;http://linkText&gt;': '&lt;http://linkText&gt;',
'&lt;https:open.rocket.chat/ | Open Site For Rocket.Chat&gt;': '&lt;https:open.rocket.chat/ | Open Site For Rocket.Chat&gt;',
'https://open.rocket.chat/|Open Site For Rocket.Chat': 'https://open.rocket.chat/|Open Site For Rocket.Chat',
'&lt;www.open.rocket.chat/|Open Site For Rocket.Chat&gt;': '&lt;www.open.rocket.chat/|Open Site For Rocket.Chat&gt;',
'&lt;htps://rocket.chat/|Rocket.Chat Site&gt;': '&lt;htps://rocket.chat/|Rocket.Chat Site&gt;',
'&lt;ttps://rocket.chat/|Rocket.Chat Site&gt;': '&lt;ttps://rocket.chat/|Rocket.Chat Site&gt;',
'&lt;tps://rocket.chat/|Rocket.Chat Site&gt;': '&lt;tps://rocket.chat/|Rocket.Chat Site&gt;',
'&lt;open.rocket.chat/|Open Site For Rocket.Chat&gt;': '&lt;open.rocket.chat/|Open Site For Rocket.Chat&gt;',
'&lt;htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;': '&lt;htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;',
'&lt;http://link|Text&gt;': s.escapeHTML('&lt;http://link|Text&gt;'),
'&lt;https://open.rocket.chat/|Open Site For Rocket.Chat&gt;': s.escapeHTML('&lt;https://open.rocket.chat/|Open Site For Rocket.Chat&gt;'),
'&lt;https://open.rocket.chat/ | Open Site For Rocket.Chat&gt;': s.escapeHTML('&lt;https://open.rocket.chat/ | Open Site For Rocket.Chat&gt;'),
'&lt;https://rocket.chat/|Rocket.Chat Site&gt;': s.escapeHTML('&lt;https://rocket.chat/|Rocket.Chat Site&gt;'),
'&lt;https://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;': s.escapeHTML('&lt;https://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;'),
'&lt;http://linkText&gt;': s.escapeHTML('&lt;http://linkText&gt;'),
'&lt;https:open.rocket.chat/ | Open Site For Rocket.Chat&gt;': s.escapeHTML('&lt;https:open.rocket.chat/ | Open Site For Rocket.Chat&gt;'),
'https://open.rocket.chat/|Open Site For Rocket.Chat': s.escapeHTML('https://open.rocket.chat/|Open Site For Rocket.Chat'),
'&lt;www.open.rocket.chat/|Open Site For Rocket.Chat&gt;': s.escapeHTML('&lt;www.open.rocket.chat/|Open Site For Rocket.Chat&gt;'),
'&lt;htps://rocket.chat/|Rocket.Chat Site&gt;': s.escapeHTML('&lt;htps://rocket.chat/|Rocket.Chat Site&gt;'),
'&lt;ttps://rocket.chat/|Rocket.Chat Site&gt;': s.escapeHTML('&lt;ttps://rocket.chat/|Rocket.Chat Site&gt;'),
'&lt;tps://rocket.chat/|Rocket.Chat Site&gt;': s.escapeHTML('&lt;tps://rocket.chat/|Rocket.Chat Site&gt;'),
'&lt;open.rocket.chat/|Open Site For Rocket.Chat&gt;': s.escapeHTML('&lt;open.rocket.chat/|Open Site For Rocket.Chat&gt;'),
'&lt;htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;': s.escapeHTML('&lt;htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;'),

'<http://link|Text>': linkWrapped('http://link', 'Text'),
'<https://open.rocket.chat/|Open Site For Rocket.Chat>': linkWrapped('https://open.rocket.chat/', 'Open Site For Rocket.Chat'),
'<https://open.rocket.chat/ | Open Site For Rocket.Chat>': linkWrapped('https://open.rocket.chat/ ', ' Open Site For Rocket.Chat'),
'<https://rocket.chat/|Rocket.Chat Site>': linkWrapped('https://rocket.chat/', 'Rocket.Chat Site'),
'<https://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site>': linkWrapped('https://rocket.chat/docs/developer-guides/testing/#testing', 'Testing Entry on Rocket.Chat Docs Site'),
'<http://linkText>': '<http://linkText>',
'<https:open.rocket.chat/ | Open Site For Rocket.Chat>': '<https:open.rocket.chat/ | Open Site For Rocket.Chat>',
'<www.open.rocket.chat/|Open Site For Rocket.Chat>': '<www.open.rocket.chat/|Open Site For Rocket.Chat>',
'<htps://rocket.chat/|Rocket.Chat Site>': '<htps://rocket.chat/|Rocket.Chat Site>',
'<ttps://rocket.chat/|Rocket.Chat Site>': '<ttps://rocket.chat/|Rocket.Chat Site>',
'<tps://rocket.chat/|Rocket.Chat Site>': '<tps://rocket.chat/|Rocket.Chat Site>',
'<open.rocket.chat/|Open Site For Rocket.Chat>': '<open.rocket.chat/|Open Site For Rocket.Chat>',
'<htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site>': '<htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site>',
'<http://linkText>': s.escapeHTML('<http://linkText>'),
'<https:open.rocket.chat/ | Open Site For Rocket.Chat>': s.escapeHTML('<https:open.rocket.chat/ | Open Site For Rocket.Chat>'),
'<www.open.rocket.chat/|Open Site For Rocket.Chat>': s.escapeHTML('<www.open.rocket.chat/|Open Site For Rocket.Chat>'),
'<htps://rocket.chat/|Rocket.Chat Site>': s.escapeHTML('<htps://rocket.chat/|Rocket.Chat Site>'),
'<ttps://rocket.chat/|Rocket.Chat Site>': s.escapeHTML('<ttps://rocket.chat/|Rocket.Chat Site>'),
'<tps://rocket.chat/|Rocket.Chat Site>': s.escapeHTML('<tps://rocket.chat/|Rocket.Chat Site>'),
'<open.rocket.chat/|Open Site For Rocket.Chat>': s.escapeHTML('<open.rocket.chat/|Open Site For Rocket.Chat>'),
'<htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site>': s.escapeHTML('<htts://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site>'),

'[Text](http://link)': linkWrapped('http://link', 'Text'),
'[Open Site For Rocket.Chat](https://open.rocket.chat/)': linkWrapped('https://open.rocket.chat/', 'Open Site For Rocket.Chat'),
Expand Down Expand Up @@ -219,15 +221,19 @@ const code = {
'```__code__```': codeWrapper('<span class="hljs-strong">__code__</span>', 'markdown'),
};

const nested = {
'> some quote\n`window.location.reload();`': `${ quoteWrapper(' some quote') }${ inlinecodeWrapper('window.location.reload();') }`,
};

const defaultObjectTest = (result, object, objectKey) => assert.equal(result.html, object[objectKey]);

const testObject = (object, parser = original, test = defaultObjectTest) => {
Object.keys(object).forEach((objectKey) => {
describe(objectKey, () => {
const result = parser({ html: objectKey });
result.tokens.forEach((token) => {
result.html = result.html.replace(token.token, token.text);
});
const message = {
html: s.escapeHTML(objectKey),
};
const result = Markdown.mountTokensBack(parser(message));
it(`should be equal to ${ object[objectKey] }`, () => {
test(result, object, objectKey);
});
Expand Down Expand Up @@ -259,6 +265,8 @@ describe('Original', function() {
describe('Inline Code', () => testObject(inlinecode));

describe('Code', () => testObject(code));

describe('Nested', () => testObject(nested));
});

// describe.only('Marked', function() {
Expand Down

0 comments on commit e5c9be6

Please sign in to comment.