From 52f694a714ea80ec980b5a612cdb1cd1bd113175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Sat, 19 May 2018 18:58:44 +0700 Subject: [PATCH 01/11] fixed null attachment --- browser/main/lib/dataApi/attachmentManagement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index efacd47cd..7c4b46bec 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -172,7 +172,7 @@ function getAttachmentsInContent (markdownContent) { * @returns {String[]} Absolute paths of the referenced attachments */ function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) { - const temp = getAttachmentsInContent(markdownContent) + const temp = getAttachmentsInContent(markdownContent) || [] const result = [] for (const relativePath of temp) { result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER))) From d6c28da3a8d61c149a2c5f9c6af46a1b57407f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Sat, 19 May 2018 19:39:08 +0700 Subject: [PATCH 02/11] added export escape html config --- browser/components/MarkdownPreview.js | 3 +- browser/main/lib/ConfigManager.js | 3 + .../main/modals/PreferencesModal/Export.js | 120 ++++++++++++++++++ browser/main/modals/PreferencesModal/index.js | 11 +- 4 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 browser/main/modals/PreferencesModal/Export.js diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 6646f7494..ef11a0cc0 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -15,6 +15,7 @@ import copy from 'copy-to-clipboard' import mdurl from 'mdurl' import exportNote from 'browser/main/lib/dataApi/exportNote' import { escapeHtmlCharacters } from 'browser/lib/utils' +import ConfigManager from 'browser/main/lib/ConfigManager' const { remote } = require('electron') const attachmentManagement = require('../main/lib/dataApi/attachmentManagement') @@ -218,7 +219,7 @@ export default class MarkdownPreview extends React.Component { const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams() const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) - let body = this.markdown.render(escapeHtmlCharacters(noteContent)) + let body = this.markdown.render(ConfigManager.get().exports.escapeHtml ? escapeHtmlCharacters(noteContent) : noteContent) const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath) diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 79fe0f5f0..5b2631911 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -65,6 +65,9 @@ export const DEFAULT_CONFIG = { token: '', username: '', password: '' + }, + exports: { + escapeHtml: true } } diff --git a/browser/main/modals/PreferencesModal/Export.js b/browser/main/modals/PreferencesModal/Export.js new file mode 100644 index 000000000..ae57cc14c --- /dev/null +++ b/browser/main/modals/PreferencesModal/Export.js @@ -0,0 +1,120 @@ +import React from 'react' +import ConfigManager from 'browser/main/lib/ConfigManager' +import i18n from 'browser/lib/i18n' +import styles from './ConfigTab.styl' +import CSSModules from 'browser/lib/CSSModules' +import store from 'browser/main/store' + +const electron = require('electron') +const ipc = electron.ipcRenderer + +class Export extends React.Component { + constructor(props) { + super(props) + this.state = { + config: props.config + } + } + + componentDidMount () { + this.handleSettingDone = () => { + this.setState({ExportAlert: { + type: 'success', + message: i18n.__('Successfully applied!') + }}) + } + this.handleSettingError = (err) => { + this.setState({ExportAlert: { + type: 'error', + message: err.message != null ? err.message : i18n.__('Error occurs!') + }}) + } + ipc.addListener('APP_SETTING_DONE', this.handleSettingDone) + ipc.addListener('APP_SETTING_ERROR', this.handleSettingError) + } + + componentWillUnmount () { + ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone) + ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError) + } + + handleUIChange (e) { + const newConfig = { + exports: { + escapeHtml: this.refs.escapeHtmlExport.checked + } + } + + this.setState({ config: newConfig }, () => { + const { exports } = this.props.config + this.currentConfig = { exports } + if (_.isEqual(this.currentConfig, this.state.config)) { + this.props.haveToSave() + } else { + this.props.haveToSave({ + tab: 'EXPORT', + type: 'warning', + message: i18n.__('You have to save!') + }) + } + }) + } + + handleSaveUIClick (e) { + const newConfig = { + exports: this.state.config.exports + } + + ConfigManager.set(newConfig) + + store.dispatch({ + type: 'SET_UI', + config: newConfig + }) + this.clearMessage() + this.props.haveToSave() + } + + clearMessage () { + _.debounce(() => { + this.setState({ + UiAlert: null + }) + }, 2000)() + } + + render () { + const ExportAlert = this.state.ExportAlert + const ExportAlertElement = ExportAlert != null + ?

+ {ExportAlert.message} +

+ : null + return ( +
+
+
{i18n.__('Export')}
+ +
+ +
+
+ + {ExportAlertElement} +
+
+
+ ) + } +} + +export default CSSModules(Export, styles) diff --git a/browser/main/modals/PreferencesModal/index.js b/browser/main/modals/PreferencesModal/index.js index 70e25a883..a68cd588c 100644 --- a/browser/main/modals/PreferencesModal/index.js +++ b/browser/main/modals/PreferencesModal/index.js @@ -7,6 +7,7 @@ import InfoTab from './InfoTab' import Crowdfunding from './Crowdfunding' import StoragesTab from './StoragesTab' import Blog from './Blog' +import Export from './Export' import ModalEscButton from 'browser/components/ModalEscButton' import CSSModules from 'browser/lib/CSSModules' import styles from './PreferencesModal.styl' @@ -86,6 +87,13 @@ class Preferences extends React.Component { haveToSave={alert => this.setState({BlogAlert: alert})} /> ) + case 'EXPORT': + return ( + this.setState({ExportAlert: alert})} + /> + ) case 'STORAGES': default: return ( @@ -123,7 +131,8 @@ class Preferences extends React.Component { {target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert}, {target: 'INFO', label: i18n.__('About')}, {target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')}, - {target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert} + {target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert}, + {target: 'EXPORT', label: i18n.__('Export')} ] const navButtons = tabs.map((tab) => { From 9893fd9ae55169498d1ee55fbf152b11322303ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Sat, 19 May 2018 19:52:47 +0700 Subject: [PATCH 03/11] fixed eslint --- browser/main/modals/PreferencesModal/Export.js | 2 +- browser/main/modals/PreferencesModal/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/main/modals/PreferencesModal/Export.js b/browser/main/modals/PreferencesModal/Export.js index ae57cc14c..c7d99c8cb 100644 --- a/browser/main/modals/PreferencesModal/Export.js +++ b/browser/main/modals/PreferencesModal/Export.js @@ -9,7 +9,7 @@ const electron = require('electron') const ipc = electron.ipcRenderer class Export extends React.Component { - constructor(props) { + constructor (props) { super(props) this.state = { config: props.config diff --git a/browser/main/modals/PreferencesModal/index.js b/browser/main/modals/PreferencesModal/index.js index a68cd588c..027102897 100644 --- a/browser/main/modals/PreferencesModal/index.js +++ b/browser/main/modals/PreferencesModal/index.js @@ -89,7 +89,7 @@ class Preferences extends React.Component { ) case 'EXPORT': return ( - this.setState({ExportAlert: alert})} /> From 1516807ed548d95e031f4ff64506764fd9c50e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Sun, 20 May 2018 19:24:36 +0700 Subject: [PATCH 04/11] fixed double escape html --- browser/lib/utils.js | 43 ++++++------------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/browser/lib/utils.js b/browser/lib/utils.js index f67ca3776..d27449e03 100644 --- a/browser/lib/utils.js +++ b/browser/lib/utils.js @@ -15,43 +15,12 @@ export function escapeHtmlCharacters (text) { return str } - let escape - let html = '' - let index = 0 - let lastIndex = 0 - - for (index = match.index; index < str.length; index++) { - switch (str.charCodeAt(index)) { - case 34: // " - escape = '"' - break - case 38: // & - escape = '&' - break - case 39: // ' - escape = ''' - break - case 60: // < - escape = '<' - break - case 62: // > - escape = '>' - break - default: - continue - } - - if (lastIndex !== index) { - html += str.substring(lastIndex, index) - } - - lastIndex = index + 1 - html += escape - } - - return lastIndex !== index - ? html + str.substring(lastIndex, index) - : html + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') } export default { From 707356bffe54a4d00c944b50330134930a90ceed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Tue, 29 May 2018 18:15:57 +0700 Subject: [PATCH 05/11] revert to the original function for better performance --- browser/lib/utils.js | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/browser/lib/utils.js b/browser/lib/utils.js index d27449e03..ee4d4ad09 100644 --- a/browser/lib/utils.js +++ b/browser/lib/utils.js @@ -15,12 +15,43 @@ export function escapeHtmlCharacters (text) { return str } - return str - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') + let escape + let html = '' + let index = 0 + let lastIndex = 0 + + for (index = match.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: // " + escape = '"' + break + case 38: // & + escape = '&ssssss;' + break + case 39: // ' + escape = ''' + break + case 60: // < + escape = '<' + break + case 62: // > + escape = '>' + break + default: + continue + } + + if (lastIndex !== index) { + html += str.substring(lastIndex, index) + } + + lastIndex = index + 1 + html += escape + } + + return lastIndex !== index + ? html + str.substring(lastIndex, index) + : html } export default { From 0ae1263d9deff16c25f849e9a438abd0e250e671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Wed, 6 Jun 2018 00:25:49 +0700 Subject: [PATCH 06/11] abort --- browser/components/MarkdownPreview.js | 2 +- browser/lib/markdown.js | 1 - browser/main/lib/ConfigManager.js | 3 - .../main/modals/PreferencesModal/Export.js | 120 ------------------ browser/main/modals/PreferencesModal/index.js | 11 +- 5 files changed, 2 insertions(+), 135 deletions(-) delete mode 100644 browser/main/modals/PreferencesModal/Export.js diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index ef11a0cc0..04fc29fd9 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -219,7 +219,7 @@ export default class MarkdownPreview extends React.Component { const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams() const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) - let body = this.markdown.render(ConfigManager.get().exports.escapeHtml ? escapeHtmlCharacters(noteContent) : noteContent) + let body = this.markdown.render(escapeHtmlCharacters(noteContent)) const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath) diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 5848aeea6..2d81cd313 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -239,4 +239,3 @@ class Markdown { } export default Markdown - diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 5b2631911..79fe0f5f0 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -65,9 +65,6 @@ export const DEFAULT_CONFIG = { token: '', username: '', password: '' - }, - exports: { - escapeHtml: true } } diff --git a/browser/main/modals/PreferencesModal/Export.js b/browser/main/modals/PreferencesModal/Export.js deleted file mode 100644 index c7d99c8cb..000000000 --- a/browser/main/modals/PreferencesModal/Export.js +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react' -import ConfigManager from 'browser/main/lib/ConfigManager' -import i18n from 'browser/lib/i18n' -import styles from './ConfigTab.styl' -import CSSModules from 'browser/lib/CSSModules' -import store from 'browser/main/store' - -const electron = require('electron') -const ipc = electron.ipcRenderer - -class Export extends React.Component { - constructor (props) { - super(props) - this.state = { - config: props.config - } - } - - componentDidMount () { - this.handleSettingDone = () => { - this.setState({ExportAlert: { - type: 'success', - message: i18n.__('Successfully applied!') - }}) - } - this.handleSettingError = (err) => { - this.setState({ExportAlert: { - type: 'error', - message: err.message != null ? err.message : i18n.__('Error occurs!') - }}) - } - ipc.addListener('APP_SETTING_DONE', this.handleSettingDone) - ipc.addListener('APP_SETTING_ERROR', this.handleSettingError) - } - - componentWillUnmount () { - ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone) - ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError) - } - - handleUIChange (e) { - const newConfig = { - exports: { - escapeHtml: this.refs.escapeHtmlExport.checked - } - } - - this.setState({ config: newConfig }, () => { - const { exports } = this.props.config - this.currentConfig = { exports } - if (_.isEqual(this.currentConfig, this.state.config)) { - this.props.haveToSave() - } else { - this.props.haveToSave({ - tab: 'EXPORT', - type: 'warning', - message: i18n.__('You have to save!') - }) - } - }) - } - - handleSaveUIClick (e) { - const newConfig = { - exports: this.state.config.exports - } - - ConfigManager.set(newConfig) - - store.dispatch({ - type: 'SET_UI', - config: newConfig - }) - this.clearMessage() - this.props.haveToSave() - } - - clearMessage () { - _.debounce(() => { - this.setState({ - UiAlert: null - }) - }, 2000)() - } - - render () { - const ExportAlert = this.state.ExportAlert - const ExportAlertElement = ExportAlert != null - ?

- {ExportAlert.message} -

- : null - return ( -
-
-
{i18n.__('Export')}
- -
- -
-
- - {ExportAlertElement} -
-
-
- ) - } -} - -export default CSSModules(Export, styles) diff --git a/browser/main/modals/PreferencesModal/index.js b/browser/main/modals/PreferencesModal/index.js index 027102897..70e25a883 100644 --- a/browser/main/modals/PreferencesModal/index.js +++ b/browser/main/modals/PreferencesModal/index.js @@ -7,7 +7,6 @@ import InfoTab from './InfoTab' import Crowdfunding from './Crowdfunding' import StoragesTab from './StoragesTab' import Blog from './Blog' -import Export from './Export' import ModalEscButton from 'browser/components/ModalEscButton' import CSSModules from 'browser/lib/CSSModules' import styles from './PreferencesModal.styl' @@ -87,13 +86,6 @@ class Preferences extends React.Component { haveToSave={alert => this.setState({BlogAlert: alert})} /> ) - case 'EXPORT': - return ( - this.setState({ExportAlert: alert})} - /> - ) case 'STORAGES': default: return ( @@ -131,8 +123,7 @@ class Preferences extends React.Component { {target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert}, {target: 'INFO', label: i18n.__('About')}, {target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')}, - {target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert}, - {target: 'EXPORT', label: i18n.__('Export')} + {target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert} ] const navButtons = tabs.map((tab) => { From c2f0147cff70f19f5aeac6d9aa2364cc50edb89c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Wed, 4 Jul 2018 13:50:05 +0700 Subject: [PATCH 07/11] updated new escape html function --- browser/lib/utils.js | 81 ++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/browser/lib/utils.js b/browser/lib/utils.js index ee4d4ad09..10df31b2c 100644 --- a/browser/lib/utils.js +++ b/browser/lib/utils.js @@ -6,52 +6,45 @@ export function lastFindInArray (array, callback) { } } -export function escapeHtmlCharacters (text) { - const matchHtmlRegExp = /["'&<>]/ - const str = '' + text - const match = matchHtmlRegExp.exec(str) - - if (!match) { - return str - } - - let escape - let html = '' - let index = 0 - let lastIndex = 0 - - for (index = match.index; index < str.length; index++) { - switch (str.charCodeAt(index)) { - case 34: // " - escape = '"' - break - case 38: // & - escape = '&ssssss;' - break - case 39: // ' - escape = ''' - break - case 60: // < - escape = '<' - break - case 62: // > - escape = '>' - break - default: - continue +function escapeHtmlCharacters (html) { + const matchHtmlRegExp = /["'&<>]/g + const escapes = ['"', '&', ''', '<', '>'] + let match = null + const replaceAt = (str, index, replace) => + str.substr(0, index) + + replace + + str.substr(index + replace.length - (replace.length - 1)) + + while ((match = matchHtmlRegExp.exec(html)) != null) { + const current = { char: match[0], index: match.index } + if (current.char === '&') { + let nextStr = '' + let nextIndex = current.index + let escapedStr = false + // maximum length of an escape string is 5. For example ('"') + while (nextStr.length <= 5) { + nextStr += html[nextIndex] + nextIndex++ + if (escapes.indexOf(nextStr) !== -1) { + escapedStr = true + break + } + } + if (!escapedStr) { + // this & char is not a part of an escaped string + html = replaceAt(html, current.index, '&') + } + } else if (current.char === '"') { + html = replaceAt(html, current.index, '"') + } else if (current.char === "'") { + html = replaceAt(html, current.index, ''') + } else if (current.char === '<') { + html = replaceAt(html, current.index, '<') + } else if (current.char === '>') { + html = replaceAt(html, current.index, '>') } - - if (lastIndex !== index) { - html += str.substring(lastIndex, index) - } - - lastIndex = index + 1 - html += escape } - - return lastIndex !== index - ? html + str.substring(lastIndex, index) - : html + return html } export default { From 55d86d853abe139838eb9dda8b9df1db8f358d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Wed, 4 Jul 2018 15:27:30 +0700 Subject: [PATCH 08/11] improved escape function --- browser/lib/utils.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/browser/lib/utils.js b/browser/lib/utils.js index c8420d912..6fc6ae276 100644 --- a/browser/lib/utils.js +++ b/browser/lib/utils.js @@ -6,7 +6,7 @@ export function lastFindInArray (array, callback) { } } -function escapeHtmlCharacters (html) { +export function escapeHtmlCharacters (html) { const matchHtmlRegExp = /["'&<>]/g const escapes = ['"', '&', ''', '<', '>'] let match = null @@ -15,8 +15,25 @@ function escapeHtmlCharacters (html) { replace + str.substr(index + replace.length - (replace.length - 1)) + // detecting code block while ((match = matchHtmlRegExp.exec(html)) != null) { const current = { char: match[0], index: match.index } + // position of the nearest line start + let previousLineEnd = current.index - 1 + while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) { + previousLineEnd-- + } + // 4 spaces means this character is in a code block + if ( + html[previousLineEnd + 1] === ' ' && + html[previousLineEnd + 2] === ' ' && + html[previousLineEnd + 3] === ' ' && + html[previousLineEnd + 4] === ' ' + ) { + // so skip it + continue + } + // otherwise, escape it !!! if (current.char === '&') { let nextStr = '' let nextIndex = current.index From 9cc7b8bcc62ae0f8970c0a91012d0230e4d7a202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Wed, 4 Jul 2018 15:35:46 +0700 Subject: [PATCH 09/11] fixed redundant code --- browser/components/MarkdownPreview.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 889074e16..31d7e631c 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -15,7 +15,6 @@ import copy from 'copy-to-clipboard' import mdurl from 'mdurl' import exportNote from 'browser/main/lib/dataApi/exportNote' import { escapeHtmlCharacters } from 'browser/lib/utils' -import ConfigManager from 'browser/main/lib/ConfigManager' const { remote } = require('electron') const attachmentManagement = require('../main/lib/dataApi/attachmentManagement') @@ -413,7 +412,7 @@ export default class MarkdownPreview extends React.Component { value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock)) }) } - let renderedHTML = this.markdown.render(value) + const renderedHTML = this.markdown.render(value) attachmentManagement.migrateAttachments(renderedHTML, storagePath, noteKey) this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath) From bc640834cdc5d7eec8dc679d5c99aa960fc06c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Fri, 6 Jul 2018 23:45:18 +0700 Subject: [PATCH 10/11] allow detect code block or not in escapeHtml function --- browser/components/MarkdownPreview.js | 2 +- browser/lib/utils.js | 32 ++++++++++++++------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index 31d7e631c..c3d3a7305 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -219,7 +219,7 @@ export default class MarkdownPreview extends React.Component { const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams() const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) - let body = this.markdown.render(escapeHtmlCharacters(noteContent)) + let body = this.markdown.render(escapeHtmlCharacters(noteContent, { detectCodeBlock: true })) const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath) diff --git a/browser/lib/utils.js b/browser/lib/utils.js index 6fc6ae276..564ed3d25 100644 --- a/browser/lib/utils.js +++ b/browser/lib/utils.js @@ -6,7 +6,7 @@ export function lastFindInArray (array, callback) { } } -export function escapeHtmlCharacters (html) { +export function escapeHtmlCharacters (html, opt = { detectCodeBlock: false }) { const matchHtmlRegExp = /["'&<>]/g const escapes = ['"', '&', ''', '<', '>'] let match = null @@ -18,20 +18,22 @@ export function escapeHtmlCharacters (html) { // detecting code block while ((match = matchHtmlRegExp.exec(html)) != null) { const current = { char: match[0], index: match.index } - // position of the nearest line start - let previousLineEnd = current.index - 1 - while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) { - previousLineEnd-- - } - // 4 spaces means this character is in a code block - if ( - html[previousLineEnd + 1] === ' ' && - html[previousLineEnd + 2] === ' ' && - html[previousLineEnd + 3] === ' ' && - html[previousLineEnd + 4] === ' ' - ) { - // so skip it - continue + if (opt.detectCodeBlock) { + // position of the nearest line start + let previousLineEnd = current.index - 1 + while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) { + previousLineEnd-- + } + // 4 spaces means this character is in a code block + if ( + html[previousLineEnd + 1] === ' ' && + html[previousLineEnd + 2] === ' ' && + html[previousLineEnd + 3] === ' ' && + html[previousLineEnd + 4] === ' ' + ) { + // so skip it + continue + } } // otherwise, escape it !!! if (current.char === '&') { From 563fdcba94b60b62e308d9cfa8d6b03ae7a15234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Vi=E1=BB=87t=20H=C6=B0ng?= Date: Sat, 7 Jul 2018 00:04:11 +0700 Subject: [PATCH 11/11] added tests escape html function --- tests/lib/escapeHtmlCharacters-test.js | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/lib/escapeHtmlCharacters-test.js diff --git a/tests/lib/escapeHtmlCharacters-test.js b/tests/lib/escapeHtmlCharacters-test.js new file mode 100644 index 000000000..f13ab2975 --- /dev/null +++ b/tests/lib/escapeHtmlCharacters-test.js @@ -0,0 +1,48 @@ +const { escapeHtmlCharacters } = require('browser/lib/utils') +const test = require('ava') + +test('escapeHtmlCharacters should return the original string if nothing needed to escape', t => { + const input = 'Nothing to be escaped' + const expected = 'Nothing to be escaped' + const actual = escapeHtmlCharacters(input) + t.is(actual, expected) +}) + +test('escapeHtmlCharacters should skip code block if that option is enabled', t => { + const input = ` +` + const expected = ` +<escapeMe>` + const actual = escapeHtmlCharacters(input, { detectCodeBlock: true }) + t.is(actual, expected) +}) + +test('escapeHtmlCharacters should NOT skip character not in code block but start with 4 spaces', t => { + const input = '4 spaces &' + const expected = '4 spaces &' + const actual = escapeHtmlCharacters(input, { detectCodeBlock: true }) + t.is(actual, expected) +}) + +test('escapeHtmlCharacters should NOT skip code block if that option is NOT enabled', t => { + const input = ` +` + const expected = ` <no escape> +<escapeMe>` + const actual = escapeHtmlCharacters(input) + t.is(actual, expected) +}) + +test('escapeHtmlCharacters should NOT escape & character if it\'s a part of an escaped character', t => { + const input = 'Do not escape & or " but do escape &' + const expected = 'Do not escape & or " but do escape &' + const actual = escapeHtmlCharacters(input) + t.is(actual, expected) +}) + +test('escapeHtmlCharacters should return the correct result', t => { + const input = '& < > " \'' + const expected = '& < > " '' + const actual = escapeHtmlCharacters(input) + t.is(actual, expected) +})