From ae0b15be3f272953af16f555f460a275f39f5ab3 Mon Sep 17 00:00:00 2001 From: Franco Martin Borrelli Date: Wed, 6 May 2020 19:35:03 -0300 Subject: [PATCH] Sanitize html input --- index.js | 580 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 330 insertions(+), 250 deletions(-) diff --git a/index.js b/index.js index 0b28388..92da38f 100644 --- a/index.js +++ b/index.js @@ -1,54 +1,83 @@ var URL = window.URL || window.webkitURL || window.mozURL || window.msURL; -navigator.saveBlob = navigator.saveBlob || navigator.msSaveBlob || navigator.mozSaveBlob || navigator.webkitSaveBlob; -window.saveAs = window.saveAs || window.webkitSaveAs || window.mozSaveAs || window.msSaveAs; +navigator.saveBlob = + navigator.saveBlob || + navigator.msSaveBlob || + navigator.mozSaveBlob || + navigator.webkitSaveBlob; +window.saveAs = + window.saveAs || window.webkitSaveAs || window.mozSaveAs || window.msSaveAs; // Because highlight.js is a bit awkward at times var languageOverrides = { - js: 'javascript', - html: 'xml' + js: "javascript", + html: "xml", }; var livestyles; emojify.setConfig({ - img_dir: 'emoji' + img_dir: "emoji", }); var md = markdownit({ - html: true, - highlight: function(code, lang) { - if (languageOverrides[lang]) lang = languageOverrides[lang]; - if (lang && hljs.getLanguage(lang)) { - try { - return hljs.highlight(lang, code).value; - } catch (e) {} - } - return ''; - } - }) - .use(markdownitFootnote); + html: true, + highlight: function (code, lang) { + if (languageOverrides[lang]) lang = languageOverrides[lang]; + if (lang && hljs.getLanguage(lang)) { + try { + return hljs.highlight(lang, code).value; + } catch (e) {} + } + return ""; + }, +}).use(markdownitFootnote); var hashto; +function isOdd(x) { + return x & 1; +} -function update(e) { - setOutput(e.getValue()); +function sanitize(html) { + var temp = document.createElement("div"); + temp.textContent = html; - //If a title is added to the document it will be the new document.title, otherwise use default - var headerElements = document.querySelectorAll('h1'); - if (headerElements.length > 0 && headerElements[0].textContent.length > 0) { - title = headerElements[0].textContent; - } else { - title = 'Markdown Editor' - } + html = temp.innerHTML; - //To avoid to much title changing we check if is not the same as before - oldTitle = document.title; - if (oldTitle != title) { - oldTitle = title; - document.title = title; - } - //clearTimeout(hashto); - //hashto = setTimeout(updateHash, 1000); + return html + .split("```") + .map((subHTML, index) => { + if (!isOdd(index)) return subHTML; + + let old; + do { + old = subHTML; + subHTML = subHTML.replace("<", "<").replace(">", "/>"); + } while (subHTML !== old); + + return subHTML; + }) + .join("```"); +} + +function update(e) { + setOutput(sanitize(e.getValue())); + + //If a title is added to the document it will be the new document.title, otherwise use default + var headerElements = document.querySelectorAll("h1"); + if (headerElements.length > 0 && headerElements[0].textContent.length > 0) { + title = headerElements[0].textContent; + } else { + title = "Markdown Editor"; + } + + //To avoid to much title changing we check if is not the same as before + oldTitle = document.title; + if (oldTitle != title) { + oldTitle = title; + document.title = title; + } + //clearTimeout(hashto); + //hashto = setTimeout(updateHash, 1000); } /* @@ -57,311 +86,362 @@ If regex matches the string to task-list markdown format, then the task-list is rendered to its correct form. User: @austinmm */ -var render_tasklist = function(str){ - // Checked task-list box match - if(str.match(/
  • \[x\]\s+\w+/gi)){ - str = str.replace(/(\[x\]\s+)(\w+)/gi, - `$1 style="list-style-type: none;">\[x\]\s+\w+/gi)) { + str = str.replace( + /(\[x\]\s+)(\w+)/gi, + `$1 style="list-style-type: none;"> $3`); - } - // Unchecked task-list box match - if (str.match(/
  • \[ \]\s+\w+/gi)){ - str = str.replace(/(\[ \]\s+)(\w+)/gi, - `$1 style="list-style-type: none;"> $3` + ); + } + // Unchecked task-list box match + if (str.match(/
  • \[ \]\s+\w+/gi)) { + str = str.replace( + /(\[ \]\s+)(\w+)/gi, + `$1 style="list-style-type: none;"> $3`); - } - return str -} + margin: 0 0.2em 0 -1.3em;" disabled> $3` + ); + } + return str; +}; function setOutput(val) { - val = val.replace(/((.*?\n)*?.*?)<\/equation>/ig, function(a, b) { - return ''; - }); - - var out = document.getElementById('out'); - var old = out.cloneNode(true); - out.innerHTML = md.render(val); - emojify.run(out); - console.log(out.innerHTML); - // Checks if there are any task-list present in out.innerHTML - out.innerHTML = render_tasklist(out.innerHTML); - - var allold = old.getElementsByTagName("*"); - if (allold === undefined) return; - - var allnew = out.getElementsByTagName("*"); - if (allnew === undefined) return; - - for (var i = 0, max = Math.min(allold.length, allnew.length); i < max; i++) { - if (!allold[i].isEqualNode(allnew[i])) { - out.scrollTop = allnew[i].offsetTop; - return; - } + val = val.replace(/((.*?\n)*?.*?)<\/equation>/gi, function (a, b) { + return ( + '' + ); + }); + + var out = document.getElementById("out"); + var old = out.cloneNode(true); + out.innerHTML = md.render(val); + emojify.run(out); + console.log(out.innerHTML); + // Checks if there are any task-list present in out.innerHTML + out.innerHTML = render_tasklist(out.innerHTML); + + var allold = old.getElementsByTagName("*"); + if (allold === undefined) return; + + var allnew = out.getElementsByTagName("*"); + if (allnew === undefined) return; + + for (var i = 0, max = Math.min(allold.length, allnew.length); i < max; i++) { + if (!allold[i].isEqualNode(allnew[i])) { + out.scrollTop = allnew[i].offsetTop; + return; } + } } CodeMirrorSpellChecker({ - codeMirrorInstance: CodeMirror, + codeMirrorInstance: CodeMirror, }); -var editor = CodeMirror.fromTextArea(document.getElementById('code'), { - mode: "spell-checker", - backdrop: "gfm", - lineNumbers: false, - matchBrackets: true, - lineWrapping: true, - theme: 'base16-light', - extraKeys: { - "Enter": "newlineAndIndentContinueMarkdownList" - } +var editor = CodeMirror.fromTextArea(document.getElementById("code"), { + mode: "spell-checker", + backdrop: "gfm", + lineNumbers: false, + matchBrackets: true, + lineWrapping: true, + theme: "base16-light", + extraKeys: { + Enter: "newlineAndIndentContinueMarkdownList", + }, }); -editor.on('change', update); - -function selectionChanger(selection,operator,endoperator){ - if(selection == ""){ - return operator; - } - if(!endoperator){ - endoperator = operator - } - var isApplied = selection.slice(0, 2) === operator && selection.slice(-2) === endoperator; - var finaltext = isApplied ? selection.slice(2, -2) : operator + selection + endoperator; - return finaltext; +editor.on("change", update); + +function selectionChanger(selection, operator, endoperator) { + if (selection == "") { + return operator; + } + if (!endoperator) { + endoperator = operator; + } + var isApplied = + selection.slice(0, 2) === operator && selection.slice(-2) === endoperator; + var finaltext = isApplied + ? selection.slice(2, -2) + : operator + selection + endoperator; + return finaltext; } editor.addKeyMap({ - // bold - 'Ctrl-B': function(cm) { - cm.replaceSelection(selectionChanger(cm.getSelection(),'**')); - }, - // italic - 'Ctrl-I': function(cm) { - cm.replaceSelection(selectionChanger(cm.getSelection(),'_')); - }, - // code - 'Ctrl-K': function(cm) { - cm.replaceSelection(selectionChanger(cm.getSelection(),'`')); - }, - // keyboard shortcut - 'Ctrl-L': function(cm) { - cm.replaceSelection(selectionChanger(cm.getSelection(),'','')); - } + // bold + "Ctrl-B": function (cm) { + cm.replaceSelection(selectionChanger(cm.getSelection(), "**")); + }, + // italic + "Ctrl-I": function (cm) { + cm.replaceSelection(selectionChanger(cm.getSelection(), "_")); + }, + // code + "Ctrl-K": function (cm) { + cm.replaceSelection(selectionChanger(cm.getSelection(), "`")); + }, + // keyboard shortcut + "Ctrl-L": function (cm) { + cm.replaceSelection(selectionChanger(cm.getSelection(), "", "")); + }, }); -document.addEventListener('drop', function(e) { +document.addEventListener( + "drop", + function (e) { e.preventDefault(); e.stopPropagation(); var reader = new FileReader(); - reader.onload = function(e) { - editor.setValue(e.target.result); + reader.onload = function (e) { + editor.setValue(e.target.result); }; reader.readAsText(e.dataTransfer.files[0]); -}, false); + }, + false +); //Print the document named as the document title encoded to avoid strange chars and spaces function saveAsMarkdown() { - save(editor.getValue(), document.title.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/\s]/gi, '') + ".md"); + save( + editor.getValue(), + document.title.replace( + /[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/\s]/gi, + "" + ) + ".md" + ); } //Print the document named as the document title encoded to avoid strange chars and spaces function saveAsHtml() { - save(document.getElementById('out').innerHTML, document.title.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/\s]/gi, '') + ".html"); + save( + document.getElementById("out").innerHTML, + document.title.replace( + /[`~!@#$%^&*()_|+\-=?;:'",.<>\{\}\[\]\\\/\s]/gi, + "" + ) + ".html" + ); } -document.getElementById('saveas-markdown').addEventListener('click', function() { +document + .getElementById("saveas-markdown") + .addEventListener("click", function () { saveAsMarkdown(); hideMenu(); -}); + }); -document.getElementById('saveas-html').addEventListener('click', function() { - saveAsHtml(); - hideMenu(); +document.getElementById("saveas-html").addEventListener("click", function () { + saveAsHtml(); + hideMenu(); }); function save(code, name) { - var blob = new Blob([code], { - type: 'text/plain' - }); - if (window.saveAs) { - window.saveAs(blob, name); - } else if (navigator.saveBlob) { - navigator.saveBlob(blob, name); - } else { - url = URL.createObjectURL(blob); - var link = document.createElement("a"); - link.setAttribute("href", url); - link.setAttribute("download", name); - var event = document.createEvent('MouseEvents'); - event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null); - link.dispatchEvent(event); - } + var blob = new Blob([code], { + type: "text/plain", + }); + if (window.saveAs) { + window.saveAs(blob, name); + } else if (navigator.saveBlob) { + navigator.saveBlob(blob, name); + } else { + url = URL.createObjectURL(blob); + var link = document.createElement("a"); + link.setAttribute("href", url); + link.setAttribute("download", name); + var event = document.createEvent("MouseEvents"); + event.initMouseEvent( + "click", + true, + true, + window, + 1, + 0, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + null + ); + link.dispatchEvent(event); + } } var menuVisible = false; -var menu = document.getElementById('menu'); +var menu = document.getElementById("menu"); function showMenu() { - menuVisible = true; - menu.style.display = 'block'; + menuVisible = true; + menu.style.display = "block"; } function hideMenu() { - menuVisible = false; - menu.style.display = 'none'; + menuVisible = false; + menu.style.display = "none"; } function openFile(evt) { - if (window.File && window.FileReader && window.FileList && window.Blob) { - var files = evt.target.files; - console.log(files); - var reader = new FileReader(); - reader.onload = function(file) { - console.log(file.target.result); - editor.setValue(file.target.result); - return true; - }; - reader.readAsText(files[0]); - - } else { - alert('The File APIs are not fully supported in this browser.'); - } + if (window.File && window.FileReader && window.FileList && window.Blob) { + var files = evt.target.files; + console.log(files); + var reader = new FileReader(); + reader.onload = function (file) { + console.log(file.target.result); + editor.setValue(file.target.result); + return true; + }; + reader.readAsText(files[0]); + } else { + alert("The File APIs are not fully supported in this browser."); + } } -document.getElementById('close-menu').addEventListener('click', function() { - hideMenu(); +document.getElementById("close-menu").addEventListener("click", function () { + hideMenu(); }); -document.addEventListener('keydown', function(e) { - if (e.keyCode == 83 && (e.ctrlKey || e.metaKey)) { - if ( localStorage.getItem('content') == editor.getValue() ) { - e.preventDefault(); - return false; - } - e.shiftKey ? showMenu() : saveInBrowser(); - - e.preventDefault(); - return false; +document.addEventListener("keydown", function (e) { + if (e.keyCode == 83 && (e.ctrlKey || e.metaKey)) { + if (localStorage.getItem("content") == editor.getValue()) { + e.preventDefault(); + return false; } + e.shiftKey ? showMenu() : saveInBrowser(); - if (e.keyCode === 27 && menuVisible) { - hideMenu(); + e.preventDefault(); + return false; + } - e.preventDefault(); - return false; - } + if (e.keyCode === 27 && menuVisible) { + hideMenu(); + + e.preventDefault(); + return false; + } }); function clearEditor() { - editor.setValue(""); + editor.setValue(""); } function saveInBrowser() { - var text = editor.getValue(); - if (localStorage.getItem('content')) { - swal({ - title: "Existing Data Detected", - text: "You will overwrite the data previously saved!", - type: "warning", - showCancelButton: true, - confirmButtonColor: "#DD6B55", - confirmButtonText: "Yes, overwrite!", - closeOnConfirm: false - }, - function() { - localStorage.setItem('content', text); - swal("Saved", "Your Document has been saved.", "success"); - }); - } else { - localStorage.setItem('content', text); + var text = editor.getValue(); + if (localStorage.getItem("content")) { + swal( + { + title: "Existing Data Detected", + text: "You will overwrite the data previously saved!", + type: "warning", + showCancelButton: true, + confirmButtonColor: "#DD6B55", + confirmButtonText: "Yes, overwrite!", + closeOnConfirm: false, + }, + function () { + localStorage.setItem("content", text); swal("Saved", "Your Document has been saved.", "success"); - } - console.log("Saved"); + } + ); + } else { + localStorage.setItem("content", text); + swal("Saved", "Your Document has been saved.", "success"); + } + console.log("Saved"); } function toggleNightMode(button) { - button.classList.toggle('selected'); - document.getElementById('toplevel').classList.toggle('nightmode'); + button.classList.toggle("selected"); + document.getElementById("toplevel").classList.toggle("nightmode"); } function toggleReadMode(button) { - button.classList.toggle('selected'); - document.getElementById('out').classList.toggle('focused'); - document.getElementById('in').classList.toggle('hidden'); + button.classList.toggle("selected"); + document.getElementById("out").classList.toggle("focused"); + document.getElementById("in").classList.toggle("hidden"); } function toggleSpellCheck(button) { - button.classList.toggle('selected'); - document.body.classList.toggle('no-spellcheck'); + button.classList.toggle("selected"); + document.body.classList.toggle("no-spellcheck"); } function updateHash() { - window.location.hash = btoa( // base64 so url-safe - RawDeflate.deflate( // gzip - unescape(encodeURIComponent( // convert to utf8 - editor.getValue() - )) + window.location.hash = btoa( + // base64 so url-safe + RawDeflate.deflate( + // gzip + unescape( + encodeURIComponent( + // convert to utf8 + editor.getValue() ) - ); + ) + ) + ); } function processQueryParams() { - var params = window.location.search.split('?')[1]; - if (window.location.hash) { - document.getElementById('readbutton').click(); // Show reading view + var params = window.location.search.split("?")[1]; + if (window.location.hash) { + document.getElementById("readbutton").click(); // Show reading view + } + if (params) { + var obj = {}; + params.split("&").forEach(function (elem) { + obj[elem.split("=")[0]] = elem.split("=")[1]; + }); + if (obj.reading === "false") { + document.getElementById("readbutton").click(); // Hide reading view } - if (params) { - var obj = {}; - params.split('&').forEach(function(elem) { - obj[elem.split('=')[0]] = elem.split('=')[1]; - }); - if (obj.reading === 'false') { - document.getElementById('readbutton').click(); // Hide reading view - } - if (obj.dark === 'true') { - document.getElementById('nightbutton').click(); // Show night view - } + if (obj.dark === "true") { + document.getElementById("nightbutton").click(); // Show night view } + } } function start() { - processQueryParams(); - if (window.location.hash) { - var h = window.location.hash.replace(/^#/, ''); - if (h.slice(0, 5) == 'view:') { - setOutput(decodeURIComponent(escape(RawDeflate.inflate(atob(h.slice(5)))))); - document.body.className = 'view'; - } else { - editor.setValue( - decodeURIComponent(escape( - RawDeflate.inflate( - atob( - h - ) - ) - )) - ); - } - } else if (localStorage.getItem('content')) { - editor.setValue(localStorage.getItem('content')); + processQueryParams(); + if (window.location.hash) { + var h = window.location.hash.replace(/^#/, ""); + if (h.slice(0, 5) == "view:") { + setOutput( + decodeURIComponent(escape(RawDeflate.inflate(atob(h.slice(5))))) + ); + document.body.className = "view"; + } else { + editor.setValue(decodeURIComponent(escape(RawDeflate.inflate(atob(h))))); } - update(editor); - editor.focus(); - document.getElementById('fileInput').addEventListener('change', openFile, false); + } else if (localStorage.getItem("content")) { + editor.setValue(localStorage.getItem("content")); + } + update(editor); + editor.focus(); + document + .getElementById("fileInput") + .addEventListener("change", openFile, false); } window.addEventListener("beforeunload", function (e) { - if (!editor.getValue() || editor.getValue() == localStorage.getItem('content')) { - return; - } - var confirmationMessage = 'It looks like you have been editing something. ' - + 'If you leave before saving, your changes will be lost.'; - (e || window.event).returnValue = confirmationMessage; //Gecko + IE - return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc. + if ( + !editor.getValue() || + editor.getValue() == localStorage.getItem("content") + ) { + return; + } + var confirmationMessage = + "It looks like you have been editing something. " + + "If you leave before saving, your changes will be lost."; + (e || window.event).returnValue = confirmationMessage; //Gecko + IE + return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc. }); start();