From 43006eaebd0b606ac17b5d23a522247b79723edc Mon Sep 17 00:00:00 2001 From: Nikhil Nambiar Date: Sat, 7 Oct 2017 16:37:09 +0530 Subject: [PATCH] Added regular expression filter for whitelist --- README.md | 2 +- lib/plugin.js | 67 +++++++----- test/plugin.js | 272 ++++++++++++++++++++++++++----------------------- 3 files changed, 184 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index 44abb3c..ca1f61e 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ var posthtml = require('posthtml'); var uglify = require('posthtml-uglify'); posthtml() - .use(uglify({ whitelist: '.bar' })) + .use(uglify({ whitelist: ['.bar', '#foo', /\.col-xs-.*/, /#main.+/] })) .process('
baz
') .then(function(result) { console.log(result.html); //=> '
baz
' diff --git a/lib/plugin.js b/lib/plugin.js index 8884114..9e1a806 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -18,16 +18,16 @@ function HTMLUglify(config) { this.whitelist = config.whitelist || []; } -HTMLUglify.prototype.checkForStandardPointer = function(type, value, lookups) { +HTMLUglify.prototype.checkForStandardPointer = function (type, value, lookups) { return lookups[type] && lookups[type][value]; }; -HTMLUglify.prototype.checkForAttributePointer = function(type, value, lookups) { +HTMLUglify.prototype.checkForAttributePointer = function (type, value, lookups) { var typeLookups = lookups[type] || {}; var keys = Object.keys(typeLookups); var pointer; - keys.some(function(key) { + keys.some(function (key) { if (value.indexOf(key) !== -1) { pointer = value.replace(key, typeLookups[key]); return true; @@ -38,7 +38,7 @@ HTMLUglify.prototype.checkForAttributePointer = function(type, value, lookups) { return pointer; }; -HTMLUglify.prototype.generatePointer = function(lookups) { +HTMLUglify.prototype.generatePointer = function (lookups) { var idCount = Object.keys(lookups['id'] || {}).length; var classCount = Object.keys(lookups['class'] || {}).length; var counter = idCount + classCount; @@ -46,20 +46,20 @@ HTMLUglify.prototype.generatePointer = function(lookups) { return this.hashids.encode(counter); }; -HTMLUglify.prototype.pointer = function(type, value, lookups) { +HTMLUglify.prototype.pointer = function (type, value, lookups) { return this.checkForStandardPointer(type, value, lookups) || this.checkForAttributePointer(type, value, lookups) || this.generatePointer(lookups); }; -HTMLUglify.prototype.insertLookup = function(type, value, pointer, lookups) { +HTMLUglify.prototype.insertLookup = function (type, value, pointer, lookups) { if (!lookups[type]) { lookups[type] = {}; } lookups[type][value] = pointer; }; -HTMLUglify.prototype.createLookup = function(type, value, lookups) { +HTMLUglify.prototype.createLookup = function (type, value, lookups) { var pointer; if (value && !this.isWhitelisted(type, value)) { pointer = this.pointer(type, value, lookups); @@ -68,7 +68,7 @@ HTMLUglify.prototype.createLookup = function(type, value, lookups) { return pointer; }; -HTMLUglify.prototype.isWhitelisted = function(type, value) { +HTMLUglify.prototype.isWhitelisted = function (type, value) { switch (type) { case 'class': value = '.' + value; @@ -80,15 +80,26 @@ HTMLUglify.prototype.isWhitelisted = function(type, value) { break; } - return this.whitelist.indexOf(value) >= 0; + var whitelisted = false; + + this.whitelist.every(el => { + if (el && el.toString()[0] != "/") { + whitelisted = (el == value); + } else if (el && el.toString()[0] == "/") { + whitelisted = el.test(value); + } + return !whitelisted; + }); + + return whitelisted; }; -HTMLUglify.prototype.pointerizeClass = function(node, lookups) { +HTMLUglify.prototype.pointerizeClass = function (node, lookups) { var self = this; var classes = node.attrs.class; if (classes) { - node.attrs.class = classes.split(/\s+/).map(function(value) { + node.attrs.class = classes.split(/\s+/).map(function (value) { var pointer = self.createLookup('class', value, lookups); if (pointer) { return pointer; @@ -99,31 +110,31 @@ HTMLUglify.prototype.pointerizeClass = function(node, lookups) { } }; -HTMLUglify.prototype.pointerizeIdAndFor = function(type, node, lookups) { +HTMLUglify.prototype.pointerizeIdAndFor = function (type, node, lookups) { var pointer = this.createLookup('id', node.attrs[type], lookups); if (pointer) { node.attrs[type] = pointer; } }; -HTMLUglify.prototype.processRules = function(rules, lookups) { +HTMLUglify.prototype.processRules = function (rules, lookups) { var self = this; - rules.forEach(function(rule) { + rules.forEach(function (rule) { // go deeper inside media rule to find css rules if (rule.type === 'atrule' && (rule.name === 'media' || rule.name === 'supports')) { self.processRules(rule.nodes, lookups); } else if (rule.type === 'rule') { - postcssSelectorParser(function(selectors) { - selectors.walk(function(selector) { + postcssSelectorParser(function (selectors) { + selectors.walk(function (selector) { var pointer; if ((selector.type === 'class') - || (selector.type === 'attribute' && selector.attribute === 'class')) { + || (selector.type === 'attribute' && selector.attribute === 'class')) { pointer = self.createLookup('class', selector.value, lookups); } else if ((selector.type === 'id') - || (selector.type === 'attribute' && selector.attribute === 'id') - || (selector.type === 'attribute' && selector.attribute === 'for')) { + || (selector.type === 'attribute' && selector.attribute === 'id') + || (selector.type === 'attribute' && selector.attribute === 'for')) { pointer = self.createLookup('id', selector.value, lookups); } @@ -138,12 +149,12 @@ HTMLUglify.prototype.processRules = function(rules, lookups) { }); }; -HTMLUglify.prototype.rewriteElements = function(tree, lookups) { +HTMLUglify.prototype.rewriteElements = function (tree, lookups) { var self = this; lookups = lookups || {}; - return tree.walk(function(node) { + return tree.walk(function (node) { if (node.attrs) { if (node.attrs.class) { self.pointerizeClass(node, lookups); @@ -161,12 +172,12 @@ HTMLUglify.prototype.rewriteElements = function(tree, lookups) { }); }; -HTMLUglify.prototype.rewriteStyles = function(tree, lookups) { +HTMLUglify.prototype.rewriteStyles = function (tree, lookups) { var self = this; lookups = lookups || {}; - return tree.walk(function(node) { + return tree.walk(function (node) { if (node.tag === 'style' && node.content) { var ast = postcssSafeParser([].concat(node.content).join('')); self.processRules(ast.nodes, lookups); @@ -176,7 +187,7 @@ HTMLUglify.prototype.rewriteStyles = function(tree, lookups) { }); }; -HTMLUglify.prototype.process = function(tree) { +HTMLUglify.prototype.process = function (tree) { var lookups = {}; tree = this.rewriteStyles(tree, lookups); @@ -185,14 +196,14 @@ HTMLUglify.prototype.process = function(tree) { return tree; }; -module.exports = function(options) { - return function posthtmlUglify(tree) { +module.exports = function (options) { + return function posthtmlUglify(tree) { return new HTMLUglify(options).process(tree); } }; module.exports.HTMLUglify = HTMLUglify; -module.exports.process = function(html, options) { - return posthtml().use(module.exports(options)).process(html, { sync: true }); +module.exports.process = function (html, options) { + return posthtml().use(module.exports(options)).process(html, { sync: true }); }; diff --git a/test/plugin.js b/test/plugin.js index f794290..2a0d689 100644 --- a/test/plugin.js +++ b/test/plugin.js @@ -16,42 +16,58 @@ function parse(html) { return tree; } -describe('HTMLUglify', function() { - describe('#isWhitelisted', function() { +describe('HTMLUglify', function () { + describe('#isWhitelisted', function () { var whitelist; - beforeEach(function() { - whitelist = ['#theid', '.theclass', '#★', '.★']; - htmlUglify = new HTMLUglify({whitelist: whitelist}); + beforeEach(function () { + whitelist = ['#theid', '.theclass', '#★', '.★', /\.col-xs-.*/, /#main.+/]; + htmlUglify = new HTMLUglify({ whitelist: whitelist }); }); - it('returns true if id is in whitelist', function() { + it('returns true if id is in whitelist', function () { var whitelisted = htmlUglify.isWhitelisted('id', 'theid'); assert.isTrue(whitelisted); }); - it('returns false if id is in the whitelist but only checking for classes', function() { + it('returns false if id is in the whitelist but only checking for classes', function () { var whitelisted = htmlUglify.isWhitelisted('class', 'theid'); assert.isFalse(whitelisted); }); - it('returns true if class is in whitelist', function() { + it('returns true if class is in whitelist', function () { var whitelisted = htmlUglify.isWhitelisted('class', 'theclass'); assert.isTrue(whitelisted); }); - it('returns true if id is in whitelist for a unicode character', function() { + it('returns true if id is in whitelist for a unicode character', function () { var whitelisted = htmlUglify.isWhitelisted('id', '★'); assert.isTrue(whitelisted); }); - it('returns true if class is in whitelist for a unicode character', function() { + it('returns true if class is in whitelist for a unicode character', function () { var whitelisted = htmlUglify.isWhitelisted('class', '★'); assert.isTrue(whitelisted); }); - it('returns false if an unsupported type is passed', function() { + it('returns true if id is valid for a regular expression in whitelist', function () { + var whitelisted = htmlUglify.isWhitelisted('id', 'main-div'); + assert.isTrue(whitelisted); + }); + it('returns false if id is invalid for a regular expression in whitelist', function () { + var whitelisted = htmlUglify.isWhitelisted('id', 'main'); + assert.isFalse(whitelisted); + }); + it('returns true if class is valid for a regular expression in whitelist', function () { + var whitelisted = htmlUglify.isWhitelisted('class', 'col-xs-12'); + assert.isTrue(whitelisted); + }); + it('returns true if class is invalid for a regular expression in whitelist', function () { + var whitelisted = htmlUglify.isWhitelisted('class', 'col-md-12'); + assert.isFalse(whitelisted); + }); + it('returns false if an unsupported type is passed', function () { var whitelisted = htmlUglify.isWhitelisted('foo', 'bar'); assert.isFalse(whitelisted); }); }); - describe('#checkForStandardPointer', function() { - it('returns undefined when name not found', function() { + describe('#checkForStandardPointer', function () { + it('returns undefined when name not found', function () { var lookups = { 'class': { 'something': 'zzz' } }; @@ -60,7 +76,7 @@ describe('HTMLUglify', function() { assert.isUndefined(pointer); }); - it('returns pointer when found', function() { + it('returns pointer when found', function () { var lookups = { 'class': { 'something': 'zzz' } }; @@ -71,8 +87,8 @@ describe('HTMLUglify', function() { }); }); - describe('#checkForAttributePointer', function() { - it('returns undefined when not found', function() { + describe('#checkForAttributePointer', function () { + it('returns undefined when not found', function () { var lookups = { 'class': { 'something': 'zzz' } }; @@ -81,7 +97,7 @@ describe('HTMLUglify', function() { assert.isUndefined(pointer); }); - it('returns the pointer when value contains same string as an existing lookup', function() { + it('returns the pointer when value contains same string as an existing lookup', function () { var lookups = { 'class': { 'something': 'zzz' } }; @@ -92,72 +108,72 @@ describe('HTMLUglify', function() { }); }); - describe('#insertLookup', function() { + describe('#insertLookup', function () { var lookups; - beforeEach(function() { + beforeEach(function () { lookups = {}; }); - it('updates lookups', function() { + it('updates lookups', function () { htmlUglify.insertLookup('class', 'testClass', 'xz', lookups); assert.equal(lookups['class'].testClass, 'xz'); }); }); - describe('#generatePointer', function() { - it('returns xz for counter 0 lookups', function() { + describe('#generatePointer', function () { + it('returns xz for counter 0 lookups', function () { var lookups = {}; var pointer = htmlUglify.generatePointer(lookups); assert.equal(pointer, 'xz'); }); - it('returns wk for 1 lookups', function() { + it('returns wk for 1 lookups', function () { var lookups = { 'id': { 'a': 'xz' } }; var pointer = htmlUglify.generatePointer(lookups); assert.equal(pointer, 'wk'); }); - it('returns en for 2 lookups', function() { + it('returns en for 2 lookups', function () { var lookups = { 'id': { 'a': 'xz' }, 'class': { 'b': 'wk' } }; var pointer = htmlUglify.generatePointer(lookups); assert.equal(pointer, 'en'); }); }); - describe('#pointer', function() { - it('generates a new pointer', function() { + describe('#pointer', function () { + it('generates a new pointer', function () { var lookups = {}; var pointer = htmlUglify.pointer('class', 'newClass', {}); assert.equal(pointer, 'xz', lookups); }); - it('generates a new pointer given a different one exists', function() { + it('generates a new pointer given a different one exists', function () { var lookups = { 'class': { 'otherClass': 'wk' } }; var pointer = htmlUglify.pointer('class', 'newClass', lookups); assert.equal(pointer, 'wk', lookups); }); - it('generates a new pointer given a different one exists in a different attribute', function() { + it('generates a new pointer given a different one exists in a different attribute', function () { var lookups = { 'id': { 'someId': 'wk' } }; var pointer = htmlUglify.pointer('class', 'newClass', lookups); assert.equal(pointer, 'wk', lookups); }); - it('finds an existing class pointer', function() { + it('finds an existing class pointer', function () { var lookups = { 'class': { 'someClass': 'xz' } }; var pointer = htmlUglify.pointer('class', 'someClass', lookups); assert.equal(pointer, 'xz', lookups); }); - it('finds an existing id pointer', function() { + it('finds an existing id pointer', function () { var lookups = { 'id': { 'someId': 'en' } }; var pointer = htmlUglify.pointer('id', 'someId', lookups); assert.equal(pointer, 'en'); }); - it('finds a more complex existing pointer', function() { + it('finds a more complex existing pointer', function () { var lookups = { class: { test: 'xz', @@ -170,275 +186,275 @@ describe('HTMLUglify', function() { }); }); - describe('#pointerizeIdAndFor', function() { + describe('#pointerizeIdAndFor', function () { var node; - beforeEach(function() { + beforeEach(function () { var html = '

'; node = parse(html)[0]; }); - it('works with empty lookups', function() { + it('works with empty lookups', function () { var lookups = {}; htmlUglify.pointerizeIdAndFor('id', node, lookups); assert.deepEqual(lookups, { id: { one: 'xz' } }); }); - it('works with existing lookup', function() { + it('works with existing lookup', function () { var lookups = { class: { one: 'ab' } }; htmlUglify.pointerizeClass(node, lookups); assert.deepEqual(lookups, { class: { one: 'ab' } }); }); - it('works with whitelist', function() { + it('works with whitelist', function () { var lookups = {}; - htmlUglify.whitelist = [ '#one' ]; + htmlUglify.whitelist = ['#one']; htmlUglify.pointerizeClass(node, lookups); assert.deepEqual(lookups, {}); }); }); - describe('#pointerizeClass', function() { - var node; + describe('#pointerizeClass', function () { + var node; - beforeEach(function() { - var html = '

'; - node = parse(html)[0]; - }); + beforeEach(function () { + var html = '

'; + node = parse(html)[0]; + }); - it('works with empty lookups', function() { - var lookups = {}; - htmlUglify.pointerizeClass(node, lookups); - assert.deepEqual(lookups, { class: { one: 'xz', two: 'wk' } }); - }); - it('works with single lookup', function() { - var lookups = { class: { one: 'ab' } }; - htmlUglify.pointerizeClass(node, lookups); - assert.deepEqual(lookups, { class: { one: 'ab', two: 'wk' } }); - }); - it('works with whitelist', function() { - var lookups = {}; - htmlUglify.whitelist = ['.two']; - htmlUglify.pointerizeClass(node, lookups); - assert.deepEqual(lookups, { class: { one: 'xz' } }); - }); + it('works with empty lookups', function () { + var lookups = {}; + htmlUglify.pointerizeClass(node, lookups); + assert.deepEqual(lookups, { class: { one: 'xz', two: 'wk' } }); + }); + it('works with single lookup', function () { + var lookups = { class: { one: 'ab' } }; + htmlUglify.pointerizeClass(node, lookups); + assert.deepEqual(lookups, { class: { one: 'ab', two: 'wk' } }); + }); + it('works with whitelist', function () { + var lookups = {}; + htmlUglify.whitelist = ['.two']; + htmlUglify.pointerizeClass(node, lookups); + assert.deepEqual(lookups, { class: { one: 'xz' } }); }); + }); - describe('#rewriteStyles', function() { - it('rewrites an id given lookups', function() { + describe('#rewriteStyles', function () { + it('rewrites an id given lookups', function () { var lookups = { 'id': { 'foo': 'bar' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('rewrites an id', function() { - var lookups = { }; + it('rewrites an id', function () { + var lookups = {}; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('rewrites an id with the same name as the element', function() { - var lookups = {'id': {'label': 'ab' }}; + it('rewrites an id with the same name as the element', function () { + var lookups = { 'id': { 'label': 'ab' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('rewrites a for= given lookups', function() { - var lookups = { 'id': {'email': 'ab'} }; + it('rewrites a for= given lookups', function () { + var lookups = { 'id': { 'email': 'ab' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ""); }); - it('rewrites a for=', function() { + it('rewrites a for=', function () { var lookups = {}; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ""); }); - it('rewrites a for= with quotes given lookups', function() { - var lookups = { 'id': {'email': 'ab'} }; + it('rewrites a for= with quotes given lookups', function () { + var lookups = { 'id': { 'email': 'ab' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('rewrites a for= with the same name as the element', function() { - var lookups = { 'id': { 'label': 'ab' }}; + it('rewrites a for= with the same name as the element', function () { + var lookups = { 'id': { 'label': 'ab' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('rewrites an id= given lookups', function() { - var lookups = { 'id': {'email': 'ab'} }; + it('rewrites an id= given lookups', function () { + var lookups = { 'id': { 'email': 'ab' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('rewrites an id= with quotes given lookups', function() { + it('rewrites an id= with quotes given lookups', function () { var lookups = { 'id': { 'email': 'ab' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('rewrites an id= with quotes and with the same name as the element', function() { - var lookups = { 'id': {'label': 'ab'} }; + it('rewrites an id= with quotes and with the same name as the element', function () { + var lookups = { 'id': { 'label': 'ab' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('rewrites a class given lookups', function() { - var lookups = { 'class': { 'email': 'ab' }}; + it('rewrites a class given lookups', function () { + var lookups = { 'class': { 'email': 'ab' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('rewrites a class with the same name as the element', function() { - var lookups = { 'class': { 'label': 'ab' }}; + it('rewrites a class with the same name as the element', function () { + var lookups = { 'class': { 'label': 'ab' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('rewrites a class= given lookups', function() { - var lookups = { 'class': { 'email': 'ab' }}; + it('rewrites a class= given lookups', function () { + var lookups = { 'class': { 'email': 'ab' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ""); }); - it('rewrites multi-selector rule', function() { - var lookups = { 'class': { 'email': 'ab' }}; + it('rewrites multi-selector rule', function () { + var lookups = { 'class': { 'email': 'ab' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('rewrites css media queries', function() { - var lookups = { 'id': { 'abe': 'wz' }}; + it('rewrites css media queries', function () { + var lookups = { 'id': { 'abe': 'wz' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('rewrites nested css media queries', function() { - var lookups = { 'id': { 'abe': 'wz' }}; + it('rewrites nested css media queries', function () { + var lookups = { 'id': { 'abe': 'wz' } }; var html = ''; var result = htmlUglify.rewriteStyles(parse(html), lookups); assert.equal(render(result), ''); }); - it('handles malformed syntax', function() { + it('handles malformed syntax', function () { var html = ''; var result = htmlUglify.rewriteStyles(parse(html)); assert.equal(render(result), ''); }); }); - describe('#rewriteElements', function() { - it('rewrites an id', function() { + describe('#rewriteElements', function () { + it('rewrites an id', function () { var html = '

Header

'; var result = htmlUglify.rewriteElements(parse(html)); assert.equal(render(result), '

Header

'); }); - it('rewrites a class', function() { + it('rewrites a class', function () { var html = '

Header

'; var result = htmlUglify.rewriteElements(parse(html)); assert.equal(render(result), '

Header

'); }); - it('rewrites a multiple classes', function() { + it('rewrites a multiple classes', function () { var html = '

Header

'; var result = htmlUglify.rewriteElements(parse(html)); assert.equal(render(result), '

Header

'); }); - it('rewrites a multiple classes with more than one space between them', function() { + it('rewrites a multiple classes with more than one space between them', function () { var html = '

Header

'; var result = htmlUglify.rewriteElements(parse(html)); assert.equal(render(result), '

Header

'); }); - it('rewrites a for', function() { + it('rewrites a for', function () { var html = '