[SECURITY] Prevent XSS in modal component and PageTree 01/59101/2
authorFrank Naegler <frank.naegler@typo3.org>
Tue, 11 Dec 2018 09:57:05 +0000 (10:57 +0100)
committerOliver Hader <oliver.hader@typo3.org>
Tue, 11 Dec 2018 09:57:07 +0000 (10:57 +0100)
Resolves: #84190
Releases: master, 8.7, 7.6
Security-Commit: 0aaf266ae75d04efc6a943f9414448d5c2787309
Security-Bulletin: TYPO3-CORE-SA-2018-007
Change-Id: Ic5beb5217326d8f3d72445d4bf9cc7ace705a5a0
Reviewed-on: https://review.typo3.org/59101
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
typo3/sysext/backend/Resources/Private/TypeScript/Modal.ts
typo3/sysext/backend/Resources/Public/JavaScript/Modal.js
typo3/sysext/backend/Resources/Public/JavaScript/PageTree/PageTreeDragDrop.js
typo3/sysext/core/Resources/Private/TypeScript/SecurityUtility.ts [new file with mode: 0644]
typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js [new file with mode: 0644]

index 24f9001..76d53ab 100644 (file)
@@ -16,6 +16,7 @@ import 'bootstrap';
 import * as $ from 'jquery';
 import Icons = require('./Icons');
 import Severity = require('./Severity');
+import SecurityUtility = require('TYPO3/CMS/Core/SecurityUtility');
 
 enum Identifiers {
   modal = '.t3js-modal',
@@ -116,7 +117,10 @@ class Modal {
     ajaxTarget: null
   };
 
-  constructor() {
+  private readonly securityUtility: SecurityUtility;
+
+  constructor(securityUtility: SecurityUtility) {
+    this.securityUtility = securityUtility;
     $(document).on('modal-dismiss', this.dismiss);
     this.initializeMarkupTrigger(document);
   }
@@ -250,9 +254,15 @@ class Modal {
     configuration.title = typeof configuration.title === 'string'
       ? configuration.title
       : this.defaultConfiguration.title;
-    configuration.content = typeof configuration.content === 'string' || typeof configuration.content === 'object'
-      ? configuration.content
-      : this.defaultConfiguration.content;
+    if (typeof configuration.content === 'string') {
+      // A string means, no markup allowed, let's ensure this
+      configuration.content = this.securityUtility.encodeHtml(configuration.content);
+    } else if (typeof configuration.content === 'object') {
+      // An object means, a valid jQuery object with markup, let's get the markup
+      configuration.content = configuration.content.html();
+    } else {
+      configuration.content = this.defaultConfiguration.content;
+    }
     configuration.severity = typeof configuration.severity !== 'undefined'
       ? configuration.severity
       : this.defaultConfiguration.severity;
@@ -375,10 +385,7 @@ class Modal {
       });
     } else {
       if (typeof configuration.content === 'string') {
-        // we need html, check if we have to wrap content in <p>
-        if (!/^<[a-z][\s\S]*>/i.test(configuration.content)) {
-          configuration.content = $('<p />').html(configuration.content);
-        }
+        configuration.content = $('<p />').html(configuration.content);
       }
       currentModal.find(Identifiers.body).append(configuration.content);
     }
@@ -488,7 +495,7 @@ try {
 }
 
 if (!modalObject) {
-  modalObject = new Modal();
+  modalObject = new Modal(new SecurityUtility());
 
   // expose as global object
   TYPO3.Modal = modalObject;
index d9c34ca..b1ac6bf 100644 (file)
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-var __values=this&&this.__values||function(t){var e="function"==typeof Symbol&&t[Symbol.iterator],a=0;return e?e.call(t):{next:function(){return t&&a>=t.length&&(t=void 0),{value:t&&t[a++],done:!t}}}};define(["require","exports","./Enum/Severity","jquery","./Icons","./Severity","bootstrap"],function(t,e,a,n,o,i){"use strict";var l,s,r,d,c,u,f,m;(s=l||(l={})).modal=".t3js-modal",s.content=".t3js-modal-content",s.title=".t3js-modal-title",s.close=".t3js-modal-close",s.body=".t3js-modal-body",s.footer=".t3js-modal-footer",s.iframe=".t3js-modal-iframe",s.iconPlaceholder=".t3js-modal-icon-placeholder",(d=r||(r={})).small="small",d.default="default",d.medium="medium",d.large="large",d.full="full",(u=c||(c={})).default="default",u.light="light",u.dark="dark",(m=f||(f={})).default="default",m.ajax="ajax",m.iframe="iframe";var g=function(){function t(){this.sizes=r,this.styles=c,this.types=f,this.currentModal=null,this.instances=[],this.$template=n('<div class="t3js-modal modal fade"><div class="modal-dialog"><div class="t3js-modal-content modal-content"><div class="modal-header"><button class="t3js-modal-close close"><span aria-hidden="true"><span class="t3js-modal-icon-placeholder" data-icon="actions-close"></span></span><span class="sr-only"></span></button><h4 class="t3js-modal-title modal-title"></h4></div><div class="t3js-modal-body modal-body"></div><div class="t3js-modal-footer modal-footer"></div></div></div></div>'),this.defaultConfiguration={type:f.default,title:"Information",content:"No content provided, please check your <code>Modal</code> configuration.",severity:a.SeverityEnum.notice,buttons:[],style:c.default,size:r.default,additionalCssClasses:[],callback:n.noop(),ajaxCallback:n.noop(),ajaxTarget:null},n(document).on("modal-dismiss",this.dismiss),this.initializeMarkupTrigger(document)}return t.prototype.dismiss=function(){this.currentModal&&this.currentModal.modal("hide")},t.prototype.confirm=function(t,e,o,l,s){return void 0===o&&(o=a.SeverityEnum.warning),void 0===l&&(l=[]),0===l.length&&l.push({text:n(this).data("button-close-text")||TYPO3.lang["button.cancel"]||"Cancel",active:!0,btnClass:"btn-default",name:"cancel"},{text:n(this).data("button-ok-text")||TYPO3.lang["button.ok"]||"OK",btnClass:"btn-"+i.getCssClass(o),name:"ok"}),this.advanced({title:t,content:e,severity:o,buttons:l,additionalCssClasses:s,callback:function(t){t.on("button.clicked",function(t){"cancel"===t.target.getAttribute("name")?n(t.currentTarget).trigger("confirm.button.cancel"):"ok"===t.target.getAttribute("name")&&n(t.currentTarget).trigger("confirm.button.ok")})}})},t.prototype.loadUrl=function(t,e,n,o,i,l){return void 0===e&&(e=a.SeverityEnum.info),this.advanced({type:f.ajax,title:t,severity:e,buttons:n,ajaxCallback:i,ajaxTarget:l})},t.prototype.show=function(t,e,n,o,i){return void 0===n&&(n=a.SeverityEnum.info),this.advanced({type:f.default,title:t,content:e,severity:n,buttons:o,additionalCssClasses:i})},t.prototype.advanced=function(t){return t.type="string"==typeof t.type&&t.type in f?t.type:this.defaultConfiguration.type,t.title="string"==typeof t.title?t.title:this.defaultConfiguration.title,t.content="string"==typeof t.content||"object"==typeof t.content?t.content:this.defaultConfiguration.content,t.severity=void 0!==t.severity?t.severity:this.defaultConfiguration.severity,t.buttons=t.buttons||this.defaultConfiguration.buttons,t.size="string"==typeof t.size&&t.size in r?t.size:this.defaultConfiguration.size,t.style="string"==typeof t.style&&t.style in c?t.style:this.defaultConfiguration.style,t.additionalCssClasses=t.additionalCssClasses||this.defaultConfiguration.additionalCssClasses,t.callback="function"==typeof t.callback?t.callback:this.defaultConfiguration.callback,t.ajaxCallback="function"==typeof t.ajaxCallback?t.ajaxCallback:this.defaultConfiguration.ajaxCallback,t.ajaxTarget="string"==typeof t.ajaxTarget?t.ajaxTarget:this.defaultConfiguration.ajaxTarget,this.generate(t)},t.prototype.initializeMarkupTrigger=function(t){var e=this;n(t).on("click",".t3js-modal-trigger",function(t){t.preventDefault();var o=n(t.currentTarget),l=o.data("content")||"Are you sure?",s=void 0!==a.SeverityEnum[o.data("severity")]?a.SeverityEnum[o.data("severity")]:a.SeverityEnum.info,r=o.data("url")||null;null!==r&&(r=r+(r.indexOf("?")>-1?"&":"?")+n.param({data:o.data()}));e.advanced({type:null!==r?f.ajax:f.default,title:o.data("title")||"Alert",content:null!==r?r:l,severity:s,buttons:[{text:o.data("button-close-text")||TYPO3.lang["button.close"]||"Close",active:!0,btnClass:"btn-default",trigger:function(){e.currentModal.trigger("modal-dismiss")}},{text:o.data("button-ok-text")||TYPO3.lang["button.ok"]||"OK",btnClass:"btn-"+i.getCssClass(s),trigger:function(){e.currentModal.trigger("modal-dismiss"),t.target.ownerDocument.location.href=o.data("href")||o.attr("href")}}]})})},t.prototype.generate=function(t){var e,a,s=this,r=this.$template.clone();if(t.additionalCssClasses.length>0)try{for(var d=__values(t.additionalCssClasses),c=d.next();!c.done;c=d.next()){var u=c.value;r.addClass(u)}}catch(t){e={error:t}}finally{try{c&&!c.done&&(a=d.return)&&a.call(d)}finally{if(e)throw e.error}}if(r.addClass("modal-type-"+t.type),r.addClass("modal-severity-"+i.getCssClass(t.severity)),r.addClass("modal-style-"+t.style),r.addClass("modal-size-"+t.size),r.attr("tabindex","-1"),r.find(l.title).text(t.title),r.find(l.close).on("click",function(){r.modal("hide")}),"ajax"===t.type){var f=t.ajaxTarget?t.ajaxTarget:l.body,m=r.find(f);o.getIcon("spinner-circle",o.sizes.default,null,null,o.markupIdentifiers.inline).done(function(e){m.html('<div class="modal-loading">'+e+"</div>"),n.get(t.content,function(e){s.currentModal.find(f).empty().append(e),t.ajaxCallback&&t.ajaxCallback(),s.currentModal.trigger("modal-loaded")},"html")})}else"iframe"===t.type?(r.find(l.body).append(n("<iframe />",{src:t.content,name:"modal_frame",class:"modal-iframe t3js-modal-iframe"})),r.find(l.iframe).on("load",function(){r.find(l.title).text(r.find(l.iframe).get(0).contentDocument.title)})):("string"==typeof t.content&&(/^<[a-z][\s\S]*>/i.test(t.content)||(t.content=n("<p />").html(t.content))),r.find(l.body).append(t.content));if(t.buttons.length>0){for(var g=function(e){var a=t.buttons[e],o=n("<button />",{class:"btn"});o.html("<span>"+a.text+"</span>"),a.active&&o.addClass("t3js-active"),""!==a.btnClass&&o.addClass(a.btnClass),""!==a.name&&o.attr("name",a.name),a.trigger&&o.on("click",a.trigger),a.dataAttributes&&Object.keys(a.dataAttributes).length>0&&Object.keys(a.dataAttributes).map(function(t){o.attr("data-"+t,a.dataAttributes[t])}),a.icon&&o.prepend('<span class="t3js-modal-icon-placeholder" data-icon="'+a.icon+'"></span>'),r.find(l.footer).append(o)},p=0;p<t.buttons.length;p++)g(p);r.find(l.footer).find("button").on("click",function(t){n(t.currentTarget).trigger("button.clicked")})}else r.find(l.footer).remove();return r.on("shown.bs.modal",function(t){var e=n(t.currentTarget);e.find(l.footer).find(".t3js-active").first().focus(),e.find(l.iconPlaceholder).each(function(t,e){o.getIcon(n(e).data("icon"),o.sizes.small,null,null,o.markupIdentifiers.inline).done(function(t){s.currentModal.find(l.iconPlaceholder+"[data-icon="+n(t).data("identifier")+"]").replaceWith(t)})})}),r.on("hidden.bs.modal",function(t){if(s.instances.length>0){var e=s.instances.length-1;s.instances.splice(e,1),s.currentModal=s.instances[e-1]}r.trigger("modal-destroyed"),n(t.currentTarget).remove(),s.instances.length>0&&n("body").addClass("modal-open")}),r.on("show.bs.modal",function(t){s.currentModal=n(t.currentTarget),s.instances.push(s.currentModal)}),r.on("modal-dismiss",function(t){n(t.currentTarget).modal("hide")}),t.callback&&t.callback(r),r.modal()},t}(),p=null;try{parent&&parent.window.TYPO3&&parent.window.TYPO3.Modal?(parent.window.TYPO3.Modal.initializeMarkupTrigger(document),p=parent.window.TYPO3.Modal):top&&top.TYPO3.Modal&&(top.TYPO3.Modal.initializeMarkupTrigger(document),p=top.TYPO3.Modal)}catch(t){}return p||(p=new g,TYPO3.Modal=p),p});
\ No newline at end of file
+var __values=this&&this.__values||function(t){var e="function"==typeof Symbol&&t[Symbol.iterator],a=0;return e?e.call(t):{next:function(){return t&&a>=t.length&&(t=void 0),{value:t&&t[a++],done:!t}}}};define(["require","exports","./Enum/Severity","jquery","./Icons","./Severity","TYPO3/CMS/Core/SecurityUtility","bootstrap"],function(t,e,a,n,i,o,l){"use strict";var s,r,d,c,u,f,m,g;(r=s||(s={})).modal=".t3js-modal",r.content=".t3js-modal-content",r.title=".t3js-modal-title",r.close=".t3js-modal-close",r.body=".t3js-modal-body",r.footer=".t3js-modal-footer",r.iframe=".t3js-modal-iframe",r.iconPlaceholder=".t3js-modal-icon-placeholder",(c=d||(d={})).small="small",c.default="default",c.medium="medium",c.large="large",c.full="full",(f=u||(u={})).default="default",f.light="light",f.dark="dark",(g=m||(m={})).default="default",g.ajax="ajax",g.iframe="iframe";var p=function(){function t(t){this.sizes=d,this.styles=u,this.types=m,this.currentModal=null,this.instances=[],this.$template=n('<div class="t3js-modal modal fade"><div class="modal-dialog"><div class="t3js-modal-content modal-content"><div class="modal-header"><button class="t3js-modal-close close"><span aria-hidden="true"><span class="t3js-modal-icon-placeholder" data-icon="actions-close"></span></span><span class="sr-only"></span></button><h4 class="t3js-modal-title modal-title"></h4></div><div class="t3js-modal-body modal-body"></div><div class="t3js-modal-footer modal-footer"></div></div></div></div>'),this.defaultConfiguration={type:m.default,title:"Information",content:"No content provided, please check your <code>Modal</code> configuration.",severity:a.SeverityEnum.notice,buttons:[],style:u.default,size:d.default,additionalCssClasses:[],callback:n.noop(),ajaxCallback:n.noop(),ajaxTarget:null},this.securityUtility=t,n(document).on("modal-dismiss",this.dismiss),this.initializeMarkupTrigger(document)}return t.prototype.dismiss=function(){this.currentModal&&this.currentModal.modal("hide")},t.prototype.confirm=function(t,e,i,l,s){return void 0===i&&(i=a.SeverityEnum.warning),void 0===l&&(l=[]),0===l.length&&l.push({text:n(this).data("button-close-text")||TYPO3.lang["button.cancel"]||"Cancel",active:!0,btnClass:"btn-default",name:"cancel"},{text:n(this).data("button-ok-text")||TYPO3.lang["button.ok"]||"OK",btnClass:"btn-"+o.getCssClass(i),name:"ok"}),this.advanced({title:t,content:e,severity:i,buttons:l,additionalCssClasses:s,callback:function(t){t.on("button.clicked",function(t){"cancel"===t.target.getAttribute("name")?n(t.currentTarget).trigger("confirm.button.cancel"):"ok"===t.target.getAttribute("name")&&n(t.currentTarget).trigger("confirm.button.ok")})}})},t.prototype.loadUrl=function(t,e,n,i,o,l){return void 0===e&&(e=a.SeverityEnum.info),this.advanced({type:m.ajax,title:t,severity:e,buttons:n,ajaxCallback:o,ajaxTarget:l})},t.prototype.show=function(t,e,n,i,o){return void 0===n&&(n=a.SeverityEnum.info),this.advanced({type:m.default,title:t,content:e,severity:n,buttons:i,additionalCssClasses:o})},t.prototype.advanced=function(t){return t.type="string"==typeof t.type&&t.type in m?t.type:this.defaultConfiguration.type,t.title="string"==typeof t.title?t.title:this.defaultConfiguration.title,"string"==typeof t.content?t.content=this.securityUtility.encodeHtml(t.content):"object"==typeof t.content?t.content=t.content.html():t.content=this.defaultConfiguration.content,t.severity=void 0!==t.severity?t.severity:this.defaultConfiguration.severity,t.buttons=t.buttons||this.defaultConfiguration.buttons,t.size="string"==typeof t.size&&t.size in d?t.size:this.defaultConfiguration.size,t.style="string"==typeof t.style&&t.style in u?t.style:this.defaultConfiguration.style,t.additionalCssClasses=t.additionalCssClasses||this.defaultConfiguration.additionalCssClasses,t.callback="function"==typeof t.callback?t.callback:this.defaultConfiguration.callback,t.ajaxCallback="function"==typeof t.ajaxCallback?t.ajaxCallback:this.defaultConfiguration.ajaxCallback,t.ajaxTarget="string"==typeof t.ajaxTarget?t.ajaxTarget:this.defaultConfiguration.ajaxTarget,this.generate(t)},t.prototype.initializeMarkupTrigger=function(t){var e=this;n(t).on("click",".t3js-modal-trigger",function(t){t.preventDefault();var i=n(t.currentTarget),l=i.data("content")||"Are you sure?",s=void 0!==a.SeverityEnum[i.data("severity")]?a.SeverityEnum[i.data("severity")]:a.SeverityEnum.info,r=i.data("url")||null;null!==r&&(r=r+(r.indexOf("?")>-1?"&":"?")+n.param({data:i.data()}));e.advanced({type:null!==r?m.ajax:m.default,title:i.data("title")||"Alert",content:null!==r?r:l,severity:s,buttons:[{text:i.data("button-close-text")||TYPO3.lang["button.close"]||"Close",active:!0,btnClass:"btn-default",trigger:function(){e.currentModal.trigger("modal-dismiss")}},{text:i.data("button-ok-text")||TYPO3.lang["button.ok"]||"OK",btnClass:"btn-"+o.getCssClass(s),trigger:function(){e.currentModal.trigger("modal-dismiss"),t.target.ownerDocument.location.href=i.data("href")||i.attr("href")}}]})})},t.prototype.generate=function(t){var e,a,l=this,r=this.$template.clone();if(t.additionalCssClasses.length>0)try{for(var d=__values(t.additionalCssClasses),c=d.next();!c.done;c=d.next()){var u=c.value;r.addClass(u)}}catch(t){e={error:t}}finally{try{c&&!c.done&&(a=d.return)&&a.call(d)}finally{if(e)throw e.error}}if(r.addClass("modal-type-"+t.type),r.addClass("modal-severity-"+o.getCssClass(t.severity)),r.addClass("modal-style-"+t.style),r.addClass("modal-size-"+t.size),r.attr("tabindex","-1"),r.find(s.title).text(t.title),r.find(s.close).on("click",function(){r.modal("hide")}),"ajax"===t.type){var f=t.ajaxTarget?t.ajaxTarget:s.body,m=r.find(f);i.getIcon("spinner-circle",i.sizes.default,null,null,i.markupIdentifiers.inline).done(function(e){m.html('<div class="modal-loading">'+e+"</div>"),n.get(t.content,function(e){l.currentModal.find(f).empty().append(e),t.ajaxCallback&&t.ajaxCallback(),l.currentModal.trigger("modal-loaded")},"html")})}else"iframe"===t.type?(r.find(s.body).append(n("<iframe />",{src:t.content,name:"modal_frame",class:"modal-iframe t3js-modal-iframe"})),r.find(s.iframe).on("load",function(){r.find(s.title).text(r.find(s.iframe).get(0).contentDocument.title)})):("string"==typeof t.content&&(t.content=n("<p />").html(t.content)),r.find(s.body).append(t.content));if(t.buttons.length>0){for(var g=function(e){var a=t.buttons[e],i=n("<button />",{class:"btn"});i.html("<span>"+a.text+"</span>"),a.active&&i.addClass("t3js-active"),""!==a.btnClass&&i.addClass(a.btnClass),""!==a.name&&i.attr("name",a.name),a.trigger&&i.on("click",a.trigger),a.dataAttributes&&Object.keys(a.dataAttributes).length>0&&Object.keys(a.dataAttributes).map(function(t){i.attr("data-"+t,a.dataAttributes[t])}),a.icon&&i.prepend('<span class="t3js-modal-icon-placeholder" data-icon="'+a.icon+'"></span>'),r.find(s.footer).append(i)},p=0;p<t.buttons.length;p++)g(p);r.find(s.footer).find("button").on("click",function(t){n(t.currentTarget).trigger("button.clicked")})}else r.find(s.footer).remove();return r.on("shown.bs.modal",function(t){var e=n(t.currentTarget);e.find(s.footer).find(".t3js-active").first().focus(),e.find(s.iconPlaceholder).each(function(t,e){i.getIcon(n(e).data("icon"),i.sizes.small,null,null,i.markupIdentifiers.inline).done(function(t){l.currentModal.find(s.iconPlaceholder+"[data-icon="+n(t).data("identifier")+"]").replaceWith(t)})})}),r.on("hidden.bs.modal",function(t){if(l.instances.length>0){var e=l.instances.length-1;l.instances.splice(e,1),l.currentModal=l.instances[e-1]}r.trigger("modal-destroyed"),n(t.currentTarget).remove(),l.instances.length>0&&n("body").addClass("modal-open")}),r.on("show.bs.modal",function(t){l.currentModal=n(t.currentTarget),l.instances.push(l.currentModal)}),r.on("modal-dismiss",function(t){n(t.currentTarget).modal("hide")}),t.callback&&t.callback(r),r.modal()},t}(),y=null;try{parent&&parent.window.TYPO3&&parent.window.TYPO3.Modal?(parent.window.TYPO3.Modal.initializeMarkupTrigger(document),y=parent.window.TYPO3.Modal):top&&top.TYPO3.Modal&&(top.TYPO3.Modal.initializeMarkupTrigger(document),y=top.TYPO3.Modal)}catch(t){}return y||(y=new p(new l),TYPO3.Modal=y),y});
\ No newline at end of file
index aecb344..ba76bc6 100644 (file)
@@ -20,10 +20,13 @@ define([
   'jquery',
   'd3',
   'TYPO3/CMS/Backend/Modal',
-  'TYPO3/CMS/Backend/Severity'
-], function($, d3, Modal, Severity) {
+  'TYPO3/CMS/Backend/Severity',
+  'TYPO3/CMS/Core/SecurityUtility'
+], function($, d3, Modal, Severity, SecurityUtility) {
   'use strict';
 
+  var securityUtility = new SecurityUtility();
+
   /**
    * PageTreeDragDrop class
    *
@@ -841,16 +844,25 @@ define([
      * @returns {String}
      */
     template: function(icon, name) {
-      return '<div class="node-dd node-dd--nodrop">' +
-        '<div class="node-dd__ctrl-icon">' +
-        '</div>' +
-        '<div class="node-dd__text">' +
-        '<span class="node-dd__icon">' +
-        '<svg aria-hidden="true" width="16px" height="16px"><use xlink:href="' + icon + '"/></svg>' +
-        '</span>' +
-        '<span class="node-dd__name">' + name + '</span>' +
-        '</div>' +
-        '</div>';
+      return $('<div>').append(
+        $('<div>', {'class': 'node-dd node-dd--nodrop'}).append(
+          $('<div>', {'class': 'node-dd__ctrl-icon'}),
+          $('<div>', {'class': 'node-dd__text'}).append(
+            $('<span>', {'class': 'node-dd__icon'}).append(
+              $('<svg>', {
+                'aria-hidden': 'true',
+                'width': '16px',
+                'height': '16px'
+              }).append(
+                $('<use>', {
+                  'xlink:href': icon
+                })
+              )
+            ),
+            $('<span>', {'class': 'node-dd__name'}).text(securityUtility.encodeHtml(name))
+          )
+        )
+      ).html();
     }
   };
 
diff --git a/typo3/sysext/core/Resources/Private/TypeScript/SecurityUtility.ts b/typo3/sysext/core/Resources/Private/TypeScript/SecurityUtility.ts
new file mode 100644 (file)
index 0000000..99d6559
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Module: TYPO3/CMS/Core/SecurityUtility
+ * contains method to escape input to prevent XSS and other security related things
+ * @exports TYPO3/CMS/Core/SecurityUtility
+ */
+class SecurityUtility {
+  private readonly documentRef: Document;
+
+  /**
+   * @param {Document} documentRef
+   */
+  constructor(documentRef: Document = document) {
+    this.documentRef = documentRef;
+  }
+
+  /**
+   * Encodes HTML to use according entities. Behavior is similar to PHP's
+   * htmlspecialchars. Input might contain XSS, output has it encoded.
+   *
+   * @param {string} value Input value to be encoded
+   * @param {boolean} doubleEncode (default `true`)
+   * @return {string}
+   */
+  public encodeHtml(value: string, doubleEncode: boolean = true): string {
+    let anvil: HTMLSpanElement = this.createAnvil();
+    if (!doubleEncode) {
+      // decode HTML entities step-by-step
+      // but NEVER(!) as a whole, since that would allow XSS
+      value = value.replace(/&[#A-Za-z0-9]+;/g, (html: string) => {
+        anvil.innerHTML = html;
+        return anvil.innerText;
+      });
+    }
+    // apply arbitrary data a text node
+    // thus browser is capable of properly encoding
+    anvil.innerText = value;
+    return anvil.innerHTML;
+  }
+
+  /**
+   * @param {string} value
+   */
+  public debug(value: string): void {
+    if (value !== this.encodeHtml(value)) {
+      console.warn('XSS?!', value);
+    }
+  }
+
+  /**
+   * @return {HTMLSpanElement}
+   */
+  private createAnvil(): HTMLSpanElement {
+    return this.documentRef.createElement('span');
+  }
+}
+
+export = SecurityUtility;
diff --git a/typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js b/typo3/sysext/core/Resources/Public/JavaScript/SecurityUtility.js
new file mode 100644 (file)
index 0000000..6c8dc08
--- /dev/null
@@ -0,0 +1,13 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+define(["require","exports"],function(e,n){"use strict";return function(){function e(e){void 0===e&&(e=document),this.documentRef=e}return e.prototype.encodeHtml=function(e,n){void 0===n&&(n=!0);var t=this.createAnvil();return n||(e=e.replace(/&[#A-Za-z0-9]+;/g,function(e){return t.innerHTML=e,t.innerText})),t.innerText=e,t.innerHTML},e.prototype.debug=function(e){e!==this.encodeHtml(e)&&console.warn("XSS?!",e)},e.prototype.createAnvil=function(){return this.documentRef.createElement("span")},e}()});
\ No newline at end of file