fix: 引入 v-dompurify-html 指令解决 v-html 的安全隐患

This commit is contained in:
shizhong 2023-07-27 18:36:06 +08:00
parent 144be6ef44
commit c2168466f3
7 changed files with 55 additions and 24 deletions

View File

@ -65,6 +65,7 @@
"url": "^0.11.0", "url": "^0.11.0",
"video.js": "^8.3.0", "video.js": "^8.3.0",
"vue": "3.3.4", "vue": "3.3.4",
"vue-dompurify-html": "^4.1.4",
"vue-i18n": "9.2.2", "vue-i18n": "9.2.2",
"vue-router": "^4.2.1", "vue-router": "^4.2.1",
"vue-types": "^5.0.3", "vue-types": "^5.0.3",

View File

@ -1,16 +1,16 @@
<script lang="tsx"> <script lang="tsx">
import { PropType, defineComponent, ref, computed, unref, watch, onMounted } from 'vue' import { computed, defineComponent, onMounted, PropType, ref, unref, watch } from 'vue'
import { ElForm, ElFormItem, ElRow, ElCol, ElTooltip } from 'element-plus' import { ElCol, ElForm, ElFormItem, ElRow, ElTooltip } from 'element-plus'
import { componentMap } from './componentMap' import { componentMap } from './componentMap'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { getSlot } from '@/utils/tsxHelper' import { getSlot } from '@/utils/tsxHelper'
import { import {
setTextPlaceholder,
setGridProp,
setComponentProps,
setItemComponentSlots,
initModel, initModel,
setFormItemSlots setComponentProps,
setFormItemSlots,
setGridProp,
setItemComponentSlots,
setTextPlaceholder
} from './helper' } from './helper'
import { useRenderSelect } from './components/useRenderSelect' import { useRenderSelect } from './components/useRenderSelect'
import { useRenderRadio } from './components/useRenderRadio' import { useRenderRadio } from './components/useRenderRadio'
@ -196,7 +196,7 @@ export default defineComponent({
<span>{item.label}</span> <span>{item.label}</span>
<ElTooltip placement="right" raw-content> <ElTooltip placement="right" raw-content>
{{ {{
content: () => <span v-html={item.labelMessage}></span>, content: () => <span v-dompurify-html={item.labelMessage}></span>,
default: () => ( default: () => (
<Icon <Icon
icon="ep:warning" icon="ep:warning"

View File

@ -41,9 +41,10 @@ import App from './App.vue'
import './permission' import './permission'
import '@/plugins/tongji' // 百度统计 import '@/plugins/tongji' // 百度统计
import Logger from '@/utils/Logger' import Logger from '@/utils/Logger'
import VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患
// 创建实例 // 创建实例
const setupAll = async () => { const setupAll = async () => {
const app = createApp(App) const app = createApp(App)
@ -66,6 +67,8 @@ const setupAll = async () => {
await router.isReady() await router.isReady()
app.use(VueDOMPurifyHTML)
app.mount('#app') app.mount('#app')
} }

View File

@ -3,8 +3,6 @@
<el-row> <el-row>
<el-col> <el-col>
<div class="mb-2 float-right"> <div class="mb-2 float-right">
<el-button size="small" @click="setJson"> 导入JSON</el-button>
<el-button size="small" @click="setOption"> 导入Options</el-button>
<el-button size="small" type="primary" @click="showJson">生成 JSON</el-button> <el-button size="small" type="primary" @click="showJson">生成 JSON</el-button>
<el-button size="small" type="success" @click="showOption">生成 Options</el-button> <el-button size="small" type="success" @click="showOption">生成 Options</el-button>
<el-button size="small" type="danger" @click="showTemplate">生成组件</el-button> <el-button size="small" type="danger" @click="showTemplate">生成组件</el-button>
@ -18,18 +16,18 @@
</ContentWrap> </ContentWrap>
<!-- 弹窗表单预览 --> <!-- 弹窗表单预览 -->
<Dialog :title="dialogTitle" v-model="dialogVisible" max-height="600"> <Dialog v-model="dialogVisible" :title="dialogTitle" max-height="600">
<div ref="editor" v-if="dialogVisible"> <div v-if="dialogVisible" ref="editor">
<XTextButton style="float: right" :title="t('common.copy')" @click="copy(formData)" /> <XTextButton style="float: right" :title="t('common.copy')" @click="copy(formData)" />
<el-scrollbar height="580"> <el-scrollbar height="580">
<div> <div>
<pre><code class="hljs" v-html="highlightedCode(formData)"></code></pre> <pre><code v-dompurify-html="highlightedCode(formData)" class="hljs"></code></pre>
</div> </div>
</el-scrollbar> </el-scrollbar>
</div> </div>
</Dialog> </Dialog>
</template> </template>
<script setup lang="ts" name="InfraBuild"> <script lang="ts" name="InfraBuild" setup>
import FcDesigner from '@form-create/designer' import FcDesigner from '@form-create/designer'
// import { useClipboard } from '@vueuse/core' // import { useClipboard } from '@vueuse/core'
import { isString } from '@/utils/is' import { isString } from '@/utils/is'
@ -54,12 +52,6 @@ const openModel = (title: string) => {
dialogTitle.value = title dialogTitle.value = title
} }
const setJson = () => {
openModel('导入JSON--未实现')
}
const setOption = () => {
openModel('导入Options--未实现')
}
const showJson = () => { const showJson = () => {
openModel('生成 JSON') openModel('生成 JSON')
formType.value = 0 formType.value = 0

View File

@ -22,7 +22,7 @@
:key="item.filePath" :key="item.filePath"
> >
<XTextButton style="float: right" :title="t('common.copy')" @click="copy(item.code)" /> <XTextButton style="float: right" :title="t('common.copy')" @click="copy(item.code)" />
<pre>{{ item.code }}</pre> <pre><code v-dompurify-html="highlightedCode(item)" class="hljs"></code></pre>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-card> </el-card>
@ -35,6 +35,14 @@ import { handleTree2 } from '@/utils/tree'
import { previewCodegenApi } from '@/api/infra/codegen' import { previewCodegenApi } from '@/api/infra/codegen'
import { CodegenTableVO, CodegenPreviewVO } from '@/api/infra/codegen/types' import { CodegenTableVO, CodegenPreviewVO } from '@/api/infra/codegen/types'
import hljs from 'highlight.js' //
import 'highlight.js/styles/github.css' //
import java from 'highlight.js/lib/languages/java'
import xml from 'highlight.js/lib/languages/java'
import javascript from 'highlight.js/lib/languages/javascript'
import sql from 'highlight.js/lib/languages/sql'
import typescript from 'highlight.js/lib/languages/typescript'
const { t } = useI18n() // const { t } = useI18n() //
const message = useMessage() // const message = useMessage() //
// ======== ======== // ======== ========
@ -148,6 +156,28 @@ const copy = async (text: string) => {
message.success(t('common.copySuccess')) message.success(t('common.copySuccess'))
oInput.remove() oInput.remove()
} }
/**
* 代码高亮
*/
const highlightedCode = (item) => {
const language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1)
const result = hljs.highlight(language, item.code || '', true)
return result.value || '&nbsp;'
}
/** 初始化 **/
onMounted(async () => {
//
hljs.registerLanguage('java', java)
hljs.registerLanguage('xml', xml)
hljs.registerLanguage('html', xml)
hljs.registerLanguage('vue', xml)
hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('sql', sql)
hljs.registerLanguage('typescript', typescript)
})
defineExpose({ defineExpose({
show show
}) })

View File

@ -19,7 +19,7 @@
> >
<!-- 展示 HTML 内容 --> <!-- 展示 HTML 内容 -->
<template #description="{ row }"> <template #description="{ row }">
<div style="width: 600px" v-html="row.description"></div> <div v-dompurify-html="row.description" style="width: 600px"></div>
</template> </template>
</Descriptions> </Descriptions>
</template> </template>

View File

@ -37,7 +37,12 @@
v-if="actionType === 'detail'" v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema" :schema="allSchemas.detailSchema"
:data="detailData" :data="detailData"
/> >
<!-- 展示 HTML 内容 -->
<template #templateContent="{ row }">
<div v-dompurify-html="row.templateContent"></div>
</template>
</Descriptions>
<template #footer> <template #footer>
<!-- 按钮关闭 --> <!-- 按钮关闭 -->
<XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" /> <XButton :loading="actionLoading" :title="t('dialog.close')" @click="modelVisible = false" />