From 87f38fe97ef7881be982b3d47c727cd280f1b057 Mon Sep 17 00:00:00 2001 From: Nikolay Kostyurin Date: Sun, 5 Jul 2020 15:23:22 +0200 Subject: [PATCH] fix(html): escape bad html (#67) * feat(preset-html5): add feature to filter javascript: urls * fix(plugin-helper): escape html in attrs * fix(plugin-helper): tests for html escape * refactor(preset-html5): remove html escape from preset * feat(preset): add ability to pass and extend preset options --- packages/bbob-plugin-helper/src/index.js | 14 +++++++--- .../bbob-plugin-helper/test/index.test.js | 17 +++++++++++- packages/bbob-preset-html5/src/defaultTags.js | 10 ++++--- packages/bbob-preset/src/index.js | 11 +++++--- packages/bbob-preset/test/index.test.js | 26 +++++++++++++++---- 5 files changed, 63 insertions(+), 15 deletions(-) diff --git a/packages/bbob-plugin-helper/src/index.js b/packages/bbob-plugin-helper/src/index.js index a69d02a..eb5d473 100644 --- a/packages/bbob-plugin-helper/src/index.js +++ b/packages/bbob-plugin-helper/src/index.js @@ -27,7 +27,14 @@ const appendToNode = (node, value) => { * Replaces " to &qquot; * @param {String} value */ -const escapeQuote = (value) => value.replace(/"/g, '"'); +const escapeHTML = (value) => value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + // eslint-disable-next-line no-script-url + .replace('javascript:', 'javascript%3A'); /** * Acept name and value and return valid html5 attribute string @@ -41,8 +48,8 @@ const attrValue = (name, value) => { const types = { boolean: () => (value ? `${name}` : ''), number: () => `${name}="${value}"`, - string: () => `${name}="${escapeQuote(value)}"`, - object: () => `${name}="${escapeQuote(JSON.stringify(value))}"`, + string: () => `${name}="${escapeHTML(value)}"`, + object: () => `${name}="${escapeHTML(JSON.stringify(value))}"`, }; return types[type] ? types[type]() : ''; @@ -78,6 +85,7 @@ export { attrsToString, attrValue, appendToNode, + escapeHTML, getNodeLength, getUniqAttr, isTagNode, diff --git a/packages/bbob-plugin-helper/test/index.test.js b/packages/bbob-plugin-helper/test/index.test.js index 3fc4f30..ac9cc12 100644 --- a/packages/bbob-plugin-helper/test/index.test.js +++ b/packages/bbob-plugin-helper/test/index.test.js @@ -80,11 +80,26 @@ describe('@bbob/plugin-helper', () => { disabled: true })).toBe(` tag="test" foo="bar" disabled`) }); - + test('attrsToString undefined', () => { expect(attrsToString(undefined)).toBe('') }); + describe('attrsToString escape', () => { + test(`javascript:alert("hello")`, () => { + expect(attrsToString({ + onclick: `javascript:alert('hello')`, + href: `javascript:alert('hello')`, + })).toBe(` onclick="javascript%3Aalert('hello')" href="javascript%3Aalert('hello')"`) + }); + test(``, () => { + expect(attrsToString({ + onclick: ``, + href: ``, + })).toBe(` onclick="<tag>" href="<tag>"`) + }); + }); + test('getUniqAttr with unq attr', () => { expect(getUniqAttr({foo: true, 'http://bar.com': 'http://bar.com'})).toBe('http://bar.com') }); diff --git a/packages/bbob-preset-html5/src/defaultTags.js b/packages/bbob-preset-html5/src/defaultTags.js index 40175a9..2ebfd7a 100644 --- a/packages/bbob-preset-html5/src/defaultTags.js +++ b/packages/bbob-preset-html5/src/defaultTags.js @@ -1,5 +1,5 @@ /* eslint-disable no-plusplus,no-lonely-if */ -import { isStringNode, isTagNode, getUniqAttr } from '@bbob/plugin-helper'; +import { getUniqAttr, isStringNode, isTagNode } from '@bbob/plugin-helper'; import TagNode from '@bbob/plugin-helper/lib/TagNode'; const isStartsWith = (node, type) => (node[0] === type); @@ -55,6 +55,10 @@ const asListItems = (content) => { return [].concat(listItems); }; +const renderUrl = (node, render) => (getUniqAttr(node.attrs) + ? getUniqAttr(node.attrs) + : render(node.content)); + export default { b: (node) => ({ tag: 'span', @@ -84,10 +88,10 @@ export default { }, content: node.content, }), - url: (node, { render }) => ({ + url: (node, { render }, options) => ({ tag: 'a', attrs: { - href: getUniqAttr(node.attrs) ? getUniqAttr(node.attrs) : render(node.content), + href: renderUrl(node, render, options), }, content: node.content, }), diff --git a/packages/bbob-preset/src/index.js b/packages/bbob-preset/src/index.js index 4def82b..b10aceb 100644 --- a/packages/bbob-preset/src/index.js +++ b/packages/bbob-preset/src/index.js @@ -1,9 +1,9 @@ /* eslint-disable indent */ import { isTagNode } from '@bbob/plugin-helper'; -function process(tags, tree, core) { +function process(tags, tree, core, options) { tree.walk((node) => (isTagNode(node) && tags[node.tag] - ? tags[node.tag](node, core) + ? tags[node.tag](node, core, options) : node)); } @@ -14,7 +14,12 @@ function process(tags, tree, core) { function createPreset(defTags) { const instance = (opts = {}) => { instance.options = Object.assign(instance.options || {}, opts); - return (tree, core) => process(defTags, tree, core); + + const creator = (tree, core) => process(defTags, tree, core, instance.options); + + creator.options = instance.options; + + return creator; }; instance.extend = (callback) => createPreset(callback(defTags, instance.options)); diff --git a/packages/bbob-preset/test/index.test.js b/packages/bbob-preset/test/index.test.js index a2aa927..646564b 100644 --- a/packages/bbob-preset/test/index.test.js +++ b/packages/bbob-preset/test/index.test.js @@ -1,17 +1,33 @@ -import {createPreset} from "../src/index"; +import { createPreset } from '../src/index'; describe('@bbob/preset', () => { test('create', () => { const preset = createPreset({ test: true }); - expect(preset.extend).toBeDefined(); - expect(preset).toBeInstanceOf(Function); + expect(preset.extend) + .toBeDefined(); + expect(preset) + .toBeInstanceOf(Function); }); test('extend', () => { const preset = createPreset({ foo: true }); const newPreset = preset.extend(props => ({ bar: true })); - expect(preset).toBeInstanceOf(Function); - expect(newPreset).toBeInstanceOf(Function); + expect(preset) + .toBeInstanceOf(Function); + expect(newPreset) + .toBeInstanceOf(Function); + }); + test('pass options', () => { + const preset = createPreset({ test: true }); + const newPreset = preset.extend((props, options) => ({ bar: true })); + + const instance = preset({ foo: 'bar' }); + const instance2 = newPreset({ some: true }); + + expect(instance.options) + .toEqual({ foo: 'bar' }); + expect(instance2.options) + .toEqual({ some: true }); }); });