From 9c09a8f282302a9d7dcae124d10e3a409c726e9c Mon Sep 17 00:00:00 2001 From: Alejandro Romero Herrera Date: Thu, 26 Mar 2020 00:40:02 +0200 Subject: [PATCH 1/6] Document usage of htmlDecode to avoid XSS Fixes #715 --- README.md | 95 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 74221419..48bf5422 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,8 @@ - 支持 AMD / CMD 模块化加载(支持 [Require.js](https://pandao.github.io/editor.md/examples/use-requirejs.html) & [Sea.js](https://pandao.github.io/editor.md/examples/use-seajs.html)),并且支持[自定义扩展插件](https://pandao.github.io/editor.md/examples/define-plugin.html); - 兼容主流的浏览器(IE8+)和 [Zepto.js](https://pandao.github.io/editor.md/examples/use-zepto.html),且支持 iPad 等平板设备; -#### Download & install - +#### Download & install + Download: [Github download](https://github.com/pandao/editor.md/archive/master.zip) @@ -58,13 +58,13 @@ Bower install : bower install editor.md ``` -#### Usages - +#### Usages + ##### Create a Markdown editor ```html -
+
@@ -72,9 +72,9 @@ bower install editor.md -``` - -> See the full example: [http://editor.md.ipandao.com/examples/html-preview-markdown-to-html.html](http://editor.md.ipandao.com/examples/html-preview-markdown-to-html.html) - -##### HTML to Markdown? - + // htmlDecode : "style,script,iframe|on*", // Note: If enabled, you should filter some dangerous HTML tags for website security, you can also filter trigers. + }); + }); + +``` + +> See the full example: [http://editor.md.ipandao.com/examples/html-preview-markdown-to-html.html](http://editor.md.ipandao.com/examples/html-preview-markdown-to-html.html) + +##### HTML to Markdown? + Sorry, Editor.md not support HTML to Markdown parsing, Maybe In the future. #### Examples -[https://pandao.github.io/editor.md/examples/index.html](https://pandao.github.io/editor.md/examples/index.html) - -#### Options - -Editor.md options and default values: - -```javascript +[https://pandao.github.io/editor.md/examples/index.html](https://pandao.github.io/editor.md/examples/index.html) + +#### Options + +Editor.md options and default values: + +```javascript { mode : "gfm", // gfm or markdown name : "", // Form element name for post @@ -229,19 +229,32 @@ Editor.md options and default values: name : "zh-cn", description : "开源在线Markdown编辑器
Open source online Markdown editor.", tocTitle : "目录", - toolbar : { - //... - }, - button: { - //... + toolbar : { + //... + }, + button: { + //... }, - dialog : { - //... - } - //... - } -} + dialog : { + //... + } + //... + } +} +``` + +#### Avoid XSS + +If you enable htmlDecode be sure to avoid code injection by restricting dangerous elements, instead of setting it to true, you should define wich elements to avoid as well as wich attributes + ``` +htmlDecode : "style,script,iframe,sub,sup|on*" +``` + +specifying: +> coma separated list of filtered elements + | +> coma separated list of filtered attributes #### Dependents From 0c6cddedd5f4418994a01823b86d015c1db8545e Mon Sep 17 00:00:00 2001 From: Alejandro Romero Herrera Date: Fri, 27 Mar 2020 22:31:20 +0300 Subject: [PATCH 2/6] Fix #715 Disable script and on events by default to avoid XSS. User can enable by setting allowScript|allowOn Readme updated --- README.md | 16 ++++++++++------ src/editormd.js | 30 ++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 48bf5422..307c370a 100644 --- a/README.md +++ b/README.md @@ -245,16 +245,20 @@ Editor.md options and default values: #### Avoid XSS -If you enable htmlDecode be sure to avoid code injection by restricting dangerous elements, instead of setting it to true, you should define wich elements to avoid as well as wich attributes +Script and events are disabled by default to avoid XSS + +If you want to enable you need to pass in htmlDecode: + +allowScript as FilterTag +allowOn as FilterAttribute ``` -htmlDecode : "style,script,iframe,sub,sup|on*" +htmlDecode : "allowScript|allowOn" ``` -specifying: -> coma separated list of filtered elements - | -> coma separated list of filtered attributes +extra filters can be set in coma separated list format + +Be warned that enabled scripting can be dangerous and lead to [XSS attacks](https://en.wikipedia.org/wiki/Cross-site_scripting) #### Dependents diff --git a/src/editormd.js b/src/editormd.js index bf4f7f17..bcdb76e3 100644 --- a/src/editormd.js +++ b/src/editormd.js @@ -3804,13 +3804,20 @@ } if (typeof filters !== "string") { - return html; + // If no filters set use "script|on*" by default to avoid XSS + filters = "script|on*"; } var expression = filters.split("|"); var filterTags = expression[0].split(","); var attrs = expression[1]; + if(!filterTags.includes('allowScript') && !filterTags.includes('script')) + { + // Only allow script if requested specifically + filterTags.push('script'); + } + for (var i = 0, len = filterTags.length; i < len; i++) { var tag = filterTags[i]; @@ -3820,18 +3827,34 @@ //return html; + if (typeof attrs === "undefined") + { + // If no attrs set block "on*" to avoid XSS + attrs = "on*" + } + if (typeof attrs !== "undefined") { var htmlTagRegex = /\<(\w+)\s*([^\>]*)\>([^\>]*)\<\/(\w+)\>/ig; + var filterAttrs = attrs.split(","); + var filterOn = true; + + if(filterAttrs.includes('allowOn')) + { + // Only allow on* if requested specifically + filterOn = false; + } + if (attrs === "*") { html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) { return "<" + $2 + ">" + $4 + ""; }); } - else if (attrs === "on*") + else if ((attrs === "on*") || filterOn) { + html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) { var el = $("<" + $2 + ">" + $4 + ""); var _attrs = $($1)[0].attributes; @@ -3854,10 +3877,9 @@ return el[0].outerHTML + text; }); } - else + if(filterAttrs.length > 1 || (filterAttrs[0]!=="*" && filterAttrs[0]!=="on*")) { html = html.replace(htmlTagRegex, function($1, $2, $3, $4) { - var filterAttrs = attrs.split(","); var el = $($1); el.html($4); From 3d822bcdaf46b7fba494e886090003ac2c86117c Mon Sep 17 00:00:00 2001 From: Alejandro Romero Herrera Date: Thu, 10 Sep 2020 00:43:39 +0300 Subject: [PATCH 3/6] Enhance filterHTML Regexp to handle self closing tags Reported in huntr https://github.com/418sec/huntr/pull/443 --- src/editormd.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/editormd.js b/src/editormd.js index bcdb76e3..6c35bc29 100644 --- a/src/editormd.js +++ b/src/editormd.js @@ -3822,7 +3822,8 @@ { var tag = filterTags[i]; - html = html.replace(new RegExp("\<\s*" + tag + "\s*([^\>]*)\>([^\>]*)\<\s*\/" + tag + "\s*\>", "igm"), ""); + html = html.replace(new RegExp("\<\s*" + tag + "\s*([^\>]*)\>(?:([^\>]*)\<\s*\/" + tag + "\s*\>)?", "igm"), ""); + } //return html; @@ -3835,7 +3836,7 @@ if (typeof attrs !== "undefined") { - var htmlTagRegex = /\<(\w+)\s*([^\>]*)\>([^\>]*)\<\/(\w+)\>/ig; + var htmlTagRegex = /\<(\w+)\s*([^\>]*)\>(?:([^\>]*)\<\/(\1)\>)?/ig; var filterAttrs = attrs.split(","); var filterOn = true; @@ -3849,14 +3850,24 @@ if (attrs === "*") { html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) { - return "<" + $2 + ">" + $4 + ""; - }); + + if(typeof($4)!== 'undefined'){ + return "<" + $2 + ">" + $4 + ""; + }else{ + return "<" + $2 + "/>"; + } + }); } else if ((attrs === "on*") || filterOn) { html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) { - var el = $("<" + $2 + ">" + $4 + ""); + var el; + if(typeof($4)!== 'undefined'){ + el = $("<" + $2 + ">" + $4 + ""); + }else{ + el = $("<" + $2 + "/>"); + } var _attrs = $($1)[0].attributes; var $attrs = {}; @@ -3881,7 +3892,9 @@ { html = html.replace(htmlTagRegex, function($1, $2, $3, $4) { var el = $($1); - el.html($4); + if(typeof($4)!== 'undefined'){ + el.html($4); + } $.each(filterAttrs, function(i) { el.attr(filterAttrs[i], null); From 7d12de600662d154cd38e0db07e4d571c6c6a149 Mon Sep 17 00:00:00 2001 From: Alejandro Romero Herrera Date: Thu, 10 Sep 2020 12:35:06 +0300 Subject: [PATCH 4/6] cmMessge.txt --- src/editormd.js | 63 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/editormd.js b/src/editormd.js index 6c35bc29..b1b758e3 100644 --- a/src/editormd.js +++ b/src/editormd.js @@ -3798,11 +3798,21 @@ */ editormd.filterHTMLTags = function(html, filters) { - + + const basicAttrs ={ + 'img': 'src', + 'a': 'href' + } + if (typeof html !== "string") { html = new String(html); } - + + try{ + html = decodeURI(html) + }catch(error){ + return "Invalid encoding detected" + } if (typeof filters !== "string") { // If no filters set use "script|on*" by default to avoid XSS filters = "script|on*"; @@ -3830,7 +3840,7 @@ if (typeof attrs === "undefined") { - // If no attrs set block "on*" to avoid XSS + // If no attrs set, block "on*" to avoid XSS attrs = "on*" } @@ -3850,7 +3860,18 @@ if (attrs === "*") { html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) { - + // Add basic attrs to elements that need them + Object.entries(basicAttrs).forEach( item => + { + var match; + if(item[0].toUpperCase() === $2.toUpperCase()) + { + var regBas = new RegExp(item[1]+`\s*=\s*("|')(?:(?!\\1).)*\\1`,"i"); + if(match = regBas.exec($3)){ + $2 += ' ' + match[0]; + } + } + }); if(typeof($4)!== 'undefined'){ return "<" + $2 + ">" + $4 + ""; }else{ @@ -3863,28 +3884,30 @@ html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) { var el; - if(typeof($4)!== 'undefined'){ - el = $("<" + $2 + ">" + $4 + ""); - }else{ - el = $("<" + $2 + "/>"); + try{ + if(typeof($4)!== 'undefined'){ + el = $("<" + $2 + ">" + $4 + ""); + }else{ + el = $("<" + $2 + "/>"); + } + } catch (error){ + console.log('Trying to create invalid element'); + return ''; } - var _attrs = $($1)[0].attributes; +// var _attrs = $($1)[0].attributes; // ARH: Replace with regexp, beacause this triggers execution of onLoad ... (Also should be faster now) + var match; var $attrs = {}; - - $.each(_attrs, function(i, e) { - if (e.nodeName !== '"') $attrs[e.nodeName] = e.nodeValue; - }); - - $.each($attrs, function(i) { - if (i.indexOf("on") === 0) { - delete $attrs[i]; + var regOn = /^on*/i + + var regAttr = /(\w*)\s*=\s*("|')((?:(?!\2).)*)\2/gi; + while(match = regAttr.exec($3)){ + if (!regOn.exec(match[1]) && match[1].length>0){ + $attrs[match[1]] = match[3]; } - }); - + } el.attr($attrs); var text = (typeof el[1] !== "undefined") ? $(el[1]).text() : ""; - return el[0].outerHTML + text; }); } From f5cb82c39400294a75f4b5b9e997e4939cee7084 Mon Sep 17 00:00:00 2001 From: Alejandro Romero Herrera Date: Thu, 10 Sep 2020 12:35:06 +0300 Subject: [PATCH 5/6] Improve RegExp filter fixes #pandao/editor.md#612 fixes #pandao/editor.md#662 fixes #pandao/editor.md#697 fixes #pandao/editor.md#700 fixes #pandao/editor.md#701 fixes #pandao/editor.md#709 fixes #pandao/editor.md#715 fixes #pandao/editor.md#764 fixes #pandao/editor.md#816 ### Probably: fixes #pandao/editor.md#307 fixes #pandao/editor.md#560 --- src/editormd.js | 63 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 20 deletions(-) diff --git a/src/editormd.js b/src/editormd.js index 6c35bc29..b1b758e3 100644 --- a/src/editormd.js +++ b/src/editormd.js @@ -3798,11 +3798,21 @@ */ editormd.filterHTMLTags = function(html, filters) { - + + const basicAttrs ={ + 'img': 'src', + 'a': 'href' + } + if (typeof html !== "string") { html = new String(html); } - + + try{ + html = decodeURI(html) + }catch(error){ + return "Invalid encoding detected" + } if (typeof filters !== "string") { // If no filters set use "script|on*" by default to avoid XSS filters = "script|on*"; @@ -3830,7 +3840,7 @@ if (typeof attrs === "undefined") { - // If no attrs set block "on*" to avoid XSS + // If no attrs set, block "on*" to avoid XSS attrs = "on*" } @@ -3850,7 +3860,18 @@ if (attrs === "*") { html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) { - + // Add basic attrs to elements that need them + Object.entries(basicAttrs).forEach( item => + { + var match; + if(item[0].toUpperCase() === $2.toUpperCase()) + { + var regBas = new RegExp(item[1]+`\s*=\s*("|')(?:(?!\\1).)*\\1`,"i"); + if(match = regBas.exec($3)){ + $2 += ' ' + match[0]; + } + } + }); if(typeof($4)!== 'undefined'){ return "<" + $2 + ">" + $4 + ""; }else{ @@ -3863,28 +3884,30 @@ html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) { var el; - if(typeof($4)!== 'undefined'){ - el = $("<" + $2 + ">" + $4 + ""); - }else{ - el = $("<" + $2 + "/>"); + try{ + if(typeof($4)!== 'undefined'){ + el = $("<" + $2 + ">" + $4 + ""); + }else{ + el = $("<" + $2 + "/>"); + } + } catch (error){ + console.log('Trying to create invalid element'); + return ''; } - var _attrs = $($1)[0].attributes; +// var _attrs = $($1)[0].attributes; // ARH: Replace with regexp, beacause this triggers execution of onLoad ... (Also should be faster now) + var match; var $attrs = {}; - - $.each(_attrs, function(i, e) { - if (e.nodeName !== '"') $attrs[e.nodeName] = e.nodeValue; - }); - - $.each($attrs, function(i) { - if (i.indexOf("on") === 0) { - delete $attrs[i]; + var regOn = /^on*/i + + var regAttr = /(\w*)\s*=\s*("|')((?:(?!\2).)*)\2/gi; + while(match = regAttr.exec($3)){ + if (!regOn.exec(match[1]) && match[1].length>0){ + $attrs[match[1]] = match[3]; } - }); - + } el.attr($attrs); var text = (typeof el[1] !== "undefined") ? $(el[1]).text() : ""; - return el[0].outerHTML + text; }); } From 394544987477df70210476febbcfc510601d6c04 Mon Sep 17 00:00:00 2001 From: Alejandro Romero Herrera Date: Thu, 17 Sep 2020 23:51:46 +0300 Subject: [PATCH 6/6] Update of editor.md on base dir Cleanup for uglify --- editormd.js | 218 ++++++++++++++++++++++++++++++------------------ src/editormd.js | 18 ++-- 2 files changed, 140 insertions(+), 96 deletions(-) diff --git a/editormd.js b/editormd.js index c33c097a..8689b69f 100644 --- a/editormd.js +++ b/editormd.js @@ -7,7 +7,7 @@ * @license MIT License * @author Pandao * {@link https://github.com/pandao/editor.md} - * @updateTime 2015-06-09 + * @updateTime 2020-09-17 */ ;(function(factory) { @@ -363,9 +363,8 @@ options = id; } - var _this = this; var classPrefix = this.classPrefix = editormd.classPrefix; - var settings = this.settings = $.extend(true, {}, editormd.defaults, options); + var settings = this.settings = $.extend(true, editormd.defaults, options); id = (typeof id === "object") ? settings.id : id; @@ -813,7 +812,6 @@ } var cm = this.cm; - var editor = this.editor; var count = cm.lineCount(); var preview = this.preview; @@ -1112,7 +1110,6 @@ } var editor = this.editor; - var preview = this.preview; var classPrefix = this.classPrefix; var toolbar = this.toolbar = editor.children("." + classPrefix + "toolbar"); @@ -1247,7 +1244,7 @@ var toolbarIcons = this.toolbarIcons = toolbar.find("." + classPrefix + "menu > li > a"); var toolbarIconHandlers = this.getToolbarHandles(); - toolbarIcons.bind(editormd.mouseOrTouch("click", "touchend"), function(event) { + toolbarIcons.bind(editormd.mouseOrTouch("click", "touchend"), function() { var icon = $(this).children(".fa"); var name = icon.attr("name"); @@ -1371,8 +1368,7 @@ $("html,body").css("overflow-x", "hidden"); - var _this = this; - var editor = this.editor; + var editor = this.editor; var settings = this.settings; var infoDialog = this.infoDialog = editor.children("." + this.classPrefix + "dialog-info"); @@ -1434,7 +1430,6 @@ */ recreate : function() { - var _this = this; var editor = this.editor; var settings = this.settings; @@ -1629,18 +1624,15 @@ { case 120: $.proxy(toolbarHandlers["watch"], _this)(); - return false; - break; + return false; case 121: $.proxy(toolbarHandlers["preview"], _this)(); - return false; - break; + return false; case 122: $.proxy(toolbarHandlers["fullscreen"], _this)(); - return false; - break; + return false; default: break; @@ -1965,15 +1957,14 @@ save : function() { - var _this = this; - var state = this.state; - var settings = this.settings; - - if (timer === null && !(!settings.watch && state.preview)) + if (timer === null) { return this; } + var _this = this; + var state = this.state; + var settings = this.settings; var cm = this.cm; var cmValue = cm.getValue(); var previewContainer = this.previewContainer; @@ -3183,17 +3174,20 @@ } }; + var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0; + var key = isMac ? "Cmd" : "Ctrl"; + editormd.keyMaps = { - "Ctrl-1" : "h1", - "Ctrl-2" : "h2", - "Ctrl-3" : "h3", - "Ctrl-4" : "h4", - "Ctrl-5" : "h5", - "Ctrl-6" : "h6", - "Ctrl-B" : "bold", // if this is string == editormd.toolbarHandlers.xxxx - "Ctrl-D" : "datetime", - - "Ctrl-E" : function() { // emoji + [key + "-1"] : "h1", + [key + "-2"] : "h2", + [key + "-3"] : "h3", + [key + "-4"] : "h4", + [key + "-5"] : "h5", + [key + "-6"] : "h6", + [key + "-B"] : "bold", // if this is string == editormd.toolbarHandlers.xxxx + [key + "-D"] : "datetime", + + [key + "Ctrl-E"] : function() { // emoji var cm = this.cm; var cursor = cm.getCursor(); var selection = cm.getSelection(); @@ -3210,10 +3204,10 @@ cm.setCursor(cursor.line, cursor.ch + 1); } }, - "Ctrl-Alt-G" : "goto-line", - "Ctrl-H" : "hr", - "Ctrl-I" : "italic", - "Ctrl-K" : "code", + [key + "-Alt-G"] : "goto-line", + [key + "-H"] : "hr", + [key + "-I"] : "italic", + [key + "-K"] : "code", "Ctrl-L" : function() { var cm = this.cm; @@ -3228,7 +3222,7 @@ cm.setCursor(cursor.line, cursor.ch + 1); } }, - "Ctrl-U" : "list-ul", + [key + "-U"] : "list-ul", "Shift-Ctrl-A" : function() { var cm = this.cm; @@ -3248,10 +3242,10 @@ } }, - "Shift-Ctrl-C" : "code", - "Shift-Ctrl-Q" : "quote", - "Shift-Ctrl-S" : "del", - "Shift-Ctrl-K" : "tex", // KaTeX + ["Shift" + key + "-C"] : "code", + ["Shift" + key + "Q"] : "quote", + ["Shift" + key + "S"] : "del", + ["Shift" + key + "K"] : "tex", // KaTeX "Shift-Alt-C" : function() { var cm = this.cm; @@ -3265,16 +3259,16 @@ } }, - "Shift-Ctrl-Alt-C" : "code-block", - "Shift-Ctrl-H" : "html-entities", - "Shift-Alt-H" : "help", - "Shift-Ctrl-E" : "emoji", - "Shift-Ctrl-U" : "uppercase", - "Shift-Alt-U" : "ucwords", - "Shift-Ctrl-Alt-U" : "ucfirst", - "Shift-Alt-L" : "lowercase", + ["Shift-" + key + "-Alt-C"] : "code-block", + ["Shift-" + key + "-H"] : "html-entities", + "Shift-Alt-H" : "help", + ["Shift-" + key + "-E"] : "emoji", + ["Shift-" + key + "-U"] : "uppercase", + "Shift-Alt-U" : "ucwords", + ["Shift-" + key + "-Alt-U"] : "ucfirst", + "Shift-Alt-L" : "lowercase", - "Shift-Ctrl-I" : function() { + ["Shift-" + key + "-I"] : function() { var cm = this.cm; var cursor = cm.getCursor(); var selection = cm.getSelection(); @@ -3288,15 +3282,15 @@ } }, - "Shift-Ctrl-Alt-I" : "image", - "Shift-Ctrl-L" : "link", - "Shift-Ctrl-O" : "list-ol", - "Shift-Ctrl-P" : "preformatted-text", - "Shift-Ctrl-T" : "table", - "Shift-Alt-P" : "pagebreak", - "F9" : "watch", - "F10" : "preview", - "F11" : "fullscreen", + ["Shift-" + key + "-Alt-I"] : "image", + ["Shift-" + key + "-L"] : "link", + ["Shift-" + key + "-O"] : "list-ol", + ["Shift-" + key + "-P"] : "preformatted-text", + ["Shift-" + key + "-T"] : "table", + "Shift-Alt-P" : "pagebreak", + "F9" : "watch", + "F10" : "preview", + "F11" : "fullscreen", }; /** @@ -3356,7 +3350,7 @@ email : /(\w+)@(\w+)\.(\w+)\.?(\w+)?/g, emailLink : /(mailto:)?([\w\.\_]+)@(\w+)\.(\w+)\.?(\w+)?/g, emoji : /:([\w\+-]+):/g, - emojiDatetime : /(\d{2}:\d{2}:\d{2})/g, + emojiDatetime : /(\d{1,2}:\d{1,2}:\d{1,2})/g, twemoji : /:(tw-([\w]+)-?(\w+)?):/g, fontAwesome : /:(fa-([\w]+)(-(\w+)){0,}):/g, editormdLogo : /:(editormd-logo-?(\w+)?):/g, @@ -3365,7 +3359,7 @@ // Emoji graphics files url path editormd.emoji = { - path : "https://www.webpagefx.com/tools/emoji-cheat-sheet/graphics/emojis/", + path : "http://www.emoji-cheat-sheet.com/graphics/emojis/", ext : ".png" }; @@ -3808,68 +3802,126 @@ */ editormd.filterHTMLTags = function(html, filters) { - + + const basicAttrs ={ + 'img': 'src', + 'a': 'href' + } + if (typeof html !== "string") { html = new String(html); } - + + try{ + html = decodeURI(html) + }catch(error){ + return "Invalid encoding detected" + } if (typeof filters !== "string") { - return html; + // If no filters set use "script|on*" by default to avoid XSS + filters = "script|on*"; } var expression = filters.split("|"); var filterTags = expression[0].split(","); var attrs = expression[1]; + if(!filterTags.includes('allowScript') && !filterTags.includes('script')) + { + // Only allow script if requested specifically + filterTags.push('script'); + } + for (var i = 0, len = filterTags.length; i < len; i++) { var tag = filterTags[i]; - html = html.replace(new RegExp("\<\s*" + tag + "\s*([^\>]*)\>([^\>]*)\<\s*\/" + tag + "\s*\>", "igm"), ""); + html = html.replace(new RegExp("\<\s*" + tag + "\s*([^\>]*)\>(?:([^\>]*)\<\s*\/" + tag + "\s*\>)?", "igm"), ""); + } //return html; + if (typeof attrs === "undefined") + { + // If no attrs set, block "on*" to avoid XSS + attrs = "on*" + } + if (typeof attrs !== "undefined") { - var htmlTagRegex = /\<(\w+)\s*([^\>]*)\>([^\>]*)\<\/(\w+)\>/ig; + var htmlTagRegex = /\<(\w+)\s*([^\>]*)\>(?:([^\>]*)\<\/(\1)\>)?/ig; + + var filterAttrs = attrs.split(","); + var filterOn = true; + + if(filterAttrs.includes('allowOn')) + { + // Only allow on* if requested specifically + filterOn = false; + } if (attrs === "*") { html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) { - return "<" + $2 + ">" + $4 + ""; - }); + // Add basic attrs to elements that need them + Object.entries(basicAttrs).forEach( item => + { + var match; + if(item[0].toUpperCase() === $2.toUpperCase()) + { + var regBas = new RegExp(item[1]+`\s*=\s*("|')(?:(?!\\1).)*\\1`,"i"); + if(match = regBas.exec($3)){ + $2 += ' ' + match[0]; + } + } + }); + if(typeof($4)!== 'undefined'){ + return "<" + $2 + ">" + $4 + ""; + }else{ + return "<" + $2 + "/>"; + } + }); } - else if (attrs === "on*") + else if ((attrs === "on*") || filterOn) { + html = html.replace(htmlTagRegex, function($1, $2, $3, $4, $5) { - var el = $("<" + $2 + ">" + $4 + ""); - var _attrs = $($1)[0].attributes; + var el; + try{ + if(typeof($4)!== 'undefined'){ + el = $("<" + $2 + ">" + $4 + ""); + }else{ + el = $("<" + $2 + "/>"); + } + } catch (error){ + console.log('Trying to create invalid element'); + return ''; + } +// var _attrs = $($1)[0].attributes; // ARH: Replace with regexp, beacause this triggers execution of onLoad ... (Also should be faster now) + var match; var $attrs = {}; - - $.each(_attrs, function(i, e) { - if (e.nodeName !== '"') $attrs[e.nodeName] = e.nodeValue; - }); - - $.each($attrs, function(i) { - if (i.indexOf("on") === 0) { - delete $attrs[i]; + var regOn = /^on*/i + + var regAttr = /(\w*)\s*=\s*("|')((?:(?!\2).)*)\2/gi; + while(match = regAttr.exec($3)){ + if (!regOn.exec(match[1]) && match[1].length>0){ + $attrs[match[1]] = match[3]; } - }); - + } el.attr($attrs); var text = (typeof el[1] !== "undefined") ? $(el[1]).text() : ""; - return el[0].outerHTML + text; }); } - else + if(filterAttrs.length > 1 || (filterAttrs[0]!=="*" && filterAttrs[0]!=="on*")) { html = html.replace(htmlTagRegex, function($1, $2, $3, $4) { - var filterAttrs = attrs.split(","); var el = $($1); - el.html($4); + if(typeof($4)!== 'undefined'){ + el.html($4); + } $.each(filterAttrs, function(i) { el.attr(filterAttrs[i], null); diff --git a/src/editormd.js b/src/editormd.js index b1b758e3..55f9e37e 100644 --- a/src/editormd.js +++ b/src/editormd.js @@ -351,7 +351,6 @@ options = id; } - var _this = this; var classPrefix = this.classPrefix = editormd.classPrefix; var settings = this.settings = $.extend(true, editormd.defaults, options); @@ -801,7 +800,6 @@ } var cm = this.cm; - var editor = this.editor; var count = cm.lineCount(); var preview = this.preview; @@ -1100,7 +1098,6 @@ } var editor = this.editor; - var preview = this.preview; var classPrefix = this.classPrefix; var toolbar = this.toolbar = editor.children("." + classPrefix + "toolbar"); @@ -1235,7 +1232,7 @@ var toolbarIcons = this.toolbarIcons = toolbar.find("." + classPrefix + "menu > li > a"); var toolbarIconHandlers = this.getToolbarHandles(); - toolbarIcons.bind(editormd.mouseOrTouch("click", "touchend"), function(event) { + toolbarIcons.bind(editormd.mouseOrTouch("click", "touchend"), function() { var icon = $(this).children(".fa"); var name = icon.attr("name"); @@ -1359,8 +1356,7 @@ $("html,body").css("overflow-x", "hidden"); - var _this = this; - var editor = this.editor; + var editor = this.editor; var settings = this.settings; var infoDialog = this.infoDialog = editor.children("." + this.classPrefix + "dialog-info"); @@ -1422,7 +1418,6 @@ */ recreate : function() { - var _this = this; var editor = this.editor; var settings = this.settings; @@ -1617,18 +1612,15 @@ { case 120: $.proxy(toolbarHandlers["watch"], _this)(); - return false; - break; + return false; case 121: $.proxy(toolbarHandlers["preview"], _this)(); - return false; - break; + return false; case 122: $.proxy(toolbarHandlers["fullscreen"], _this)(); - return false; - break; + return false; default: break;