Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
be630f1
Update UrlPattern to include private addresses and update test cases
VictoriousRaptor Dec 28, 2025
e9a68d2
Fix match logic
VictoriousRaptor Dec 28, 2025
d0a274a
Add test case
VictoriousRaptor Dec 28, 2025
77f81cf
Fix matching pattern for IPv6 addresses with consecutive ":"
VictoriousRaptor Dec 28, 2025
a8e0d65
Refactor URL plugin test
VictoriousRaptor Dec 29, 2025
1872755
Refactor URL match logic
VictoriousRaptor Feb 25, 2026
4942eab
Simplify parsing logic
VictoriousRaptor Feb 25, 2026
73cc5a9
Fix ipv6 any address and numeric TLD
VictoriousRaptor Feb 26, 2026
0c51c41
Merge branch 'dev' into just-fix-url-small-issues
Jack251970 Feb 26, 2026
ffc9b81
Use invariant culture to prevent unintentional IP parsing
VictoriousRaptor Feb 27, 2026
f2f14b1
Add URL path test coverage to UrlPluginTest (#4307)
Copilot Mar 1, 2026
09d310e
Enclose IPv6 addresses in brackets for URL formatting
VictoriousRaptor Mar 1, 2026
f6ca3c8
Don't apply brackets for already closed
VictoriousRaptor Mar 1, 2026
74c18d8
Use IPEndPoint to avoid potential URL with IP ports
VictoriousRaptor Mar 1, 2026
9017ce6
Ensure IPv6 are bracketed if it's with query or path
VictoriousRaptor Mar 2, 2026
5161a83
Merge branch 'dev' into just-fix-url-small-issues
VictoriousRaptor May 3, 2026
a9be19b
Add more url test cases to UrlPluginTest
DavidGBrett Jun 11, 2026
fe23bfe
Make UrlSchemes the single source of truth for accepted schemes in Ur…
DavidGBrett Jun 11, 2026
4ba16ae
Improve comment where http/https scheme is added to urls without one …
DavidGBrett Jun 11, 2026
80d4e30
Merge branch 'dev' into just-fix-url-small-issues
DavidGBrett Jun 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 59 additions & 19 deletions Flow.Launcher.Test/Plugins/UrlPluginTest.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,73 @@
using NUnit.Framework;

Check warning on line 1 in Flow.Launcher.Test/Plugins/UrlPluginTest.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`NUnit` is not a recognized word. (unrecognized-spelling)
Comment thread
Jack251970 marked this conversation as resolved.
using NUnit.Framework.Legacy;
using Flow.Launcher.Plugin.Url;
using System.Reflection;

namespace Flow.Launcher.Test.Plugins
{
[TestFixture]
public class UrlPluginTest
{
[Test]
public void URLMatchTest()
private static Main plugin;

[OneTimeSetUp]
public void OneTimeSetup()
{
var plugin = new Main();
ClassicAssert.IsTrue(plugin.IsURL("http://www.google.com"));
ClassicAssert.IsTrue(plugin.IsURL("https://www.google.com"));
ClassicAssert.IsTrue(plugin.IsURL("http://google.com"));
ClassicAssert.IsTrue(plugin.IsURL("www.google.com"));
ClassicAssert.IsTrue(plugin.IsURL("google.com"));
ClassicAssert.IsTrue(plugin.IsURL("http://localhost"));
ClassicAssert.IsTrue(plugin.IsURL("https://localhost"));
ClassicAssert.IsTrue(plugin.IsURL("http://localhost:80"));
ClassicAssert.IsTrue(plugin.IsURL("https://localhost:80"));
ClassicAssert.IsTrue(plugin.IsURL("http://110.10.10.10"));
ClassicAssert.IsTrue(plugin.IsURL("110.10.10.10"));
ClassicAssert.IsTrue(plugin.IsURL("ftp://110.10.10.10"));
var settingsField = typeof(Main).GetField("Settings", BindingFlags.NonPublic | BindingFlags.Static);
settingsField?.SetValue(null, new Settings());
Comment thread
Jack251970 marked this conversation as resolved.
Outdated

plugin = new Main();
}

[TestCase("http://www.google.com")]
[TestCase("https://www.google.com")]
[TestCase("http://google.com")]
[TestCase("ftp://google.com")]
[TestCase("www.google.com")]

Check warning on line 25 in Flow.Launcher.Test/Plugins/UrlPluginTest.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`google` is not a recognized word. (unrecognized-spelling)
[TestCase("google.com")]

Check warning on line 26 in Flow.Launcher.Test/Plugins/UrlPluginTest.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`google` is not a recognized word. (unrecognized-spelling)

Check warning on line 26 in Flow.Launcher.Test/Plugins/UrlPluginTest.cs

View workflow job for this annotation

GitHub Actions / Check Spelling

`google` is not a recognized word. (unrecognized-spelling)
[TestCase("http://localhost")]
[TestCase("https://localhost")]
[TestCase("http://localhost:80")]
[TestCase("https://localhost:80")]
[TestCase("localhost")]
[TestCase("localhost:8080")]
[TestCase("http://110.10.10.10")]
[TestCase("110.10.10.10")]
[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]")]
[TestCase("https://[2001:db8::1]:8080")]
[TestCase("[::1]")]
[TestCase("[::1]:8080")]
[TestCase("2001:db8::1")]
[TestCase("fe80:1:2::3:4")]
Comment thread
VictoriousRaptor marked this conversation as resolved.
[TestCase("::1")]
Comment thread
VictoriousRaptor marked this conversation as resolved.
[TestCase("HTTP://EXAMPLE.COM")]
[TestCase("HTTPS://EXAMPLE.COM")]
[TestCase("EXAMPLE.COM")]
[TestCase("LOCALHOST")]
Comment thread
DavidGBrett marked this conversation as resolved.
public void WhenValidUrlThenIsUrlReturnsTrue(string url)
{
Assert.That(plugin.IsURL(url), Is.True);
}
Comment thread
DavidGBrett marked this conversation as resolved.
Comment thread
VictoriousRaptor marked this conversation as resolved.

ClassicAssert.IsFalse(plugin.IsURL("wwww"));
ClassicAssert.IsFalse(plugin.IsURL("wwww.c"));
ClassicAssert.IsFalse(plugin.IsURL("wwww.c"));
[TestCase("wwww")]
[TestCase("wwww.c")]
[TestCase("not a url")]
[TestCase("just text")]
[TestCase("http://")]
[TestCase("://example.com")]
[TestCase("0.0.0.0")] // Pattern excludes 0.0.0.0
Comment thread
Jack251970 marked this conversation as resolved.
Outdated
[TestCase("256.1.1.1")] // Invalid IPv4
[TestCase("example")] // No TLD
[TestCase(".com")]
[TestCase("http://.com")]
public void WhenInvalidUrlThenIsUrlReturnsFalse(string url)
{
Assert.That(plugin.IsURL(url), Is.False);
}
Comment thread
DavidGBrett marked this conversation as resolved.
}
}
63 changes: 34 additions & 29 deletions Plugins/Flow.Launcher.Plugin.Url/Main.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using Flow.Launcher.Plugin.SharedCommands;
Expand All @@ -15,19 +16,28 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider
// user:pass authentication
"(?:\\S+(?::\\S*)?@)?" +
"(?:" +
// IP address exclusion
// private & local networks
"(?!(?:10|127)(?:\\.\\d{1,3}){3})" +
"(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" +
"(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})" +
// IP address dotted notation octets
// excludes loopback network 0.0.0.0
// excludes reserved space >= 224.0.0.0
// excludes network & broacast addresses
// (first & last IP address of each class)
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" +
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" +
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" +
// IPv6 address with optional brackets (brackets required if followed by port)
// IPv6 with brackets
"(?:\\[(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\]|" + // standard IPv6
"\\[(?:[0-9a-fA-F]{1,4}:){1,7}:\\]|" + // IPv6 with trailing ::
"\\[(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}\\]|" + // IPv6 compressed
"\\[::(?:[0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}\\]|" + // IPv6 with leading ::
"\\[(?:(?:[0-9a-fA-F]{1,4}:){1,6}|:):(?:[0-9a-fA-F]{1,4}:){0,5}[0-9a-fA-F]{1,4}\\]|" + // IPv6 with :: in the middle
"\\[::1\\])" + // IPv6 loopback
"|" +
Comment thread
VictoriousRaptor marked this conversation as resolved.
Outdated
// IPv6 without brackets (only when no port follows)
"(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|" + // standard IPv6
"(?:[0-9a-fA-F]{1,4}:){1,7}:|" + // IPv6 with trailing ::
"(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + // IPv6 compressed
"::(?:[0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}|" + // IPv6 with leading ::
"(?:(?:[0-9a-fA-F]{1,4}:){1,6}|:):(?:[0-9a-fA-F]{1,4}:){0,5}[0-9a-fA-F]{1,4}|" + // IPv6 with :: in the middle
"::1)(?!:[0-9])" + // IPv6 loopback (not followed by port)
Comment thread
Jack251970 marked this conversation as resolved.
Outdated
"|" +
// IPv4 address - all valid addresses including private networks (excluding 0.0.0.0)
"(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|[1-9])\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d))" +
"|" +
// localhost
"localhost" +
"|" +
// host name
"(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" +
Expand All @@ -37,20 +47,25 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider
"(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))" +
")" +
// port number
"(?::\\d{2,5})?" +
"(?::\\d{1,5})?" +
Comment thread
Jack251970 marked this conversation as resolved.
Outdated
// resource path
"(?:/\\S*)?" +
"$";
private readonly Regex UrlRegex = new(UrlPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
internal static PluginInitContext Context { get; private set; }
internal static Settings Settings { get; private set; }

private static readonly string[] UrlSchemes = ["http://", "https://", "ftp://"];

@DavidGBrett DavidGBrett Jun 11, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell browsers no longer support the ftp protocol for security reasons

Probably worth removing support for that in this case, though this may be beyond the scope of this PR

Beyond that it might be worth supporting more scheme types that work for browsers

Of note is internal addresses like chrome:// which is requested in issue #4014
The equivalent for firefox is about: I believe
chrome-extension:// is another of that kind

Beyond those internal addresses we also have ones like view-source: and file://

Though supporting these could also be considered out of scope and done in a subsequent PR


public List<Result> Query(Query query)
{
var raw = query.Search;
if (IsURL(raw))
if (!IsURL(raw))
{
return
return [];
}
Comment thread
VictoriousRaptor marked this conversation as resolved.

return
[
new()
{
Expand All @@ -60,7 +75,8 @@ public List<Result> Query(Query query)
Score = 8,
Action = _ =>
{
if (!raw.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !raw.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
// not a recognized scheme, add preferred http scheme
if (!UrlSchemes.Any(scheme => raw.StartsWith(scheme, StringComparison.OrdinalIgnoreCase)))
Comment thread
Jack251970 marked this conversation as resolved.
Outdated
{
raw = GetHttpPreference() + "://" + raw;
Comment thread
VictoriousRaptor marked this conversation as resolved.
Outdated
}

Copilot AI Feb 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a bare IPv6 address is detected as valid (e.g., "2001:db8::1"), and a protocol needs to be added in the Action method (line 38), the code will create an invalid URL like "http://2001:db8::1". IPv6 addresses must be enclosed in brackets when used in URLs. The Action method should check if the input is a bare IPv6 address and wrap it in brackets before prepending the protocol, e.g., "http://[2001:db8::1]".

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VictoriousRaptor adding IPv6 support we will need to support this URL creation as well so this suggestion is needed to be addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VictoriousRaptor adding IPv6 support we will need to support this URL creation as well so this suggestion is needed to be addressed.

Good catch and will fix later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@VictoriousRaptor adding IPv6 support we will need to support this URL creation as well so this suggestion is needed to be addressed.

@jjw24 Fixed and added this case to unit test. Please review.

Expand Down Expand Up @@ -92,9 +108,6 @@ public List<Result> Query(Query query)
}
}
];
}

return [];
}

private static string GetHttpPreference()
Expand All @@ -108,14 +121,6 @@ public bool IsURL(string raw)

if (UrlRegex.Match(raw).Value == raw) return true;

if (raw == "localhost" || raw.StartsWith("localhost:") ||
raw == "http://localhost" || raw.StartsWith("http://localhost:") ||
raw == "https://localhost" || raw.StartsWith("https://localhost:")
)
{
return true;
}

return false;
}

Expand Down
Loading