enhance sandbox security and optimize share hint toast
Signed-off-by: plainheart <yhen@all-my-life.cn>
diff --git a/src/editor/Editor.vue b/src/editor/Editor.vue
index 6797a3f..a14442e 100644
--- a/src/editor/Editor.vue
+++ b/src/editor/Editor.vue
@@ -251,7 +251,6 @@
loadExampleCode().then((code) => {
// No editor available. Set to runCode directly.
store.runCode = parseSourceCode(code);
- this.showShareHint();
});
} else {
loadExampleCode().then((code) => {
@@ -260,7 +259,6 @@
if (store.initialCode !== CODE_CHANGED_FLAG) {
store.initialCode = this.initialCode;
}
- this.showShareHint();
});
window.addEventListener('mousemove', (e) => {
@@ -278,17 +276,6 @@
},
methods: {
- showShareHint() {
- if (store.isSharedCode) {
- this.$message.closeAll();
- this.$message({
- type: 'warning',
- message: this.$t('editor.share.hint'),
- duration: 5000,
- showClose: true
- });
- }
- },
toExternalEditor(vendor) {
const previewRef = this.$refs.preview;
if (!previewRef) {
diff --git a/src/editor/Preview.vue b/src/editor/Preview.vue
index 2dafe44..889933f 100644
--- a/src/editor/Preview.vue
+++ b/src/editor/Preview.vue
@@ -329,9 +329,6 @@
mounted() {
this.run();
- if (store.isSharedCode) {
- this.showShareHint();
- }
this.fetchVersionList();
},
@@ -368,6 +365,8 @@
if (!this.debouncedRun) {
// First run
this.run();
+ // show share hint on first run if code is user-shared
+ store.isSharedCode && this.showShareHint();
} else {
this.debouncedRun();
}
@@ -406,7 +405,9 @@
this.sandbox = null;
}
},
- download,
+ download() {
+ download(store.isSharedCode && this.$t('editor.share.hint'));
+ },
screenshot() {
this.sandbox &&
this.sandbox.screenshot(
@@ -421,7 +422,7 @@
type: 'warning',
message: this.$t('editor.share.hint'),
customClass: 'toast-shared-url',
- duration: 5000,
+ duration: 8000,
showClose: true
});
},
diff --git a/src/editor/View.vue b/src/editor/View.vue
index 6b43664..e6e96ad 100644
--- a/src/editor/View.vue
+++ b/src/editor/View.vue
@@ -15,24 +15,6 @@
loadExampleCode().then((code) => {
store.runCode = parseSourceCode(code);
});
-
- this.showShareHint();
- },
-
- methods: {
- showShareHint() {
- if (store.isSharedCode) {
- this.$message.closeAll();
- this.$message({
- type: 'warning',
- message: this.$t('editor.share.hint'),
- duration: 5000,
- showClose: true
- });
- }
- }
}
};
</script>
-
-<style lang="scss"></style>
diff --git a/src/editor/downloadExample.js b/src/editor/downloadExample.js
index de3dc38..f33d12e 100644
--- a/src/editor/downloadExample.js
+++ b/src/editor/downloadExample.js
@@ -2,7 +2,7 @@
import { URL_PARAMS, SCRIPT_URLS } from '../common/config';
import { downloadBlob } from '../common/helper';
-export function download() {
+export function download(shareHint) {
const hasRootPath = store.sourceCode.indexOf('ROOT_PATH') > -1;
const rootPathCode = hasRootPath ? `var ROOT_PATH = '${store.cdnRoot}';` : '';
const lang = store.locale && store.locale.indexOf('zh') > -1 ? 'zh-CN' : 'en';
@@ -18,7 +18,7 @@
);
const echarts4Dir = SCRIPT_URLS.echartsDir.replace('{{version}}', '4.9.0');
const code = `<!--
- THIS EXAMPLE WAS DOWNLOADED FROM ${window.location.href}
+ ${shareHint || `THIS EXAMPLE WAS DOWNLOADED FROM ${window.location.href}`}
-->
<!DOCTYPE html>
<html lang="${lang}" style="height: 100%">
diff --git a/src/editor/sandbox/setup.js b/src/editor/sandbox/setup.js
index 4ed795e..ed889d5 100644
--- a/src/editor/sandbox/setup.js
+++ b/src/editor/sandbox/setup.js
@@ -84,8 +84,51 @@
{
location: Object.freeze(JSON.parse(JSON.stringify(location))),
document: (() => {
- const nativeCreateElement = document.createElement;
- const nativeCreateElementNS = document.createElementNS;
+ const disallowedElements = [
+ 'script',
+ 'video',
+ 'audio',
+ 'iframe',
+ 'frame',
+ 'frameset',
+ 'embed',
+ 'object',
+ // PENDING
+ 'foreignobject'
+ ];
+ const disallowedElementsMatcher = new RegExp(
+ `<(${disallowedElements.join('|')}).*>`
+ );
+ const nativeSetters = {
+ innerHTML: Object.getOwnPropertyDescriptor(
+ Element.prototype,
+ 'innerHTML'
+ ).set,
+ outerHTML: Object.getOwnPropertyDescriptor(
+ Element.prototype,
+ 'outerHTML'
+ ).set,
+ innerText: Object.getOwnPropertyDescriptor(
+ HTMLElement.prototype,
+ 'innerText'
+ ).set,
+ outerText: Object.getOwnPropertyDescriptor(
+ HTMLElement.prototype,
+ 'outerText'
+ ).set
+ };
+ ['inner', 'outer'].forEach((prop) => {
+ const htmlProp = prop + 'HTML';
+ Object.defineProperty(Element.prototype, htmlProp, {
+ set(value) {
+ return (
+ disallowedElementsMatcher.test(value)
+ ? nativeSetters[prop + 'Text']
+ : nativeSetters[htmlProp]
+ ).call(this, value);
+ }
+ });
+ });
const fakeDoc = document.cloneNode();
// To enable the created elements to be inserted to body
// Object.defineProperties(fakeDoc, {
@@ -100,24 +143,32 @@
// }
// }
// });
- fakeDoc.createElement = function () {
- const tagName = arguments[0];
- if (tagName && tagName.toLowerCase() === 'script') {
- return console.error(
- `Disallowed attempting to create dynamic script!`
- );
- }
- return nativeCreateElement.apply(document, arguments);
- };
- fakeDoc.nativeCreateElementNS = function () {
- const tagName = arguments[0];
- if (tagName && tagName.toLowerCase() === 'script') {
- return console.error(
- `Disallowed attempting to create dynamic script!`
- );
- }
- return nativeCreateElementNS.apply(document, arguments);
- };
+ [
+ ['write', document.write, 0, true],
+ ['writeln', document.writeln, 0, true],
+ ['createElement', document.createElement, 0],
+ ['createElementNS', document.createElementNS, 1]
+ ].forEach((api) => {
+ const nativeFn = api[1];
+ const argIndx = api[2];
+ const fullTextSearch = api[3];
+ fakeDoc[api[0]] = function () {
+ let val = arguments[argIndx];
+ val && (val = val.toLowerCase());
+ if (
+ val &&
+ (fullTextSearch
+ ? ((val = val.match(disallowedElementsMatcher)),
+ (val = val && val[1]))
+ : disallowedElements.includes(val))
+ ) {
+ return console.error(
+ `Disallowed attempting to create ${val} element!`
+ );
+ }
+ return nativeFn.apply(document, arguments);
+ };
+ });
return fakeDoc;
})(),
history: void 0,