From 19868a9a74a6ed6891501b307b2491d040c459e4 Mon Sep 17 00:00:00 2001 From: fcmod Date: Fri, 22 Jan 2016 14:20:58 +0100 Subject: [PATCH 1/2] Added validate option (function) to be able to selectively filter tokens See tests for usage --- src/linkify-element.js | 5 ++- src/linkify-html.js | 6 +++- src/linkify-jquery.js | 1 + src/linkify-string.js | 8 +++-- src/linkify/utils/options.js | 5 +++ test/qunit/test/main.js | 2 ++ test/spec/html/linkified-alt.html | 8 ++--- test/spec/html/linkified-validate.html | 3 ++ test/spec/html/linkified.html | 6 ++-- test/spec/html/options.js | 9 ++++++ test/spec/html/original.html | 2 +- test/spec/linkify-element-test.js | 9 +++++- test/spec/linkify-html-test.js | 36 +++++++++++++++++++++- test/spec/linkify-jquery-test.js | 13 ++++++-- test/spec/linkify-string-test.js | 42 ++++++++++++++++++++++++-- 15 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 test/spec/html/linkified-validate.html diff --git a/src/linkify-element.js b/src/linkify-element.js index 7e43ca6f..88d9d837 100644 --- a/src/linkify-element.js +++ b/src/linkify-element.js @@ -32,8 +32,11 @@ function tokensToNodes(tokens, opts, doc) { for (let i = 0; i < tokens.length; i++) { let token = tokens[i]; + let validated = options.resolve(opts.validate, + token.hasProtocol ? token.hasProtocol() : false, + token.toString(), token.type); - if (token.isLink) { + if (token.isLink && validated) { let href = token.toHref(opts.defaultProtocol), diff --git a/src/linkify-html.js b/src/linkify-html.js index 29661423..3476f7df 100644 --- a/src/linkify-html.js +++ b/src/linkify-html.js @@ -77,6 +77,10 @@ function linkifyChars(str, opts) { for (var i = 0; i < tokens.length; i++) { let token = tokens[i]; + let validated = linkify.options.resolve(opts.validate, + token.hasProtocol ? token.hasProtocol() : false, + token.toString(), token.type); + if (token.type === 'nl' && opts.nl2br) { result.push({ type: StartTag, @@ -85,7 +89,7 @@ function linkifyChars(str, opts) { selfClosing: true }); continue; - } else if (!token.isLink) { + } else if (!token.isLink || !validated) { result.push({type: Chars, chars: token.toString()}); continue; } diff --git a/src/linkify-jquery.js b/src/linkify-jquery.js index e9d82083..090df225 100644 --- a/src/linkify-jquery.js +++ b/src/linkify-jquery.js @@ -59,6 +59,7 @@ function apply($, doc=null) { tagName: data.linkifyTagname, target: data.linkifyTarget, linkClass: data.linkifyLinkclass, + validate: data.linkifyValidate, }; let $target = target === 'this' ? $this : $this.find(target); $target.linkify(options); diff --git a/src/linkify-string.js b/src/linkify-string.js index 1bb3c91a..c253bdb0 100644 --- a/src/linkify-string.js +++ b/src/linkify-string.js @@ -37,11 +37,16 @@ function linkifyStr(str, opts={}) { for (let i = 0; i < tokens.length; i++) { let token = tokens[i]; - if (token.isLink) { + let validated = options.resolve(opts.validate, + token.hasProtocol ? token.hasProtocol() : false, + token.toString(), token.type); + if (token.isLink && validated) { + let href = token.toHref(opts.defaultProtocol), formatted = options.resolve(opts.format, token.toString(), token.type), + formattedHref = options.resolve(opts.formatHref, href, token.type), attributesHash = options.resolve(opts.attributes, href, token.type), tagName = options.resolve(opts.tagName, href, token.type), @@ -59,7 +64,6 @@ function linkifyStr(str, opts={}) { link += `>${escapeText(formatted)}`; result.push(link); - } else if (token.type === 'nl' && opts.nl2br) { if (opts.newLine) { result.push(opts.newLine); diff --git a/src/linkify/utils/options.js b/src/linkify/utils/options.js index 0f3bdae4..2740ab68 100644 --- a/src/linkify/utils/options.js +++ b/src/linkify/utils/options.js @@ -2,6 +2,10 @@ function noop(val) { return val; } +function yes(val) { + return true; +} + function typeToTarget(href, type) { return type === 'url' ? '_blank' : null; } @@ -14,6 +18,7 @@ function normalize(opts) { defaultProtocol: opts.defaultProtocol || 'http', events: opts.events || null, format: opts.format || noop, + validate: opts.validate || yes, formatHref: opts.formatHref || noop, newLine: opts.newLine || false, // deprecated nl2br: !!newLine || opts.nl2br || false, diff --git a/test/qunit/test/main.js b/test/qunit/test/main.js index 29f5fdfd..3bd0cab4 100644 --- a/test/qunit/test/main.js +++ b/test/qunit/test/main.js @@ -99,6 +99,7 @@ QUnit.test('returns in the hash of default options when given an empty object', defaultProtocol: 'http', events: null, format: function () {}, + validate: function () {}, formatHref: function () {}, newLine: false, // deprecated nl2br: false, @@ -107,6 +108,7 @@ QUnit.test('returns in the hash of default options when given an empty object', linkClass: 'linkified' }); assert.equal(typeof result.format, 'function'); + assert.equal(typeof result.validate, 'function'); assert.equal(result.format('test'), 'test'); assert.equal(typeof result.formatHref, 'function'); assert.equal(result.formatHref('test'), 'test'); diff --git a/test/spec/html/linkified-alt.html b/test/spec/html/linkified-alt.html index fb0ecd04..a6b137f3 100644 --- a/test/spec/html/linkified-alt.html +++ b/test/spec/html/linkified-alt.html @@ -1,4 +1,4 @@ -Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

-Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

-Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

-Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

+Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

and another link to www.google.com +Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

and another link to www.google.com +Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

and another link to www.google.com +Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

and another link to www.google.com diff --git a/test/spec/html/linkified-validate.html b/test/spec/html/linkified-validate.html new file mode 100644 index 00000000..f2ee5dbb --- /dev/null +++ b/test/spec/html/linkified-validate.html @@ -0,0 +1,3 @@ +Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

and another link to www.google.com +Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

and another link to www.google.com +Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

and another link to www.google.com \ No newline at end of file diff --git a/test/spec/html/linkified.html b/test/spec/html/linkified.html index 5c08d56e..f9dc5477 100644 --- a/test/spec/html/linkified.html +++ b/test/spec/html/linkified.html @@ -1,3 +1,3 @@ -Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

-Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

-Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

+Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

and another link to www.google.com +Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

and another link to www.google.com +Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

and another link to www.google.com diff --git a/test/spec/html/options.js b/test/spec/html/options.js index 3f31d78b..54098a0d 100644 --- a/test/spec/html/options.js +++ b/test/spec/html/options.js @@ -13,6 +13,9 @@ module.exports = { linkifiedAlt: fs.readFileSync(__dirname + '/linkified-alt.html', 'utf8') .trim() .split('\n'), + linkifiedValidate: fs.readFileSync(__dirname + '/linkified-validate.html', 'utf8') + .trim() + .split('\n'), extra: fs.readFileSync(__dirname + '/extra.html', 'utf8').trim(), // for jQuery plugin tests altOptions: { @@ -27,5 +30,11 @@ module.exports = { throw 'Hovered!'; } } + }, + + validateOptions: { + validate: function (hasProtocol, text, type) { + return type === 'email' || (hasProtocol || text.slice(0,3) === 'www'); + } } }; diff --git a/test/spec/html/original.html b/test/spec/html/original.html index 5f0729b7..0d8f7dac 100644 --- a/test/spec/html/original.html +++ b/test/spec/html/original.html @@ -1 +1 @@ -Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

+Hello here are some links to ftp://awesome.com/?where=this and localhost:8080, pretty neat right?

Here's a nested github.com/SoapBox/linkifyjs paragraph

and another link to www.google.com diff --git a/test/spec/linkify-element-test.js b/test/spec/linkify-element-test.js index 2be06583..e2480b50 100644 --- a/test/spec/linkify-element-test.js +++ b/test/spec/linkify-element-test.js @@ -61,7 +61,7 @@ describe('linkify-element', function () { expect(htmlOptions.linkified).to.contain(testContainer.innerHTML); }); - it('Works with overriden options', function () { + it('Works with overriden options (general)', function () { expect(testContainer).to.be.ok; expect(testContainer).to.be.a('object'); var result = linkifyElement(testContainer, htmlOptions.altOptions, doc); @@ -80,4 +80,11 @@ describe('linkify-element', function () { */ }); + it('Works with overriden options (validate)', function () { + expect(testContainer).to.be.ok; + expect(testContainer).to.be.a('object'); + var result = linkifyElement(testContainer, htmlOptions.validateOptions, doc); + expect(result).to.equal(testContainer); // should return the same element + expect(htmlOptions.linkifiedValidate).to.contain(testContainer.innerHTML); + }); }); diff --git a/test/spec/linkify-html-test.js b/test/spec/linkify-html-test.js index 50caf2e5..881fc1a6 100644 --- a/test/spec/linkify-html-test.js +++ b/test/spec/linkify-html-test.js @@ -58,12 +58,46 @@ describe('linkify-html', function () { }); }); - it('Works with overriden options', function () { + it('Works with overriden options (general)', function () { tests.map(function (test) { expect(linkifyHtml(test[0], options)).to.be.eql(test[2]); }); }); + it('Works with overriden options (validate)', function () { + var options_validate = { + validate: function (hasProtocol, text, type) { + return type === 'email' || (hasProtocol || text.slice(0,3) === 'www'); + } + }, + + tests_validate = [ + [ + '1.Test with no links', + '1.Test with no links' + ], [ + '2.The URL is google.com and the email is test@example.com', + '2.The URL is google.com and the email is test@example.com' + ], [ + '3.Super long maps URL https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en, a #hash-tag, and an email: test."wut".yo@gmail.co.uk!', + '3.Super long maps URL https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en, a #hash-tag, and an email: test."wut".yo@gmail.co.uk!' + ], [ + '4a.This link is already in an anchor tag google.com LOL and this one

isnt http://github.com

', + '4a.This link is already in an anchor tag google.com LOL and this one

isnt http://github.com

' + ], [ + '4b.This link is already in an anchor tag google.com LOL and this one

isnt github.com

', + '4b.This link is already in an anchor tag google.com LOL and this one

isnt github.com

' + ], [ + '5.Unterminated anchor tag This is a link google.com and this works!! https://reddit.com/r/photography/', + '5.Unterminated anchor tag This is a link google.com and this works!! https://reddit.com/r/photography/' + ] + ]; + + tests_validate.map(function (test) { + expect(linkifyHtml(test[0], options_validate)).to.be.eql(test[1]); + }); + }); + it('Works with HTML and default options', function () { var linkified = linkifyHtml(htmlOptions.original); expect(htmlOptions.linkified).to.contain(linkified); diff --git a/test/spec/linkify-jquery-test.js b/test/spec/linkify-jquery-test.js index feeebd2e..0b9236d5 100644 --- a/test/spec/linkify-jquery-test.js +++ b/test/spec/linkify-jquery-test.js @@ -103,7 +103,7 @@ describe('linkify-jquery', function () { testContainer.innerHTML = htmlOptions.original; }); - // This works but is inconsisten across browsers + // This works but is inconsistent across browsers xit('Works with the DOM Data API', function () { expect($('header').first().html()).to.be.eql( 'Have a link to:
github.com!' @@ -125,7 +125,7 @@ describe('linkify-jquery', function () { expect(htmlOptions.linkified).to.contain($container.html()); }); - it('Works with overriden options', function () { + it('Works with overriden options (general)', function () { var $container = $('#linkify-jquery-test-container'); expect(($container.length)).to.be.eql(1); var result = $container.linkify(htmlOptions.altOptions); @@ -133,4 +133,13 @@ describe('linkify-jquery', function () { expect((result === $container)).to.be.ok; // should return the same element expect(htmlOptions.linkifiedAlt).to.contain($container.html()); }); + + it('Works with overriden options (validate)', function () { + var $container = $('#linkify-jquery-test-container'); + expect(($container.length)).to.be.eql(1); + var result = $container.linkify(htmlOptions.validateOptions); + // `should` is not defined on jQuery objects + expect((result === $container)).to.be.ok; // should return the same element + expect(htmlOptions.linkifiedValidate).to.contain($container.html()); + }); }); diff --git a/test/spec/linkify-string-test.js b/test/spec/linkify-string-test.js index be0f35a7..cd94cca5 100644 --- a/test/spec/linkify-string-test.js +++ b/test/spec/linkify-string-test.js @@ -53,11 +53,12 @@ describe('linkify-string', function () { [ 'Test with no links', 'Test with no links', - 'Test with no links' + 'Test with no links', ], [ 'The URL is google.com and the email is test@example.com', 'The URL is google.com and the email is test@example.com', - 'The URL is google.com and the email is test@example.com' + 'The URL is google.com and the email is test@example.com', + 'The URL is google.com and the email is test@example.com', ], [ 'Super long maps URL https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en, a #hash-tag, and an email: test."wut".yo@gmail.co.uk!\n', 'Super long maps URL https://www.google.ca/maps/@43.472082,-80.5426668,18z?hl=en, a #hash-tag, and an email: test."wut".yo@gmail.co.uk!\n', @@ -71,9 +72,44 @@ describe('linkify-string', function () { }); }); - it('Works with overriden options', function () { + it('Works with overriden options (general)', function () { tests.map(function (test) { expect(linkifyStr(test[0], options)).to.be.eql(test[2]); }); }); + + // Test specific options + it('Works with overriden options (validate)', function () { + var options_validate = { + validate: function (hasProtocol, text, type) { + return type === 'email' || (hasProtocol || text.slice(0,3) === 'www'); + } + }, + + tests_validate = [ + [ + '1.Test with no links', + '1.Test with no links' + ], [ + '2.The URL is google.com and the email is test@example.com', + '2.The URL is google.com and the email is test@example.com' + ], [ + '3.The URL is www.google.com', + '3.The URL is www.google.com' + ], [ + '4.The URL is http://google.com', + '4.The URL is http://google.com' + ], [ + '5.The URL is ftp://google.com', + '5.The URL is ftp://google.com' + ], [ + '6.Test with no links.It is sloppy avoiding spaces after the dot', + '6.Test with no links.It is sloppy avoiding spaces after the dot' + ] + ]; + + tests_validate.map(function (test) { + expect(linkifyStr(test[0], options_validate)).to.be.eql(test[1]); + }); + }); }); From f1868595439723f4558b0fe9f25331ebb22fb030 Mon Sep 17 00:00:00 2001 From: fcmod Date: Fri, 22 Jan 2016 17:41:04 +0100 Subject: [PATCH 2/2] [validate] Remove hasProtocol parameter, add short-circuit checks --- src/linkify-element.js | 4 +--- src/linkify-html.js | 4 +--- src/linkify-string.js | 4 +--- test/spec/html/options.js | 4 ++-- test/spec/linkify-html-test.js | 12 ++++++------ test/spec/linkify-string-test.js | 12 ++++++------ 6 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/linkify-element.js b/src/linkify-element.js index 88d9d837..456b5a18 100644 --- a/src/linkify-element.js +++ b/src/linkify-element.js @@ -32,9 +32,7 @@ function tokensToNodes(tokens, opts, doc) { for (let i = 0; i < tokens.length; i++) { let token = tokens[i]; - let validated = options.resolve(opts.validate, - token.hasProtocol ? token.hasProtocol() : false, - token.toString(), token.type); + let validated = token.isLink && options.resolve(opts.validate, token.toString(), token.type); if (token.isLink && validated) { diff --git a/src/linkify-html.js b/src/linkify-html.js index 3476f7df..17dcac78 100644 --- a/src/linkify-html.js +++ b/src/linkify-html.js @@ -77,9 +77,7 @@ function linkifyChars(str, opts) { for (var i = 0; i < tokens.length; i++) { let token = tokens[i]; - let validated = linkify.options.resolve(opts.validate, - token.hasProtocol ? token.hasProtocol() : false, - token.toString(), token.type); + let validated = token.isLink && linkify.options.resolve(opts.validate, token.toString(), token.type); if (token.type === 'nl' && opts.nl2br) { result.push({ diff --git a/src/linkify-string.js b/src/linkify-string.js index c253bdb0..77d5d13d 100644 --- a/src/linkify-string.js +++ b/src/linkify-string.js @@ -37,9 +37,7 @@ function linkifyStr(str, opts={}) { for (let i = 0; i < tokens.length; i++) { let token = tokens[i]; - let validated = options.resolve(opts.validate, - token.hasProtocol ? token.hasProtocol() : false, - token.toString(), token.type); + let validated = token.isLink && options.resolve(opts.validate, token.toString(), token.type); if (token.isLink && validated) { diff --git a/test/spec/html/options.js b/test/spec/html/options.js index 54098a0d..0badc50a 100644 --- a/test/spec/html/options.js +++ b/test/spec/html/options.js @@ -33,8 +33,8 @@ module.exports = { }, validateOptions: { - validate: function (hasProtocol, text, type) { - return type === 'email' || (hasProtocol || text.slice(0,3) === 'www'); + validate: function (text, type) { + return type !== 'url' || /^(http|ftp)s?:\/\//.test(text) || text.slice(0,3) === 'www'; } } }; diff --git a/test/spec/linkify-html-test.js b/test/spec/linkify-html-test.js index 881fc1a6..672edb4c 100644 --- a/test/spec/linkify-html-test.js +++ b/test/spec/linkify-html-test.js @@ -65,13 +65,13 @@ describe('linkify-html', function () { }); it('Works with overriden options (validate)', function () { - var options_validate = { - validate: function (hasProtocol, text, type) { - return type === 'email' || (hasProtocol || text.slice(0,3) === 'www'); + var optionsValidate = { + validate: function (text, type) { + return type !== 'url' || /^(http|ftp)s?:\/\//.test(text) || text.slice(0,3) === 'www'; } }, - tests_validate = [ + testsValidate = [ [ '1.Test with no links', '1.Test with no links' @@ -93,8 +93,8 @@ describe('linkify-html', function () { ] ]; - tests_validate.map(function (test) { - expect(linkifyHtml(test[0], options_validate)).to.be.eql(test[1]); + testsValidate.map(function (test) { + expect(linkifyHtml(test[0], optionsValidate)).to.be.eql(test[1]); }); }); diff --git a/test/spec/linkify-string-test.js b/test/spec/linkify-string-test.js index cd94cca5..c54614bd 100644 --- a/test/spec/linkify-string-test.js +++ b/test/spec/linkify-string-test.js @@ -80,13 +80,13 @@ describe('linkify-string', function () { // Test specific options it('Works with overriden options (validate)', function () { - var options_validate = { - validate: function (hasProtocol, text, type) { - return type === 'email' || (hasProtocol || text.slice(0,3) === 'www'); + var optionsValidate = { + validate: function (text, type) { + return type !== 'url' || /^(http|ftp)s?:\/\//.test(text) || text.slice(0,3) === 'www'; } }, - tests_validate = [ + testsValidate = [ [ '1.Test with no links', '1.Test with no links' @@ -108,8 +108,8 @@ describe('linkify-string', function () { ] ]; - tests_validate.map(function (test) { - expect(linkifyStr(test[0], options_validate)).to.be.eql(test[1]); + testsValidate.map(function (test) { + expect(linkifyStr(test[0], optionsValidate)).to.be.eql(test[1]); }); }); });