From d04f37d02aa651b122d15ddf955749f3a182ed69 Mon Sep 17 00:00:00 2001 From: Bob Czarnecki Date: Wed, 5 Feb 2014 12:26:11 -0500 Subject: [PATCH 1/4] Tag specific sanitization support --- .../lib/sanitizecontenthandler.js | 362 ++++++++++-------- 1 file changed, 200 insertions(+), 162 deletions(-) diff --git a/src/plugins/common/contenthandler/lib/sanitizecontenthandler.js b/src/plugins/common/contenthandler/lib/sanitizecontenthandler.js index 2886df4694..bd0655956b 100644 --- a/src/plugins/common/contenthandler/lib/sanitizecontenthandler.js +++ b/src/plugins/common/contenthandler/lib/sanitizecontenthandler.js @@ -1,9 +1,9 @@ /* sanitizecontenthandler.js is part of Aloha Editor project http://aloha-editor.org * - * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. + * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria. - * Contributors http://aloha-editor.org/contribution.php - * + * Contributors http://aloha-editor.org/contribution.php + * * Aloha Editor is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * + * * As an additional permission to the GNU GPL version 2, you may distribute * non-source (e.g., minimized or compacted) forms of the Aloha-Editor * source code without the copy of the GNU GPL normally required, @@ -31,161 +31,199 @@ define([ 'aloha/plugin', 'aloha/console', 'vendor/sanitize' -], -function( Aloha, jQuery, ContentHandlerManager, Plugin, console ) { - "use strict"; - - var sanitize; - - // predefined set of sanitize options if no dynamic or custom config is used - if( !Aloha.defaults.sanitize ) { - Aloha.defaults.sanitize = {} - } - - // very restricted sanitize config - Aloha.defaults.sanitize.restricted = { - elements: [ 'b', 'em', 'i', 'strong', 'u', 'del', 'p', 'span', 'div', 'br' ] - } - - // sanitize config allowing a bit more (no tables) - Aloha.defaults.sanitize.basic = { - elements: [ - 'a', 'abbr', 'b', 'blockquote', 'br', 'cite', 'code', 'dd', 'del', 'dl', 'dt', 'em', - 'i', 'li', 'ol', 'p', 'pre', 'q', 'small', 'strike', 'strong', 'sub', - 'sup', 'u', 'ul' ], - - attributes: { - 'a' : ['href'], - 'blockquote' : ['cite'], - 'q' : ['cite'], - 'abbr': ['title'] - }, - - //add_attributes: { - // 'a': {'rel': 'nofollow'} - //}, - - protocols: { - 'a' : {'href': ['ftp', 'http', 'https', 'mailto', '__relative__']}, - 'blockquote' : {'cite': ['http', 'https', '__relative__']}, - 'q' : {'cite': ['http', 'https', '__relative__']} - } - } - - // relaxed sanitize config allows also tables - Aloha.defaults.sanitize.relaxed = { - elements: [ - 'a', 'abbr', 'b', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', - 'colgroup', 'dd', 'del', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', - 'i', 'img', 'li', 'ol', 'p', 'pre', 'q', 'small', 'strike', 'strong', - 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'u', - 'ul', 'span', 'hr', 'object', 'div' - ], - - attributes: { - 'a': ['href', 'title', 'id', 'class', 'target', 'data-gentics-aloha-repository', 'data-gentics-aloha-object-id'], - 'div': ['id','class','style'], - 'abbr': ['title'], - 'blockquote': ['cite'], - 'br': ['class'], - 'col': ['span', 'width'], - 'colgroup': ['span', 'width'], - 'img': ['align', 'alt', 'height', 'src', 'title', 'width', 'class', 'data-caption', 'data-align', 'data-width', 'data-original-image'], - 'ol': ['start', 'type'], - 'p': ['class', 'style', 'id'], - 'q': ['cite'], - 'table': ['summary', 'width'], - // For IE7 it matters the uppercase 'S' in rowSpan, colSpan - 'td': ['abbr', 'axis', 'colSpan', 'rowSpan', 'colspan', 'rowspan', 'width'], - 'th': ['abbr', 'axis', 'colSpan', 'rowSpan', 'colspan', 'rowspan', 'scope', 'width'], - 'ul': ['type'], - 'span': ['class','style','lang','xml:lang','role'] - }, - - protocols: { - 'a': {'href': ['ftp', 'http', 'https', 'mailto', '__relative__']}, - 'blockquote': {'cite': ['http', 'https', '__relative__']}, - 'img': {'src' : ['http', 'https', '__relative__']}, - 'q': {'cite': ['http', 'https', '__relative__']} - } - } - - function initSanitize (configAllows) { - var - filter = [ 'restricted', 'basic', 'relaxed' ], - config = Aloha.defaults.supports; // @TODO: needs to be implemented into all plugins - - // @TODO think about Aloha.settings.contentHandler.sanitize name/options - if (Aloha.settings.contentHandler.sanitize && - jQuery.inArray(Aloha.settings.contentHandler.sanitize, filter) > -1) { - config = Aloha.defaults.sanitize[Aloha.settings.contentHandler.sanitize]; - } else { - // use relaxed filter by default - config = Aloha.defaults.sanitize.relaxed; - } - - // @TODO move to Aloha.settings.contentHandler.sanitize.allows ? - if (Aloha.settings.contentHandler.allows) { - config = Aloha.settings.contentHandler.allows; - } - - if (configAllows) { - config = configAllows; - } - - // add a filter to stop cleaning elements with contentEditable "false" - config.filters = [function( elem ) { - return elem.contentEditable != "false"; - }]; - sanitize = new Sanitize( config, jQuery ); - } - - var SanitizeContentHandler = ContentHandlerManager.createHandler({ - /** - * Handle the content from eg. paste action and sanitize the html - * @param content - */ - handleContent: function (content, options, editable) { - if (!editable) { - return content; - } - - var sanitizeConfig; - var contentHandlerConfig; - - if (Aloha.settings.contentHandler && - Aloha.settings.contentHandler.handler && - Aloha.settings.contentHandler.handler.sanitize) { - // individual sanitize config per editable -- should support merging of configs from other plugins ... - if (Aloha.settings.contentHandler.handler.sanitize) { - contentHandlerConfig = Aloha.settings.contentHandler.handler.sanitize; - } - var containerId = contentHandlerConfig['#' + editable.getId()]; - if (typeof containerId !== 'undefined') { - sanitizeConfig = contentHandlerConfig; - } else { - var containerClasses = editable.obj.attr('class').split(' '); - for (var i = 0; i < containerClasses.length; i++) { - if (typeof contentHandlerConfig['.' + containerClasses[i]] !== 'undefined') { - sanitizeConfig = contentHandlerConfig['.' + containerClasses[i]]; - } - } - } - } - - if ( typeof sanitize === 'undefined' || typeof sanitizeConfig !== 'undefined') { - initSanitize(sanitizeConfig); - } - - if (typeof content === 'string'){ - content = jQuery('
' + content + '
').get(0); - } else if (content instanceof jQuery) { - content = jQuery('
').append(content).get(0); - } - - return jQuery('
').append(sanitize.clean_node(content)).html(); - } - }); - - return SanitizeContentHandler; -}); +], function(Aloha, jQuery, ContentHandlerManager, Plugin, console) { + "use strict"; + + var sanitize; + + // predefined set of sanitize options if no dynamic or custom config is used + if( !Aloha.defaults.sanitize ) { + Aloha.defaults.sanitize = {} + } + + // very restricted sanitize config + Aloha.defaults.sanitize.restricted = { + elements: [ 'b', 'em', 'i', 'strong', 'u', 'del', 'p', 'span', 'div', 'br' ] + } + + // sanitize config allowing a bit more (no tables) + Aloha.defaults.sanitize.basic = { + elements: [ + 'a', 'abbr', 'b', 'blockquote', 'br', 'cite', 'code', 'dd', 'del', 'dl', 'dt', 'em', + 'i', 'li', 'ol', 'p', 'pre', 'q', 'small', 'strike', 'strong', 'sub', + 'sup', 'u', 'ul' ], + + attributes: { + 'a' : ['href'], + 'blockquote' : ['cite'], + 'q' : ['cite'], + 'abbr': ['title'] + }, + + //add_attributes: { + // 'a': {'rel': 'nofollow'} + //}, + + protocols: { + 'a' : {'href': ['ftp', 'http', 'https', 'mailto', '__relative__']}, + 'blockquote' : {'cite': ['http', 'https', '__relative__']}, + 'q' : {'cite': ['http', 'https', '__relative__']} + } + } + + // relaxed sanitize config allows also tables + Aloha.defaults.sanitize.relaxed = { + elements: [ + 'a', 'abbr', 'b', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', + 'colgroup', 'dd', 'del', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', + 'i', 'img', 'li', 'ol', 'p', 'pre', 'q', 'small', 'strike', 'strong', + 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'u', + 'ul', 'span', 'hr', 'object', 'div' + ], + + attributes: { + 'a': ['href', 'title', 'id', 'class', 'target', 'data-gentics-aloha-repository', 'data-gentics-aloha-object-id'], + 'div': ['id','class','style'], + 'abbr': ['title'], + 'blockquote': ['cite'], + 'br': ['class'], + 'col': ['span', 'width'], + 'colgroup': ['span', 'width'], + 'img': ['align', 'alt', 'height', 'src', 'title', 'width', 'class', 'data-caption', 'data-align', 'data-width', 'data-original-image'], + 'ol': ['start', 'type'], + 'p': ['class', 'style', 'id'], + 'q': ['cite'], + 'table': ['summary', 'width'], + // For IE7 it matters the uppercase 'S' in rowSpan, colSpan + 'td': ['abbr', 'axis', 'colSpan', 'rowSpan', 'colspan', 'rowspan', 'width'], + 'th': ['abbr', 'axis', 'colSpan', 'rowSpan', 'colspan', 'rowspan', 'scope', 'width'], + 'ul': ['type'], + 'span': ['class','style','lang','xml:lang','role'] + }, + + protocols: { + 'a': {'href': ['ftp', 'http', 'https', 'mailto', '__relative__']}, + 'blockquote': {'cite': ['http', 'https', '__relative__']}, + 'img': {'src' : ['http', 'https', '__relative__']}, + 'q': {'cite': ['http', 'https', '__relative__']} + } + } + + var sanitizers = (function() { + var idOrClassRegExp = /^[\.#]/; + var defaultKey = '_default_'; + var map = {}; + // Filter to stop cleaning elements with contentEditable "false", added to all configs + var filters = [function(elem) { + return elem.contentEditable != "false"; + }]; + + var normalizeTagName = function(editableSelector) { + var isIdOrClass = function(selector) { + return idOrClassRegExp.test(selector); + }; + return isIdOrClass(editableSelector) ? editableSelector : editableSelector.toLowerCase(); + }; + + var initDefault = function() { + var config = Aloha.defaults.sanitize.relaxed; + + if (Aloha.settings.contentHandler) { + if (Aloha.settings.contentHandler.allows) { + config = Aloha.settings.contentHandler.allows; + } else if (Aloha.settings.contentHandler.sanitize && Aloha.defaults.sanitize[Aloha.settings.contentHandler.sanitize]) { + config = Aloha.defaults.sanitize[Aloha.settings.contentHandler.sanitize]; + } + } + + config.filters = filters; + + map[defaultKey] = new Sanitize(config, jQuery); + }; + + var initEditableSpecific = function() { + if (Aloha.settings.contentHandler && + Aloha.settings.contentHandler.handler && + Aloha.settings.contentHandler.handler.sanitize) { + var config, editableSelector; + for (editableSelector in Aloha.settings.contentHandler.handler.sanitize) { + if (Aloha.settings.contentHandler.handler.sanitize.hasOwnProperty(editableSelector)) { + config = Aloha.settings.contentHandler.handler.sanitize[editableSelector]; + config.filters = filters; + editableSelector = normalizeTagName(editableSelector); + editableSelector = /^[\.#]/.test(editableSelector) ? editableSelector : editableSelector.toLowerCase(); + map[editableSelector] = new Sanitize(config, jQuery); + } + } + } + }; + + return { + init: function() { + if (!jQuery.isEmptyObject(map)) { return; } + + initDefault(); + initEditableSpecific(); + }, + + instanceForEditable: function() { + var editableSelector = defaultKey; + if (Aloha.activeEditable) { + var selector = null; + + var containerId = '#' + Aloha.activeEditable.getId(); + if (map[containerId]) { + selector = containerId; + } + + if (!selector) { + var containerClasses = Aloha.activeEditable.obj.attr('class').split(' '); + for (var i=0; i < containerClasses.length; i++) { + if (map['.' + containerClasses[i]]) { + selector = containerClasses[i]; + break; + } + } + } + + if (!selector) { + var containerTag = Aloha.activeEditable.obj.prop('tagName').toLowerCase(); + if (map[containerTag]) { + selector = containerTag; + } + } + + if (selector) { + editableSelector = selector; + } + } + + return map[editableSelector]; + } + }; + })(); + + var SanitizeContentHandler = ContentHandlerManager.createHandler({ + /** + * Handle the content from eg. paste action and sanitize the html + * @param content + */ + handleContent: function(content, options, editable) { + if (!editable) { + return content; + } + + sanitizers.init(); + + if ( typeof content === 'string' ){ + content = jQuery( '
' + content + '
' ).get(0); + } else if ( content instanceof jQuery ) { + content = jQuery( '
' ).append(content).get(0); + } + + return jQuery('
').append(sanitizers.instanceForEditable().clean_node(content)).html(); + } + }); + + return SanitizeContentHandler; +}); \ No newline at end of file From e684ab3a31f3b425bb0e028cfdd2e171e9d647f7 Mon Sep 17 00:00:00 2001 From: Bob Czarnecki Date: Wed, 5 Feb 2014 16:32:50 -0500 Subject: [PATCH 2/4] changelog and guide --- ...ctct-sanitize-content-handler-changelog.md | 6 ++++++ .../source/plugin_contenthandler.textile | 21 +++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 build/ctct-sanitize-content-handler-changelog.md diff --git a/build/ctct-sanitize-content-handler-changelog.md b/build/ctct-sanitize-content-handler-changelog.md new file mode 100644 index 0000000000..7fbda00b20 --- /dev/null +++ b/build/ctct-sanitize-content-handler-changelog.md @@ -0,0 +1,6 @@ +- **ENHANCEMENT**: Sanitize Content Handler + Adds the ability to configure and run the Sanitize Content Handler by editable element + tag name, in addition to element ID and class attributes. Refactored the Sanitize Content + Handler initialization and setup to cache Sanitizers per config. Also, cleanup up + relevant section in plugin_contenthandler guide page to match released implementation and + this enhancement. \ No newline at end of file diff --git a/doc/guides/source/plugin_contenthandler.textile b/doc/guides/source/plugin_contenthandler.textile index 385d7ae849..55c3fa66c2 100644 --- a/doc/guides/source/plugin_contenthandler.textile +++ b/doc/guides/source/plugin_contenthandler.textile @@ -88,12 +88,12 @@ h4. Writing your own Content Handler ['aloha', 'jquery', 'aloha/contenthandlermanager'], function(Aloha, jQuery, ContentHandlerManager) { "use strict"; - + var MyContentHandler = ContentHandlerManager.createHandler({ handleContent: function( content ) { - + // do something with the content - + return content; // return as HTML text not jQuery/DOM object } }); @@ -164,7 +164,7 @@ WARNING: The Sanitize Content Handler does not work reliably on IE7, and will th The Sanitize Content Handler will remove all dom elements and attributes not covered by it's configuration. You may specify your own configuration based on these default settings: -Aloha.settings.contentHandler.sanitize = { +Aloha.settings.contentHandler.allows = { // elements allowed in the content elements: [ 'a', 'abbr', 'b', 'blockquote', 'br', 'cite', 'code', 'dd', 'del', 'dl', 'dt', 'em', @@ -184,4 +184,17 @@ Aloha.settings.contentHandler.sanitize = { 'q' : {'cite': ['http', 'https', '__relative__']} } } + +// OR + +Aloha.settings.contentHandler.sanitize = 'relaxed'; // 'basic'|'restricted'|'relaxed' + +// OR + +Aloha.settings.contentHandler.sanitize = { + '#myId': { elements: [...], attributes: {...}, protocols;{...} }, + '.myclass': { elements: [...], attributes: {...}, protocols;{...} },, + 'p': { elements: [...], attributes: {...}, protocols;{...} }, + 'div': { elements: [...], attributes: {...}, protocols;{...} } +} From 21e420f45388dead9d311b25c079cbba413e3d7e Mon Sep 17 00:00:00 2001 From: Bob Czarnecki Date: Wed, 5 Feb 2014 16:43:43 -0500 Subject: [PATCH 3/4] guide --- doc/guides/source/plugin_contenthandler.textile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/guides/source/plugin_contenthandler.textile b/doc/guides/source/plugin_contenthandler.textile index 55c3fa66c2..41bcbf2087 100644 --- a/doc/guides/source/plugin_contenthandler.textile +++ b/doc/guides/source/plugin_contenthandler.textile @@ -19,6 +19,7 @@ There are some Content Handler available: * Word * oEmbed * Sanitize +* Style Attribute Plugins also provide Content Handler: * Block +common/block+ plugin @@ -123,6 +124,7 @@ The Contenthandler Plugin has no user interface and is used in conjunction with * word * generic * sanitize +* style attribute h4. Word Content Handler From aa0c87da6ffa38634883f59ea0de86e03d36a717 Mon Sep 17 00:00:00 2001 From: Bob Czarnecki Date: Tue, 4 Mar 2014 11:56:11 -0500 Subject: [PATCH 4/4] PR comment: use editable passed to handleContent() rather than Aloha.activeEditable --- .../contenthandler/lib/sanitizecontenthandler.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/plugins/common/contenthandler/lib/sanitizecontenthandler.js b/src/plugins/common/contenthandler/lib/sanitizecontenthandler.js index bd0655956b..d47c9bb89b 100644 --- a/src/plugins/common/contenthandler/lib/sanitizecontenthandler.js +++ b/src/plugins/common/contenthandler/lib/sanitizecontenthandler.js @@ -166,18 +166,18 @@ define([ initEditableSpecific(); }, - instanceForEditable: function() { + instanceForEditable: function(editable) { var editableSelector = defaultKey; - if (Aloha.activeEditable) { + if (editable) { var selector = null; - var containerId = '#' + Aloha.activeEditable.getId(); + var containerId = '#' + editable.getId(); if (map[containerId]) { selector = containerId; } if (!selector) { - var containerClasses = Aloha.activeEditable.obj.attr('class').split(' '); + var containerClasses = editable.obj.attr('class').split(' '); for (var i=0; i < containerClasses.length; i++) { if (map['.' + containerClasses[i]]) { selector = containerClasses[i]; @@ -187,7 +187,7 @@ define([ } if (!selector) { - var containerTag = Aloha.activeEditable.obj.prop('tagName').toLowerCase(); + var containerTag = editable.obj.prop('tagName').toLowerCase(); if (map[containerTag]) { selector = containerTag; } @@ -221,7 +221,7 @@ define([ content = jQuery( '
' ).append(content).get(0); } - return jQuery('
').append(sanitizers.instanceForEditable().clean_node(content)).html(); + return jQuery('
').append(sanitizers.instanceForEditable(editable).clean_node(content)).html(); } });