Created
June 6, 2025 03:07
-
-
Save manabuyasuda/473d6766475b72e3a1f9049bfeaba51a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import DOMPurify from 'isomorphic-dompurify'; | |
/** | |
* ユーザー投稿コンテンツをサニタイズし、安全にレンダリングする。 | |
* | |
* @param {string | null | undefined} content - サニタイズおよびレンダリングするコンテンツ | |
* @returns {React.ReactNode} サニタイズされ、レンダリング可能なコンテンツ | |
* | |
* @example | |
* // 基本的な使用例 | |
* sanitizeAndRenderUserContent("こんにちは\nこれはテストです"); | |
* // 出力: こんにちは<br />これはテストです | |
* | |
* // HTMLタグを含む入力 | |
* sanitizeAndRenderUserContent('<p>こんにちは</p><script>alert("XSS")</script><br>これは<b>テスト</b>です'); | |
* // 出力: こんにちは<br />これはテストです | |
* | |
* // 複数の改行を含む入力 | |
* sanitizeAndRenderUserContent('1行目\n2行目\n\n3行目'); | |
* // 出力: 1行目<br />2行目<br /><br />3行目 | |
* | |
* // 特殊文字を含む入力 | |
* sanitizeAndRenderUserContent('&<>\"\'`'); | |
* // 出力: &<>"'` | |
* // NOTE: `&<>"'``が望ましいが、DOMPurifyではエスケープされない | |
* | |
* // 空の入力 | |
* sanitizeAndRenderUserContent(''); | |
* // 出力: null | |
* | |
* // null入力 | |
* sanitizeAndRenderUserContent(null); | |
* // 出力: null | |
* | |
* // undefined入力 | |
* sanitizeAndRenderUserContent(undefined); | |
* // 出力: null | |
*/ | |
export function sanitizeAndRenderUserContent( | |
content: string | null | undefined, | |
): React.ReactNode { | |
if (content == null || content === '') return null; | |
// DOMPurifyを使用してスクリプトタグや危険な属性を除去し、安全なHTMLに変換する。 | |
const sanitizedContent = DOMPurify.sanitize(content, { | |
ALLOWED_TAGS: ['br'], // brタグのみ許可する | |
KEEP_CONTENT: true, // 許可されていないタグは削除するが、中身は保持する | |
ALLOW_DATA_ATTR: false, // カスタムデータ属性を除去する | |
}); | |
// サニタイズされたコンテンツを改行で分割し、Reactの要素に変換する | |
const formattedContent = sanitizedContent | |
.split(/(<br>|\n)/) | |
.map((part, index) => { | |
if (part === '<br>' || part === '\n') { | |
return <br key={index} />; | |
} | |
return <React.Fragment key={index}>{part}</React.Fragment>; | |
}); | |
return formattedContent; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment