-
-
Notifications
You must be signed in to change notification settings - Fork 603
Update UrlPattern in URL plugin to include private addresses and update test cases #4185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
be630f1
e9a68d2
d0a274a
77f81cf
8971d04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| using NUnit.Framework; | ||
| using NUnit.Framework.Legacy; | ||
|
Check warning on line 2 in Flow.Launcher.Test/Plugins/UrlPluginTest.cs
|
||
| using Flow.Launcher.Plugin.Url; | ||
|
|
||
| namespace Flow.Launcher.Test.Plugins | ||
|
|
@@ -14,20 +14,47 @@ | |
| 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("ftp://google.com")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("www.google.com")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("google.com")); | ||
|
Check warning on line 19 in Flow.Launcher.Test/Plugins/UrlPluginTest.cs
|
||
| 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("localhost")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("localhost:8080")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("http://110.10.10.10")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("110.10.10.10")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("110.10.10.10:8080")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("192.168.1.1")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("192.168.1.1:3000")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("ftp://110.10.10.10")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("[2001:db8::1]")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("[2001:db8::1]:8080")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("http://[2001:db8::1]")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("https://[2001:db8::1]:8080")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("[::1]")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("[::1]:8080")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("2001:db8::1")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("::1")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("HTTP://EXAMPLE.COM")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("HTTPS://EXAMPLE.COM")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("EXAMPLE.COM")); | ||
| ClassicAssert.IsTrue(plugin.IsURL("LOCALHOST")); | ||
|
|
||
|
|
||
| ClassicAssert.IsFalse(plugin.IsURL("wwww")); | ||
| ClassicAssert.IsFalse(plugin.IsURL("wwww.c")); | ||
| ClassicAssert.IsFalse(plugin.IsURL("wwww.c")); | ||
| ClassicAssert.IsFalse(plugin.IsURL("not a url")); | ||
| ClassicAssert.IsFalse(plugin.IsURL("just text")); | ||
| ClassicAssert.IsFalse(plugin.IsURL("http://")); | ||
| ClassicAssert.IsFalse(plugin.IsURL("://example.com")); | ||
| ClassicAssert.IsFalse(plugin.IsURL("0.0.0.0")); // Pattern excludes 0.0.0.0 | ||
|
VictoriousRaptor marked this conversation as resolved.
Outdated
|
||
| ClassicAssert.IsFalse(plugin.IsURL("256.1.1.1")); // Invalid IPv4 | ||
| ClassicAssert.IsFalse(plugin.IsURL("example")); // No TLD | ||
| ClassicAssert.IsFalse(plugin.IsURL(".com")); | ||
| ClassicAssert.IsFalse(plugin.IsURL("http://.com")); | ||
| } | ||
|
||
| } | ||
| } | ||
| 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; | ||||||
|
|
@@ -15,19 +16,26 @@ 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 :: | ||||||
| "\\[::1\\])" + // IPv6 loopback | ||||||
| "|" + | ||||||
| // 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 :: | ||||||
| "::1)(?!:[0-9])" + // IPv6 loopback (not followed by port) | ||||||
|
||||||
| "::1)(?!:[0-9])" + // IPv6 loopback (not followed by port) | |
| "::1)(?!:[0-9]+)" + // IPv6 loopback (not followed by port) |
Copilot
AI
Dec 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The port number pattern allows single-digit ports (1-9) with the pattern \d{1,5}, but also allows invalid port numbers up to 99999. Valid TCP/UDP port numbers range from 1 to 65535. The pattern should validate that the port number is within this valid range, or at minimum should be \d{2,5} to match the original pattern (though that still allows invalid ports like 99999). Consider using a more restrictive pattern like (?:6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}) to properly validate port numbers.
| "(?::\\d{1,5})?" + | |
| "(?::(?:6553[0-5]|655[0-2]\\d|65[0-4]\\d{2}|6[0-4]\\d{3}|[1-5]\\d{4}|[1-9]\\d{0,3}))?" + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's right but not necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Bare IPv6 addresses won't open correctly.
The Action handler adds the scheme to bare IPv6 addresses without wrapping them in brackets. For input "2001:db8::1", this produces "http://2001:db8::1" instead of the required "http://[2001:db8::1]". Browsers will reject these URLs.
The IsURL method correctly handles bracket-wrapping for validation (lines 137-157), but the Action handler needs the same logic.
🔎 Proposed fix
{
// not a recognized scheme, add preferred http scheme
if (!UrlSchemes.Any(scheme => raw.StartsWith(scheme, StringComparison.OrdinalIgnoreCase)))
{
- raw = GetHttpPreference() + "://" + raw;
+ // Wrap bare IPv6 addresses in brackets
+ if (raw.Count(c => c == ':') > 1 && !raw.Contains("://") && !raw.Contains('@') && !raw.StartsWith('['))
+ {
+ // Check if there's already a port pattern (]:port)
+ var portMatch = Regex.Match(raw, @"\]:(\d{1,5})$");
+ if (!portMatch.Success)
+ {
+ raw = $"{GetHttpPreference()}://[{raw}]";
+ }
+ else
+ {
+ raw = GetHttpPreference() + "://" + raw;
+ }
+ }
+ else
+ {
+ raw = GetHttpPreference() + "://" + raw;
+ }
}Alternatively, extract the bracket-wrapping logic into a helper method to avoid duplication with IsURL.
🤖 Prompt for AI Agents
In Plugins/Flow.Launcher.Plugin.Url/Main.cs around lines 63 to 67, the code
prepends the HTTP scheme to strings but does not bracket-wrap bare IPv6
addresses; change the logic so that when adding the scheme you detect a bare
IPv6 (contains ':' and does not start with '[' and is not already a recognized
scheme) and wrap it with '[' and ']' before prefixing the scheme (or extract the
existing bracket-wrapping logic from IsURL into a shared helper and call that
here) so resulting URLs become "http://[2001:db8::1]" instead of
"http://2001:db8::1".
Uh oh!
There was an error while loading. Please reload this page.