mirror of
https://github.com/codex-team/editor.js
synced 2024-06-18 13:45:20 +02:00
api sanitizer improvements (#457)
* api sanitizer improvements * update * sanitize recursively * clear from logs and update comments * optimize * update * perfect recursive method * update request * upd * update docs * update comments * update * update docs * update last comment * update * update docs * update docs * update * upd docs * add extra condition * update * update docs link
This commit is contained in:
parent
972c47e73a
commit
ff80ca6e92
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -6,8 +6,6 @@ Uses lightweight npm package with simple API [html-janitor](https://www.npmjs.co
|
|||
Sanitizer class implements basic Module class that holds User configuration
|
||||
and default CodeX Editor instances
|
||||
|
||||
You can read more about Module class [here]()
|
||||
|
||||
## Properties
|
||||
|
||||
Default Editor Sanitizer configuration according to the html-janitor API
|
||||
|
|
115
docs/tools.md
115
docs/tools.md
|
@ -205,3 +205,118 @@ static get onPaste() {
|
|||
```
|
||||
|
||||
### Sanitize
|
||||
|
||||
CodeX Editor provides [API](sanitizer.md) to clean taint strings.
|
||||
Use it manually at the `save()` method or or pass `sanitizer` config to do it automatically.
|
||||
|
||||
|
||||
#### Sanitizer Configuration
|
||||
|
||||
The example of sanitizer configuration
|
||||
|
||||
```javascript
|
||||
let sanitizerConfig = {
|
||||
b: true, // leave <b>
|
||||
p: true, // leave <p>
|
||||
}
|
||||
```
|
||||
|
||||
Keys of config object is tags and the values is a rules.
|
||||
|
||||
##### Rule
|
||||
|
||||
Rule can be boolean, object or function. Object is a dictionary of rules for tag's attributes.
|
||||
|
||||
You can set `true`, to allow tag with all attributes or `false|{}` to remove all attributes,
|
||||
but leave tag.
|
||||
|
||||
Also you can pass special attributes that you want to leave.
|
||||
|
||||
```javascript
|
||||
a: {
|
||||
href: true
|
||||
}
|
||||
```
|
||||
|
||||
If you want to use a custom handler, use should specify a function
|
||||
that returns a rule.
|
||||
|
||||
```javascript
|
||||
b: function(el) {
|
||||
return !el.textContent.includes('bad text');
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```javascript
|
||||
a: function(el) {
|
||||
let anchorHref = el.getAttribute('href');
|
||||
if (anchorHref && anchorHref.substring(0, 4) === 'http') {
|
||||
return {
|
||||
href: true,
|
||||
target: '_blank'
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
href: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Manual sanitize
|
||||
|
||||
Call API method `sanitizer.clean()` at the save method for each field in returned data.
|
||||
|
||||
```javascript
|
||||
save() {
|
||||
return {
|
||||
text: this.api.sanitizer.clean(taintString, sanitizerConfig)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Automatic sanitize
|
||||
|
||||
If you pass the sanitizer config, CodeX Editor will automatically sanitize your saved data.
|
||||
|
||||
You can define rules for each field
|
||||
|
||||
```javascript
|
||||
get sanitize() {
|
||||
return {
|
||||
text: {},
|
||||
items: {
|
||||
b: true, // leave <b>
|
||||
a: false, // remove <a>
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Don't forget to set the rule for each embedded subitems otherwise they will
|
||||
not be sanitized.
|
||||
|
||||
if you want to sanitize everything and get data without any tags, use `{}` or just
|
||||
ignore field in case if you want to get pure HTML
|
||||
|
||||
```javascript
|
||||
get sanitize() {
|
||||
return {
|
||||
text: {},
|
||||
items: {}, // this rules will be used for all properties of this object
|
||||
// or
|
||||
items: {
|
||||
// other objects here won't be sanitized
|
||||
subitems: {
|
||||
// leave <a> and <b> in subitems
|
||||
a: true,
|
||||
b: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "codex.editor",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.1",
|
||||
"description": "Codex Editor. Native JS, based on API and Open Source",
|
||||
"main": "build/codex-editor.js",
|
||||
"scripts": {
|
||||
|
|
|
@ -17,6 +17,7 @@ type Tool = any;
|
|||
import MoveUpTune from './block-tunes/block-tune-move-up';
|
||||
import DeleteTune from './block-tunes/block-tune-delete';
|
||||
import MoveDownTune from './block-tunes/block-tune-move-down';
|
||||
import {IAPI} from './interfaces/api';
|
||||
|
||||
/**
|
||||
* @classdesc Abstract Block class that contains Block information, Tool name and Tool class instance
|
||||
|
@ -228,7 +229,7 @@ export default class Block {
|
|||
public settings: object;
|
||||
public holder: HTMLDivElement;
|
||||
public tunes: IBlockTune[];
|
||||
private readonly api: object;
|
||||
private readonly api: IAPI;
|
||||
private inputIndex = 0;
|
||||
|
||||
/**
|
||||
|
@ -239,7 +240,7 @@ export default class Block {
|
|||
* @param {Object} settings - default settings
|
||||
* @param {Object} apiMethods - Editor API
|
||||
*/
|
||||
constructor(toolName: string, toolInstance: Tool, toolClass: object, settings: object, apiMethods: object) {
|
||||
constructor(toolName: string, toolInstance: Tool, toolClass: object, settings: object, apiMethods: IAPI) {
|
||||
this.name = toolName;
|
||||
this.tool = toolInstance;
|
||||
this.class = toolClass;
|
||||
|
@ -285,8 +286,16 @@ export default class Block {
|
|||
* Groups Tool's save processing time
|
||||
* @return {Object}
|
||||
*/
|
||||
public save(): Promise<void|{tool: string, data: any, time: number}> {
|
||||
const extractedBlock = this.tool.save(this.pluginsContent);
|
||||
public async save(): Promise<void|{tool: string, data: any, time: number}> {
|
||||
let extractedBlock = await this.tool.save(this.pluginsContent);
|
||||
|
||||
/**
|
||||
* if Tool provides custom sanitizer config
|
||||
* then use this config
|
||||
*/
|
||||
if (this.tool.sanitize && typeof this.tool.sanitize === 'object') {
|
||||
extractedBlock = this.sanitizeBlock(extractedBlock, this.tool.sanitize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measuring execution time
|
||||
|
@ -364,6 +373,73 @@ export default class Block {
|
|||
return tunesElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method recursively reduces Block's data and cleans with passed rules
|
||||
*
|
||||
* @param {Object|string} blockData - taint string or object/array that contains taint string
|
||||
* @param {Object} rules - object with sanitizer rules
|
||||
*/
|
||||
private sanitizeBlock(blockData, rules) {
|
||||
|
||||
/**
|
||||
* Case 1: Block data is Array
|
||||
* Array's in JS can not be enumerated with for..in because result will be Object not Array
|
||||
* which conflicts with Consistency
|
||||
*/
|
||||
if (Array.isArray(blockData)) {
|
||||
/**
|
||||
* Create new "cleanData" array and fill in with sanitizer data
|
||||
*/
|
||||
return blockData.map((item) => {
|
||||
return this.sanitizeBlock(item, rules);
|
||||
});
|
||||
} else if (typeof blockData === 'object') {
|
||||
|
||||
/**
|
||||
* Create new "cleanData" object and fill with sanitized objects
|
||||
*/
|
||||
const cleanData = {};
|
||||
|
||||
/**
|
||||
* Object's may have 3 cases:
|
||||
* 1. When Data is Array. Then call again itself and recursively clean arrays items
|
||||
* 2. When Data is Object that can have object's inside. Do the same, call itself and clean recursively
|
||||
* 3. When Data is base type (string, int, bool, ...). Check if rule is passed
|
||||
*/
|
||||
for (const data in blockData) {
|
||||
if (Array.isArray(blockData[data]) || typeof blockData[data] === 'object') {
|
||||
/**
|
||||
* Case 1 & Case 2
|
||||
*/
|
||||
if (rules[data]) {
|
||||
cleanData[data] = this.sanitizeBlock(blockData[data], rules[data]);
|
||||
} else if (_.isEmpty(rules)) {
|
||||
cleanData[data] = this.sanitizeBlock(blockData[data], rules);
|
||||
} else {
|
||||
cleanData[data] = blockData[data];
|
||||
}
|
||||
|
||||
} else {
|
||||
/**
|
||||
* Case 3.
|
||||
*/
|
||||
if (rules[data]) {
|
||||
cleanData[data] = this.api.sanitizer.clean(blockData[data], rules[data]);
|
||||
} else {
|
||||
cleanData[data] = this.api.sanitizer.clean(blockData[data], rules);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cleanData;
|
||||
} else {
|
||||
/**
|
||||
* In case embedded objects use parent rules
|
||||
*/
|
||||
return this.api.sanitizer.clean(blockData, rules);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle drop target state
|
||||
* @param {boolean} state
|
||||
|
|
|
@ -27,6 +27,11 @@ export default interface IBlockTool extends ITool {
|
|||
*/
|
||||
toolboxIcon?: string;
|
||||
|
||||
/**
|
||||
* Sanitizer rules description
|
||||
*/
|
||||
sanitizer?: object;
|
||||
|
||||
/**
|
||||
* Return Tool's main block-wrapper
|
||||
* @return {HTMLElement}
|
||||
|
|
|
@ -45,12 +45,10 @@ export default class Sanitizer extends Module {
|
|||
constructor({config}) {
|
||||
super({config});
|
||||
|
||||
// default config
|
||||
this.defaultConfig = null;
|
||||
this._sanitizerInstance = null;
|
||||
|
||||
/** Custom configuration */
|
||||
this.sanitizerConfig = config.settings ? config.settings.sanitizer : {};
|
||||
this.sanitizerConfig = config.settings ? config.settings.sanitizer : null;
|
||||
|
||||
/** HTML Janitor library */
|
||||
this.sanitizerInstance = require('html-janitor');
|
||||
|
@ -66,30 +64,37 @@ export default class Sanitizer extends Module {
|
|||
* @param {HTMLJanitor} library - sanitizer extension
|
||||
*/
|
||||
set sanitizerInstance(library) {
|
||||
this._sanitizerInstance = new library(this.defaultConfig);
|
||||
if (this.sanitizerConfig) {
|
||||
this._sanitizerInstance = new library(this.sanitizerConfig);
|
||||
}
|
||||
|
||||
return this._sanitizerInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets sanitizer configuration. Uses default config if user didn't pass the restriction
|
||||
* @param {SanitizerConfig} config
|
||||
*/
|
||||
set sanitizerConfig(config) {
|
||||
if (_.isEmpty(config)) {
|
||||
this.defaultConfig = {
|
||||
tags: {
|
||||
p: {},
|
||||
a: {
|
||||
href: true,
|
||||
target: '_blank',
|
||||
rel: 'nofollow'
|
||||
},
|
||||
b: {},
|
||||
i: {}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
this.defaultConfig = config;
|
||||
}
|
||||
get defaultConfig() {
|
||||
return {
|
||||
tags: {
|
||||
p: {},
|
||||
a: {
|
||||
href: true,
|
||||
target: '_blank',
|
||||
rel: 'nofollow'
|
||||
},
|
||||
b: {},
|
||||
i: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return sanitizer instance
|
||||
* @return {null|library}
|
||||
*/
|
||||
get sanitizerInstance() {
|
||||
return this._sanitizerInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -98,11 +103,25 @@ export default class Sanitizer extends Module {
|
|||
* @param {Object} customConfig - custom sanitizer configuration. Method uses default if param is empty
|
||||
* @return {String} clean HTML
|
||||
*/
|
||||
clean(taintString, customConfig = {}) {
|
||||
if (_.isEmpty(customConfig)) {
|
||||
return this._sanitizerInstance.clean(taintString);
|
||||
clean(taintString, customConfig) {
|
||||
if (customConfig && typeof customConfig === 'object') {
|
||||
/**
|
||||
* API client can use custom config to manage sanitize process
|
||||
*/
|
||||
let newConfig = {
|
||||
tags: customConfig
|
||||
};
|
||||
|
||||
return Sanitizer.clean(taintString, newConfig);
|
||||
} else {
|
||||
return Sanitizer.clean(taintString, customConfig);
|
||||
/**
|
||||
* Ignore sanitizing when nothing passed in config
|
||||
*/
|
||||
if (!this.sanitizerInstance) {
|
||||
return taintString;
|
||||
} else {
|
||||
return this.sanitizerInstance.clean(taintString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue