From 6b073bf7e11ceaeb9d6bff70cd5aa047e7be13d3 Mon Sep 17 00:00:00 2001 From: terenceneedssurgery Date: Sat, 13 May 2017 12:39:32 +0100 Subject: [PATCH 1/7] Feature: Allow for additional attributes --- urlize.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/urlize.js b/urlize.js index 24ba320..eba733e 100644 --- a/urlize.js +++ b/urlize.js @@ -212,7 +212,8 @@ nofollow: args[1], autoescape: args[2], trim_url_limit: args[3], - target: args[4] + target: args[4], + attrs: args[5] }; } if (!('django_compatible' in options)) options.django_compatible = true; @@ -273,6 +274,12 @@ var url = undefined; var nofollow_attr = options.nofollow ? ' rel="nofollow"' : ''; var target_attr = options.target ? ' target="' + options.target + '"' : ''; + + var other_attr = ''; + + for(attr in options.attrs) { + other_attr = options.attrs[attr] ? ' ' + attr + '="' + options.attrs[attr] + '"' : ''; + } if (middle.match(simple_url_re)) url = smart_urlquote(middle); else if (middle.match(simple_url_2_re)) url = smart_urlquote('http://' + middle); @@ -292,7 +299,7 @@ url = urlescape(url); trimmed = htmlescape(trimmed, options); } - middle = '' + trimmed + ''; + middle = '' + trimmed + ''; words[i] = lead + middle + trail; } else { if (safe_input) { @@ -315,4 +322,4 @@ urlize.test.convert_arguments = convert_arguments; return urlize; -})); +})); \ No newline at end of file From d0fe7744ccf4f7923f505da0869d482d0c049aca Mon Sep 17 00:00:00 2001 From: Terence O'Donoghue Date: Fri, 18 Aug 2017 19:20:13 +0100 Subject: [PATCH 2/7] Update urlize.js --- urlize.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/urlize.js b/urlize.js index eba733e..7f38ba8 100644 --- a/urlize.js +++ b/urlize.js @@ -278,7 +278,7 @@ var other_attr = ''; for(attr in options.attrs) { - other_attr = options.attrs[attr] ? ' ' + attr + '="' + options.attrs[attr] + '"' : ''; + other_attr = options.attrs[attr] ? ' ' + attr + '="' + encodeURIComponent( options.attrs[attr] ) + '"' : ''; } if (middle.match(simple_url_re)) url = smart_urlquote(middle); @@ -322,4 +322,4 @@ urlize.test.convert_arguments = convert_arguments; return urlize; -})); \ No newline at end of file +})); From aaecea84760c0a605c9dc68da2faef40b177bf50 Mon Sep 17 00:00:00 2001 From: Terence O'Donoghue Date: Fri, 18 Aug 2017 19:25:40 +0100 Subject: [PATCH 3/7] Update test_urlize.js --- test_urlize.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test_urlize.js b/test_urlize.js index 8c272ee..8629bea 100644 --- a/test_urlize.js +++ b/test_urlize.js @@ -368,6 +368,25 @@ test('no trim', function () { }); +QUnit.module('Additional HTML Attributes'); + +test('add single additional attributes', function () { + equal(urlize('http://www.example.com/', {attrs: {position: 'left'}}), + 'www.example.com/'); +}); +test('add several additional attributes', function () { + equal(urlize('http://www.example.com/', {attrs: {position: 'left', 'open-in-app': true}}), + 'www.example.com/'); +}); +test('add additional attributes with illegal values', function () { + equal(urlize('http://www.example.com/', {attrs: {position: 'left', 'open-in-app': '",true'}}), + 'www.example.com/'); +}); +test('add additional attributes with illegal keys', function () { + equal(urlize('http://www.example.com/', {attrs: {position: 'left', '"open,-in-app': '",true'}}), + 'www.example.com/'); +}); + //module dance boilerplate }; if (typeof define === 'function' && define.amd) { From 47d3b9b6c18acb7101c7a31429a47aa2150b73c8 Mon Sep 17 00:00:00 2001 From: Terence O'Donoghue Date: Fri, 18 Aug 2017 19:26:25 +0100 Subject: [PATCH 4/7] Update urlize.js --- urlize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urlize.js b/urlize.js index 7f38ba8..31c0052 100644 --- a/urlize.js +++ b/urlize.js @@ -278,7 +278,7 @@ var other_attr = ''; for(attr in options.attrs) { - other_attr = options.attrs[attr] ? ' ' + attr + '="' + encodeURIComponent( options.attrs[attr] ) + '"' : ''; + other_attr = options.attrs[attr] ? ' ' + encodeURIComponent(attr) + '="' + encodeURIComponent( options.attrs[attr] ) + '"' : ''; } if (middle.match(simple_url_re)) url = smart_urlquote(middle); From cf19a990794826bde88033015f23860d55fb65d6 Mon Sep 17 00:00:00 2001 From: Terence O'Donoghue Date: Fri, 18 Aug 2017 20:18:53 +0100 Subject: [PATCH 5/7] Escape value charatcers and remove illegal key characters --- test_urlize.js | 9 +++++---- urlize.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/test_urlize.js b/test_urlize.js index 8629bea..21fba9a 100644 --- a/test_urlize.js +++ b/test_urlize.js @@ -210,6 +210,7 @@ QUnit.module('convert_arguments'); test('single argument', function () { deepEqual(urlize.test.convert_arguments(['foo']), { + attrs: undefined, autoescape: undefined, nofollow: undefined, target: undefined, @@ -372,19 +373,19 @@ QUnit.module('Additional HTML Attributes'); test('add single additional attributes', function () { equal(urlize('http://www.example.com/', {attrs: {position: 'left'}}), - 'www.example.com/'); + 'http://www.example.com/'); }); test('add several additional attributes', function () { equal(urlize('http://www.example.com/', {attrs: {position: 'left', 'open-in-app': true}}), - 'www.example.com/'); + 'http://www.example.com/'); }); test('add additional attributes with illegal values', function () { equal(urlize('http://www.example.com/', {attrs: {position: 'left', 'open-in-app': '",true'}}), - 'www.example.com/'); + 'http://www.example.com/'); }); test('add additional attributes with illegal keys', function () { equal(urlize('http://www.example.com/', {attrs: {position: 'left', '"open,-in-app': '",true'}}), - 'www.example.com/'); + 'http://www.example.com/'); }); //module dance boilerplate diff --git a/urlize.js b/urlize.js index 31c0052..2a9fb70 100644 --- a/urlize.js +++ b/urlize.js @@ -278,7 +278,7 @@ var other_attr = ''; for(attr in options.attrs) { - other_attr = options.attrs[attr] ? ' ' + encodeURIComponent(attr) + '="' + encodeURIComponent( options.attrs[attr] ) + '"' : ''; + other_attr += options.attrs[attr] ? ' ' + attr.replace(/[^a-zA-Z0-9_.-]/gi, '') + '="' + encodeURIComponent( options.attrs[attr] ) + '"' : ''; } if (middle.match(simple_url_re)) url = smart_urlquote(middle); From e69ff8df6e77ea419e18d208012b2f723eed3a17 Mon Sep 17 00:00:00 2001 From: Terence O'Donoghue Date: Fri, 18 Aug 2017 20:41:14 +0100 Subject: [PATCH 6/7] Additional attributes tests --- test_urlize.js | 135 +++++++------------------------------------------ 1 file changed, 18 insertions(+), 117 deletions(-) diff --git a/test_urlize.js b/test_urlize.js index e472eb2..4f41f4e 100644 --- a/test_urlize.js +++ b/test_urlize.js @@ -203,97 +203,13 @@ describe('Basic functionality', function () { it('Mixed-case www', function () { assert.equal(urlize('My Link Www.example.no'), 'My Link Www.example.no'); -<<<<<<< HEAD -}); - -QUnit.module('convert_arguments'); - -test('single argument', function () { - deepEqual(urlize.test.convert_arguments(['foo']), { - attrs: undefined, - autoescape: undefined, - nofollow: undefined, - target: undefined, - trim_url_limit: undefined, - django_compatible: true - }); -}); - -test('two arguments, second is object', function () { - var d = {nofollow: true}; - equal(urlize.test.convert_arguments(['foo', d]), d); -}); - -test('two arguments, second is boolean', function () { - ok(!urlize.test.convert_arguments(['foo', false]).nofollow); - equal(urlize.test.convert_arguments(['foo', true]).nofollow, true); -}); - -test('three arguments, third is boolean', function () { - equal(urlize.test.convert_arguments(['foo', false, false]).autoescape, false); - equal(urlize.test.convert_arguments(['foo', false, true]).autoescape, true); -}); - -test('four arguments, fourth is integer', function () { - equal(urlize.test.convert_arguments(['foo', false, false, undefined]).trim_url_limit, undefined); - equal(urlize.test.convert_arguments(['foo', false, false, 0]).trim_url_limit, 0); - equal(urlize.test.convert_arguments(['foo', false, false, 10]).trim_url_limit, 10); -}); - -test('five arguments, fifth is string', function () { - equal(urlize.test.convert_arguments(['foo', false, false, undefined, undefined]).target, undefined); - equal(urlize.test.convert_arguments(['foo', false, false, undefined, '_blank']).target, '_blank'); -}); - -test('django_compatible', function () { - ok(urlize.test.convert_arguments(['foo']).django_compatible); - ok(!urlize.test.convert_arguments(['foo', {django_compatible: false}]).django_compatible); -}); - - - -QUnit.module('Improvements over Django'); - -test('adjacent angle brackets', function () { - equal(urlize('http://example.com'), 'http://example.com'); - equal(urlize('http://example.com', {django_compatible: false}), - 'http://example.com'); - equal(urlize('www.example.com'), 'www.example.com'); - equal(urlize('www.example.com', {django_compatible: false}), - 'www.example.com'); -}); - -test('enclosing fancy double quotes', function () { - equal(urlize('The link “http://example.com” is broken'), - 'The link “http://example.com” is broken'); - equal(urlize('The link “http://example.com” is broken', {django_compatible: false}), - 'The link “http://example.com” is broken'); - equal(urlize('The link “www.example.com” is broken'), - 'The link “www.example.com” is broken'); - equal(urlize('The link “www.example.com” is broken', {django_compatible: false}), - 'The link “www.example.com” is broken'); -}); - -test('enclosing fancy single quotes', function () { - equal(urlize('The link ‘http://example.com’ is broken'), - 'The link ‘http://example.com’ is broken'); - equal(urlize('The link ‘http://example.com’ is broken', {django_compatible: false}), - 'The link ‘http://example.com’ is broken'); - equal(urlize('The link ‘www.example.com’ is broken'), - 'The link ‘www.example.com’ is broken'); - equal(urlize('The link ‘www.example.com’ is broken', {django_compatible: false}), - 'The link ‘www.example.com’ is broken'); -}); - -test('enclosing double quotes', function () { - equal(urlize('The link "http://example.com" is broken'), -======= }); }); describe('convert_arguments', function () { it('single argument', function () { deepEqual(urlize.test.convert_arguments(['foo']), { + attrs: undefined, autoescape: undefined, nofollow: undefined, target: undefined, @@ -369,7 +285,6 @@ describe('Improvements over Django', function () { it('enclosing double quotes', function () { equal(urlize('The link "http://example.com" is broken'), ->>>>>>> ljosa/master 'The link "http://example.com" is broken'); equal(urlize('The link "http://example.com" is broken', {django_compatible: false}), 'The link "http://example.com" is broken'); @@ -450,36 +365,22 @@ describe('Trimming', function () { 'http://www.example.com/'); }); }); -<<<<<<< HEAD - - -QUnit.module('Additional HTML Attributes'); -test('add single additional attributes', function () { - equal(urlize('http://www.example.com/', {attrs: {position: 'left'}}), - 'http://www.example.com/'); -}); -test('add several additional attributes', function () { - equal(urlize('http://www.example.com/', {attrs: {position: 'left', 'open-in-app': true}}), - 'http://www.example.com/'); +describe('Additional HTML Attributes', function () { + it('add single additional attributes', function () { + equal(urlize('http://www.example.com/', {attrs: {position: 'left'}}), + 'http://www.example.com/'); + }); + it('add several additional attributes', function () { + equal(urlize('http://www.example.com/', {attrs: {position: 'left', 'open-in-app': true}}), + 'http://www.example.com/'); + }); + it('add additional attributes with illegal values', function () { + equal(urlize('http://www.example.com/', {attrs: {position: 'left', 'open-in-app': '",true'}}), + 'http://www.example.com/'); + }); + it('add additional attributes with illegal keys', function () { + equal(urlize('http://www.example.com/', {attrs: {position: 'left', '"open,-in-app': '",true'}}), + 'http://www.example.com/'); + }); }); -test('add additional attributes with illegal values', function () { - equal(urlize('http://www.example.com/', {attrs: {position: 'left', 'open-in-app': '",true'}}), - 'http://www.example.com/'); -}); -test('add additional attributes with illegal keys', function () { - equal(urlize('http://www.example.com/', {attrs: {position: 'left', '"open,-in-app': '",true'}}), - 'http://www.example.com/'); -}); - -//module dance boilerplate -}; -if (typeof define === 'function' && define.amd) { - define(['./urlize_tlds'], tests); -} else if (typeof exports === 'object') { - module.exports = tests(require('./urlize_tlds')); -} else { - this.urlize = tests(this.urlize); -} -======= ->>>>>>> ljosa/master From 3efdb965e14e1679ec00519a2c7e42f2dc6f8551 Mon Sep 17 00:00:00 2001 From: Terence O'Donoghue Date: Sat, 19 Aug 2017 21:04:02 +0100 Subject: [PATCH 7/7] HTML Escape Update the HTMLEntities Escape through extending String and Regex prototypes Updated tests to expect HTMLEntities Neatened up indenting via JSFiddle --- test_urlize.js | 8 +++--- urlize.js | 73 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/test_urlize.js b/test_urlize.js index 4f41f4e..cd87720 100644 --- a/test_urlize.js +++ b/test_urlize.js @@ -376,11 +376,11 @@ describe('Additional HTML Attributes', function () { 'http://www.example.com/'); }); it('add additional attributes with illegal values', function () { - equal(urlize('http://www.example.com/', {attrs: {position: 'left', 'open-in-app': '",true'}}), - 'http://www.example.com/'); + equal(urlize('http://www.example.com/', {attrs: {position: 'left', 'open-in-app': '"/true'}}), + 'http://www.example.com/'); }); it('add additional attributes with illegal keys', function () { - equal(urlize('http://www.example.com/', {attrs: {position: 'left', '"open,-in-app': '",true'}}), - 'http://www.example.com/'); + equal(urlize('http://www.example.com/', {attrs: {position: 'left', '"open,-in-app': '"/true'}}), + 'http://www.example.com/'); }); }); diff --git a/urlize.js b/urlize.js index 2a9fb70..a7ad5fe 100644 --- a/urlize.js +++ b/urlize.js @@ -1,4 +1,4 @@ -(function (root, factory) { +(function(root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define('urlize', [], factory); @@ -9,7 +9,7 @@ // Browser globals (root is window) root.urlize = factory(root.b); } -}(this, function () { +}(this, function() { // From http://blog.stevenlevithan.com/archives/cross-browser-split // modified to not add itself to String.prototype. @@ -47,13 +47,13 @@ var split; // Avoid running twice; that would break the `nativeSplit` reference - split = split || function (undef) { + split = split || function(undef) { var nativeSplit = String.prototype.split, compliantExecNpcg = /()??/.exec("")[1] === undef, // NPCG: nonparticipating capturing group self; - self = function (str, separator, limit) { + self = function(str, separator, limit) { // If `separator` is not a regex, use `nativeSplit` if (Object.prototype.toString.call(separator) !== "[object RegExp]") { return nativeSplit.call(str, separator, limit); @@ -92,7 +92,7 @@ // Fix browsers whose `exec` methods don't consistently return `undefined` for // nonparticipating capturing groups if (!compliantExecNpcg && match.length > 1) { - match[0].replace(separator2, function () { + match[0].replace(separator2, function() { for (var i = 1; i < arguments.length - 2; i++) { if (arguments[i] === undef) { match[i] = undef; @@ -125,7 +125,36 @@ return self; }(); + + RegExp.escape = function(text) { + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + } + String.prototype.mapReplace = function (replacements) { + var regex = []; + + for (var prop in replacements) { + regex.push(RegExp.escape(prop)); + } + + regex = new RegExp( regex.join('|'), "g" ); + + return this.replace(regex, function(match){ + return replacements[match]; + }); + } + + String.prototype.escapeHtml = function() { + return this.mapReplace({ + '<': '<', + '>': '>', + ' ': ' ', + '"': '"', + "'": '‘', + '/': '/', + '=': '=' + }); + }; function startswith(string, prefix) { return string.substr(0, prefix.length) == prefix; @@ -172,9 +201,18 @@ var trailing_punctuation_django = ['.', ',', ':', ';']; var trailing_punctuation_improved = ['.', ',', ':', ';', '.)']; - var wrapping_punctuation_django = [['(', ')'], ['<', '>'], ['<', '>']]; - var wrapping_punctuation_improved = [['(', ')'], ['<', '>'], ['<', '>'], - ['“', '”'], ['‘', '’']]; + var wrapping_punctuation_django = [ + ['(', ')'], + ['<', '>'], + ['<', '>'] + ]; + var wrapping_punctuation_improved = [ + ['(', ')'], + ['<', '>'], + ['<', '>'], + ['“', '”'], + ['‘', '’'] + ]; var word_split_re_django = /(\s+)/; var word_split_re_improved = /([\s<>"]+)/; var simple_url_re = /^https?:\/\/\w/i; @@ -205,7 +243,7 @@ function convert_arguments(args) { var options; - if (args.length == 2 && typeof (args[1]) == 'object') { + if (args.length == 2 && typeof(args[1]) == 'object') { options = args[1]; } else { options = { @@ -236,10 +274,10 @@ var word_split_re = options.django_compatible ? word_split_re_django : word_split_re_improved; var trailing_punctuation = options.django_compatible ? trailing_punctuation_django : trailing_punctuation_improved; var wrapping_punctuation = options.django_compatible ? wrapping_punctuation_django : wrapping_punctuation_improved; - var simple_url_2_re = new RegExp('^www\\.|^(?!http)\\w[^@]+\\.(' + - (options.top_level_domains || django_top_level_domains).join('|') + - ')$', - "i"); + var simple_url_2_re = new RegExp('^www\\.|^(?!http)\\w[^@]+\\.(' + + (options.top_level_domains || django_top_level_domains).join('|') + + ')$', + "i"); var words = split(text, word_split_re); for (var i = 0; i < words.length; i++) { var word = words[i]; @@ -274,11 +312,11 @@ var url = undefined; var nofollow_attr = options.nofollow ? ' rel="nofollow"' : ''; var target_attr = options.target ? ' target="' + options.target + '"' : ''; - + var other_attr = ''; - - for(attr in options.attrs) { - other_attr += options.attrs[attr] ? ' ' + attr.replace(/[^a-zA-Z0-9_.-]/gi, '') + '="' + encodeURIComponent( options.attrs[attr] ) + '"' : ''; + + for (attr in options.attrs) { + other_attr += options.attrs[attr] ? ' ' + attr.replace(/[^a-zA-Z0-9_.-]/gi, '') + '="' + String(options.attrs[attr]).escapeHtml() + '"' : ''; } if (middle.match(simple_url_re)) url = smart_urlquote(middle); @@ -323,3 +361,4 @@ return urlize; })); +