import React from 'react';
import { useState, useMemo } from 'react';
import DOMPurify from 'dompurify';
interface UserProfileProps {
userInput: string;
className?: string;
style?: React.CSSProperties;
}
// Input validation helpers
const validateClassName = (className: string): string => {
if (!className) return '';
// Allow only alphanumeric, hyphens, and underscores
const sanitized = className.replace(/[^a-zA-Z0-9\-_\s]/g, '');
// Limit length
return sanitized.substring(0, 100);
};
const validateStyle = (style: React.CSSProperties | undefined): React.CSSProperties => {
if (!style || typeof style !== 'object') return {};
// Allowlist of safe CSS properties
const allowedProperties = [
'color', 'backgroundColor', 'fontSize', 'fontWeight',
'margin', 'padding', 'width', 'height', 'border'
];
const sanitizedStyle: React.CSSProperties = {};
Object.entries(style).forEach(([key, value]) => {
if (allowedProperties.includes(key) && typeof value === 'string') {
// Basic CSS value validation
const cleanValue = value.replace(/[^a-zA-Z0-9\s#%.-]/g, '');
if (cleanValue.length <= 50) {
sanitizedStyle[key as keyof React.CSSProperties] = cleanValue;
}
}
});
return sanitizedStyle;
};
const sanitizeText = (text: string): string => {
if (!text) return '';
// Use DOMPurify for HTML sanitization
return DOMPurify.sanitize(text, { ALLOWED_TAGS: [], ALLOWED_ATTR: [] });
};
// Secure: Validated user input in properties
const UserProfile: React.FC<UserProfileProps> = ({ userInput, className, style }) => {
const sanitizedInput = useMemo(() => sanitizeText(userInput), [userInput]);
const validClassName = useMemo(() => validateClassName(className || ''), [className]);
const validStyle = useMemo(() => validateStyle(style), [style]);
return (
<div
className={validClassName}
style={validStyle}
>
<h1>{sanitizedInput}</h1>
</div>
);
};
// Secure: Controlled property assignment
interface AllowedProps {
title?: string;
description?: string;
theme?: 'light' | 'dark';
}
const SecureDynamicComponent: React.FC = () => {
const [userProps, setUserProps] = useState<AllowedProps>({});
const handleUserInput = (input: string) => {
try {
const parsedInput = JSON.parse(input);
// Validate and sanitize each property
const validProps: AllowedProps = {};
if (parsedInput.title && typeof parsedInput.title === 'string') {
validProps.title = sanitizeText(parsedInput.title).substring(0, 100);
}
if (parsedInput.description && typeof parsedInput.description === 'string') {
validProps.description = sanitizeText(parsedInput.description).substring(0, 500);
}
if (parsedInput.theme && ['light', 'dark'].includes(parsedInput.theme)) {
validProps.theme = parsedInput.theme;
}
setUserProps(validProps);
} catch {
console.error('Invalid JSON input');
}
};
return (
<div className={`theme-${userProps.theme || 'light'}`}>
{userProps.title && <h2>{userProps.title}</h2>}
{userProps.description && <p>{userProps.description}</p>}
Content
</div>
);
};
// Secure: URL validation and safe href handling
const validateUrl = (url: string): string | null => {
if (!url) return null;
try {
const parsedUrl = new URL(url);
// Only allow safe schemes
const allowedSchemes = ['http:', 'https:', 'mailto:'];
if (allowedSchemes.includes(parsedUrl.protocol)) {
return parsedUrl.toString();
}
return null;
} catch {
return null;
}
};
const SecureLinkComponent: React.FC<{ url: string; title: string }> = ({ url, title }) => {
const validUrl = useMemo(() => validateUrl(url), [url]);
const sanitizedTitle = useMemo(() => sanitizeText(title).substring(0, 100), [title]);
if (!validUrl) {
return <span>Invalid URL</span>;
}
return (
<a
href={validUrl}
title={sanitizedTitle}
rel="noopener noreferrer" // Security best practice
target="_blank"
>
Click here
</a>
);
};
// Secure: Predefined event handlers
type AllowedAction = 'save' | 'cancel' | 'delete' | 'edit';
interface SecureButtonProps {
action: AllowedAction;
label: string;
onAction: (action: AllowedAction) => void;
}
const SecureButtonComponent: React.FC<SecureButtonProps> = ({ action, label, onAction }) => {
const sanitizedLabel = useMemo(() => sanitizeText(label).substring(0, 50), [label]);
const handleClick = () => {
// Safe: Predefined actions only
if (['save', 'cancel', 'delete', 'edit'].includes(action)) {
onAction(action);
}
};
return (
<button onClick={handleClick}>
{sanitizedLabel}
</button>
);
};
// Advanced: Property validation with TypeScript
interface ValidatedComponentProps {
data: {
id: number;
name: string;
email: string;
isActive: boolean;
};
}
const validateComponentData = (data: any): ValidatedComponentProps['data'] | null => {
if (!data || typeof data !== 'object') return null;
const { id, name, email, isActive } = data;
// Type and format validation
if (typeof id !== 'number' || id <= 0) return null;
if (typeof name !== 'string' || name.length === 0 || name.length > 100) return null;
if (typeof email !== 'string' || !email.includes('@') || email.length > 200) return null;
if (typeof isActive !== 'boolean') return null;
return {
id,
name: sanitizeText(name),
email: sanitizeText(email),
isActive
};
};
const ValidatedComponent: React.FC<{ rawData: any }> = ({ rawData }) => {
const validData = useMemo(() => validateComponentData(rawData), [rawData]);
if (!validData) {
return <div>Invalid data provided</div>;
}
return (
<div className={`user-card ${validData.isActive ? 'active' : 'inactive'}`}>
<h3>{validData.name}</h3>
<p>{validData.email}</p>
<span>ID: {validData.id}</span>
</div>
);
};
// Custom hook for safe property handling
const useSafeProps = <T extends Record<string, any>>(
props: T,
validator: (props: T) => Partial<T>
): Partial<T> => {
return useMemo(() => validator(props), [props, validator]);
};
export {
UserProfile,
SecureDynamicComponent,
SecureLinkComponent,
SecureButtonComponent,
ValidatedComponent,
useSafeProps
};