From 7bbea288312bf89569f33db71d735b77c81cc927 Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Thu, 24 Aug 2017 13:53:42 +0200 Subject: [PATCH] minor anti-xss changes... --- customize.dist/ckeditor-config.js | 1 - www/common/sframe-chainpad-netflux-inner.js | 4 +- www/common/sframe-chainpad-netflux-outer.js | 16 +- www/pad2/ckeditor-inner.html | 3 + www/pad2/main.js | 23 +- www/pad2/wysiwygarea-plugin.js | 735 ++++++++++++++++++++ 6 files changed, 765 insertions(+), 17 deletions(-) create mode 100644 www/pad2/ckeditor-inner.html create mode 100644 www/pad2/wysiwygarea-plugin.js diff --git a/customize.dist/ckeditor-config.js b/customize.dist/ckeditor-config.js index a39616e6e..ac7989294 100644 --- a/customize.dist/ckeditor-config.js +++ b/customize.dist/ckeditor-config.js @@ -61,7 +61,6 @@ CKEDITOR.editorConfig = function( config ) { // every part of ckeditor will get in the browser cache. var fix = function (x) { if (x.map) { return x.map(fix); } - console.log('> ' + x); return (/\/bower_components\/.*\.css$/.test(x)) ? (x + '?ver=' + CKEDITOR.timestamp) : x; }; CKEDITOR.tools._buildStyleHtml = CKEDITOR.tools.buildStyleHtml; diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js index 04659fe17..a1359f522 100644 --- a/www/common/sframe-chainpad-netflux-inner.js +++ b/www/common/sframe-chainpad-netflux-inner.js @@ -67,7 +67,9 @@ define([ logLevel: logLevel }); chainpad.onMessage(function(message, cb) { - sframeChan.query('Q_RT_MESSAGE', message, cb); + sframeChan.query('Q_RT_MESSAGE', message, function (ret) { + if (ret === 'OK') { cb(); } + }); }); chainpad.onPatch(function () { onRemote({ realtime: chainpad }); diff --git a/www/common/sframe-chainpad-netflux-outer.js b/www/common/sframe-chainpad-netflux-outer.js index f1186d79d..ff6e522c8 100644 --- a/www/common/sframe-chainpad-netflux-outer.js +++ b/www/common/sframe-chainpad-netflux-outer.js @@ -153,12 +153,16 @@ define([], function () { if (message) { // Do not remove wcObject, it allows us to use a new 'wc' without changing the handler if we // want to keep the same chainpad (realtime) object - wcObject.wc.bcast(message).then(function() { - cb('OK'); - }, function(err) { - // The message has not been sent, display the error. - console.error(err); - }); + try { + wcObject.wc.bcast(message).then(function() { + cb('OK'); + }, function(err) { + // The message has not been sent, display the error. + console.error(err); + }); + } catch (e) { + cb('ERROR'); + } } }; queue.forEach(function (arr) { messageFromInner(arr[0], arr[1]); }); diff --git a/www/pad2/ckeditor-inner.html b/www/pad2/ckeditor-inner.html new file mode 100644 index 000000000..511bd5f40 --- /dev/null +++ b/www/pad2/ckeditor-inner.html @@ -0,0 +1,3 @@ + +Rich Text Editor, editor1


diff --git a/www/pad2/main.js b/www/pad2/main.js index 3027bab63..497082f1f 100644 --- a/www/pad2/main.js +++ b/www/pad2/main.js @@ -117,13 +117,6 @@ define([ 'AUDIO' ]; - var openLink = function (e) { - var el = e.currentTarget; - if (!el || el.nodeName !== 'A') { return; } - var href = el.getAttribute('href'); - if (href) { window.open(href, '_blank'); } - }; - var getHTML = function (inner) { return ('\n' + '\n' + inner.innerHTML); }; @@ -303,12 +296,21 @@ define([ el.setAttribute('class', 'non-realtime'); }); - var documentBody = $html.find('iframe')[0].contentWindow.document.body; + var ifrWindow = $html.find('iframe')[0].contentWindow; + + var documentBody = ifrWindow.document.body; var inner = window.inner = documentBody; var cursor = module.cursor = Cursor(inner); + var openLink = function (e) { + var el = e.currentTarget; + if (!el || el.nodeName !== 'A') { return; } + var href = el.getAttribute('href'); + if (href) { ifrWindow.open(href, '_blank'); } + }; + var setEditable = module.setEditable = function (bool) { if (bool) { $(inner).css({ @@ -733,7 +735,10 @@ define([ var common; nThen(function (waitFor) { - ckEditorAvailable(waitFor(function (ck) { Ckeditor = ck; })); + ckEditorAvailable(waitFor(function (ck) { + Ckeditor = ck; + require(['/pad2/wysiwygarea-plugin.js'], waitFor()); + })); $(waitFor(function () { Cryptpad.addLoadingScreen(); })); diff --git a/www/pad2/wysiwygarea-plugin.js b/www/pad2/wysiwygarea-plugin.js new file mode 100644 index 000000000..c7f827000 --- /dev/null +++ b/www/pad2/wysiwygarea-plugin.js @@ -0,0 +1,735 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +/** + * @fileOverview The WYSIWYG Area plugin. It registers the "wysiwyg" editing + * mode, which handles the main editing area space. + */ + +define(['/api/config'], function (ApiConfig) { + var framedWysiwyg; + var iframe; + + CKEDITOR.plugins.registered.wysiwygarea.init = function( editor ) { + if ( editor.config.fullPage ) { + editor.addFeature( { + allowedContent: 'html head title; style [media,type]; body (*)[id]; meta link [*]', + requiredContent: 'body' + } ); + } + + editor.addMode( 'wysiwyg', function( callback ) { + var src = 'document.open();' + + // In IE, the document domain must be set any time we call document.open(). + ( CKEDITOR.env.ie ? '(' + CKEDITOR.tools.fixDomain + ')();' : '' ) + + 'document.close();'; + + // With IE, the custom domain has to be taken care at first, + // for other browers, the 'src' attribute should be left empty to + // trigger iframe's 'load' event. + // Microsoft Edge throws "Permission Denied" if treated like an IE (http://dev.ckeditor.com/ticket/13441). + if ( CKEDITOR.env.air ) { + src = 'javascript:void(0)'; // jshint ignore:line + } else if ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) { + src = 'javascript:void(function(){' + encodeURIComponent( src ) + '}())'; // jshint ignore:line + } else { + src = ''; + } + + // CryptPad + src = '/pad/ckeditor-inner.html?' + ApiConfig.requireConf.urlArgs; + + iframe = CKEDITOR.dom.element.createFromHtml( '' ); + iframe.setStyles( { width: '100%', height: '100%' } ); + iframe.addClass( 'cke_wysiwyg_frame' ).addClass( 'cke_reset' ); + + // CryptPad + // this is impossible because ckeditor uses some (non-inline) script inside of the iframe... + //iframe.setAttribute('sandbox', 'allow-same-origin'); + + var contentSpace = editor.ui.space( 'contents' ); + contentSpace.append( iframe ); + + + // Asynchronous iframe loading is only required in IE>8 and Gecko (other reasons probably). + // Do not use it on WebKit as it'll break the browser-back navigation. + var useOnloadEvent = ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) || CKEDITOR.env.gecko; + if ( useOnloadEvent ) + iframe.on( 'load', onLoad ); + + var frameLabel = editor.title, + helpLabel = editor.fire( 'ariaEditorHelpLabel', {} ).label; + + if ( frameLabel ) { + if ( CKEDITOR.env.ie && helpLabel ) + frameLabel += ', ' + helpLabel; + + iframe.setAttribute( 'title', frameLabel ); + } + + if ( helpLabel ) { + var labelId = CKEDITOR.tools.getNextId(), + desc = CKEDITOR.dom.element.createFromHtml( '' + helpLabel + '' ); + + contentSpace.append( desc, 1 ); + iframe.setAttribute( 'aria-describedby', labelId ); + } + + // Remove the ARIA description. + editor.on( 'beforeModeUnload', function( evt ) { + evt.removeListener(); + if ( desc ) + desc.remove(); + } ); + + iframe.setAttributes( { + tabIndex: editor.tabIndex, + allowTransparency: 'true' + } ); + + // Execute onLoad manually for all non IE||Gecko browsers. + !useOnloadEvent && onLoad(); + + editor.fire( 'ariaWidget', iframe ); + + function onLoad( evt ) { + evt && evt.removeListener(); + var fw = new framedWysiwyg( editor, iframe.$.contentWindow.document.body ); + editor.editable( fw ); + editor.setData( editor.getData( 1 ), callback ); + } + } ); + }; + + /** + * Adds the path to a stylesheet file to the exisiting {@link CKEDITOR.config#contentsCss} value. + * + * **Note:** This method is available only with the `wysiwygarea` plugin and only affects + * classic editors based on it (so it does not affect inline editors). + * + * editor.addContentsCss( 'assets/contents.css' ); + * + * @since 4.4 + * @param {String} cssPath The path to the stylesheet file which should be added. + * @member CKEDITOR.editor + */ + CKEDITOR.editor.prototype.addContentsCss = function( cssPath ) { + var cfg = this.config, + curContentsCss = cfg.contentsCss; + + // Convert current value into array. + if ( !CKEDITOR.tools.isArray( curContentsCss ) ) + cfg.contentsCss = curContentsCss ? [ curContentsCss ] : []; + + cfg.contentsCss.push( cssPath ); + }; + + function onDomReady( win ) { + var editor = this.editor, + doc = win.document, + body = doc.body; + + // Remove helper scripts from the DOM. + var script = doc.getElementById( 'cke_actscrpt' ); + script && script.parentNode.removeChild( script ); + script = doc.getElementById( 'cke_shimscrpt' ); + script && script.parentNode.removeChild( script ); + script = doc.getElementById( 'cke_basetagscrpt' ); + script && script.parentNode.removeChild( script ); + + body.contentEditable = true; + + if ( CKEDITOR.env.ie ) { + // Don't display the focus border. + body.hideFocus = true; + + // Disable and re-enable the body to avoid IE from + // taking the editing focus at startup. (http://dev.ckeditor.com/ticket/141 / http://dev.ckeditor.com/ticket/523) + body.disabled = true; + body.removeAttribute( 'disabled' ); + } + + delete this._.isLoadingData; + + // Play the magic to alter element reference to the reloaded one. + this.$ = body; + + doc = new CKEDITOR.dom.document( doc ); + + this.setup(); + this.fixInitialSelection(); + + var editable = this; + + // Without it IE8 has problem with removing selection in nested editable. (http://dev.ckeditor.com/ticket/13785) + if ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) { + doc.getDocumentElement().addClass( doc.$.compatMode ); + } + + // Prevent IE/Edge from leaving a new paragraph/div after deleting all contents in body. (http://dev.ckeditor.com/ticket/6966, http://dev.ckeditor.com/ticket/13142) + if ( CKEDITOR.env.ie && !CKEDITOR.env.edge && editor.enterMode != CKEDITOR.ENTER_P ) { + removeSuperfluousElement( 'p' ); + } else if ( CKEDITOR.env.edge && editor.enterMode != CKEDITOR.ENTER_DIV ) { + removeSuperfluousElement( 'div' ); + } + + // Fix problem with cursor not appearing in Webkit and IE11+ when clicking below the body (http://dev.ckeditor.com/ticket/10945, http://dev.ckeditor.com/ticket/10906). + // Fix for older IEs (8-10 and QM) is placed inside selection.js. + if ( CKEDITOR.env.webkit || ( CKEDITOR.env.ie && CKEDITOR.env.version > 10 ) ) { + doc.getDocumentElement().on( 'mousedown', function( evt ) { + if ( evt.data.getTarget().is( 'html' ) ) { + // IE needs this timeout. Webkit does not, but it does not cause problems too. + setTimeout( function() { + editor.editable().focus(); + } ); + } + } ); + } + + // Config props: disableObjectResizing and disableNativeTableHandles handler. + objectResizeDisabler( editor ); + + // Enable dragging of position:absolute elements in IE. + try { + editor.document.$.execCommand( '2D-position', false, true ); + } catch ( e ) {} + + if ( CKEDITOR.env.gecko || CKEDITOR.env.ie && editor.document.$.compatMode == 'CSS1Compat' ) { + this.attachListener( this, 'keydown', function( evt ) { + var keyCode = evt.data.getKeystroke(); + + // PageUp OR PageDown + if ( keyCode == 33 || keyCode == 34 ) { + // PageUp/PageDown scrolling is broken in document + // with standard doctype, manually fix it. (http://dev.ckeditor.com/ticket/4736) + if ( CKEDITOR.env.ie ) { + setTimeout( function() { + editor.getSelection().scrollIntoView(); + }, 0 ); + } + // Page up/down cause editor selection to leak + // outside of editable thus we try to intercept + // the behavior, while it affects only happen + // when editor contents are not overflowed. (http://dev.ckeditor.com/ticket/7955) + else if ( editor.window.$.innerHeight > this.$.offsetHeight ) { + var range = editor.createRange(); + range[ keyCode == 33 ? 'moveToElementEditStart' : 'moveToElementEditEnd' ]( this ); + range.select(); + evt.data.preventDefault(); + } + } + } ); + } + + if ( CKEDITOR.env.ie ) { + // [IE] Iframe will still keep the selection when blurred, if + // focus is moved onto a non-editing host, e.g. link or button, but + // it becomes a problem for the object type selection, since the resizer + // handler attached on it will mark other part of the UI, especially + // for the dialog. (http://dev.ckeditor.com/ticket/8157) + // [IE<8 & Opera] Even worse For old IEs, the cursor will not vanish even if + // the selection has been moved to another text input in some cases. (http://dev.ckeditor.com/ticket/4716) + // + // Now the range restore is disabled, so we simply force IE to clean + // up the selection before blur. + this.attachListener( doc, 'blur', function() { + // Error proof when the editor is not visible. (http://dev.ckeditor.com/ticket/6375) + try { + doc.$.selection.empty(); + } catch ( er ) {} + } ); + } + + if ( CKEDITOR.env.iOS ) { + // [iOS] If touch is bound to any parent of the iframe blur happens on any touch + // event and body becomes the focused element (http://dev.ckeditor.com/ticket/10714). + this.attachListener( doc, 'touchend', function() { + win.focus(); + } ); + } + + var title = editor.document.getElementsByTag( 'title' ).getItem( 0 ); + // document.title is malfunctioning on Chrome, so get value from the element (http://dev.ckeditor.com/ticket/12402). + title.data( 'cke-title', title.getText() ); + + // [IE] JAWS will not recognize the aria label we used on the iframe + // unless the frame window title string is used as the voice label, + // backup the original one and restore it on output. + if ( CKEDITOR.env.ie ) + editor.document.$.title = this._.docTitle; + + CKEDITOR.tools.setTimeout( function() { + // Editable is ready after first setData. + if ( this.status == 'unloaded' ) + this.status = 'ready'; + + editor.fire( 'contentDom' ); + + if ( this._.isPendingFocus ) { + editor.focus(); + this._.isPendingFocus = false; + } + + setTimeout( function() { + editor.fire( 'dataReady' ); + }, 0 ); + }, 0, this ); + + function removeSuperfluousElement( tagName ) { + var lockRetain = false; + + // Superfluous elements appear after keydown + // and before keyup, so the procedure is as follows: + // 1. On first keydown mark all elements with + // a specified tag name as non-superfluous. + editable.attachListener( editable, 'keydown', function() { + var body = doc.getBody(), + retained = body.getElementsByTag( tagName ); + + if ( !lockRetain ) { + for ( var i = 0; i < retained.count(); i++ ) { + retained.getItem( i ).setCustomData( 'retain', true ); + } + lockRetain = true; + } + }, null, null, 1 ); + + // 2. On keyup remove all elements that were not marked + // as non-superfluous (which means they must have had appeared in the meantime). + // Also we should preserve all temporary elements inserted by editor – otherwise we'd likely + // leak fake selection's content into editable due to removing hidden selection container (http://dev.ckeditor.com/ticket/14831). + editable.attachListener( editable, 'keyup', function() { + var elements = doc.getElementsByTag( tagName ); + if ( lockRetain ) { + if ( elements.count() == 1 && !elements.getItem( 0 ).getCustomData( 'retain' ) && + !elements.getItem( 0 ).hasAttribute( 'data-cke-temp' ) ) { + elements.getItem( 0 ).remove( 1 ); + } + lockRetain = false; + } + } ); + } + } + + framedWysiwyg = CKEDITOR.tools.createClass( { + $: function() { + this.base.apply( this, arguments ); + + this._.frameLoadedHandler = CKEDITOR.tools.addFunction( function( win ) { + // Avoid opening design mode in a frame window thread, + // which will cause host page scrolling.(http://dev.ckeditor.com/ticket/4397) + CKEDITOR.tools.setTimeout( onDomReady, 0, this, win ); + }, this ); + + this._.docTitle = this.getWindow().getFrame().getAttribute( 'title' ); + }, + + base: CKEDITOR.editable, + + proto: { + setData: function( data, isSnapshot ) { + var editor = this.editor; + + if ( isSnapshot ) { + this.setHtml( data ); + this.fixInitialSelection(); + + // Fire dataReady for the consistency with inline editors + // and because it makes sense. (http://dev.ckeditor.com/ticket/10370) + editor.fire( 'dataReady' ); + } + else { + this._.isLoadingData = true; + editor._.dataStore = { id: 1 }; + + var config = editor.config, + fullPage = config.fullPage, + docType = config.docType; + + // Build the additional stuff to be included into . + var headExtra = CKEDITOR.tools.buildStyleHtml( iframeCssFixes() ).replace( /