diff --git a/PowerToys.sln b/PowerToys.sln index ca9bb2e58c..db9b22afda 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -189,6 +189,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Launcher", "src\m EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\modules\launcher\PowerLauncher\PowerLauncher.csproj", "{F97E5003-F263-4D4A-A964-0F1F3C82DEF2}" ProjectSection(ProjectDependencies) = postProject + {03276A39-D4E9-417C-8FFD-200B0EE5E871} = {03276A39-D4E9-417C-8FFD-200B0EE5E871} {FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4} {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E} {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} @@ -263,6 +264,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorPickerUI", "src\module EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "colorpicker", "colorpicker", "{1D78B84B-CA39-406C-98F4-71F7EC266CC0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Uri", "src\modules\launcher\Plugins\Microsoft.Plugin.Uri\Microsoft.Plugin.Uri.csproj", "{03276A39-D4E9-417C-8FFD-200B0EE5E871}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Uri.UnitTests", "src\modules\launcher\Plugins\Microsoft.Plugin.Uri.UnitTests\Microsoft.Plugin.Uri.UnitTests.csproj", "{B81FB7B6-D30E-428F-908A-41422EFC1172}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -521,6 +526,14 @@ Global {BA58206B-1493-4C75-BFEA-A85768A1E156}.Debug|x64.Build.0 = Debug|x64 {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x64.ActiveCfg = Release|x64 {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x64.Build.0 = Release|x64 + {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Debug|x64.ActiveCfg = Debug|x64 + {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Debug|x64.Build.0 = Debug|x64 + {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Release|x64.ActiveCfg = Release|x64 + {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Release|x64.Build.0 = Release|x64 + {B81FB7B6-D30E-428F-908A-41422EFC1172}.Debug|x64.ActiveCfg = Debug|x64 + {B81FB7B6-D30E-428F-908A-41422EFC1172}.Debug|x64.Build.0 = Debug|x64 + {B81FB7B6-D30E-428F-908A-41422EFC1172}.Release|x64.ActiveCfg = Release|x64 + {B81FB7B6-D30E-428F-908A-41422EFC1172}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -595,6 +608,8 @@ Global {655C9AF2-18D3-4DA6-80E4-85504A7722BA} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0} {BA58206B-1493-4C75-BFEA-A85768A1E156} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0} {1D78B84B-CA39-406C-98F4-71F7EC266CC0} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {03276A39-D4E9-417C-8FFD-200B0EE5E871} = {4AFC9975-2456-4C70-94A4-84073C1CED93} + {B81FB7B6-D30E-428F-908A-41422EFC1172} = {4AFC9975-2456-4C70-94A4-84073C1CED93} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 60ae35fb10..8dcbca9bc9 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -275,6 +275,11 @@ + + + + + @@ -1013,6 +1018,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/Microsoft.Plugin.Uri.UnitTests.csproj b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/Microsoft.Plugin.Uri.UnitTests.csproj new file mode 100644 index 0000000000..288c310638 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/Microsoft.Plugin.Uri.UnitTests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + false + x64 + Microsoft.Plugin.Uri.UnitTests + + + + + + + + + + + + + diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/UriHelper/ExtendedUriParserTests.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/UriHelper/ExtendedUriParserTests.cs new file mode 100644 index 0000000000..eea81a4c39 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/UriHelper/ExtendedUriParserTests.cs @@ -0,0 +1,53 @@ +using Microsoft.Plugin.Uri.UriHelper; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using NUnit.Framework; + +namespace Microsoft.Plugin.Uri.UnitTests.UriHelper +{ + [TestFixture] + public class ExtendedUriParserTests + { + [TestCase("google.com", true, "http://google.com/")] + [TestCase("localhost", true, "http://localhost/")] + [TestCase("127.0.0.1", true, "http://127.0.0.1/")] + [TestCase("127.0.0.1:80", true, "http://127.0.0.1/")] + [TestCase("127", true, "http://0.0.0.127/")] + [TestCase("", false, null)] + [TestCase("https://google.com", true, "https://google.com/")] + [TestCase("ftps://google.com", true, "ftps://google.com/")] + [TestCase(null, false, null)] + [TestCase("bing.com/search?q=gmx", true, "http://bing.com/search?q=gmx")] + [TestCase("h", true, "http://h/")] + [TestCase("ht", true, "http://ht/")] + [TestCase("htt", true, "http://htt/")] + [TestCase("http", true, "http://http/")] + [TestCase("http:", false, null)] + [TestCase("http:/", false, null)] + [TestCase("http://", false, null)] + [TestCase("http://t", true, "http://t/")] + [TestCase("http://te", true, "http://te/")] + [TestCase("http://tes", true, "http://tes/")] + [TestCase("http://test", true, "http://test/")] + [TestCase("http://test.", false, null)] + [TestCase("http://test.c", true, "http://test.c/")] + [TestCase("http://test.co", true, "http://test.co/")] + [TestCase("http://test.com", true, "http://test.com/")] + [TestCase("http:3", true,"http://http:3/")] + [TestCase("[::]", true, "http://[::]")] + [TestCase("[2001:0DB8::1]", true, "http://[2001:0DB8::1]/")] + [TestCase("[2001:0DB8::1]:80",true, "http://[2001:0DB8::1]/")] + public void TryParse_CanParseHostName(string query, bool expectedSuccess, string expectedResult) + { + // Arrange + var parser = new ExtendedUriParser(); + + // Act + var success = parser.TryParse(query, out var result); + + // Assert + Assert.AreEqual(expectedResult, result?.ToString()); + Assert.AreEqual(expectedSuccess, success); + } + } +} + diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.dark.png b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.dark.png new file mode 100644 index 0000000000..6c34ed1582 Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.dark.png differ diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.light.png b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.light.png new file mode 100644 index 0000000000..bea54d005c Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.light.png differ diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IRegistryWrapper.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IRegistryWrapper.cs new file mode 100644 index 0000000000..4b1682df16 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IRegistryWrapper.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Plugin.Uri.Interfaces +{ + public interface IRegistryWrapper + { + string GetRegistryValue(string registryLocation, string valueName); + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUriParser.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUriParser.cs new file mode 100644 index 0000000000..aa0154bc35 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUriParser.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Plugin.Uri.Interfaces +{ + public interface IUriParser + { + bool TryParse(string input, out System.Uri result); + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUrlResolver.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUrlResolver.cs new file mode 100644 index 0000000000..717b67bf68 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUrlResolver.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Plugin.Uri.Interfaces +{ + public interface IUrlResolver + { + bool IsValidHost(System.Uri uri); + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/de.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/de.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/de.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/en.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/en.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/en.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/ja.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/ja.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/ja.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/pl.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/pl.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/pl.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/tr.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/tr.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/tr.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-cn.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-cn.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-tw.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-tw.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs new file mode 100644 index 0000000000..0479b566d3 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs @@ -0,0 +1,179 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using Microsoft.Plugin.Uri.UriHelper; +using Wox.Infrastructure.Logger; +using Wox.Infrastructure.Storage; +using Wox.Plugin; + +namespace Microsoft.Plugin.Uri +{ + public class Main : IPlugin, IPluginI18n, IContextMenu, ISavable, IDisposable + { + private readonly ExtendedUriParser _uriParser; + private readonly UriResolver _uriResolver; + private readonly PluginJsonStorage _storage; + private bool _disposed; + private UriSettings _uriSettings; + private RegisteryWrapper _registeryWrapper; + + public Main() + { + _storage = new PluginJsonStorage(); + _uriSettings = _storage.Load(); + _uriParser = new ExtendedUriParser(); + _uriResolver = new UriResolver(); + _registeryWrapper = new RegisteryWrapper(); + } + + public string BrowserIconPath { get; set; } + + public string DefaultIconPath { get; set; } + + public PluginInitContext Context { get; protected set; } + + public List LoadContextMenus(Result selectedResult) + { + return new List(0); + } + + public List Query(Query query) + { + var results = new List(); + + if (!string.IsNullOrEmpty(query?.Search) + && _uriParser.TryParse(query.Search, out var uriResult) + && _uriResolver.IsValidHost(uriResult)) + { + var uriResultString = uriResult.ToString(); + + results.Add(new Result + { + Title = uriResultString, + SubTitle = Context.API.GetTranslation("Microsoft_plugin_uri_website"), + IcoPath = _uriSettings.ShowBrowserIcon + ? BrowserIconPath + : DefaultIconPath, + Action = action => + { + Process.Start(new ProcessStartInfo(uriResultString) + { + UseShellExecute = true, + }); + return true; + }, + }); + } + + return results; + } + + public void Init(PluginInitContext context) + { + Context = context ?? throw new ArgumentNullException(nameof(context)); + Context.API.ThemeChanged += OnThemeChanged; + UpdateIconPath(Context.API.GetCurrentTheme()); + UpdateBrowserIconPath(Context.API.GetCurrentTheme()); + } + + public string GetTranslatedPluginTitle() + { + return "Url Handler"; + } + + public string GetTranslatedPluginDescription() + { + return "Handles urls"; + } + + public void Save() + { + _storage.Save(); + } + + private void OnThemeChanged(Theme oldtheme, Theme newTheme) + { + UpdateIconPath(newTheme); + UpdateBrowserIconPath(newTheme); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive but will log the exception")] + private void UpdateBrowserIconPath(Theme newTheme) + { + try + { + var progId = _registeryWrapper.GetRegistryValue("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice", "ProgId"); + var programLocation = + + // Resolve App Icon (UWP) + _registeryWrapper.GetRegistryValue("HKEY_CLASSES_ROOT\\" + progId + "\\Application", "ApplicationIcon") + + // Resolves default file association icon (UWP + Normal) + ?? _registeryWrapper.GetRegistryValue("HKEY_CLASSES_ROOT\\" + progId + "\\DefaultIcon", null); + + // "Handles 'Indirect Strings' (UWP programs)" + if (programLocation.StartsWith("@", StringComparison.Ordinal)) + { + var directProgramLocationStringBuilder = new StringBuilder(128); + if (NativeMethods.SHLoadIndirectString(programLocation, directProgramLocationStringBuilder, (uint)directProgramLocationStringBuilder.Capacity, IntPtr.Zero) == + NativeMethods.Hresult.Ok) + { + // Check if there's a postfix with contract-white/contrast-black icon is available and use that instead + var directProgramLocation = directProgramLocationStringBuilder.ToString(); + var themeIcon = newTheme == Theme.Light || newTheme == Theme.HighContrastWhite ? "contrast-white" : "contrast-black"; + var extension = Path.GetExtension(directProgramLocation); + var themedProgLocation = $"{directProgramLocation.Substring(0, directProgramLocation.Length - extension.Length)}_{themeIcon}{extension}"; + BrowserIconPath = File.Exists(themedProgLocation) + ? themedProgLocation + : directProgramLocation; + } + } + else + { + var indexOfComma = programLocation.IndexOf(',', StringComparison.Ordinal); + BrowserIconPath = indexOfComma > 0 + ? programLocation.Substring(0, indexOfComma) + : programLocation; + } + } + catch (Exception e) + { + BrowserIconPath = DefaultIconPath; + Log.Exception("Exception when retreiving icon", e); + } + } + + private void UpdateIconPath(Theme theme) + { + if (theme == Theme.Light || theme == Theme.HighContrastWhite) + { + DefaultIconPath = "Images/uri.light.png"; + } + else + { + DefaultIconPath = "Images/uri.dark.png"; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed && disposing) + { + Context.API.ThemeChanged -= OnThemeChanged; + _disposed = true; + } + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Microsoft.Plugin.Uri.csproj b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Microsoft.Plugin.Uri.csproj new file mode 100644 index 0000000000..966f89b04c --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Microsoft.Plugin.Uri.csproj @@ -0,0 +1,133 @@ + + + + netcoreapp3.1 + {03276a39-d4e9-417c-8ffd-200b0ee5e871} + Properties + Microsoft.Plugin.Uri + Microsoft.Plugin.Uri + true + false + false + x64 + + + + true + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Microsoft.Plugin.Uri\ + DEBUG;TRACE + full + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + 4 + false + true + + + + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Microsoft.Plugin.Uri\ + TRACE + true + pdbonly + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + 4 + true + + + + + GlobalSuppressions.cs + + + StyleCop.json + + + + + + + + + + + + + + + + PreserveNewest + Designer + MSBuild:Compile + + + PreserveNewest + Designer + MSBuild:Compile + + + PreserveNewest + Designer + MSBuild:Compile + + + PreserveNewest + Designer + MSBuild:Compile + + + PreserveNewest + Designer + MSBuild:Compile + + + PreserveNewest + Designer + MSBuild:Compile + + + PreserveNewest + Designer + MSBuild:Compile + + + + + + + + + + + PreserveNewest + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + PreserveNewest + + + PreserveNewest + + + + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/NativeMethods.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/NativeMethods.cs new file mode 100644 index 0000000000..c64881736b --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/NativeMethods.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.Plugin.Uri +{ + internal static class NativeMethods + { + internal enum Hresult : uint + { + Ok = 0x0000, + } + + [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] + internal static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved); + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/RegisteryWrapper.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/RegisteryWrapper.cs new file mode 100644 index 0000000000..86993799ae --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/RegisteryWrapper.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Plugin.Uri.Interfaces; +using Microsoft.Win32; + +namespace Microsoft.Plugin.Uri +{ + public class RegisteryWrapper : IRegistryWrapper + { + public string GetRegistryValue(string registryLocation, string valueName) + { + return Registry.GetValue(registryLocation, valueName, null) as string; + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs new file mode 100644 index 0000000000..0d6e202e82 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.Plugin.Uri.Interfaces; + +namespace Microsoft.Plugin.Uri.UriHelper +{ + public class ExtendedUriParser : IUriParser + { + public bool TryParse(string input, out System.Uri result) + { + if (string.IsNullOrEmpty(input)) + { + result = default; + return false; + } + + // Handle common cases UriBuilder does not handle + if (input.EndsWith(":", StringComparison.Ordinal) + || input.EndsWith(".", StringComparison.Ordinal) + || input.EndsWith(":/", StringComparison.Ordinal)) + { + result = default; + return false; + } + + try + { + var urlBuilder = new UriBuilder(input); + + result = urlBuilder.Uri; + return true; + } + catch (System.UriFormatException) + { + result = default; + return false; + } + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/UriResolver.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/UriResolver.cs new file mode 100644 index 0000000000..b22c693395 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/UriResolver.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Plugin.Uri.Interfaces; + +namespace Microsoft.Plugin.Uri.UriHelper +{ + public class UriResolver : IUrlResolver + { + public bool IsValidHost(System.Uri uri) + { + return true; + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriSettings.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriSettings.cs new file mode 100644 index 0000000000..7f91676425 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriSettings.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Plugin.Uri +{ + public class UriSettings + { + public bool ShowBrowserIcon { get; set; } = true; + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/plugin.json b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/plugin.json new file mode 100644 index 0000000000..38347f46b0 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/plugin.json @@ -0,0 +1,12 @@ +{ + "ID": "03276A39D4E9417C8FFD200B0EE5E871", + "ActionKeyword": "*", + "Name": "Windows Uri Handler", + "Description": "Handles urls", + "Author": "Microsoft", + "Version": "1.0.0", + "Language": "csharp", + "Website": "http://aka.ms/PowerToys", + "ExecuteFileName": "Microsoft.Plugin.Uri.dll", + "IcoPath": "Images\\uri.dark.png" +}