/** * iframe 속성 문자열을 객체로 변환한다. * @param {string} value - iframe 여는 태그의 속성 문자열 * @returns {Record} 속성 객체 */ 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 allow 속성과 fullscreen 권한을 정리한다. * @param {Record} attributes - iframe 속성 객체 * @param {string} openerAttributes - iframe 여는 태그 원본 속성 문자열 * @returns {string} 정리된 allow 값 */ const normalizeAllowValue = (attributes, openerAttributes) => { const allow = sanitizeAllowValue(attributes.allow || 'fullscreen; picture-in-picture; encrypted-media; web-share') if (!/\ballowfullscreen\b/i.test(openerAttributes) || /\bfullscreen\b/i.test(allow)) { return allow } return [allow, 'fullscreen'].filter(Boolean).join('; ') } /** * iframe HTML 코드인지 확인한다. * @param {string} value - 검사할 문자열 * @returns {boolean} iframe 코드 여부 */ export const isIframeEmbedMarkup = (value) => /^\s*)?$/i.test(String(value || '').trim()) /** * iframe HTML 코드에서 렌더링 가능한 정보만 추출한다. * @param {string} value - iframe HTML 코드 * @returns {{ src: string, title: string, allow: string, width: string, height: string }|null} iframe 정보 */ export const parseIframeEmbedMarkup = (value) => { const source = String(value || '').trim() const opener = source.match(/^]*)>/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: normalizeAllowValue(attributes, opener[1]), width: sanitizeSizeValue(attributes.width), height: sanitizeSizeValue(attributes.height) } }