]> BookStack Code Mirror - bookstack/commitdiff
Made MD editor display a sandboxed iframe
authorDan Brown <redacted>
Mon, 26 Aug 2019 11:16:50 +0000 (12:16 +0100)
committerDan Brown <redacted>
Mon, 26 Aug 2019 11:16:50 +0000 (12:16 +0100)
- Also added escaping of srcdoc elements in escape logic.

Related to #1531

app/Entities/Repos/EntityRepo.php
resources/assets/js/components/markdown-editor.js
resources/assets/sass/_forms.scss
resources/assets/sass/_layout.scss
resources/views/pages/markdown-editor.blade.php
tests/Entity/PageContentTest.php

index 7ca25b785286cb7f4e5e44ccbb4216c1c1881c4e..996873bccaa1629dbbee56e7bca8b60c7cf2e776 100644 (file)
@@ -766,7 +766,7 @@ class EntityRepo
         }
 
         // Remove data or JavaScript iFrames
-        $badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')]');
+        $badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')] | //*[@srcdoc]');
         foreach ($badIframes as $badIframe) {
             $badIframe->parentNode->removeChild($badIframe);
         }
index 7cb56eef831ee5fb75636a1f91345a505dd528a8..ac77cb4594b2fb00875f09976b2634ac5a3d58e2 100644 (file)
@@ -18,6 +18,8 @@ class MarkdownEditor {
         this.markdown.use(mdTasksLists, {label: true});
 
         this.display = this.elem.querySelector('.markdown-display');
+        this.displayDoc = this.display.contentDocument;
+        this.displayStylesLoaded = false;
         this.input = this.elem.querySelector('textarea');
         this.htmlInput = this.elem.querySelector('input[name=html]');
         this.cm = code.markdownEditor(this.input);
@@ -38,7 +40,7 @@ class MarkdownEditor {
         let lastClick = 0;
 
         // Prevent markdown display link click redirect
-        this.display.addEventListener('click', event => {
+        this.displayDoc.addEventListener('click', event => {
             let isDblClick = Date.now() - lastClick < 300;
 
             let link = event.target.closest('a');
@@ -96,17 +98,37 @@ class MarkdownEditor {
 
     // Update the input content and render the display.
     updateAndRender() {
-        let content = this.cm.getValue();
+        const content = this.cm.getValue();
         this.input.value = content;
-        let html = this.markdown.render(content);
+        const html = this.markdown.render(content);
         window.$events.emit('editor-html-change', html);
         window.$events.emit('editor-markdown-change', content);
-        this.display.innerHTML = html;
+
+        // Set body content
+        this.displayDoc.body.className = 'page-content';
+        this.displayDoc.body.innerHTML = html;
         this.htmlInput.value = html;
+
+        // Copy styles from page head and set custom styles for editor
+        this.loadStylesIntoDisplay();
+    }
+
+    loadStylesIntoDisplay() {
+        if (this.displayStylesLoaded) return;
+        this.displayDoc.documentElement.className = 'markdown-editor-display';
+
+        this.displayDoc.head.innerHTML = '';
+        const styles = document.head.querySelectorAll('style,link[rel=stylesheet]');
+        for (let style of styles) {
+            const copy = style.cloneNode(true);
+            this.displayDoc.head.appendChild(copy);
+        }
+
+        this.displayStylesLoaded = true;
     }
 
     onMarkdownScroll(lineCount) {
-        const elems = this.display.children;
+        const elems = this.displayDoc.body.children;
         if (elems.length <= lineCount) return;
 
         const topElem = (lineCount === -1) ? elems[elems.length-1] : elems[lineCount];
index 2f34dc0926cdb4e27c813f27769fbbe230a647f8..64308b29e725bbe0470189b0e259e31630ee0b73 100644 (file)
 }
 
 .markdown-display {
-  padding: 0 $-m 0;
   margin-left: -1px;
-  overflow-y: scroll;
-  &.page-content {
-    margin: 0 auto;
-    width: 100%;
-    max-width: 100%;
+}
+
+.markdown-editor-display {
+  background-color: #FFFFFF;
+  body {
+    background-color: #FFFFFF;
+    padding-left: 16px;
+    padding-right: 16px;
   }
   [drawio-diagram]:hover {
     outline: 2px solid var(--color-primary);
index a280e4ed1f25514ad689b590dda7522d3aba2caa..1a7ff2cab029e615c9f7ad7025b4b4fcc8bc2b9a 100644 (file)
@@ -116,6 +116,7 @@ body.flexbox {
   min-height: 0;
   max-width: 100%;
   position: relative;
+  overflow-y: hidden;
 }
 
 .flex {
index 87bde33ac913d474b55137aec76a12d053a57ad4..d4f6323b01188538f1919c3453bb8d32e54c45e0 100644 (file)
@@ -28,8 +28,7 @@
         <div class="editor-toolbar">
             <div class="editor-toolbar-label">{{ trans('entities.pages_md_preview') }}</div>
         </div>
-        <div class="markdown-display page-content">
-        </div>
+        <iframe class="markdown-display" sandbox="allow-same-origin"></iframe>
     </div>
     <input type="hidden" name="html"/>
 
index b447a7c5d87f73c39d87c14f22a3bbcb6c81b40c..e812d5bfe7fece4d5bebda333b353a1668a75887 100644 (file)
@@ -118,7 +118,7 @@ class PageContentTest extends TestCase
             '<iframe SRC=" javascript: alert(document.cookie)"></iframe>',
             '<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
             '<iframe src=" data:text/html;base64,PHNjcmlwdD5hbGVydCgnaGVsbG8nKTwvc2NyaXB0Pg==" frameborder="0"></iframe>',
-
+            '<iframe srcdoc="<script>window.alert(document.cookie)</script>"></iframe>'
         ];
 
         $this->asEditor();