mirror of
https://github.com/tenrok/BBob.git
synced 2026-06-17 19:21:20 +03:00
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
This commit is contained in:
committed by
GitHub
parent
ba090bf997
commit
87f38fe97e
@@ -27,7 +27,14 @@ const appendToNode = (node, value) => {
|
|||||||
* Replaces " to &qquot;
|
* Replaces " to &qquot;
|
||||||
* @param {String} value
|
* @param {String} value
|
||||||
*/
|
*/
|
||||||
const escapeQuote = (value) => value.replace(/"/g, '"');
|
const escapeHTML = (value) => value
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.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
|
* Acept name and value and return valid html5 attribute string
|
||||||
@@ -41,8 +48,8 @@ const attrValue = (name, value) => {
|
|||||||
const types = {
|
const types = {
|
||||||
boolean: () => (value ? `${name}` : ''),
|
boolean: () => (value ? `${name}` : ''),
|
||||||
number: () => `${name}="${value}"`,
|
number: () => `${name}="${value}"`,
|
||||||
string: () => `${name}="${escapeQuote(value)}"`,
|
string: () => `${name}="${escapeHTML(value)}"`,
|
||||||
object: () => `${name}="${escapeQuote(JSON.stringify(value))}"`,
|
object: () => `${name}="${escapeHTML(JSON.stringify(value))}"`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return types[type] ? types[type]() : '';
|
return types[type] ? types[type]() : '';
|
||||||
@@ -78,6 +85,7 @@ export {
|
|||||||
attrsToString,
|
attrsToString,
|
||||||
attrValue,
|
attrValue,
|
||||||
appendToNode,
|
appendToNode,
|
||||||
|
escapeHTML,
|
||||||
getNodeLength,
|
getNodeLength,
|
||||||
getUniqAttr,
|
getUniqAttr,
|
||||||
isTagNode,
|
isTagNode,
|
||||||
|
|||||||
@@ -85,6 +85,21 @@ describe('@bbob/plugin-helper', () => {
|
|||||||
expect(attrsToString(undefined)).toBe('')
|
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(`<tag>`, () => {
|
||||||
|
expect(attrsToString({
|
||||||
|
onclick: `<tag>`,
|
||||||
|
href: `<tag>`,
|
||||||
|
})).toBe(` onclick="<tag>" href="<tag>"`)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('getUniqAttr with unq attr', () => {
|
test('getUniqAttr with unq attr', () => {
|
||||||
expect(getUniqAttr({foo: true, 'http://bar.com': 'http://bar.com'})).toBe('http://bar.com')
|
expect(getUniqAttr({foo: true, 'http://bar.com': 'http://bar.com'})).toBe('http://bar.com')
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable no-plusplus,no-lonely-if */
|
/* 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';
|
import TagNode from '@bbob/plugin-helper/lib/TagNode';
|
||||||
|
|
||||||
const isStartsWith = (node, type) => (node[0] === type);
|
const isStartsWith = (node, type) => (node[0] === type);
|
||||||
@@ -55,6 +55,10 @@ const asListItems = (content) => {
|
|||||||
return [].concat(listItems);
|
return [].concat(listItems);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderUrl = (node, render) => (getUniqAttr(node.attrs)
|
||||||
|
? getUniqAttr(node.attrs)
|
||||||
|
: render(node.content));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
b: (node) => ({
|
b: (node) => ({
|
||||||
tag: 'span',
|
tag: 'span',
|
||||||
@@ -84,10 +88,10 @@ export default {
|
|||||||
},
|
},
|
||||||
content: node.content,
|
content: node.content,
|
||||||
}),
|
}),
|
||||||
url: (node, { render }) => ({
|
url: (node, { render }, options) => ({
|
||||||
tag: 'a',
|
tag: 'a',
|
||||||
attrs: {
|
attrs: {
|
||||||
href: getUniqAttr(node.attrs) ? getUniqAttr(node.attrs) : render(node.content),
|
href: renderUrl(node, render, options),
|
||||||
},
|
},
|
||||||
content: node.content,
|
content: node.content,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable indent */
|
/* eslint-disable indent */
|
||||||
import { isTagNode } from '@bbob/plugin-helper';
|
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]
|
tree.walk((node) => (isTagNode(node) && tags[node.tag]
|
||||||
? tags[node.tag](node, core)
|
? tags[node.tag](node, core, options)
|
||||||
: node));
|
: node));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,7 +14,12 @@ function process(tags, tree, core) {
|
|||||||
function createPreset(defTags) {
|
function createPreset(defTags) {
|
||||||
const instance = (opts = {}) => {
|
const instance = (opts = {}) => {
|
||||||
instance.options = Object.assign(instance.options || {}, 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));
|
instance.extend = (callback) => createPreset(callback(defTags, instance.options));
|
||||||
|
|||||||
@@ -1,17 +1,33 @@
|
|||||||
import {createPreset} from "../src/index";
|
import { createPreset } from '../src/index';
|
||||||
|
|
||||||
describe('@bbob/preset', () => {
|
describe('@bbob/preset', () => {
|
||||||
test('create', () => {
|
test('create', () => {
|
||||||
const preset = createPreset({ test: true });
|
const preset = createPreset({ test: true });
|
||||||
|
|
||||||
expect(preset.extend).toBeDefined();
|
expect(preset.extend)
|
||||||
expect(preset).toBeInstanceOf(Function);
|
.toBeDefined();
|
||||||
|
expect(preset)
|
||||||
|
.toBeInstanceOf(Function);
|
||||||
});
|
});
|
||||||
test('extend', () => {
|
test('extend', () => {
|
||||||
const preset = createPreset({ foo: true });
|
const preset = createPreset({ foo: true });
|
||||||
const newPreset = preset.extend(props => ({ bar: true }));
|
const newPreset = preset.extend(props => ({ bar: true }));
|
||||||
|
|
||||||
expect(preset).toBeInstanceOf(Function);
|
expect(preset)
|
||||||
expect(newPreset).toBeInstanceOf(Function);
|
.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 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user