88 lines
2.5 KiB
JavaScript
88 lines
2.5 KiB
JavaScript
/**
|
|
* iframe 속성 문자열을 객체로 변환한다.
|
|
* @param {string} value - iframe 여는 태그의 속성 문자열
|
|
* @returns {Record<string, string>} 속성 객체
|
|
*/
|
|
const parseIframeAttributes = (value) => {
|
|
const attributes = {}
|
|
const attributePattern = /([:\w-]+)\s*=\s*("([^"]*)"|'([^']*)'|([^\s"'>]+))/g
|
|
let match = attributePattern.exec(String(value || ''))
|
|
|
|
while (match) {
|
|
attributes[match[1].toLowerCase()] = match[3] ?? match[4] ?? match[5] ?? ''
|
|
match = attributePattern.exec(String(value || ''))
|
|
}
|
|
|
|
return attributes
|
|
}
|
|
|
|
/**
|
|
* 외부 iframe src로 사용할 수 있는 URL인지 확인한다.
|
|
* @param {string} value - 검사할 URL
|
|
* @returns {boolean} 허용 여부
|
|
*/
|
|
const isSafeIframeSrc = (value) => {
|
|
try {
|
|
const parsedUrl = new URL(value)
|
|
return ['http:', 'https:'].includes(parsedUrl.protocol)
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* iframe 숫자 속성을 정리한다.
|
|
* @param {string} value - 원본 값
|
|
* @returns {string} 정리된 값
|
|
*/
|
|
const sanitizeSizeValue = (value) => {
|
|
const text = String(value || '').trim()
|
|
return /^\d{2,5}$/.test(text) ? text : ''
|
|
}
|
|
|
|
/**
|
|
* iframe allow 속성을 정리한다.
|
|
* @param {string} value - 원본 값
|
|
* @returns {string} 정리된 값
|
|
*/
|
|
const sanitizeAllowValue = (value) => String(value || '')
|
|
.replace(/[^\w\s;*.:/-]/g, '')
|
|
.trim()
|
|
|
|
/**
|
|
* iframe HTML 코드인지 확인한다.
|
|
* @param {string} value - 검사할 문자열
|
|
* @returns {boolean} iframe 코드 여부
|
|
*/
|
|
export const isIframeEmbedMarkup = (value) => /^<iframe\b[\s\S]*?(?:<\/iframe>\s*)?$/i.test(String(value || '').trim())
|
|
|
|
/**
|
|
* iframe HTML 코드에서 렌더링 가능한 정보만 추출한다.
|
|
* @param {string} value - iframe HTML 코드
|
|
* @returns {{ src: string, title: string, allow: string, width: string, height: string, allowFullscreen: boolean }|null} iframe 정보
|
|
*/
|
|
export const parseIframeEmbedMarkup = (value) => {
|
|
const source = String(value || '').trim()
|
|
const opener = source.match(/^<iframe\b([^>]*)>/i)
|
|
|
|
if (!opener) {
|
|
return null
|
|
}
|
|
|
|
const attributes = parseIframeAttributes(opener[1])
|
|
const src = attributes.src || ''
|
|
|
|
if (!isSafeIframeSrc(src)) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
src,
|
|
title: attributes.title || 'Embedded content',
|
|
allow: sanitizeAllowValue(attributes.allow || 'fullscreen; picture-in-picture; encrypted-media; web-share'),
|
|
width: sanitizeSizeValue(attributes.width),
|
|
height: sanitizeSizeValue(attributes.height),
|
|
allowFullscreen: /\ballowfullscreen\b/i.test(opener[1])
|
|
}
|
|
}
|