From fd90e73cca31f46c12c22d9a0c7423739361f3e8 Mon Sep 17 00:00:00 2001 From: David Brett Date: Tue, 16 Jun 2026 07:04:08 +0200 Subject: [PATCH 1/5] Remove support for ftp url scheme --- Plugins/Flow.Launcher.Plugin.Url/Main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Url/Main.cs b/Plugins/Flow.Launcher.Plugin.Url/Main.cs index 60a6eec92f1..083cb370aea 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Url/Main.cs @@ -12,7 +12,7 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider internal static PluginInitContext Context { get; private set; } internal static Settings Settings { get; private set; } - private static readonly string[] UrlSchemes = ["http", "https", "ftp"]; + private static readonly string[] UrlSchemes = ["http", "https"]; public List Query(Query query) { From 4ef930b4886bafb099ae4c2aaad0da5c10cbc118 Mon Sep 17 00:00:00 2001 From: David Brett Date: Tue, 16 Jun 2026 07:37:09 +0200 Subject: [PATCH 2/5] Add support for certain non standard scheme in URL Plugin - "file", "brave", "opera", "vivaldi", "edge", "chrome", "chrome-extension", "moz-extension" --- Plugins/Flow.Launcher.Plugin.Url/Main.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Plugins/Flow.Launcher.Plugin.Url/Main.cs b/Plugins/Flow.Launcher.Plugin.Url/Main.cs index 083cb370aea..7e874a0bd16 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Url/Main.cs @@ -12,7 +12,13 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider internal static PluginInitContext Context { get; private set; } internal static Settings Settings { get; private set; } - private static readonly string[] UrlSchemes = ["http", "https"]; + // Schemes requiring full host validation: domain with TLD, IP address, or localhost + private static readonly string[] HostValidatedSchemes = ["http", "https"]; + + // Schemes validated by scheme recognition alone — any valid URI structure is accepted + private static readonly string[] SchemeOnlySchemes = ["file", "brave", "opera", "vivaldi", "edge", "chrome", "chrome-extension", "moz-extension"]; + + private static readonly string[] UrlSchemes = [.. HostValidatedSchemes, .. SchemeOnlySchemes]; public List Query(Query query) { @@ -131,6 +137,10 @@ public bool IsURL(string raw) var host = uri.Host; + // Scheme-only validation: any valid URI structure is accepted, no host checks + if (SchemeOnlySchemes.Any(scheme => uri.Scheme == scheme)) + return true; + // localhost is valid if (host.Equals("localhost", StringComparison.OrdinalIgnoreCase)) return true; From bde1d462091fdd02e497867ca8f1b10935ae158e Mon Sep 17 00:00:00 2001 From: David Brett Date: Tue, 16 Jun 2026 10:36:07 +0200 Subject: [PATCH 3/5] Add support for colon only schemes to the url plugin (about:, data:, and the previously added chromium schemes) --- Plugins/Flow.Launcher.Plugin.Url/Main.cs | 41 +++++++++++++++++++----- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.Url/Main.cs b/Plugins/Flow.Launcher.Plugin.Url/Main.cs index 7e874a0bd16..8997e764916 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Url/Main.cs @@ -15,10 +15,18 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider // Schemes requiring full host validation: domain with TLD, IP address, or localhost private static readonly string[] HostValidatedSchemes = ["http", "https"]; - // Schemes validated by scheme recognition alone — any valid URI structure is accepted - private static readonly string[] SchemeOnlySchemes = ["file", "brave", "opera", "vivaldi", "edge", "chrome", "chrome-extension", "moz-extension"]; + // Chromium browser schemes accepting both :// and : forms (e.g. chrome://settings, chrome:settings) + private static readonly string[] ChromiumSchemes = ["chrome-extension", "chrome", "brave", "edge", "opera", "vivaldi"]; - private static readonly string[] UrlSchemes = [.. HostValidatedSchemes, .. SchemeOnlySchemes]; + // Schemes using :// that are validated by scheme recognition alone — any valid URI structure is accepted + private static readonly string[] NonHostValidatedDoubleSlashSchemes = [.. ChromiumSchemes, "file", "moz-extension"]; + + // Schemes using colon-only syntax (e.g. about:blank, chrome:settings) + // Chromium schemes also accept the colon form + private static readonly string[] ColonOnlySchemes = [.. ChromiumSchemes, "about", "data"]; + + // All :// schemes + private static readonly string[] AllDoubleSlashSchemes = [.. HostValidatedSchemes, .. NonHostValidatedDoubleSlashSchemes]; public List Query(Query query) { @@ -50,7 +58,12 @@ public List Query(Query query) // if url was accepted without having any of the recognized scheme, // then that means no scheme was specified (e.g. www.google.com) // so we add the preferred http/https scheme - if (!UrlSchemes.Any(scheme => raw.StartsWith(scheme + "://", StringComparison.OrdinalIgnoreCase))) + var hasScheme = ( + AllDoubleSlashSchemes.Any(scheme => raw.StartsWith(scheme + "://", StringComparison.OrdinalIgnoreCase)) + || + ColonOnlySchemes.Any(scheme => raw.StartsWith(scheme + ":", StringComparison.OrdinalIgnoreCase)) + ); + if (!hasScheme) { raw = GetHttpPreference() + "://" + raw; } @@ -122,8 +135,20 @@ public bool IsURL(string raw) return true; } + // Check colon-only schemes (e.g. about:blank, chrome:settings) + var colonIndex = input.IndexOf(':'); + if (colonIndex > 0) + { + var scheme = input[..colonIndex]; + if (ColonOnlySchemes.Any(s => scheme.Equals(s, StringComparison.OrdinalIgnoreCase))) + { + var content = input[(colonIndex + 1)..]; + return content.Length > 0 && !content.Any(char.IsWhiteSpace); + } + } + // Add protocol if missing for Uri validation - var urlToValidate = UrlSchemes.Any(s => input.StartsWith(s + "://", StringComparison.OrdinalIgnoreCase)) + var urlToValidate = AllDoubleSlashSchemes.Any(s => input.StartsWith(s + "://", StringComparison.OrdinalIgnoreCase)) ? input : GetHttpPreference() + "://" + input; @@ -131,14 +156,14 @@ public bool IsURL(string raw) return false; - // Validate protocol against known schemes - if (!UrlSchemes.Any(scheme => uri.Scheme == scheme)) + // Validate protocol against known :// schemes + if (!AllDoubleSlashSchemes.Any(scheme => uri.Scheme == scheme)) return false; var host = uri.Host; // Scheme-only validation: any valid URI structure is accepted, no host checks - if (SchemeOnlySchemes.Any(scheme => uri.Scheme == scheme)) + if (NonHostValidatedDoubleSlashSchemes.Any(scheme => uri.Scheme == scheme)) return true; // localhost is valid From 8b1d0d41e787a99b51334e0edc40a93e89c8eedd Mon Sep 17 00:00:00 2001 From: David Brett Date: Tue, 16 Jun 2026 07:54:00 +0200 Subject: [PATCH 4/5] Make BrowserBookmark plugin use SearchWeb.OpenInBrowserTab instead of Context.API.OpenUrl This ensures that it always opens in the browser - instead of letting the system decide which app should handle the URI This means that non standard schemes like chrome:// will be supported --- Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs index 07ce510fb3e..65c9ba7236e 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -104,7 +104,7 @@ public List Query(Query query) Score = BookmarkLoader.MatchProgram(c, param).Score, Action = _ => { - Context.API.OpenUrl(c.Url); + SearchWeb.OpenInBrowserTab(c.Url); return true; }, @@ -128,7 +128,7 @@ public List Query(Query query) Score = 5, Action = _ => { - Context.API.OpenUrl(c.Url); + SearchWeb.OpenInBrowserTab(c.Url); return true; }, ContextData = new BookmarkAttributes { Url = c.Url } From 19c600ca8f294472b5eb4210c0cfeeeae1eb3e8a Mon Sep 17 00:00:00 2001 From: David Brett Date: Tue, 16 Jun 2026 15:31:50 +0200 Subject: [PATCH 5/5] Add tests for new schemes to UrlPluginTest --- Flow.Launcher.Test/Plugins/UrlPluginTest.cs | 26 +++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Test/Plugins/UrlPluginTest.cs b/Flow.Launcher.Test/Plugins/UrlPluginTest.cs index e17070b1284..09fe7d01261 100644 --- a/Flow.Launcher.Test/Plugins/UrlPluginTest.cs +++ b/Flow.Launcher.Test/Plugins/UrlPluginTest.cs @@ -21,7 +21,6 @@ public void OneTimeSetup() [TestCase("http://www.google.com")] [TestCase("https://www.google.com")] [TestCase("http://google.com")] - [TestCase("ftp://google.com")] [TestCase("www.google.com")] [TestCase("google.com")] [TestCase("http://localhost")] @@ -35,7 +34,6 @@ public void OneTimeSetup() [TestCase("110.10.10.10:8080")] [TestCase("192.168.1.1")] [TestCase("192.168.1.1:3000")] - [TestCase("ftp://110.10.10.10")] [TestCase("[2001:db8::1]")] [TestCase("[2001:db8::1]:8080")] [TestCase("http://[2001:db8::1]")] @@ -66,6 +64,23 @@ public void OneTimeSetup() [TestCase("192.168.1.1#fragment")] [TestCase("localhost:8080?test=123")] [TestCase("example.com#fragment")] + // Non-host-validated :// schemes + [TestCase("chrome://settings")] + [TestCase("edge://about")] + [TestCase("brave://settings")] + [TestCase("opera://history")] + [TestCase("vivaldi://bookmarks")] + [TestCase("chrome-extension://abc123def456/")] + [TestCase("moz-extension://abc123def456/")] + [TestCase("file:///C:/path/to/file.txt")] + // Colon-only schemes + [TestCase("about:blank")] + [TestCase("about:config")] + [TestCase("data:text/plain,hello")] + [TestCase("data:,Hello%2C%20World%21")] + // Chromium schemes in colon form + [TestCase("chrome:settings")] + [TestCase("chrome-extension:settings")] public void WhenValidUrlThenIsUrlReturnsTrue(string url) { Assert.That(plugin.IsURL(url), Is.True); @@ -87,6 +102,13 @@ public void WhenValidUrlThenIsUrlReturnsTrue(string url) [TestCase(".com")] [TestCase("http://.com")] [TestCase("2001:db8:::1")] + // Colon scheme with whitespace + [TestCase("about: blank")] + // Empty colon schemes + [TestCase("about:")] + [TestCase("chrome:")] + // Empty non host validated :// + [TestCase("chrome://")] public void WhenInvalidUrlThenIsUrlReturnsFalse(string url) { Assert.That(plugin.IsURL(url), Is.False);