import { useEffect, useMemo, useRef } from 'react'; import { useParams, useNavigate } from 'react-router'; import { useTranslation } from 'react-i18next'; import { useQuery } from '@tanstack/react-query'; import { motion } from 'framer-motion'; import DOMPurify from 'dompurify'; import { newsApi } from '../api/news'; import { usePlatform } from '../platform/hooks/usePlatform'; // Icons const BackIcon = () => ( ); const ClockIcon = () => ( ); const CalendarIcon = () => ( ); /** * Sanitizes HTML content using DOMPurify to prevent XSS attacks. * Uses explicit allowlists (not ADD_TAGS/ADD_ATTR) for defense-in-depth. * * iframe elements are allowed only when their src points to a trusted * video host (YouTube, Vimeo) over HTTPS. All other iframes are stripped. */ const ALLOWED_IFRAME_HOSTS = new Set([ 'www.youtube.com', 'youtube.com', 'player.vimeo.com', 'www.youtube-nocookie.com', ]); /** * Validate that an iframe src URL points to a trusted host over HTTPS. * Returns false for any non-HTTPS, non-allowlisted, or malformed URL. */ function isAllowedIframeSrc(src: string): boolean { try { const url = new URL(src); return url.protocol === 'https:' && ALLOWED_IFRAME_HOSTS.has(url.hostname); } catch { return false; } } /** * Strict allowlist of tags and attributes for article content. * Using ALLOWED_TAGS / ALLOWED_ATTR instead of ADD_TAGS / ADD_ATTR * ensures nothing from the permissive defaults leaks through. * * IMPORTANT: is intentionally NOT in ALLOWED_TAGS — this ensures *