Skip to content

Commit be630f1

Browse files
Update UrlPattern to include private addresses and update test cases
1 parent 8092a44 commit be630f1

2 files changed

Lines changed: 60 additions & 32 deletions

File tree

Flow.Launcher.Test/Plugins/UrlPluginTest.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,47 @@ public void URLMatchTest()
1414
ClassicAssert.IsTrue(plugin.IsURL("http://www.google.com"));
1515
ClassicAssert.IsTrue(plugin.IsURL("https://www.google.com"));
1616
ClassicAssert.IsTrue(plugin.IsURL("http://google.com"));
17+
ClassicAssert.IsTrue(plugin.IsURL("ftp://google.com"));
1718
ClassicAssert.IsTrue(plugin.IsURL("www.google.com"));
1819
ClassicAssert.IsTrue(plugin.IsURL("google.com"));
1920
ClassicAssert.IsTrue(plugin.IsURL("http://localhost"));
2021
ClassicAssert.IsTrue(plugin.IsURL("https://localhost"));
2122
ClassicAssert.IsTrue(plugin.IsURL("http://localhost:80"));
2223
ClassicAssert.IsTrue(plugin.IsURL("https://localhost:80"));
24+
ClassicAssert.IsTrue(plugin.IsURL("localhost"));
25+
ClassicAssert.IsTrue(plugin.IsURL("localhost:8080"));
2326
ClassicAssert.IsTrue(plugin.IsURL("http://110.10.10.10"));
2427
ClassicAssert.IsTrue(plugin.IsURL("110.10.10.10"));
28+
ClassicAssert.IsTrue(plugin.IsURL("110.10.10.10:8080"));
29+
ClassicAssert.IsTrue(plugin.IsURL("192.168.1.1"));
30+
ClassicAssert.IsTrue(plugin.IsURL("192.168.1.1:3000"));
2531
ClassicAssert.IsTrue(plugin.IsURL("ftp://110.10.10.10"));
32+
ClassicAssert.IsTrue(plugin.IsURL("[2001:db8::1]"));
33+
ClassicAssert.IsTrue(plugin.IsURL("[2001:db8::1]:8080"));
34+
ClassicAssert.IsTrue(plugin.IsURL("http://[2001:db8::1]"));
35+
ClassicAssert.IsTrue(plugin.IsURL("https://[2001:db8::1]:8080"));
36+
ClassicAssert.IsTrue(plugin.IsURL("[::1]"));
37+
ClassicAssert.IsTrue(plugin.IsURL("[::1]:8080"));
38+
ClassicAssert.IsTrue(plugin.IsURL("2001:db8::1"));
39+
ClassicAssert.IsTrue(plugin.IsURL("::1"));
40+
ClassicAssert.IsTrue(plugin.IsURL("HTTP://EXAMPLE.COM"));
41+
ClassicAssert.IsTrue(plugin.IsURL("HTTPS://EXAMPLE.COM"));
42+
ClassicAssert.IsTrue(plugin.IsURL("EXAMPLE.COM"));
43+
ClassicAssert.IsTrue(plugin.IsURL("LOCALHOST"));
2644

2745

2846
ClassicAssert.IsFalse(plugin.IsURL("wwww"));
2947
ClassicAssert.IsFalse(plugin.IsURL("wwww.c"));
3048
ClassicAssert.IsFalse(plugin.IsURL("wwww.c"));
49+
ClassicAssert.IsFalse(plugin.IsURL("not a url"));
50+
ClassicAssert.IsFalse(plugin.IsURL("just text"));
51+
ClassicAssert.IsFalse(plugin.IsURL("http://"));
52+
ClassicAssert.IsFalse(plugin.IsURL("://example.com"));
53+
ClassicAssert.IsFalse(plugin.IsURL("0.0.0.0")); // Pattern excludes 0.0.0.0
54+
ClassicAssert.IsFalse(plugin.IsURL("256.1.1.1")); // Invalid IPv4
55+
ClassicAssert.IsFalse(plugin.IsURL("example")); // No TLD
56+
ClassicAssert.IsFalse(plugin.IsURL(".com"));
57+
ClassicAssert.IsFalse(plugin.IsURL("http://.com"));
3158
}
3259
}
3360
}

Plugins/Flow.Launcher.Plugin.Url/Main.cs

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Text.RegularExpressions;
45
using System.Windows.Controls;
56
using Flow.Launcher.Plugin.SharedCommands;
@@ -15,19 +16,26 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider
1516
// user:pass authentication
1617
"(?:\\S+(?::\\S*)?@)?" +
1718
"(?:" +
18-
// IP address exclusion
19-
// private & local networks
20-
"(?!(?:10|127)(?:\\.\\d{1,3}){3})" +
21-
"(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" +
22-
"(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})" +
23-
// IP address dotted notation octets
24-
// excludes loopback network 0.0.0.0
25-
// excludes reserved space >= 224.0.0.0
26-
// excludes network & broacast addresses
27-
// (first & last IP address of each class)
28-
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" +
29-
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" +
30-
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" +
19+
// IPv6 address with optional brackets (brackets required if followed by port)
20+
// IPv6 with brackets
21+
"(?:\\[(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\]|" + // standard IPv6
22+
"\\[(?:[0-9a-fA-F]{1,4}:){1,7}:\\]|" + // IPv6 with trailing ::
23+
"\\[(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}\\]|" + // IPv6 compressed
24+
"\\[::(?:[0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}\\]|" + // IPv6 with leading ::
25+
"\\[::1\\])" + // IPv6 loopback
26+
"|" +
27+
// IPv6 without brackets (only when no port follows)
28+
"(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|" + // standard IPv6
29+
"(?:[0-9a-fA-F]{1,4}:){1,7}:|" + // IPv6 with trailing ::
30+
"(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + // IPv6 compressed
31+
"::(?:[0-9a-fA-F]{1,4}:){0,6}[0-9a-fA-F]{1,4}|" + // IPv6 with leading ::
32+
"::1)(?!:[0-9])" + // IPv6 loopback (not followed by port)
33+
"|" +
34+
// IPv4 address - all valid addresses including private networks (excluding 0.0.0.0)
35+
"(?:(?: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))" +
36+
"|" +
37+
// localhost
38+
"localhost" +
3139
"|" +
3240
// host name
3341
"(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" +
@@ -37,20 +45,25 @@ public class Main : IPlugin, IPluginI18n, ISettingProvider
3745
"(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))" +
3846
")" +
3947
// port number
40-
"(?::\\d{2,5})?" +
48+
"(?::\\d{1,5})?" +
4149
// resource path
4250
"(?:/\\S*)?" +
4351
"$";
4452
private readonly Regex UrlRegex = new(UrlPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
4553
internal static PluginInitContext Context { get; private set; }
4654
internal static Settings Settings { get; private set; }
4755

56+
private static readonly string[] UrlSchemes = ["http://", "https://", "ftp://"];
57+
4858
public List<Result> Query(Query query)
4959
{
5060
var raw = query.Search;
51-
if (IsURL(raw))
61+
if (!IsURL(raw))
5262
{
53-
return
63+
return [];
64+
}
65+
66+
return
5467
[
5568
new()
5669
{
@@ -60,7 +73,8 @@ public List<Result> Query(Query query)
6073
Score = 8,
6174
Action = _ =>
6275
{
63-
if (!raw.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !raw.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
76+
// not a recognized scheme, add preferred http scheme
77+
if (!UrlSchemes.Any(scheme => raw.StartsWith(scheme, StringComparison.OrdinalIgnoreCase)))
6478
{
6579
raw = GetHttpPreference() + "://" + raw;
6680
}
@@ -92,9 +106,6 @@ public List<Result> Query(Query query)
92106
}
93107
}
94108
];
95-
}
96-
97-
return [];
98109
}
99110

100111
private static string GetHttpPreference()
@@ -104,17 +115,7 @@ private static string GetHttpPreference()
104115

105116
public bool IsURL(string raw)
106117
{
107-
raw = raw.ToLower();
108-
109-
if (UrlRegex.Match(raw).Value == raw) return true;
110-
111-
if (raw == "localhost" || raw.StartsWith("localhost:") ||
112-
raw == "http://localhost" || raw.StartsWith("http://localhost:") ||
113-
raw == "https://localhost" || raw.StartsWith("https://localhost:")
114-
)
115-
{
116-
return true;
117-
}
118+
if (UrlRegex.Match(raw.ToLower()).Value == raw) return true;
118119

119120
return false;
120121
}

0 commit comments

Comments
 (0)