diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js
index 476655e2..b6935396 100644
--- a/__tests__/ExpensiMark-HTML-test.js
+++ b/__tests__/ExpensiMark-HTML-test.js
@@ -159,8 +159,12 @@ test('Test markdown replacement for emails wrapped in bold/strikethrough/italic
// Markdown style links replaced successfully
test('Test markdown style links', () => {
- const testString = 'Go to [Expensify](https://www.expensify.com) to learn more. [Expensify](www.expensify.com) [Expensify](expensify.com) [It\'s really the coolest](expensify.com) [`Some` Special cases - + . = , \'](expensify.com/some?query=par|am)';
- const resultString = 'Go to Expensify to learn more. Expensify Expensify It's really the coolest Some
Special cases - + . = , '';
+ let testString = 'Go to [Expensify](https://www.expensify.com) to learn more. [Expensify](www.expensify.com) [Expensify](expensify.com) [It\'s really the coolest](expensify.com) [`Some` Special cases - + . = , \'](expensify.com/some?query=par|am)';
+ let resultString = 'Go to Expensify to learn more. Expensify Expensify It's really the coolest Some
Special cases - + . = , '';
+ expect(parser.replace(testString)).toBe(resultString);
+
+ testString = 'Go to [Localhost](http://localhost:3000) to learn more. [Expensify](www.expensify.com) [Expensify](expensify.com) [It\'s really the coolest](expensify.com) [`Some` Special cases - + . = , \'](expensify.com/some?query=par|am)';
+ resultString = 'Go to Localhost to learn more. Expensify Expensify It's really the coolest Some
Special cases - + . = , '';
expect(parser.replace(testString)).toBe(resultString);
});
@@ -181,7 +185,8 @@ test('Test critical markdown style links', () => {
+ '[link with an @ in it](https://google.com) '
+ '[link with [brackets] inside of it](https://google.com) '
+ '[link with smart quotes ‘’“”](https://google.com) '
- + '[link with someone@expensify.com email in it](https://google.com)';
+ + '[link with someone@expensify.com email in it](https://google.com)'
+ + '[Localhost](http://a:3030)';
const resultString = 'Testing '
+ 'strikethrough bold italic '
+ 'strikethrough bold italic test.com '
@@ -197,7 +202,8 @@ test('Test critical markdown style links', () => {
+ 'link with an @ in it '
+ 'link with [brackets] inside of it '
+ 'link with smart quotes ‘’“” '
- + 'link with someone@expensify.com email in it';
+ + 'link with someone@expensify.com email in it'
+ + 'Localhost';
expect(parser.replace(testString)).toBe(resultString);
});
diff --git a/__tests__/ExpensiMark-Markdown-test.js b/__tests__/ExpensiMark-Markdown-test.js
index fa7dbbd1..857086e7 100644
--- a/__tests__/ExpensiMark-Markdown-test.js
+++ b/__tests__/ExpensiMark-Markdown-test.js
@@ -590,8 +590,11 @@ test('Test html to heading1 markdown when there are adjacent h1 tags in the line
});
test('Test link with multiline text do not loses markdown', () => {
- const testString = 'multiline\ntext';
- const resultString = '[multiline\ntext](https://google.com/)';
+ let testString = 'multiline\ntext';
+ let resultString = '[multiline\ntext](https://google.com/)';
+ expect(parser.htmlToMarkdown(testString)).toBe(resultString);
+ testString = 'multiline\ntext';
+ resultString = '[multiline\ntext](http://localhost:3000/)';
expect(parser.htmlToMarkdown(testString)).toBe(resultString);
});
@@ -614,8 +617,12 @@ test('Map html list item to newline with two prefix spaces', () => {
});
test('Test list item replacement when there is an anchor tag inside
tag', () => {
- const testString = '';
- const resultString = ' [Coffee](https://example.com) -- Coffee\n [Tea](https://example.com) -- Tea\n [Milk](https://example.com) -- Milk';
+ let testString = '';
+ let resultString = ' [Coffee](https://example.com) -- Coffee\n [Tea](https://example.com) -- Tea\n [Milk](https://example.com) -- Milk';
+ expect(parser.htmlToMarkdown(testString)).toBe(resultString);
+
+ testString = '';
+ resultString = ' [Coffee](http://localhost) -- Coffee\n [Tea](http://localhost/) -- Tea\n [Milk](http://localhost/) -- Milk';
expect(parser.htmlToMarkdown(testString)).toBe(resultString);
});
diff --git a/__tests__/URL-test.js b/__tests__/URL-test.js
index 820f91f5..553f4b55 100644
--- a/__tests__/URL-test.js
+++ b/__tests__/URL-test.js
@@ -1,39 +1,59 @@
-import {URL_REGEX_WITH_REQUIRED_PROTOCOL, URL_REGEX} from '../lib/Url';
+import {URL_REGEX_WITH_REQUIRED_PROTOCOL, URL_REGEX, LOOSE_URL_REGEX} from '../lib/Url';
-describe('Mandatory protocol for URL', () => {
- it('correctly tests valid urls', () => {
- const regexToTest = new RegExp(`^${URL_REGEX_WITH_REQUIRED_PROTOCOL}$`, 'i');
- expect(regexToTest.test('https://google.com/')).toBeTruthy();
- expect(regexToTest.test('http://google.com/')).toBeTruthy();
- expect(regexToTest.test('ftp://google.com/')).toBeTruthy();
- expect(regexToTest.test('https://we.are.expensify.com/how-we-got-here')).toBeTruthy();
- expect(regexToTest.test('https://google.com:12')).toBeTruthy();
- expect(regexToTest.test('https://google.com:65535')).toBeTruthy();
- expect(regexToTest.test('https://google.com:65535/path/my')).toBeTruthy();
+describe('Strict URL validation', () => {
+ describe('Mandatory protocol for URL', () => {
+ it('correctly tests valid urls', () => {
+ const regexToTest = new RegExp(`^${URL_REGEX_WITH_REQUIRED_PROTOCOL}$`, 'i');
+ expect(regexToTest.test('https://google.com/')).toBeTruthy();
+ expect(regexToTest.test('http://google.com/')).toBeTruthy();
+ expect(regexToTest.test('ftp://google.com/')).toBeTruthy();
+ expect(regexToTest.test('https://we.are.expensify.com/how-we-got-here')).toBeTruthy();
+ expect(regexToTest.test('https://google.com:12')).toBeTruthy();
+ expect(regexToTest.test('https://google.com:65535')).toBeTruthy();
+ expect(regexToTest.test('https://google.com:65535/path/my')).toBeTruthy();
+ });
+ it('correctly tests invalid urls', () => {
+ const regexToTest = new RegExp(`^${URL_REGEX_WITH_REQUIRED_PROTOCOL}$`, 'i');
+ expect(regexToTest.test('google.com')).toBeFalsy();
+ expect(regexToTest.test('https://google.com:02')).toBeFalsy();
+ expect(regexToTest.test('https://google.com:65536')).toBeFalsy();
+ expect(regexToTest.test('smtp://google.com')).toBeFalsy();
+ });
});
- it('correctly tests invalid urls', () => {
- const regexToTest = new RegExp(`^${URL_REGEX_WITH_REQUIRED_PROTOCOL}$`, 'i');
- expect(regexToTest.test('google.com')).toBeFalsy();
- expect(regexToTest.test('https://google.com:02')).toBeFalsy();
- expect(regexToTest.test('https://google.com:65536')).toBeFalsy();
- expect(regexToTest.test('smtp://google.com')).toBeFalsy();
+
+ describe('Optional protocol for URL', () => {
+ it('correctly tests valid urls', () => {
+ const regexToTest = new RegExp(`^${URL_REGEX}$`, 'i');
+ expect(regexToTest.test('google.com/')).toBeTruthy();
+ expect(regexToTest.test('https://google.com/')).toBeTruthy();
+ expect(regexToTest.test('ftp://google.com/')).toBeTruthy();
+ expect(regexToTest.test('we.are.expensify.com/how-we-got-here')).toBeTruthy();
+ expect(regexToTest.test('google.com:12')).toBeTruthy();
+ expect(regexToTest.test('google.com:65535')).toBeTruthy();
+ expect(regexToTest.test('google.com:65535/path/my')).toBeTruthy();
+ });
+ it('correctly tests invalid urls', () => {
+ const regexToTest = new RegExp(`^${URL_REGEX}$`, 'i');
+ expect(regexToTest.test('google.com:02')).toBeFalsy();
+ expect(regexToTest.test('google.com:65536')).toBeFalsy();
+ });
});
});
-describe('Optional protocol for URL', () => {
- it('correctly tests valid urls', () => {
- const regexToTest = new RegExp(`^${URL_REGEX}$`, 'i');
- expect(regexToTest.test('google.com/')).toBeTruthy();
- expect(regexToTest.test('https://google.com/')).toBeTruthy();
- expect(regexToTest.test('ftp://google.com/')).toBeTruthy();
- expect(regexToTest.test('we.are.expensify.com/how-we-got-here')).toBeTruthy();
- expect(regexToTest.test('google.com:12')).toBeTruthy();
- expect(regexToTest.test('google.com:65535')).toBeTruthy();
- expect(regexToTest.test('google.com:65535/path/my')).toBeTruthy();
+describe('Loose URL validation', () => {
+ it('correctly tests urls that can be valid on local machine', () => {
+ const regexToTest = new RegExp(`^${LOOSE_URL_REGEX}$`, 'i');
+ expect(regexToTest.test('http://localhost:3000')).toBeTruthy();
+ expect(regexToTest.test('https://local.url')).toBeTruthy();
+ expect(regexToTest.test('http://a.b')).toBeTruthy();
+ expect(regexToTest.test('http://expensify')).toBeTruthy();
+ expect(regexToTest.test('http://google.com/abcd')).toBeTruthy();
});
+
it('correctly tests invalid urls', () => {
- const regexToTest = new RegExp(`^${URL_REGEX}$`, 'i');
- expect(regexToTest.test('google.com:02')).toBeFalsy();
- expect(regexToTest.test('google.com:65536')).toBeFalsy();
+ const regexToTest = new RegExp(`^${LOOSE_URL_REGEX}$`, 'i');
+ expect(regexToTest.test('localhost:3000')).toBeFalsy();
+ expect(regexToTest.test('local.url')).toBeFalsy();
+ expect(regexToTest.test('https://otherexample.com links get rendered first')).toBeFalsy();
});
});
diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js
index 84230c0e..1a24840e 100644
--- a/lib/ExpensiMark.js
+++ b/lib/ExpensiMark.js
@@ -1,6 +1,6 @@
import _ from 'underscore';
import Str from './str';
-import {URL_REGEX} from './Url';
+import {MARKDOWN_URL_REGEX, LOOSE_URL_REGEX, URL_REGEX} from './Url';
import {CONST} from './CONST';
const SLACK_SPAN_NEW_LINE_TAG = '';
@@ -107,7 +107,7 @@ export default class ExpensiMark {
name: 'link',
process: (textToProcess, replacement) => {
const regex = new RegExp(
- `\\[([^\\][]*(?:\\[[^\\][]*][^\\][]*)*)]\\(${URL_REGEX}\\)(?![^<]*(<\\/pre>|<\\/code>))`,
+ `\\[([^\\][]*(?:\\[[^\\][]*][^\\][]*)*)]\\(${MARKDOWN_URL_REGEX}\\)(?![^<]*(<\\/pre>|<\\/code>))`,
'gi',
);
return this.modifyTextForUrlLinks(regex, textToProcess, replacement);
@@ -144,7 +144,7 @@ export default class ExpensiMark {
process: (textToProcess, replacement) => {
const regex = new RegExp(
- `(?![^<]*>|[^<>]*<\\/)([_*~]*?)${URL_REGEX}\\1(?!((?:(?!|[^<]*(<\\/pre>|<\\/code>))`,
+ `(?![^<]*>|[^<>]*<\\/)([_*~]*?)${MARKDOWN_URL_REGEX}\\1(?!((?:(?!|[^<]*(<\\/pre>|<\\/code>))`,
'gi',
);
return this.modifyTextForUrlLinks(regex, textToProcess, replacement);
@@ -448,7 +448,7 @@ export default class ExpensiMark {
if (abort) {
replacedText = replacedText.concat(textToCheck.substr(match.index, (match[0].length)));
} else {
- const urlRegex = new RegExp(`^${URL_REGEX}$`, 'i');
+ const urlRegex = new RegExp(`^${LOOSE_URL_REGEX}$|^${URL_REGEX}$`, 'i');
// `match[1]` contains the text inside the [] of the markdown e.g. [example](https://example.com)
// At the entry of function this.replace, text is already escaped due to the rules that precede the link
diff --git a/lib/Url.js b/lib/Url.js
index 495c1f38..f8eb7e9e 100644
--- a/lib/Url.js
+++ b/lib/Url.js
@@ -11,6 +11,12 @@ const URL_REGEX = `((${URL_WEBSITE_REGEX})${URL_PATH_REGEX}(?:${URL_PARAM_REGEX}
const URL_REGEX_WITH_REQUIRED_PROTOCOL = URL_REGEX.replace(`${URL_PROTOCOL_REGEX}?`, URL_PROTOCOL_REGEX);
+const LOOSE_URL_WEBSITE_REGEX = `${URL_PROTOCOL_REGEX}([-\\w]+(\\.[-\\w]+)*)(?:\\:${ALLOWED_PORTS}|\\b|(?=_))`;
+const LOOSE_URL_REGEX = `((${LOOSE_URL_WEBSITE_REGEX})${URL_PATH_REGEX}(?:${URL_PARAM_REGEX}|${URL_FRAGMENT_REGEX})*)`;
+
+
+const MARKDOWN_URL_REGEX = `(${LOOSE_URL_REGEX}|${URL_REGEX})`;
+
export {
URL_WEBSITE_REGEX,
URL_PATH_REGEX,
@@ -19,4 +25,7 @@ export {
URL_REGEX,
URL_REGEX_WITH_REQUIRED_PROTOCOL,
URL_PROTOCOL_REGEX,
+ LOOSE_URL_REGEX,
+ LOOSE_URL_WEBSITE_REGEX,
+ MARKDOWN_URL_REGEX,
};