CmdPal: Fix RDP extension rejecting host:port connections (#45740)

## Summary

Fixes #45100

Uri.CheckHostName does not accept host:port strings (e.g.
localhost:3389), returning UriHostNameType.Unknown. This causes the RDP
extension to show an invalid hostname error when connecting to a local
forwarded port.

## Changes

- OpenRemoteDesktopCommand.cs - Strip port suffix before
Uri.CheckHostName validation. The full host:port is still passed to
mstsc /v:.
- FallbackRemoteDesktopItem.cs - Same port-aware validation so the
fallback correctly recognizes host:port queries and displays them in the
title.
- FallbackRemoteDesktopItemTests.cs - Added tests for localhost:3389 and
192.168.1.100:3390 inputs.

## Validation

Port detection uses LastIndexOf(':') + ushort.TryParse to safely
identify a trailing port number without affecting IPv6 addresses or
plain hostnames.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Michael Jolley
2026-02-22 19:14:19 -08:00
committed by GitHub
parent 368490ef79
commit 009ee75de0
3 changed files with 82 additions and 11 deletions

View File

@@ -98,6 +98,48 @@ public class FallbackRemoteDesktopItemTests
Assert.IsNull(command);
}
[TestMethod]
public void UpdateQuery_WhenQueryIsHostnameWithPort_UsesFullHostPort()
{
// Arrange
var setup = CreateFallback();
var fallback = setup.Fallback;
const string hostPort = "localhost:3389";
// Act
fallback.UpdateQuery(hostPort);
// Assert
var expectedTitle = string.Format(CultureInfo.CurrentCulture, OpenHostCompositeFormat, hostPort);
Assert.AreEqual(expectedTitle, fallback.Title);
Assert.AreEqual(Resources.remotedesktop_title, fallback.Subtitle);
var command = fallback.Command as OpenRemoteDesktopCommand;
Assert.IsNotNull(command);
Assert.AreEqual(hostPort, GetCommandHost(command));
}
[TestMethod]
public void UpdateQuery_WhenQueryIsIPWithPort_UsesFullHostPort()
{
// Arrange
var setup = CreateFallback();
var fallback = setup.Fallback;
const string hostPort = "192.168.1.100:3390";
// Act
fallback.UpdateQuery(hostPort);
// Assert
var expectedTitle = string.Format(CultureInfo.CurrentCulture, OpenHostCompositeFormat, hostPort);
Assert.AreEqual(expectedTitle, fallback.Title);
Assert.AreEqual(Resources.remotedesktop_title, fallback.Subtitle);
var command = fallback.Command as OpenRemoteDesktopCommand;
Assert.IsNotNull(command);
Assert.AreEqual(hostPort, GetCommandHost(command));
}
private static string GetCommandHost(OpenRemoteDesktopCommand command)
{
var field = typeof(OpenRemoteDesktopCommand).GetField("_rdpHost", BindingFlags.NonPublic | BindingFlags.Instance);

View File

@@ -60,18 +60,34 @@ internal sealed partial class FallbackRemoteDesktopItem : FallbackCommandItem
Title = connectionName;
Subtitle = string.Format(CultureInfo.CurrentCulture, RemoteDesktopOpenHostFormat, connectionName);
}
else if (ValidUriHostNameTypes.Contains(Uri.CheckHostName(query)))
{
var connectionName = query.Trim();
Command = new OpenRemoteDesktopCommand(connectionName);
Title = string.Format(CultureInfo.CurrentCulture, RemoteDesktopOpenHostFormat, connectionName);
Subtitle = Resources.remotedesktop_title;
}
else
{
Title = string.Empty;
Subtitle = string.Empty;
Command = _emptyCommand;
// Strip port suffix (e.g. "localhost:3389") before validation,
// since Uri.CheckHostName does not accept host:port strings.
var hostForValidation = query.Trim();
var lastColon = hostForValidation.LastIndexOf(':');
if (lastColon > 0 && lastColon < hostForValidation.Length - 1)
{
var portPart = hostForValidation.Substring(lastColon + 1);
if (ushort.TryParse(portPart, out _))
{
hostForValidation = hostForValidation.Substring(0, lastColon);
}
}
if (ValidUriHostNameTypes.Contains(Uri.CheckHostName(hostForValidation)))
{
var connectionName = query.Trim();
Command = new OpenRemoteDesktopCommand(connectionName);
Title = string.Format(CultureInfo.CurrentCulture, RemoteDesktopOpenHostFormat, connectionName);
Subtitle = Resources.remotedesktop_title;
}
else
{
Title = string.Empty;
Subtitle = string.Empty;
Command = _emptyCommand;
}
}
}
}

View File

@@ -46,7 +46,20 @@ internal sealed partial class OpenRemoteDesktopCommand : BaseObservable, IInvoka
if (!string.IsNullOrWhiteSpace(_rdpHost))
{
// validate that _rdpHost is a proper hostname or IP address
if (Uri.CheckHostName(_rdpHost) == UriHostNameType.Unknown)
// Strip port suffix (e.g. "localhost:3389") before validation,
// since Uri.CheckHostName does not accept host:port strings.
var hostForValidation = _rdpHost;
var lastColon = _rdpHost.LastIndexOf(':');
if (lastColon > 0 && lastColon < _rdpHost.Length - 1)
{
var portPart = _rdpHost.Substring(lastColon + 1);
if (ushort.TryParse(portPart, out _))
{
hostForValidation = _rdpHost.Substring(0, lastColon);
}
}
if (Uri.CheckHostName(hostForValidation) == UriHostNameType.Unknown)
{
return CommandResult.ShowToast(new ToastArgs()
{