From fe6850b60eecf6c728129656c52c195c14ede886 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 Aug 2025 08:05:42 +0000 Subject: [PATCH 1/4] Initial plan From f0223eda9205a9325711b995c3b6c43bfebf2a7e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 Aug 2025 08:13:43 +0000 Subject: [PATCH 2/4] Add URL validation to prevent unsupported streaming URLs Co-authored-by: huynhsontung <31434093+huynhsontung@users.noreply.github.com> --- Screenbox.Core/Helpers/UrlHelpers.cs | 129 +++++++++++++++++++++++ Screenbox.Core/Screenbox.Core.csproj | 1 + Screenbox/Controls/OpenUrlDialog.xaml.cs | 29 ++++- Screenbox/Strings/en-US/Resources.resw | 6 ++ 4 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 Screenbox.Core/Helpers/UrlHelpers.cs diff --git a/Screenbox.Core/Helpers/UrlHelpers.cs b/Screenbox.Core/Helpers/UrlHelpers.cs new file mode 100644 index 000000000..b96a64d63 --- /dev/null +++ b/Screenbox.Core/Helpers/UrlHelpers.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Screenbox.Core.Helpers; + +public static class UrlHelpers +{ + private static readonly HashSet UnsupportedUrlPatterns = new(StringComparer.OrdinalIgnoreCase) + { + "youtube.com", + "youtu.be", + "m.youtube.com", + "www.youtube.com", + "vimeo.com", + "www.vimeo.com", + "twitch.tv", + "www.twitch.tv", + "dailymotion.com", + "www.dailymotion.com", + "facebook.com", + "www.facebook.com", + "instagram.com", + "www.instagram.com", + "tiktok.com", + "www.tiktok.com" + }; + + private static readonly HashSet SupportedMediaExtensions = new(StringComparer.OrdinalIgnoreCase) + { + ".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".m4v", ".mpg", ".mpeg", ".3gp", ".3g2", + ".mp3", ".wav", ".wma", ".aac", ".ogg", ".flac", ".m4a", ".opus", + ".m3u8", ".m3u", ".ts", ".mts", ".m2ts", ".m2t" + }; + + /// + /// Validates if a URL is supported for direct media playback + /// + /// The URI to validate + /// True if the URL appears to be supported for direct playback, false otherwise + public static bool IsSupportedMediaUrl(Uri uri) + { + if (uri == null || !uri.IsAbsoluteUri) + return false; + + // Check if it's a local file URI - always supported if it passes other validations + if (uri.IsFile && uri.IsLoopback) + return true; + + // Check against known unsupported streaming platforms + if (IsUnsupportedStreamingUrl(uri)) + return false; + + // Check if URL has a supported media file extension + if (HasSupportedMediaExtension(uri)) + return true; + + // Allow HTTP/HTTPS URLs that don't match unsupported patterns + // These might be direct media URLs or streaming URLs that are supported + if (uri.Scheme == "http" || uri.Scheme == "https") + return true; + + // For other schemes, be conservative and return false + return false; + } + + /// + /// Checks if the URL matches known unsupported streaming platforms + /// + /// The URI to check + /// True if the URL is from an unsupported streaming platform + public static bool IsUnsupportedStreamingUrl(Uri uri) + { + if (uri == null || !uri.IsAbsoluteUri) + return false; + + string host = uri.Host.ToLowerInvariant(); + + return UnsupportedUrlPatterns.Any(pattern => + host.Equals(pattern, StringComparison.OrdinalIgnoreCase) || + host.EndsWith("." + pattern, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Checks if the URL has a supported media file extension + /// + /// The URI to check + /// True if the URL has a supported media extension + private static bool HasSupportedMediaExtension(Uri uri) + { + try + { + string path = uri.AbsolutePath; + string extension = Path.GetExtension(path); + return !string.IsNullOrEmpty(extension) && SupportedMediaExtensions.Contains(extension); + } + catch + { + return false; + } + } + + /// + /// Gets a user-friendly error message for unsupported URLs + /// + /// The unsupported URI + /// A descriptive error message + public static string GetUnsupportedUrlMessage(Uri uri) + { + if (uri == null) + return "Invalid URL provided."; + + if (IsUnsupportedStreamingUrl(uri)) + { + string host = uri.Host.ToLowerInvariant(); + if (host.Contains("youtube")) + return "YouTube URLs are not supported. Please use direct media file URLs instead."; + if (host.Contains("vimeo")) + return "Vimeo URLs are not supported. Please use direct media file URLs instead."; + if (host.Contains("twitch")) + return "Twitch URLs are not supported. Please use direct media file URLs instead."; + + return "This streaming platform is not supported. Please use direct media file URLs instead."; + } + + return "This URL format is not supported. Please use direct links to media files (MP4, MP3, M3U8, etc.)."; + } +} \ No newline at end of file diff --git a/Screenbox.Core/Screenbox.Core.csproj b/Screenbox.Core/Screenbox.Core.csproj index 31b9b83a1..a4a512a3e 100644 --- a/Screenbox.Core/Screenbox.Core.csproj +++ b/Screenbox.Core/Screenbox.Core.csproj @@ -150,6 +150,7 @@ + diff --git a/Screenbox/Controls/OpenUrlDialog.xaml.cs b/Screenbox/Controls/OpenUrlDialog.xaml.cs index b4f79ebe2..36bc452e0 100644 --- a/Screenbox/Controls/OpenUrlDialog.xaml.cs +++ b/Screenbox/Controls/OpenUrlDialog.xaml.cs @@ -2,7 +2,9 @@ using System; using System.Threading.Tasks; +using Screenbox.Core.Helpers; using Screenbox.Helpers; +using Screenbox.Strings; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -25,9 +27,30 @@ public OpenUrlDialog() OpenUrlDialog dialog = new(); ContentDialogResult result = await dialog.ShowAsync(); string url = dialog.UrlBox.Text; - return result == ContentDialogResult.Primary && Uri.TryCreate(url, UriKind.Absolute, out Uri uri) - ? uri - : null; + + if (result != ContentDialogResult.Primary || !Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) + return null; + + // Validate if the URL is supported for media playback + if (!UrlHelpers.IsSupportedMediaUrl(uri)) + { + // Show error message for unsupported URLs + string errorMessage = UrlHelpers.GetUnsupportedUrlMessage(uri); + ContentDialog errorDialog = new() + { + Title = Resources.UnsupportedUrlErrorTitle, + Content = errorMessage, + CloseButtonText = Resources.Close, + DefaultButton = ContentDialogButton.Close, + FlowDirection = GlobalizationHelper.GetFlowDirection(), + RequestedTheme = ((FrameworkElement)Window.Current.Content).RequestedTheme + }; + + await errorDialog.ShowAsync(); + return null; + } + + return uri; } private bool CanOpen(string url) diff --git a/Screenbox/Strings/en-US/Resources.resw b/Screenbox/Strings/en-US/Resources.resw index 38e172989..47620f96e 100644 --- a/Screenbox/Strings/en-US/Resources.resw +++ b/Screenbox/Strings/en-US/Resources.resw @@ -947,4 +947,10 @@ Use system language + + URL Not Supported + + + This URL format is not supported. Please use direct links to media files (MP4, MP3, M3U8, etc.). + \ No newline at end of file From e1083af00f548a0ae85684b2789ad5912933372f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 Aug 2025 08:15:52 +0000 Subject: [PATCH 3/4] Add fallback URL validation in MediaListViewModel for comprehensive protection Co-authored-by: huynhsontung <31434093+huynhsontung@users.noreply.github.com> --- Screenbox.Core/ViewModels/MediaListViewModel.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Screenbox.Core/ViewModels/MediaListViewModel.cs b/Screenbox.Core/ViewModels/MediaListViewModel.cs index bbc988dc1..417f319a7 100644 --- a/Screenbox.Core/ViewModels/MediaListViewModel.cs +++ b/Screenbox.Core/ViewModels/MediaListViewModel.cs @@ -645,13 +645,25 @@ private async Task CreatePlaylistAsync(Uri uri) { MediaViewModel media => media, StorageFile file => _mediaFactory.GetSingleton(file), - Uri uri => _mediaFactory.GetTransient(uri), + Uri uri when UrlHelpers.IsSupportedMediaUrl(uri) => _mediaFactory.GetTransient(uri), + Uri uri => throw new ArgumentException(UrlHelpers.GetUnsupportedUrlMessage(uri)), _ => null }; private async Task EnqueueAndPlay(object value) { - MediaViewModel? playNext = GetMedia(value); + MediaViewModel? playNext = null; + try + { + playNext = GetMedia(value); + } + catch (ArgumentException ex) + { + // Show error notification for unsupported URLs + Messenger.Send(new ErrorMessage("URL Not Supported", ex.Message)); + return; + } + if (playNext != null) { Enqueue(new[] { playNext }); From 80fbdb4745901a2b4cf018a3297fa572b1526e4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 10 Aug 2025 14:44:47 +0000 Subject: [PATCH 4/4] Improve URL validation UX with inline error messages Replace nested ContentDialog with inline validation in OpenUrlDialog to provide better user experience. Users now see real-time feedback when entering unsupported URLs instead of modal-on-modal dialogs. Co-authored-by: United600 <698155+United600@users.noreply.github.com> --- Screenbox/Controls/OpenUrlDialog.xaml | 17 ++++++- Screenbox/Controls/OpenUrlDialog.xaml.cs | 64 ++++++++++++++++-------- Screenbox/Strings/en-US/Resources.resw | 3 -- 3 files changed, 60 insertions(+), 24 deletions(-) diff --git a/Screenbox/Controls/OpenUrlDialog.xaml b/Screenbox/Controls/OpenUrlDialog.xaml index 85097287a..52aea62fb 100644 --- a/Screenbox/Controls/OpenUrlDialog.xaml +++ b/Screenbox/Controls/OpenUrlDialog.xaml @@ -14,6 +14,21 @@ mc:Ignorable="d"> - + + + + + + + + diff --git a/Screenbox/Controls/OpenUrlDialog.xaml.cs b/Screenbox/Controls/OpenUrlDialog.xaml.cs index 36bc452e0..709e8aacb 100644 --- a/Screenbox/Controls/OpenUrlDialog.xaml.cs +++ b/Screenbox/Controls/OpenUrlDialog.xaml.cs @@ -31,31 +31,55 @@ public OpenUrlDialog() if (result != ContentDialogResult.Primary || !Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) return null; - // Validate if the URL is supported for media playback - if (!UrlHelpers.IsSupportedMediaUrl(uri)) - { - // Show error message for unsupported URLs - string errorMessage = UrlHelpers.GetUnsupportedUrlMessage(uri); - ContentDialog errorDialog = new() - { - Title = Resources.UnsupportedUrlErrorTitle, - Content = errorMessage, - CloseButtonText = Resources.Close, - DefaultButton = ContentDialogButton.Close, - FlowDirection = GlobalizationHelper.GetFlowDirection(), - RequestedTheme = ((FrameworkElement)Window.Current.Content).RequestedTheme - }; - - await errorDialog.ShowAsync(); - return null; - } - + // The validation is now handled inline during text input + // If we reach here, the URL should be valid return uri; } private bool CanOpen(string url) { - return Uri.TryCreate(url, UriKind.Absolute, out Uri _); + if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) + return false; + + // Check if URL is supported + if (!UrlHelpers.IsSupportedMediaUrl(uri)) + return false; + + return true; + } + + private void UrlBox_TextChanged(object sender, TextChangedEventArgs e) + { + if (sender is TextBox textBox) + { + string url = textBox.Text; + + // Clear error if text is empty + if (string.IsNullOrWhiteSpace(url)) + { + ErrorMessage.Visibility = Windows.UI.Xaml.Visibility.Collapsed; + return; + } + + // Validate URL format first + if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) + { + ErrorMessage.Text = "Please enter a valid URL."; + ErrorMessage.Visibility = Windows.UI.Xaml.Visibility.Visible; + return; + } + + // Validate if URL is supported + if (!UrlHelpers.IsSupportedMediaUrl(uri)) + { + ErrorMessage.Text = UrlHelpers.GetUnsupportedUrlMessage(uri); + ErrorMessage.Visibility = Windows.UI.Xaml.Visibility.Visible; + return; + } + + // URL is valid and supported + ErrorMessage.Visibility = Windows.UI.Xaml.Visibility.Collapsed; + } } } } diff --git a/Screenbox/Strings/en-US/Resources.resw b/Screenbox/Strings/en-US/Resources.resw index 47620f96e..f699657a8 100644 --- a/Screenbox/Strings/en-US/Resources.resw +++ b/Screenbox/Strings/en-US/Resources.resw @@ -947,9 +947,6 @@ Use system language - - URL Not Supported - This URL format is not supported. Please use direct links to media files (MP4, MP3, M3U8, etc.).