Files
sori.studio/lib/markdown-iframe-embed.js

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])
}
}