Bladeren bron

Improve RegExp for detecting URLs in typed text.

Maciej Gołaszewski 5 jaren geleden
bovenliggende
commit
d6ddfe6ce6

+ 30 - 7
packages/ckeditor5-link/src/autolink.js

@@ -13,21 +13,44 @@ import getLastTextLine from '@ckeditor/ckeditor5-typing/src/utils/getlasttextlin
 
 const MIN_LINK_LENGTH_WITH_SPACE_AT_END = 4; // Ie: "t.co " (length 5).
 
+// This was tweak from https://gist.github.com/dperini/729294.
 const URL_REG_EXP = new RegExp(
 	// Group 1: Line start or after a space.
-	'(^|\\s)' + // Match .
-	// Group 2: Full detected URL.
+	'(^|\\s)' +
+	// Group 2: Detected URL (or e-mail).
 	'(' +
-		// Group 3 + 4: Protocol + domain.
-		'(([a-z]{3,9}:(?:\\/\\/)?)(?:[\\w]+)?[a-z0-9.-]+|(?:www\\.|[\\w]+)[a-z0-9.-]+)' +
-		// Group 5: Optional path + query string + location.
-		'((?:\\/[+~%/.\\w\\-_]*)?\\??(?:[-+=&;%@.\\w_]*)#?(?:[.!/\\\\\\w]*))?' +
+		// Protocol identifier or short syntax "//"
+		// a. Full form http://user@foo.bar.baz:8080/foo/bar.html#baz?foo=bar
+		'(' +
+			'(?:(?:(?:https?|ftp):)?\\/\\/)' +
+			// BasicAuth using user:pass (optional)
+			'(?:\\S+(?::\\S*)?@)?' +
+			'(?:' +
+				// Host & domain names.
+				'(?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.)+' +
+				// TLD identifier name.
+				'(?:[a-z\\u00a1-\\uffff]{2,})' +
+			')' +
+			// port number (optional)
+			'(?::\\d{2,5})?' +
+			// resource path (optional)
+			'(?:[/?#]\\S*)?' +
+		')' +
+		'|' +
+		// b. Short form (either www.example.com or example@example.com)
+		'(' +
+			'(www.|(\\S+@))' +
+			// Host & domain names.
+			'((?![-_])(?:[-\\w\\u00a1-\\uffff]{0,63}[^-_]\\.))+' +
+			// TLD identifier name.
+			'(?:[a-z\\u00a1-\\uffff]{2,})' +
+		')' +
 	')$', 'i' );
 
 const URL_GROUP_IN_MATCH = 2;
 
 // Simplified email test - should be run over previously found URL.
-const EMAIL_REG_EXP = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i;
+const EMAIL_REG_EXP = /^[\S]+@((?![-_])(?:[-\w\u00a1-\uffff]{0,63}[^-_]\.))+(?:[a-z\u00a1-\uffff]{2,})$/i;
 
 /**
  * The auto link plugin.

+ 98 - 37
packages/ckeditor5-link/tests/autolink.js

@@ -114,44 +114,105 @@ describe( 'AutoLink', () => {
 			);
 		} );
 
-		const supportedURLs = [
-			'http://cksource.com',
-			'https://cksource.com',
-			'http://www.cksource.com',
-			'hTtP://WwW.cKsOuRcE.cOm',
-			'http://foo.bar.cksource.com',
-			'http://www.cksource.com/some/path/index.html#abc',
-			'http://www.cksource.com/some/path/index.html?foo=bar',
-			'http://www.cksource.com/some/path/index.html?foo=bar#abc',
-			'http://localhost',
-			'ftp://cksource.com',
-			'mailto://cksource@cksource.com',
-			'www.cksource.com',
-			'cksource.com'
-		];
-
-		const unsupportedURLs = [
-			'http://www.cksource.com/some/path/index.html#abc?foo=bar', // Wrong #? sequence.
-			'http:/cksource.com'
-		];
-
-		for ( const supportedURL of supportedURLs ) {
-			it( `should detect "${ supportedURL }" as a valid URL`, () => {
-				simulateTyping( supportedURL + ' ' );
-
-				expect( getData( model ) ).to.equal(
-					`<paragraph><$text linkHref="${ supportedURL }">${ supportedURL }</$text> []</paragraph>` );
-			} );
-		}
-
-		for ( const unsupportedURL of unsupportedURLs ) {
-			it( `should not detect "${ unsupportedURL }" as a valid URL`, () => {
-				simulateTyping( unsupportedURL + ' ' );
+		// Some examples came from https://mathiasbynens.be/demo/url-regex.
+		describe( 'supported URL', () => {
+			const supportedURLs = [
+				'http://cksource.com',
+				'https://cksource.com',
+				'https://cksource.com:8080',
+				'http://www.cksource.com',
+				'hTtP://WwW.cKsOuRcE.cOm',
+				'www.cksource.com',
+				'http://foo.bar.cksource.com',
+				'http://www.cksource.com/some/path/index.html#abc',
+				'http://www.cksource.com/some/path/index.html?foo=bar',
+				'http://www.cksource.com/some/path/index.html?foo=bar#abc',
+				'http://www.cksource.com:8080/some/path/index.html?foo=bar#abc',
+				'http://www.cksource.com/some/path/index.html#abc?foo=bar',
+				'ftp://cksource.com',
+				'http://cksource.com/foo_bar',
+				'http://cksource.com/foo_bar/',
+				'http://cksource.com/foo_bar_(wikipedia)',
+				'http://cksource.com/foo_bar_(wikipedia)_(again)',
+				'http://www.cksource.com/wpstyle/?p=364',
+				'http://www.cksource.com/wpstyle/?bar=baz&inga=42&quux',
+				'http://userid:password@example.com:8080' +
+				'http://userid:password@example.com:8080/' +
+				'http://userid@cksource.com' +
+				'http://userid@cksource.com/' +
+				'http://userid@cksource.com:8080' +
+				'http://userid@cksource.com:8080/' +
+				'http://userid:password@cksource.com' +
+				'http://userid:password@cksource.com/' +
+				'http://🥳df.ws/123',
+				'http://🥳.ws/富',
+				'http://🥳.ws',
+				'http://🥳.ws/',
+				'http://cksource.com/blah_(wikipedia)#cite-1',
+				'http://cksource.com/blah_(wikipedia)_blah#cite-1',
+				'http://cksource.com/unicode_(🥳)_in_parens',
+				'http://cksource.com/(something)?after=parens',
+				'http://🥳.cksource.com/',
+				'http://code.cksource.com/woot/#&product=browser',
+				'http://j.mp',
+				'ftp://cksource.com/baz',
+				'http://cksource.com/?q=Test%20URL-encoded%20stuff',
+				'http://مثال.إختبار',
+				'http://例子.测试',
+				'http://उदाहरण.परीक्षा',
+				'http://1337.net',
+				'http://a.b-c.de'
+			];
+
+			for ( const supportedURL of supportedURLs ) {
+				it( `should detect "${ supportedURL }" as a valid URL`, () => {
+					simulateTyping( supportedURL + ' ' );
+
+					expect( getData( model ) ).to.equal(
+						`<paragraph><$text linkHref="${ supportedURL }">${ supportedURL }</$text> []</paragraph>` );
+				} );
+			}
+		} );
 
-				expect( getData( model ) ).to.equal(
-					`<paragraph>${ unsupportedURL } []</paragraph>` );
-			} );
-		}
+		describe( 'invalid or supported URL', () => {
+			// Some examples came from https://mathiasbynens.be/demo/url-regex.
+			const unsupportedOrInvalid = [
+				'http://',
+				'http://.',
+				'http://..',
+				'http://../',
+				'http://🥳',
+				'http://?',
+				'http://??',
+				'http://??/',
+				'http://#',
+				'http://##',
+				'http://##/',
+				'//',
+				'//a',
+				'///a',
+				'///',
+				'http:///a',
+				'rdar://1234',
+				'h://test',
+				':// foo bar',
+				'ftps://foo.bar/',
+				'http://-error-.invalid/',
+				'http://localhost',
+				'http:/cksource.com',
+				'cksource.com',
+				'ww.cksource.com'
+			];
+
+			for ( const unsupportedURL of unsupportedOrInvalid ) {
+				it( `should not detect "${ unsupportedURL }" as a valid URL`, () => {
+					simulateTyping( unsupportedURL + ' ' );
+
+					expect( getData( model ) ).to.equal(
+						`<paragraph>${ unsupportedURL } []</paragraph>` );
+				} );
+			}
+		} );
 	} );
 
 	describe( 'Undo integration', () => {

File diff suppressed because it is too large
+ 9 - 1
packages/ckeditor5-link/tests/manual/autolink.html


+ 2 - 1
packages/ckeditor5-link/tests/manual/autolink.js

@@ -15,10 +15,11 @@ import Undo from '@ckeditor/ckeditor5-undo/src/undo';
 
 import Link from '../../src/link';
 import AutoLink from '../../src/autolink';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
 
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
-		plugins: [ Link, AutoLink, Typing, Paragraph, Undo, Enter, ShiftEnter ],
+		plugins: [ Bold, Typing, Paragraph, Undo, Enter, ShiftEnter, Link, AutoLink ],
 		toolbar: [ 'link', 'undo', 'redo' ]
 	} )
 	.then( editor => {