Skip to content

Commit 88dfe8e

Browse files
committed
Address is obsolete
1 parent c09c5be commit 88dfe8e

8 files changed

Lines changed: 181 additions & 30 deletions

File tree

src/FubarDev.FtpServer.Abstractions/Address.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace FubarDev.FtpServer
1818
/// <summary>
1919
/// Abstraction for an IP address.
2020
/// </summary>
21+
[Obsolete("Use IPEndPoint instead")]
2122
public class Address
2223
{
2324
private readonly bool _isEnhanced;

src/FubarDev.FtpServer.Abstractions/DataConnection/ActiveDataConnectionFeatureFactory.cs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public ActiveDataConnectionFeatureFactory(
5252
/// <returns>The task returning the new FTP data connection feature.</returns>
5353
[NotNull]
5454
[ItemNotNull]
55+
[Obsolete("Use the overload with IPEndPoint as address instead.")]
5556
public Task<IFtpDataConnectionFeature> CreateFeatureAsync(
5657
[CanBeNull] FtpCommand ftpCommand,
5758
[NotNull] Address portAddress,
@@ -70,10 +71,42 @@ public Task<IFtpDataConnectionFeature> CreateFeatureAsync(
7071
localEndPoint = new IPEndPoint(connectionFeature.LocalEndPoint.Address, 0);
7172
}
7273

74+
var address = portAddress.IPAddress ?? connectionFeature.RemoteEndPoint.Address;
75+
var portEndPoint = new IPEndPoint(address, portAddress.Port);
7376
return Task.FromResult<IFtpDataConnectionFeature>(
7477
new ActiveDataConnectionFeature(
7578
localEndPoint,
76-
portAddress,
79+
portEndPoint,
80+
_validators,
81+
ftpCommand,
82+
connection));
83+
}
84+
85+
/// <summary>
86+
/// Creates a <see cref="IFtpDataConnectionFeature"/> implementation for an active FTP data connection.
87+
/// </summary>
88+
/// <param name="ftpCommand">The FTP command that initiated the creation of the feature.</param>
89+
/// <param name="portEndPoint">The address the client wants the FTP server to connect to.</param>
90+
/// <param name="dataPort">The source port the server should use to connect to the client.</param>
91+
/// <returns>The task returning the new FTP data connection feature.</returns>
92+
[NotNull]
93+
[ItemNotNull]
94+
public Task<IFtpDataConnectionFeature> CreateFeatureAsync(
95+
[CanBeNull] FtpCommand ftpCommand,
96+
[NotNull] IPEndPoint portEndPoint,
97+
int? dataPort)
98+
{
99+
var connection = _connectionAccessor.FtpConnection;
100+
var connectionFeature = connection.Features.Get<IConnectionFeature>();
101+
102+
var localEndPoint = dataPort != null
103+
? new IPEndPoint(connectionFeature.LocalEndPoint.Address, dataPort.Value)
104+
: new IPEndPoint(connectionFeature.LocalEndPoint.Address, 0);
105+
106+
return Task.FromResult<IFtpDataConnectionFeature>(
107+
new ActiveDataConnectionFeature(
108+
localEndPoint,
109+
portEndPoint,
77110
_validators,
78111
ftpCommand,
79112
connection));
@@ -82,7 +115,7 @@ public Task<IFtpDataConnectionFeature> CreateFeatureAsync(
82115
private class ActiveDataConnectionFeature : IFtpDataConnectionFeature
83116
{
84117
[NotNull]
85-
private readonly Address _portAddress;
118+
private readonly IPEndPoint _portAddress;
86119

87120
[NotNull]
88121
[ItemNotNull]
@@ -93,7 +126,7 @@ private class ActiveDataConnectionFeature : IFtpDataConnectionFeature
93126

94127
public ActiveDataConnectionFeature(
95128
[NotNull] IPEndPoint localEndPoint,
96-
[NotNull] Address portAddress,
129+
[NotNull] IPEndPoint portAddress,
97130
[NotNull] [ItemNotNull] List<IFtpDataConnectionValidator> validators,
98131
[CanBeNull] FtpCommand command,
99132
[NotNull] IFtpConnection ftpConnection)
@@ -141,7 +174,7 @@ public async Task<IFtpDataConnection> GetDataConnectionAsync(TimeSpan timeout, C
141174
try
142175
{
143176
tries += 1;
144-
var connectTask = client.ConnectAsync(_portAddress.IPAddress, _portAddress.Port);
177+
var connectTask = client.ConnectAsync(_portAddress.Address, _portAddress.Port);
145178
var result = await Task.WhenAny(connectTask, Task.Delay(timeout, cancellationToken))
146179
.ConfigureAwait(false);
147180

src/FubarDev.FtpServer.Abstractions/Features/IConnectionFeature.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Copyright (c) Fubar Development Junker. All rights reserved.
33
// </copyright>
44

5+
using System;
56
using System.Net;
67

78
using JetBrains.Annotations;
@@ -19,10 +20,17 @@ public interface IConnectionFeature
1920
[NotNull]
2021
IPEndPoint LocalEndPoint { get; }
2122

23+
/// <summary>
24+
/// Gets the remote end point.
25+
/// </summary>
26+
[NotNull]
27+
IPEndPoint RemoteEndPoint { get; }
28+
2229
/// <summary>
2330
/// Gets the remote address of the client.
2431
/// </summary>
2532
[NotNull]
33+
[Obsolete]
2634
Address RemoteAddress { get; }
2735
}
2836
}

src/FubarDev.FtpServer.Commands/CommandHandlers/PasvCommandHandler.cs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//-----------------------------------------------------------------------
77

88
using System;
9+
using System.Net;
910
using System.Net.Sockets;
1011
using System.Threading;
1112
using System.Threading.Tasks;
@@ -78,7 +79,7 @@ public override async Task<IFtpResponse> Process(FtpCommand command, Cancellatio
7879
addressFamily = AddressFamily.InterNetwork;
7980
}
8081

81-
var feature = await _dataConnectionFeatureFactory.CreateFeatureAsync(command, addressFamily, cancellationToken)
82+
var dataConnectionFeature = await _dataConnectionFeatureFactory.CreateFeatureAsync(command, addressFamily, cancellationToken)
8283
.ConfigureAwait(false);
8384
var oldFeature = Connection.Features.Get<IFtpDataConnectionFeature>();
8485
try
@@ -90,28 +91,43 @@ public override async Task<IFtpResponse> Process(FtpCommand command, Cancellatio
9091
// Ignore dispose errors!
9192
}
9293

93-
Connection.Features.Set(feature);
94+
Connection.Features.Set(dataConnectionFeature);
9495

95-
var address = feature.LocalEndPoint.Address;
96-
var localPort = feature.LocalEndPoint.Port;
96+
var address = dataConnectionFeature.LocalEndPoint.Address;
97+
var localPort = dataConnectionFeature.LocalEndPoint.Port;
9798
if (isEpsv || address.AddressFamily == AddressFamily.InterNetworkV6)
9899
{
99-
var listenerAddress = new Address(localPort);
100100
await FtpContext.ServerCommandWriter.WriteAsync(
101101
new SendResponseServerCommand(
102-
new FtpResponse(229, T("Entering Extended Passive Mode ({0}).", listenerAddress))),
102+
new FtpResponse(229, T("Entering Extended Passive Mode (|||{0}|).", localPort))),
103103
cancellationToken).ConfigureAwait(false);
104104
}
105105
else
106106
{
107-
var listenerAddress = new Address(address.ToString(), localPort);
107+
var listenerAddress = dataConnectionFeature.LocalEndPoint;
108108
await FtpContext.ServerCommandWriter.WriteAsync(
109109
new SendResponseServerCommand(
110-
new FtpResponse(227, T("Entering Passive Mode ({0}).", listenerAddress))),
110+
new FtpResponse(227, T("Entering Passive Mode ({0}).", ToPasvAddress(listenerAddress)))),
111111
cancellationToken).ConfigureAwait(false);
112112
}
113113

114114
return null;
115115
}
116+
117+
/// <summary>
118+
/// Returns a PASV-compatible response text for the given end point.
119+
/// </summary>
120+
/// <param name="endPoint">The end point to return the PASV-compatible response for.</param>
121+
/// <returns>The PASV-compatible response.</returns>
122+
private static string ToPasvAddress(EndPoint endPoint)
123+
{
124+
switch (endPoint)
125+
{
126+
case IPEndPoint ipep:
127+
return $"{ipep.Address.ToString().Replace('.', ',')},{ipep.Port / 256},{ipep.Port & 0xFF}";
128+
default:
129+
throw new InvalidOperationException($"Unknown end point of type {endPoint.GetType()}: {endPoint}");
130+
}
131+
}
116132
}
117133
}

src/FubarDev.FtpServer.Commands/CommandHandlers/PortCommandHandler.cs

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//-----------------------------------------------------------------------
77

88
using System;
9+
using System.Net;
910
using System.Threading;
1011
using System.Threading.Tasks;
1112

@@ -51,7 +52,8 @@ public override async Task<IFtpResponse> Process(FtpCommand command, Cancellatio
5152
{
5253
try
5354
{
54-
var address = Address.Parse(command.Argument);
55+
var connectionFeature = this.Connection.Features.Get<IConnectionFeature>();
56+
var address = Parse(command.Argument, connectionFeature.RemoteEndPoint);
5557
if (address == null)
5658
{
5759
return new FtpResponse(501, T("Syntax error in parameters or arguments."));
@@ -78,5 +80,81 @@ public override async Task<IFtpResponse> Process(FtpCommand command, Cancellatio
7880

7981
return new FtpResponse(200, T("Command okay."));
8082
}
83+
84+
/// <summary>
85+
/// Parses an IP address.
86+
/// </summary>
87+
/// <param name="address">The IP address to parse.</param>
88+
/// <param name="remoteEndPoint">The remote end point to use as reference.</param>
89+
/// <returns>The parsed IP address.</returns>
90+
private static IPEndPoint Parse(string address, IPEndPoint remoteEndPoint)
91+
{
92+
if (string.IsNullOrEmpty(address))
93+
{
94+
return null;
95+
}
96+
97+
return IsEnhancedAddress(address)
98+
? ParseEnhanced(address, remoteEndPoint)
99+
: ParseLegacy(address);
100+
}
101+
102+
private static bool IsEnhancedAddress(string address)
103+
{
104+
const string number = "0123456789";
105+
return number.IndexOf(address[0]) == -1;
106+
}
107+
108+
private static IPEndPoint ParseLegacy(string address)
109+
{
110+
var addressParts = address.Split(',');
111+
if (addressParts.Length != 6)
112+
{
113+
return null;
114+
}
115+
116+
var portHi = Convert.ToInt32(addressParts[4], 10);
117+
var portLo = Convert.ToInt32(addressParts[5], 10);
118+
var port = (portHi * 256) + portLo;
119+
var ipAddress = string.Join(".", addressParts, 0, 4);
120+
return new IPEndPoint(IPAddress.Parse(ipAddress), port);
121+
}
122+
123+
private static IPEndPoint ParseEnhanced(string address, IPEndPoint remoteEndPoint)
124+
{
125+
var dividerChar = address[0];
126+
var addressParts = address.Substring(1, address.Length - 2).Split(dividerChar);
127+
if (addressParts.Length != 3)
128+
{
129+
return null;
130+
}
131+
132+
var port = Convert.ToInt32(addressParts[2], 10);
133+
var ipAddress = addressParts[1];
134+
135+
if (string.IsNullOrEmpty(ipAddress))
136+
{
137+
return new IPEndPoint(remoteEndPoint.Address, port);
138+
}
139+
140+
int addressType;
141+
if (string.IsNullOrEmpty(addressParts[0]))
142+
{
143+
addressType = ipAddress.Contains(":") ? 2 : 1;
144+
}
145+
else
146+
{
147+
addressType = Convert.ToInt32(addressParts[0], 10);
148+
}
149+
150+
switch (addressType)
151+
{
152+
case 1:
153+
case 2:
154+
return new IPEndPoint(IPAddress.Parse(ipAddress), port);
155+
default:
156+
throw new NotSupportedException($"Unknown network protocol {addressType}");
157+
}
158+
}
81159
}
82160
}

src/FubarDev.FtpServer.Commands/CommandHandlers/ReinCommandHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ await secureConnectionFeature.CloseEncryptedControlStream(cancellationToken)
101101

102102
// Set the default FTP data connection feature
103103
var activeDataConnectionFeatureFactory = Connection.ConnectionServices.GetRequiredService<ActiveDataConnectionFeatureFactory>();
104-
var dataConnectionFeature = await activeDataConnectionFeatureFactory.CreateFeatureAsync(null, connectionFeature.RemoteAddress, _dataPort)
104+
var dataConnectionFeature = await activeDataConnectionFeatureFactory.CreateFeatureAsync(null, connectionFeature.RemoteEndPoint, _dataPort)
105105
.ConfigureAwait(false);
106106
Connection.Features.Set(dataConnectionFeature);
107107

src/FubarDev.FtpServer.Commands/DataConnection/PromiscuousPasvDataConnectionValidator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public Task<ValidationResult> ValidateAsync(
6262
}
6363

6464
var pasvRemoteAddress = dataConnection.RemoteAddress.Address;
65-
if (Equals(pasvRemoteAddress, connection.RemoteAddress.IPAddress))
65+
if (Equals(pasvRemoteAddress, connection.RemoteEndPoint.Address))
6666
{
6767
return Task.FromResult(ValidationResult.Success);
6868
}
@@ -71,7 +71,7 @@ public Task<ValidationResult> ValidateAsync(
7171
var errorMessage = string.Format(
7272
localizationFeature.Catalog.GetString("Data connection attempt from {0} for control connection from {1}, data connection rejected"),
7373
pasvRemoteAddress,
74-
connection.RemoteAddress.IPAddress);
74+
connection.RemoteEndPoint.Address);
7575
_logger?.LogWarning(errorMessage);
7676
return Task.FromResult(new ValidationResult(errorMessage));
7777
}

0 commit comments

Comments
 (0)