From 6d675811bc25b972364ff0e194e942eb81ba3d5d Mon Sep 17 00:00:00 2001 From: Kevin Dew Date: Sat, 10 Dec 2016 11:01:54 +0000 Subject: [PATCH] Only add schemes to URLs with hostnames Fixes: https://github.com/yabwe/medium-editor/issues/1250 This changes the way a scheme is prefixed to a URL when linkValidation is on. Previously any URL that did not match the scheme regex would be prefixed with 'http://' which meant that an absolute path such as '/' would be changed to 'http:///' This changes it so that the 'http://' is only prefixed if the first part of the path looks like a hostname, which allows absolute paths to be unchanged and most relative paths to be unchanged. I say most as a path such as "test.txt" would be matched as a hostname and converted to 'http://test.txt' but this seems an acceptable trade off and definitely better than trying to store a list of valid TLDs so filenames and hostnames can be determined. --- spec/anchor.spec.js | 57 +++++++++++++++++++++++++++++++++++++ src/js/extensions/anchor.js | 29 ++++++++++++------- 2 files changed, 76 insertions(+), 10 deletions(-) diff --git a/spec/anchor.spec.js b/spec/anchor.spec.js index fb62a9dcf..3a895c584 100644 --- a/spec/anchor.spec.js +++ b/spec/anchor.spec.js @@ -356,6 +356,63 @@ describe('Anchor Button TestCase', function () { expect(link.getAttribute('href')).toBe(validHashLink); }); + it('should not add a scheme to an absolute path', function () { + var editor = this.newMediumEditor('.editor', { + anchor: { + linkValidation: true + } + }), + absolutePath = '/test', + link, + anchorExtension = editor.getExtensionByName('anchor'); + + selectElementContentsAndFire(editor.elements[0]); + anchorExtension.showForm(absolutePath); + fireEvent(anchorExtension.getForm().querySelector('a.medium-editor-toolbar-save'), 'click'); + + link = editor.elements[0].querySelector('a'); + expect(link).not.toBeNull(); + expect(link.getAttribute('href')).toBe(absolutePath); + }); + + it('should not add a scheme to an obviously relative path', function () { + var editor = this.newMediumEditor('.editor', { + anchor: { + linkValidation: true + } + }), + relativePath = 'test/file.html', + link, + anchorExtension = editor.getExtensionByName('anchor'); + + selectElementContentsAndFire(editor.elements[0]); + anchorExtension.showForm(relativePath); + fireEvent(anchorExtension.getForm().querySelector('a.medium-editor-toolbar-save'), 'click'); + + link = editor.elements[0].querySelector('a'); + expect(link).not.toBeNull(); + expect(link.getAttribute('href')).toBe(relativePath); + }); + + it('should add a scheme to a localhost url', function () { + var editor = this.newMediumEditor('.editor', { + anchor: { + linkValidation: true + } + }), + localhostUrl = 'http://localhost', + link, + anchorExtension = editor.getExtensionByName('anchor'); + + selectElementContentsAndFire(editor.elements[0]); + anchorExtension.showForm('localhost'); + fireEvent(anchorExtension.getForm().querySelector('a.medium-editor-toolbar-save'), 'click'); + + link = editor.elements[0].querySelector('a'); + expect(link).not.toBeNull(); + expect(link.getAttribute('href')).toBe(localhostUrl); + }); + it('should change spaces to %20 for a valid url if linkValidation option is set to true', function () { var editor = this.newMediumEditor('.editor', { anchor: { diff --git a/src/js/extensions/anchor.js b/src/js/extensions/anchor.js index cfe25bea0..dd4388b3f 100644 --- a/src/js/extensions/anchor.js +++ b/src/js/extensions/anchor.js @@ -257,6 +257,8 @@ // Matches common external protocols "mailto:" "tel:" "maps:" // Matches relative hash link, begins with "#" var urlSchemeRegex = /^([a-z]+:)?\/\/|^(mailto|tel|maps):|^\#/i, + hasScheme = urlSchemeRegex.test(value), + scheme = '', // telRegex is a regex for checking if the string is a telephone number telRegex = /^\+?\s?\(?(?:\d\s?\-?\)?){3,20}$/, urlParts = value.match(/^(.*?)(?:\?(.*?))?(?:#(.*))?$/), @@ -266,17 +268,24 @@ if (telRegex.test(value)) { return 'tel:' + value; - } else { - // Check for URL scheme and default to http:// if none found - return (urlSchemeRegex.test(value) ? '' : 'http://') + - // Ensure path is encoded - this.ensureEncodedUri(path) + - // Ensure query is encoded - (query === undefined ? '' : '?' + this.ensureEncodedQuery(query)) + - // Include fragment unencoded as encodeUriComponent is too - // heavy handed for the many characters allowed in a fragment - (fragment === undefined ? '' : '#' + fragment); } + + if (!hasScheme) { + var host = path.split('/')[0]; + // if the host part of the path looks like a hostname + if (host.match(/.+(\.|:).+/) || host === 'localhost') { + scheme = 'http://'; + } + } + + return scheme + + // Ensure path is encoded + this.ensureEncodedUri(path) + + // Ensure query is encoded + (query === undefined ? '' : '?' + this.ensureEncodedQuery(query)) + + // Include fragment unencoded as encodeUriComponent is too + // heavy handed for the many characters allowed in a fragment + (fragment === undefined ? '' : '#' + fragment); }, doFormCancel: function () {