// SECURE: React component with proper sanitization
import React, { useState, useEffect, useMemo } from 'react';
import DOMPurify from 'dompurify';
interface Message {
id: number;
sender: string;
content: string;
timestamp: Date;
}
interface ForumPost {
id: number;
title: string;
body: string;
author: string;
tags: string[];
}
// Safe message display component
function MessageList({ messages }: { messages: Message[] }) {
return (
<div className="messages">
{messages.map(message => (
<MessageItem key={message.id} message={message} />
))}
</div>
);
}
function MessageItem({ message }: { message: Message }) {
// Option 1: Render as plain text (safest)
const renderAsText = () => (
<div className="message">
<div className="sender">{message.sender}</div>
<div className="content">{message.content}</div>
<div className="timestamp">{message.timestamp.toLocaleString()}</div>
</div>
);
// Option 2: Allow basic formatting with sanitization
const sanitizedContent = useMemo(() => {
return DOMPurify.sanitize(message.content, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em'],
ALLOWED_ATTR: [],
KEEP_CONTENT: false
});
}, [message.content]);
return (
<div className="message">
<div className="sender">{message.sender}</div>
<div
className="content"
dangerouslySetInnerHTML={{ __html: sanitizedContent }}
/>
<div className="timestamp">{message.timestamp.toLocaleString()}</div>
</div>
);
}
// Safe forum post component
function ForumPostView({ post }: { post: ForumPost }) {
const sanitizedBody = useMemo(() => {
return DOMPurify.sanitize(post.body, {
ALLOWED_TAGS: [
'p', 'br', 'strong', 'em', 'ul', 'ol', 'li',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'blockquote', 'code', 'pre', 'a'
],
ALLOWED_ATTR: {
'a': ['href', 'title'],
'blockquote': ['cite']
},
ALLOW_DATA_ATTR: false,
// Additional security configurations
ADD_TAGS: [],
ADD_ATTR: [],
FORBID_TAGS: ['script', 'object', 'embed', 'iframe'],
FORBID_ATTR: ['onclick', 'onload', 'onerror']
});
}, [post.body]);
return (
<article className="forum-post">
<h2>{post.title}</h2>
<div className="author">By: {post.author}</div>
<div
className="post-body"
dangerouslySetInnerHTML={{ __html: sanitizedBody }}
/>
<div className="tags">
{post.tags.map(tag => (
<span key={tag} className="tag">{tag}</span>
))}
</div>
</article>
);
}
// Safe rich text editor with live sanitization
function RichTextEditor() {
const [content, setContent] = useState('');
const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setContent(e.target.value);
};
// Sanitize content for preview
const sanitizedPreview = useMemo(() => {
return DOMPurify.sanitize(content, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'h1', 'h2', 'h3'],
ALLOWED_ATTR: [],
KEEP_CONTENT: false
});
}, [content]);
return (
<div className="editor">
<div className="editor-controls">
<textarea
value={content}
onChange={handleContentChange}
placeholder="Enter content with basic HTML tags..."
rows={10}
cols={50}
/>
<div className="allowed-tags">
<small>Allowed tags: p, br, strong, em, ul, ol, li, h1, h2, h3</small>
</div>
</div>
<div className="preview">
<h3>Preview:</h3>
<div
className="preview-content"
dangerouslySetInnerHTML={{ __html: sanitizedPreview }}
/>
</div>
</div>
);
}
// Safe comment system with text-only approach
function CommentThread({ comments }: { comments: any[] }) {
const formatText = (text: string) => {
// Convert newlines to paragraphs safely
return text.split('\n').filter(line => line.trim()).map((line, index) => (
<p key={index}>{line}</p>
));
};
const renderComment = (comment: any) => (
<div key={comment.id} className="comment">
<div className="comment-header">
<strong>{comment.author}</strong>
<span className="timestamp">{comment.timestamp}</span>
</div>
<div className="comment-body">
{formatText(comment.text)}
</div>
{comment.replies && (
<div className="replies">
{comment.replies.map(renderComment)}
</div>
)}
</div>
);
return (
<div className="comment-thread">
{comments.map(renderComment)}
</div>
);
}
// Alternative: Markdown-based approach for rich content
import { marked } from 'marked';
function MarkdownPost({ markdown }: { markdown: string }) {
const sanitizedHTML = useMemo(() => {
// Convert markdown to HTML, then sanitize
const rawHTML = marked(markdown);
return DOMPurify.sanitize(rawHTML, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'code', 'pre', 'blockquote'],
ALLOWED_ATTR: [],
KEEP_CONTENT: false
});
}, [markdown]);
return (
<div
className="markdown-content"
dangerouslySetInnerHTML={{ __html: sanitizedHTML }}
/>
);
}
// Custom hook for safe HTML rendering
function useSafeHTML(htmlContent: string, allowedTags: string[] = []) {
return useMemo(() => {
return DOMPurify.sanitize(htmlContent, {
ALLOWED_TAGS: allowedTags.length > 0 ? allowedTags : ['p', 'br', 'strong', 'em'],
ALLOWED_ATTR: [],
KEEP_CONTENT: false
});
}, [htmlContent, allowedTags]);
}