diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js index 281b0981..23714e37 100644 --- a/__tests__/ExpensiMark-HTML-test.js +++ b/__tests__/ExpensiMark-HTML-test.js @@ -675,6 +675,36 @@ test('Test urls with unmatched closing parentheses autolinks correctly', () => { }); }); +test('Test urls ending with special characters followed by unmatched closing parentheses autolinks correctly', () => { + const testString = 'https://github.com/Expensify/ReactNativeChat/pull/645.) ' + + 'https://github.com/Expensify/ReactNativeChat/pull/645$) ' + + 'https://github.com/Expensify/ReactNativeChat/pull/645*) ' + + 'https://github.com/Expensify/ReactNativeChat/pull/645+) ' + + 'https://github.com/Expensify/ReactNativeChat/pull/645!) ' + + 'https://github.com/Expensify/ReactNativeChat/pull/645,) ' + + 'https://github.com/Expensify/ReactNativeChat/pull/645=) ' + + 'https://staging.new.expensify.com/get-assistance/WorkspaceGeneralSettings. ' + + 'https://staging.new.expensify.com/get-assistance/WorkspaceGeneralSettings.. ' + + 'https://staging.new.expensify.com/get-assistance/WorkspaceGeneralSettings..) ' + + '[https://staging.new.expensify.com/get-assistance/WorkspaceGeneralSettings] ' + + 'https://google.com/path?param=) ' + + 'https://google.com/path#fragment!) '; + const resultString = 'https://github.com/Expensify/ReactNativeChat/pull/645.) ' + + 'https://github.com/Expensify/ReactNativeChat/pull/645$) ' + + 'https://github.com/Expensify/ReactNativeChat/pull/645*) ' + + 'https://github.com/Expensify/ReactNativeChat/pull/645+) ' + + 'https://github.com/Expensify/ReactNativeChat/pull/645!) ' + + 'https://github.com/Expensify/ReactNativeChat/pull/645,) ' + + 'https://github.com/Expensify/ReactNativeChat/pull/645=) ' + + 'https://staging.new.expensify.com/get-assistance/WorkspaceGeneralSettings. ' + + 'https://staging.new.expensify.com/get-assistance/WorkspaceGeneralSettings.. ' + + 'https://staging.new.expensify.com/get-assistance/WorkspaceGeneralSettings..) ' + + '[https://staging.new.expensify.com/get-assistance/WorkspaceGeneralSettings] ' + + 'https://google.com/path?param=) ' + + 'https://google.com/path#fragment!) '; + expect(parser.replace(testString)).toBe(resultString); +}); + test('Test urls autolinks correctly', () => { const testCases = [ { diff --git a/lib/CONST.jsx b/lib/CONST.jsx index 66de942d..f7fc1950 100644 --- a/lib/CONST.jsx +++ b/lib/CONST.jsx @@ -270,6 +270,13 @@ export const CONST = { } }, + /** + * Special characters that need to be removed when they are ending an url + * + * @type String + */ + SPECIAL_CHARS_TO_REMOVE: '$*.+!(,=', + /** * Store all the regular expression we are using for matching stuff */ diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 2d77cc3a..e946b85d 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -469,6 +469,27 @@ export default class ExpensiMark { unmatchedOpenParentheses--; } } + + // Because we are removing ) parenthesis, some special characters that shouldn't be in the href are in the href + // For example google.com/toto.) is accepted by the regular expression above and we remove the ) parenthesis, so the link becomes google.com/toto. which is not a valid link + // In that case we should also remove the "." + // Those characters should only be remove from the url if this url doesn't have a parameter or a fragment + if (!url.includes('?') && !url.includes('#')) { + let numberOfCharsToRemove = 0; + + for (let i = url.length - 1; i >= 0; i--) { + if (CONST.SPECIAL_CHARS_TO_REMOVE.includes(url[i])) { + numberOfCharsToRemove++; + } else { + break; + } + } + if (numberOfCharsToRemove) { + match[0] = match[0].substring(0, match[0].length - numberOfCharsToRemove); + url = url.substring(0, url.length - numberOfCharsToRemove); + } + } + replacedText = replacedText.concat(textToCheck.substr(startIndex, (match.index - startIndex))); // We want to avoid matching domains in email addresses so we don't render them as URLs,