diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js index 8248d215..476655e2 100644 --- a/__tests__/ExpensiMark-HTML-test.js +++ b/__tests__/ExpensiMark-HTML-test.js @@ -395,7 +395,7 @@ test('Test url replacements', () => { + 'https://expensify.cash/#/r/1234 ' + 'https://github.com/Expensify/ReactNativeChat/pull/6.45 ' + 'https://github.com/Expensify/Expensify/issues/143,231 ' - + 'testRareTLDs.beer ' + + 'testRareTLDs.beer ' + 'test@expensify.com ' + 'test.completelyFakeTLD ' + 'https://www.expensify.com/_devportal/tools/logSearch/#query=request_id:(%22Ufjjim%22)+AND+timestamp:[2021-01-08T03:48:10.389Z+TO+2021-01-08T05:48:10.389Z]&index=logs_expensify-008878) ' diff --git a/__tests__/Str-test.js b/__tests__/Str-test.js index 1c843951..a8d92a2a 100644 --- a/__tests__/Str-test.js +++ b/__tests__/Str-test.js @@ -84,3 +84,15 @@ describe('Str.isValidMention', () => { expect(Str.isValidMention('"@username@expensify.com"')).toBeTruthy(); }); }); + +describe('Str.sanitizeURL', () => { + it('Normalize domain name to lower case and add missing https:// protocol', () => { + expect(Str.sanitizeURL('https://google.com')).toBe('https://google.com'); + expect(Str.sanitizeURL('google.com')).toBe('https://google.com'); + expect(Str.sanitizeURL('Https://google.com')).toBe('https://google.com'); + expect(Str.sanitizeURL('https://GOOgle.com')).toBe('https://google.com'); + expect(Str.sanitizeURL('FOO.com/blah_BLAH')).toBe('https://foo.com/blah_BLAH'); + expect(Str.sanitizeURL('http://FOO.com/blah_BLAH')).toBe('http://foo.com/blah_BLAH'); + expect(Str.sanitizeURL('HTtp://FOO.com/blah_BLAH')).toBe('http://foo.com/blah_BLAH'); + }); +}); diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js index 52b90400..84230c0e 100644 --- a/lib/ExpensiMark.js +++ b/lib/ExpensiMark.js @@ -113,15 +113,11 @@ export default class ExpensiMark { return this.modifyTextForUrlLinks(regex, textToProcess, replacement); }, - // We use a function here to check if there is already a https:// on the link. - // If there is not, we force the link to be absolute by prepending '//' to the target. - replacement: (match, g1, g2, g3) => { + replacement: (match, g1, g2) => { if (g1.match(CONST.REG_EXP.EMOJIS) || !g1.trim()) { return match; } - - const href = g3 ? g2.replace(g3, g3.toLowerCase()) : `https://${g2}`; - return `${g1.trim()}`; + return `${g1.trim()}`; }, }, @@ -154,10 +150,8 @@ export default class ExpensiMark { return this.modifyTextForUrlLinks(regex, textToProcess, replacement); }, - // We use a function here to check if there is already a https:// on the link. - // If there is not, we force the link to be absolute by prepending '//' to the target. - replacement: (match, g1, g2, g3) => { - const href = g3 ? g2.replace(g3, g3.toLowerCase()) : `https://${g2}`; + replacement: (match, g1, g2) => { + const href = Str.sanitizeURL(g2); return `${g1}${g2}${g1}`; }, }, @@ -465,7 +459,7 @@ export default class ExpensiMark { filterRules: ['bold', 'strikethrough', 'italic'], shouldEscapeText: false, }); - replacedText = replacedText.concat(replacement(match[0], linkText, match[2], match[3])); + replacedText = replacedText.concat(replacement(match[0], linkText, match[2])); } startIndex = match.index + (match[0].length); diff --git a/lib/Url.js b/lib/Url.js index 2554d91c..495c1f38 100644 --- a/lib/Url.js +++ b/lib/Url.js @@ -7,7 +7,7 @@ const addEscapedChar = reg => `(?:${reg}|&(?:amp|quot|#x27);)`; const URL_PATH_REGEX = `(?:${addEscapedChar('[.,=(+$!*]')}?\\/${addEscapedChar('[-\\w$@.+!*:(),=%~]')}*${addEscapedChar('[-\\w~@:%)]')}|\\/)*`; const URL_PARAM_REGEX = `(?:\\?${addEscapedChar('[-\\w$@.+!*()\\/,=%{}:;\\[\\]\\|_|~]')}*)?`; const URL_FRAGMENT_REGEX = `(?:#${addEscapedChar('[-\\w$@.+!*()[\\],=%;\\/:~]')}*)?`; -const URL_REGEX = `(${URL_WEBSITE_REGEX}${URL_PATH_REGEX}(?:${URL_PARAM_REGEX}|${URL_FRAGMENT_REGEX})*)`; +const URL_REGEX = `((${URL_WEBSITE_REGEX})${URL_PATH_REGEX}(?:${URL_PARAM_REGEX}|${URL_FRAGMENT_REGEX})*)`; const URL_REGEX_WITH_REQUIRED_PROTOCOL = URL_REGEX.replace(`${URL_PROTOCOL_REGEX}?`, URL_PROTOCOL_REGEX); diff --git a/lib/str.js b/lib/str.js index 7cab4f9f..da76d7ef 100644 --- a/lib/str.js +++ b/lib/str.js @@ -3,6 +3,7 @@ import _ from 'underscore'; import {AllHtmlEntities} from 'html-entities'; import replaceAll from 'string.prototype.replaceall'; import {CONST} from './CONST'; +import {URL_REGEX} from './Url'; const REMOVE_SMS_DOMAIN_PATTERN = new RegExp(`@${CONST.SMS.DOMAIN}`, 'gi'); @@ -1021,6 +1022,22 @@ const Str = { return (typeof url === 'string' && url.startsWith('/')) ? url : `/${url}`; }, + /** + * Formats a URL by converting the domain name to lowercase and adding the missing 'https://' protocol. + * + * @param {url} url The URL to be formatted + * @returns {String} The formatted URL + */ + sanitizeURL(url) { + const regex = new RegExp(`^${URL_REGEX}$`, 'i'); + const match = regex.exec(url); + if (!match) { + return url; + } + const website = match[3] ? match[2] : `https://${match[2]}`; + return website.toLowerCase() + this.cutBefore(match[1], match[2]); + }, + /** * Checks if parameter is a string or function * if it is a function then we will call it with