diff --git a/apps/web/components/Objects/Editor/Extensions/EmbedObjects/EmbedObjectsComponent.tsx b/apps/web/components/Objects/Editor/Extensions/EmbedObjects/EmbedObjectsComponent.tsx index 385955e6..2335a166 100644 --- a/apps/web/components/Objects/Editor/Extensions/EmbedObjects/EmbedObjectsComponent.tsx +++ b/apps/web/components/Objects/Editor/Extensions/EmbedObjects/EmbedObjectsComponent.tsx @@ -16,17 +16,40 @@ const SCRIPT_BASED_EMBEDS = { // Helper function to convert YouTube URLs to embed format const getYouTubeEmbedUrl = (url: string): string => { - // Handle different YouTube URL formats - const youtubeRegex = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/i; - const match = url.match(youtubeRegex); - - if (match && match[1]) { - // Return the embed URL with the video ID - return `https://www.youtube.com/embed/${match[1]}?autoplay=0&rel=0`; + try { + // First validate that this is a proper URL + const parsedUrl = new URL(url); + + // Ensure the hostname is actually YouTube + const isYoutubeHostname = + parsedUrl.hostname === 'youtube.com' || + parsedUrl.hostname === 'www.youtube.com' || + parsedUrl.hostname === 'youtu.be' || + parsedUrl.hostname === 'www.youtu.be'; + + if (!isYoutubeHostname) { + return url; // Not a YouTube URL, return as is + } + + // Handle different YouTube URL formats with a more precise regex + const youtubeRegex = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/i; + const match = url.match(youtubeRegex); + + if (match && match[1]) { + // Validate the video ID format (should be exactly 11 characters) + const videoId = match[1]; + if (videoId.length === 11) { + // Return the embed URL with the video ID and secure protocol + return `https://www.youtube.com/embed/${videoId}?autoplay=0&rel=0`; + } + } + + // If no valid match found, return the original URL + return url; + } catch (e) { + // If URL parsing fails, return the original URL + return url; } - - // If no match found, return the original URL - return url; }; // Add new memoized component for the embed content @@ -58,10 +81,22 @@ const MemoizedEmbed = React.memo(({ embedUrl, sanitizedEmbedCode, embedType }: { }, [embedType, sanitizedEmbedCode]); if (embedType === 'url' && embedUrl) { - // Process the URL if it's a YouTube URL - const processedUrl = embedUrl.includes('youtube.com') || embedUrl.includes('youtu.be') - ? getYouTubeEmbedUrl(embedUrl) - : embedUrl; + // Process the URL if it's a YouTube URL - using proper URL validation + let isYoutubeUrl = false; + + try { + const url = new URL(embedUrl); + // Check if the hostname is exactly youtube.com or youtu.be (or www variants) + isYoutubeUrl = url.hostname === 'youtube.com' || + url.hostname === 'www.youtube.com' || + url.hostname === 'youtu.be' || + url.hostname === 'www.youtu.be'; + } catch (e) { + // Invalid URL format, not a YouTube URL + isYoutubeUrl = false; + } + + const processedUrl = isYoutubeUrl ? getYouTubeEmbedUrl(embedUrl) : embedUrl; return (