From 8cbb9614932ab684f2cf85624739369ed9e7757e Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 22 Nov 2018 05:02:38 +0900
Subject: [PATCH] [MFM] Improve URL parsing

Fix #3368
---
 src/mfm/parser.ts | 20 +++++++++++++++++---
 test/mfm.ts       | 36 +++++++++++++++++++++++++++++-------
 2 files changed, 46 insertions(+), 10 deletions(-)

diff --git a/src/mfm/parser.ts b/src/mfm/parser.ts
index 5acba7867b..047124e582 100644
--- a/src/mfm/parser.ts
+++ b/src/mfm/parser.ts
@@ -245,11 +245,25 @@ const mfm = P.createLanguage({
 			const match = text.match(/^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.,=\+\-]+/);
 			if (!match) return P.makeFailure(i, 'not a url');
 			let url = match[0];
-			const before = input[i - 1];
+			let pendingBracket = 0;
+			const end = url.split('').findIndex(char => {
+				if (char == ')') {
+					if (pendingBracket > 0) {
+						pendingBracket--;
+						return false;
+					} else {
+						return true;
+					}
+				} else if (char == '(') {
+					pendingBracket++;
+					return false;
+				} else {
+					return false;
+				}
+			});
+			if (end > 0) url = url.substr(0, end);
 			if (url.endsWith('.')) url = url.substr(0, url.lastIndexOf('.'));
 			if (url.endsWith(',')) url = url.substr(0, url.lastIndexOf(','));
-			if (url.endsWith(')') && before == '(') url = url.substr(0, url.lastIndexOf(')'));
-			if (url.endsWith(']') && before == '[') url = url.substr(0, url.lastIndexOf(']'));
 			return P.makeSuccess(i + url.length, url);
 		})
 		.map(x => makeNode('url', { url: x })),
diff --git a/test/mfm.ts b/test/mfm.ts
index d99bb2bac7..8447e798e2 100644
--- a/test/mfm.ts
+++ b/test/mfm.ts
@@ -381,6 +381,15 @@ describe('Text', () => {
 				], tokens);
 			});
 
+			it('ignore parent brackets 2', () => {
+				const tokens = analyze('(foo https://example.com/foo)');
+				assert.deepEqual([
+					text('(foo '),
+					node('url', { url: 'https://example.com/foo' }),
+					text(')')
+				], tokens);
+			});
+
 			it('ignore parent brackets with internal brackets', () => {
 				const tokens = analyze('(https://example.com/foo(bar))');
 				assert.deepEqual([
@@ -391,13 +400,26 @@ describe('Text', () => {
 			});
 		});
 
-		it('link', () => {
-			const tokens = analyze('[foo](https://example.com)');
-			assert.deepEqual([
-				nodeWithChildren('link', [
-					text('foo')
-				], { url: 'https://example.com', silent: false })
-			], tokens);
+		describe('link', () => {
+			it('simple', () => {
+				const tokens = analyze('[foo](https://example.com)');
+				assert.deepEqual([
+					nodeWithChildren('link', [
+						text('foo')
+					], { url: 'https://example.com', silent: false })
+				], tokens);
+			});
+
+			it('in text', () => {
+				const tokens = analyze('before[foo](https://example.com)after');
+				assert.deepEqual([
+					text('before'),
+					nodeWithChildren('link', [
+						text('foo')
+					], { url: 'https://example.com', silent: false }),
+					text('after'),
+				], tokens);
+			});
 		});
 
 		it('emoji', () => {