diff --git a/__tests__/ExpensiMark-test.js b/__tests__/ExpensiMark-test.js
index 436f4b1d..c54f8bf4 100644
--- a/__tests__/ExpensiMark-test.js
+++ b/__tests__/ExpensiMark-test.js
@@ -1,5 +1,6 @@
/* eslint-disable max-len */
import ExpensiMark from '../lib/ExpensiMark';
+import _ from 'underscore';
const parser = new ExpensiMark();
@@ -14,3 +15,20 @@ test('Test text is unescaped', () => {
const resultString = '& & < <';
expect(parser.htmlToText(htmlString)).toBe(resultString);
});
+
+test('Test with regex Maximum regex stack depth reached error', () => {
+ const testString = '
heading
asjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfidekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdicdjejajasjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfisjksksjsjssskssjskskssksksksksskdkddkddkdksskskdkdkdksskskskdkdkdkdkekeekdkddenejeodxkdndekkdjddkeemdjxkdenendkdjddekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdicdjejajasjjssjdjdjdjdjdjjeiwiwiwowkdjdjdieikdjfidekjcjdkekejdcjdkeekcjcdidjjcdkekdiccjdkejdjcjxisdjjdkedncicdjejejcckdsijcjdsodjcicdkejdi.cdjd';
+ const parser = new ExpensiMark();
+ // Mock method modifyTextForUrlLinks to let it throw an error to test try/catch of method ExpensiMark.replace
+ const modifyTextForUrlLinksMock = jest.fn((a, b, c) => {throw new Error('Maximum regex stack depth reached')});
+ parser.modifyTextForUrlLinks = modifyTextForUrlLinksMock;
+ expect(parser.replace(testString)).toBe(_.escape(testString));
+ expect(modifyTextForUrlLinksMock).toHaveBeenCalledTimes(1);
+
+ // Mock method extractLinksInMarkdownComment to let it return undefined to test try/catch of method ExpensiMark.extractLinksInMarkdownComment
+ const extractLinksInMarkdownCommentMock = jest.fn((a) => undefined);
+ parser.extractLinksInMarkdownComment = extractLinksInMarkdownCommentMock;
+ expect(parser.extractLinksInMarkdownComment(testString)).toBe(undefined);
+ expect(parser.getRemovedMarkdownLinks(testString, 'google.com')).toStrictEqual([]);
+ expect(extractLinksInMarkdownCommentMock).toHaveBeenCalledTimes(3);
+});
diff --git a/lib/ExpensiMark.d.ts b/lib/ExpensiMark.d.ts
index 75de4525..529e7b52 100644
--- a/lib/ExpensiMark.d.ts
+++ b/lib/ExpensiMark.d.ts
@@ -86,7 +86,7 @@ export default class ExpensiMark {
* @param textToCheck - Text to check
*/
containsNonPairTag(textToCheck: string): boolean;
- extractLinksInMarkdownComment(comment: string): string[];
+ extractLinksInMarkdownComment(comment: string): string[] | undefined;
/**
* Compares two markdown comments and returns a list of the links removed in a new comment.
*
diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js
index 33149c00..aad82fa1 100644
--- a/lib/ExpensiMark.js
+++ b/lib/ExpensiMark.js
@@ -391,23 +391,32 @@ export default class ExpensiMark {
let replacedText = shouldEscapeText ? _.escape(text) : text;
const rules = _.isEmpty(filterRules) ? this.rules : _.filter(this.rules, rule => _.contains(filterRules, rule.name));
- rules.forEach((rule) => {
- // Pre-process text before applying regex
- if (rule.pre) {
- replacedText = rule.pre(replacedText);
- }
- if (rule.process) {
- replacedText = rule.process(replacedText, rule.replacement);
- } else {
- replacedText = replacedText.replace(rule.regex, rule.replacement);
- }
+ try {
+ rules.forEach((rule) => {
+ // Pre-process text before applying regex
+ if (rule.pre) {
+ replacedText = rule.pre(replacedText);
+ }
- // Post-process text after applying regex
- if (rule.post) {
- replacedText = rule.post(replacedText);
- }
- });
+ if (rule.process) {
+ replacedText = rule.process(replacedText, rule.replacement);
+ } else {
+ replacedText = replacedText.replace(rule.regex, rule.replacement);
+ }
+
+ // Post-process text after applying regex
+ if (rule.post) {
+ replacedText = rule.post(replacedText);
+ }
+ });
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.warn('Error replacing text with html in ExpensiMark.replace', {error: e});
+
+ // We want to return text without applying rules if exception occurs during replacing
+ return shouldEscapeText ? _.escape(text) : text;
+ }
return replacedText;
}
@@ -731,15 +740,21 @@ export default class ExpensiMark {
/**
* @param {String} comment
- * @returns {Array}
+ * @returns {Array} or undefined if exception occurs when executing regex matching
*/
extractLinksInMarkdownComment(comment) {
- const escapedComment = _.escape(comment);
- const matches = [...escapedComment.matchAll(MARKDOWN_LINK_REGEX)];
-
- // Element 1 from match is the regex group if it exists which contains the link URLs
- const links = _.map(matches, match => Str.sanitizeURL(match[2]));
- return links;
+ try {
+ const escapedComment = _.escape(comment);
+ const matches = [...escapedComment.matchAll(MARKDOWN_LINK_REGEX)];
+
+ // Element 1 from match is the regex group if it exists which contains the link URLs
+ const links = _.map(matches, match => Str.sanitizeURL(match[2]));
+ return links;
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.warn('Error parsing url in ExpensiMark.extractLinksInMarkdownComment', {error: e});
+ return undefined;
+ }
}
/**
@@ -752,6 +767,6 @@ export default class ExpensiMark {
getRemovedMarkdownLinks(oldComment, newComment) {
const linksInOld = this.extractLinksInMarkdownComment(oldComment);
const linksInNew = this.extractLinksInMarkdownComment(newComment);
- return _.difference(linksInOld, linksInNew);
+ return linksInOld === undefined || linksInNew === undefined ? [] : _.difference(linksInOld, linksInNew);
}
}