using System; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Windows.Controls; using Flow.Launcher.Plugin.SharedCommands; namespace Flow.Launcher.Plugin.Url { public class Main : IPlugin, IPluginI18n, ISettingProvider { //based on https://gist.github.com/dperini/729294 private const string UrlPattern = "^" + // protocol identifier "(?:(?:https?|ftp)://|)" + // 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]))" + "|" + // host name "(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" + // domain name "(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*" + // TLD identifier "(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))" + ")" + // port number "(?::\\d{2,5})?" + // 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; } public List Query(Query query) { var raw = query.Search; if (IsURL(raw)) { return [ new() { Title = raw, SubTitle = Localize.flowlauncher_plugin_url_open_url(raw), IcoPath = "Images/url.png", Score = 8, Action = _ => { if (!raw.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !raw.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { raw = GetHttpPreference() + "://" + raw; } try { if (Settings.UseCustomBrowser) { if (Settings.OpenInNewBrowserWindow) { SearchWeb.OpenInBrowserWindow(raw, Settings.BrowserPath, Settings.OpenInPrivateMode, Settings.PrivateModeArgument); } else { SearchWeb.OpenInBrowserTab(raw, Settings.BrowserPath, Settings.OpenInPrivateMode, Settings.PrivateModeArgument); } } else { Context.API.OpenWebUrl(raw); } return true; } catch(Exception) { Context.API.ShowMsgError(Localize.flowlauncher_plugin_url_cannot_open_url(raw)); return false; } } } ]; } return []; } private static string GetHttpPreference() { return Settings.AlwaysOpenWithHttps ? "https" : "http"; } public bool IsURL(string raw) { raw = raw.ToLower(); 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; } public void Init(PluginInitContext context) { Context = context; Settings = context.API.LoadSettingJsonStorage(); } public string GetTranslatedPluginTitle() { return Localize.flowlauncher_plugin_url_plugin_name(); } public string GetTranslatedPluginDescription() { return Localize.flowlauncher_plugin_url_plugin_description(); } public Control CreateSettingPanel() { return new SettingsControl(); } } }