Files
web-temp/layers/plugins/dompurify-html.ts
2026-01-29 21:21:33 +09:00

163 lines
3.0 KiB
TypeScript

// @ts-ignore
import DOMPurify from 'dompurify'
import type { DirectiveBinding } from 'vue'
// DOMPurify 설정 타입
interface DOMPurifyConfig {
ALLOWED_TAGS: string[]
ALLOWED_ATTR: string[]
}
// 공통 허용 태그
const commonTags = [
'br',
'div',
'b',
'strong',
'i',
'em',
'u',
'a',
'p',
'ul',
'li',
's',
'ol',
'button',
'span',
'img',
'pre',
'input',
'dl',
'dt',
'dd',
'table',
'thead',
'tbody',
'tfoot',
'video',
'source',
'tr',
'th',
'td',
'section',
] as const
// 공통 허용 속성
const commonAttrs = [
'style',
'id',
'href',
'src',
'target',
'alt',
'class',
'width',
'height',
'frameborder',
'colspan',
'rowspan',
'scope',
'type',
'autoplay',
'muted',
'loop',
'playsinline',
'controls',
'preload',
'poster',
] as const
// 기본 설정 (일반 사용자용)
const defaultConfig: DOMPurifyConfig = {
ALLOWED_TAGS: [...commonTags],
ALLOWED_ATTR: [...commonAttrs],
}
// 관리자용 설정 (확장된 태그 및 속성)
const adminConfig: DOMPurifyConfig = {
ALLOWED_TAGS: [
...commonTags,
'iframe',
'link',
'style',
'script',
'blockquote',
],
ALLOWED_ATTR: [
...commonAttrs,
'onclick',
'rel',
'media',
'integrity',
'crossorigin',
'referrerpolicy',
'allowfullscreen',
],
}
/**
* HTML 문자열을 안전하게 정화하는 헬퍼 함수
* @param value - 정화할 HTML 문자열
* @param config - DOMPurify 설정
* @returns 정화된 HTML 문자열
*/
function sanitizeHtml(value: unknown, config: DOMPurifyConfig): string {
// SSR 환경에서는 빈 문자열 반환
if (!import.meta.client) {
return ''
}
// 값이 없거나 문자열이 아닌 경우 빈 문자열 반환
if (!value || typeof value !== 'string') {
return ''
}
try {
return DOMPurify.sanitize(value, config)
} catch (error) {
// 에러 발생 시 원본 값 반환하지 않고 빈 문자열 반환 (보안상 안전)
if (import.meta.dev) {
// eslint-disable-next-line no-console
console.error('[DOMPurify] Sanitization error:', error)
}
return ''
}
}
/**
* 디렉티브 핸들러 생성 함수
* @param config - DOMPurify 설정
* @returns 디렉티브 핸들러 객체
*/
function createDirectiveHandler(config: DOMPurifyConfig) {
return {
mounted(el: HTMLElement, binding: DirectiveBinding) {
el.innerHTML = sanitizeHtml(binding.value, config)
},
updated(el: HTMLElement, binding: DirectiveBinding) {
el.innerHTML = sanitizeHtml(binding.value, config)
},
}
}
export default defineNuxtPlugin(nuxtApp => {
// SSR 환경에서는 디렉티브 등록하지 않음
// if (!import.meta.client) {
// return
// }
// 기본 디렉티브: v-dompurify-html
nuxtApp.vueApp.directive(
'dompurify-html',
createDirectiveHandler(defaultConfig)
)
// 관리자용 디렉티브: v-dompurify-admin
nuxtApp.vueApp.directive(
'dompurify-admin',
createDirectiveHandler(adminConfig)
)
})