Skip to content

Instantly share code, notes, and snippets.

@manabuyasuda
Created June 6, 2025 03:07
Show Gist options
  • Save manabuyasuda/473d6766475b72e3a1f9049bfeaba51a to your computer and use it in GitHub Desktop.
Save manabuyasuda/473d6766475b72e3a1f9049bfeaba51a to your computer and use it in GitHub Desktop.
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('&<>\"\'`');
* // 出力: &amp;&lt;&gt;"'`
* // NOTE: `&amp;&lt;&gt;&quot;&#39;&#96;`が望ましいが、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