Skip to content
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

Fix opening links without http and https #640

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/Orc.Controls.Example/Views/LinkLabel.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Margin="5" >
<TextBlock Text="LinkLabel" VerticalAlignment="Center" Width="160" />
<orccontrols:LinkLabel Content="Action" ToolTip="Action" Command="{Binding DefaultAction}"
HoverForeground="Orange" Url="http://catelproject.com"
HoverForeground="Orange" Url="catelproject.com"
ClickBehavior="OpenUrlInBrowser"/>
</StackPanel>
</catel:UserControl>
79 changes: 79 additions & 0 deletions src/Orc.Controls.Tests/Controls/LinkLabel/LinkLabelTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
namespace Orc.Controls.Tests;

using System;
using System.Threading;
using System.Windows;
using Catel.IoC;
using Catel.Services;
using Moq;
using NUnit.Framework;

[TestFixture]
[Apartment(ApartmentState.STA)]
public class LinkLabelTests
{
[TestCase("catelproject.com", UriKind.Relative, "https://catelproject.com/")]
[TestCase("myapp://myaction", UriKind.Absolute, "myapp://myaction")]
[TestCase("С:\\my\\long\\path\\to\\exe\\file\\exe.exe", UriKind.Relative, "С:\\my\\long\\path\\to\\exe\\file\\exe.exe")]
public void Open_Correct_Uri(string uriPath, UriKind kind, string expectedUri)
{
//Prepare
var processServiceMock = new Mock<IProcessService>();
processServiceMock.Setup(x => x.StartProcess(It.IsAny<ProcessContext>(), It.IsAny<ProcessCompletedDelegate>()))
.Callback<ProcessContext, ProcessCompletedDelegate>((c, d) =>
{
Assert.That(c.FileName, Is.EqualTo(expectedUri));
});

DependencyResolverManager.Default = new LinkLabelDependencyResolverManager
{
DependencyResolver = new LinkLabelDependencyResolver
{
ProcessService = processServiceMock.Object
}
};

var linkLabel = new LinkLabel();
linkLabel.Url = new Uri(uriPath, kind);
linkLabel.ClickBehavior = LinkLabelClickBehavior.OpenUrlInBrowser;
linkLabel.RaiseEvent(new RoutedEventArgs(LinkLabel.ClickEvent));
}

private class LinkLabelDependencyResolver : IDependencyResolver
{
public IProcessService ProcessService { get; set; }

public object Resolve(Type type, object tag = null)
{
if (type == typeof(IProcessService))
{
return ProcessService;
}

return null;
}

public object[] ResolveMultiple(Type[] types, object tag = null) => null;
public bool CanResolve(Type type, object tag = null) => true;
public bool CanResolveMultiple(Type[] types) => false;
}

private class LinkLabelDependencyResolverManager : IDependencyResolverManager
{
public LinkLabelDependencyResolver DependencyResolver { get; set; }

public void RegisterDependencyResolverForInstance(object instance, IDependencyResolver dependencyResolver)
{
}

public IDependencyResolver GetDependencyResolverForInstance(object instance) => DependencyResolver;

public void RegisterDependencyResolverForType(Type type, IDependencyResolver dependencyResolver)
{
}

public IDependencyResolver GetDependencyResolverForType(Type type) => DependencyResolver;

public IDependencyResolver DefaultDependencyResolver { get; set; }
}
}
62 changes: 38 additions & 24 deletions src/Orc.Controls/Controls/LinkLabel/LinkLabel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
using Automation;
using Catel;
using Catel.IoC;
using Catel.Logging;
using Catel.Services;
using Automation;

/// <summary>
/// A label looking like the known hyperlink.
Expand All @@ -25,6 +28,8 @@ public class LinkLabel : Label
/// </summary>
private static readonly ILog Log = LogManager.GetCurrentClassLogger();

private readonly IProcessService _processService;

/// <summary>
/// Initializes the <see cref="LinkLabel"/> class.
/// </summary>
Expand All @@ -42,6 +47,8 @@ static LinkLabel()
public LinkLabel()
{
Unloaded += OnLinkLabelUnloaded;

_processService = this.GetDependencyResolver().ResolveRequired<IProcessService>();
}

/// <summary>
Expand Down Expand Up @@ -142,8 +149,8 @@ public LinkLabelClickBehavior ClickBehavior
/// DependencyProperty definition as the backing store for ClickBehavior
/// </summary>
public static readonly DependencyProperty ClickBehaviorProperty =
DependencyProperty.Register(nameof(ClickBehavior),
typeof(LinkLabelClickBehavior), typeof(LinkLabel), new UIPropertyMetadata(LinkLabelClickBehavior.Undefined, OnClickBehaviorChanged));
DependencyProperty.Register(nameof(ClickBehavior), typeof(LinkLabelClickBehavior), typeof(LinkLabel),
new UIPropertyMetadata(LinkLabelClickBehavior.Undefined, (sender, args) => ((LinkLabel)sender).OnClickBehaviorChanged(args)));

/// <summary>
/// Gets or sets the command parameter.
Expand Down Expand Up @@ -199,7 +206,7 @@ public IInputElement? CommandTarget
/// <summary>
/// ClickEvent
/// </summary>
[Category("Behavior")]
[Category("Behavior")]
public static readonly RoutedEvent? ClickEvent;

/// <summary>
Expand All @@ -214,7 +221,7 @@ public event RoutedEventHandler? Click
/// <summary>
/// RequestNavigateEvent
/// </summary>
[Category("Behavior")]
[Category("Behavior")]
public static readonly RoutedEvent? RequestNavigateEvent;

/// <summary>
Expand Down Expand Up @@ -258,15 +265,9 @@ private static void OnUrlChanged(DependencyObject sender, DependencyPropertyChan
/// <summary>
/// Handles a change of the ClickBehavior property.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="args">The event arguments.</param>
private static void OnClickBehaviorChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
private void OnClickBehaviorChanged(DependencyPropertyChangedEventArgs args)
{
if (sender is not LinkLabel label)
{
return;
}

if (!args.Property.Name.Equals(ClickBehaviorProperty.Name, StringComparison.Ordinal))
{
return;
Expand All @@ -277,12 +278,12 @@ private static void OnClickBehaviorChanged(DependencyObject sender, DependencyPr

if (previous == LinkLabelClickBehavior.OpenUrlInBrowser)
{
label.Click -= OpenBrowserBehaviorImpl;
Click -= OpenBrowserBehaviorImpl;
}

if (next == LinkLabelClickBehavior.OpenUrlInBrowser)
{
label.Click += OpenBrowserBehaviorImpl;
Click += OpenBrowserBehaviorImpl;
}
}

Expand Down Expand Up @@ -330,7 +331,7 @@ private void OnInnerHyperlinkClick(object sender, RoutedEventArgs e)
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="args">Event arguments</param>
private static void OpenBrowserBehaviorImpl(object sender, RoutedEventArgs args)
private void OpenBrowserBehaviorImpl(object sender, RoutedEventArgs args)
{
var hyperlinkSender = sender as Hyperlink;
var linklabelSender = sender as LinkLabel;
Expand All @@ -339,8 +340,26 @@ private static void OpenBrowserBehaviorImpl(object sender, RoutedEventArgs args)
return;
}

var destinationUrl = hyperlinkSender?.NavigateUri ?? linklabelSender?.Url;
if (destinationUrl is null || string.IsNullOrEmpty(destinationUrl.ToString()))
var uri = linklabelSender?.Url;
if (uri is null)
{
return;
}

if (!uri.IsAbsoluteUri)
{
uri = new Uri($"https://{uri}");
}
if (!uri.Scheme.Contains("://") && !uri.Scheme.StartsWithAnyIgnoreCase("http", "https") && !uri.IsFile)
{
var relativePart = uri.GetComponents(UriComponents.PathAndQuery | UriComponents.Fragment,
UriFormat.UriEscaped);

uri = new Uri($"https://{relativePart}");
}

var destinationUrl = hyperlinkSender?.NavigateUri ?? uri;
if (string.IsNullOrEmpty(destinationUrl.ToString()))
{
return;
}
Expand All @@ -351,16 +370,11 @@ private static void OpenBrowserBehaviorImpl(object sender, RoutedEventArgs args)

try
{
// UseShellExecute is disabled by default in NETCORE
var processStartInfo = new ProcessStartInfo
_processService.StartProcess(new ProcessContext
{
FileName = destinationUrl.ToString(),
UseShellExecute = true
};

#pragma warning disable IDISP004 // Don't ignore created IDisposable
Process.Start(processStartInfo);
#pragma warning restore IDISP004 // Don't ignore created IDisposable
});
}
catch (Win32Exception ex)
{
Expand Down