Skip to content

Commit 7c2183a

Browse files
sparkeh9Nick Briscoe
andauthored
Add configuration overrides for BaseEncoding, ForceFileSystem, and ServerCertificateValidationCallback (#46, #41, #33) (#53)
* Add configuration overrides for BaseEncoding, ForceFileSystem, and ServerCertificateValidationCallback (#46, #41, #33) * Address PR feedback: Use switch for filesystem force, fix MLSD fallback bug, fix parser probe on single parsers, and clarify cert callback docstring * Update README.md with advanced configuration examples --------- Co-authored-by: Nick Briscoe <nick.briscoe@razor.co.uk>
1 parent 10f879d commit 7c2183a

6 files changed

Lines changed: 98 additions & 3 deletions

File tree

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,33 @@ using ( var ftpClient = new FtpClient( new FtpClientConfiguration
8080
8181
```
8282

83+
### Advanced Configuration ###
84+
CoreFTP provides several configuration overrides in `FtpClientConfiguration` to assist with connecting to legacy, obfuscated, or non-compliant FTP servers:
85+
86+
```csharp
87+
using ( var ftpClient = new FtpClient( new FtpClientConfiguration
88+
{
89+
Host = "legacy-server.local",
90+
Username = "user",
91+
Password = "password",
92+
93+
// Force the control stream encoding for servers that don't support UTF8 (e.g. Chinese GBK or Japanese Shift_JIS)
94+
BaseEncoding = System.Text.Encoding.GetEncoding("GBK"),
95+
96+
// Force the directory listing parser if the server hides its OS in the FEAT response
97+
ForceFileSystem = FtpFileSystemType.Windows,
98+
99+
// Provide a custom callback to validate specific self-signed certificates
100+
IgnoreCertificateErrors = false,
101+
ServerCertificateValidationCallback = (cert, chain, errors) =>
102+
{
103+
return cert.GetCertHashString() == "EXPECTED_THUMBPRINT_HERE";
104+
}
105+
} ) )
106+
{
107+
await ftpClient.LoginAsync();
108+
}
109+
```
110+
83111
### Integration Tests ###
84112
Integration tests can be run against most FTP servers with passive mode enabled, credentials can be configured in appsettings.json of CoreFtp.Tests.Integration.

src/CoreFtp/Components/DirectoryListing/ListDirectoryProvider.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ public ListDirectoryProvider( FtpClient ftpClient, ILogger logger, FtpClientConf
2626
};
2727
}
2828

29+
internal void ClearParsers()
30+
{
31+
directoryParsers.Clear();
32+
}
33+
34+
internal void AddParser(IListDirectoryParser parser)
35+
{
36+
directoryParsers.Add(parser);
37+
}
38+
2939
private void EnsureLoggedIn()
3040
{
3141
if ( !ftpClient.IsConnected || !ftpClient.IsAuthenticated )
@@ -112,7 +122,9 @@ private IEnumerable<FtpNodeInformation> ParseLines( IReadOnlyList<string> lines
112122
if ( !lines.Any() )
113123
yield break;
114124

115-
var parser = directoryParsers.FirstOrDefault( x => x.Test( lines[ 0 ] ) );
125+
var parser = directoryParsers.Count == 1
126+
? directoryParsers[ 0 ]
127+
: directoryParsers.FirstOrDefault( x => x.Test( lines[ 0 ] ) );
116128

117129
if ( parser == null )
118130
yield break;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace CoreFtp.Enum
2+
{
3+
public enum FtpFileSystemType
4+
{
5+
Windows,
6+
Unix
7+
}
8+
}

src/CoreFtp/FtpClient.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,10 @@ public void Configure( FtpClientConfiguration configuration )
6060
configuration.Host = new Uri( configuration.Host ).Host;
6161
}
6262

63-
64-
ControlStream = new FtpControlStream( Configuration, new DnsResolver() );
63+
ControlStream = new FtpControlStream( Configuration, new DnsResolver() )
64+
{
65+
Encoding = Configuration.BaseEncoding
66+
};
6567
Configuration.BaseDirectory = $"/{Configuration.BaseDirectory.TrimStart( '/' )}";
6668
}
6769

@@ -490,9 +492,30 @@ public async Task<long> GetFileSizeAsync( string fileName )
490492
private IDirectoryProvider DetermineDirectoryProvider()
491493
{
492494
Logger?.LogTrace( "[FtpClient] Determining directory provider" );
495+
493496
if ( this.UsesMlsd() )
494497
return new MlsdDirectoryProvider( this, Logger, Configuration );
495498

499+
if ( Configuration.ForceFileSystem.HasValue )
500+
{
501+
var forcedProvider = new ListDirectoryProvider( this, Logger, Configuration );
502+
forcedProvider.ClearParsers();
503+
504+
switch ( Configuration.ForceFileSystem.Value )
505+
{
506+
case FtpFileSystemType.Windows:
507+
forcedProvider.AddParser( new Components.DirectoryListing.Parser.DosDirectoryParser( Logger ) );
508+
break;
509+
case FtpFileSystemType.Unix:
510+
forcedProvider.AddParser( new Components.DirectoryListing.Parser.UnixDirectoryParser( Logger ) );
511+
break;
512+
default:
513+
throw new NotSupportedException( $"Unsupported file system type: {Configuration.ForceFileSystem.Value}" );
514+
}
515+
516+
return forcedProvider;
517+
}
518+
496519
return new ListDirectoryProvider( this, Logger, Configuration );
497520
}
498521

src/CoreFtp/FtpClientConfiguration.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
namespace CoreFtp
22
{
3+
using System;
4+
using System.Net.Security;
35
using System.Security.Authentication;
46
using System.Security.Cryptography.X509Certificates;
57
using Enum;
@@ -26,5 +28,24 @@ public class FtpClientConfiguration
2628

2729
public X509CertificateCollection ClientCertificates { get; set; } = new X509CertificateCollection();
2830
public SslProtocols SslProtocols { get; set; } = SslProtocols.None;
31+
32+
/// <summary>
33+
/// Base encoding to use for the control stream. Useful for legacy servers that use Shift_JIS, GBK, etc.
34+
/// </summary>
35+
public System.Text.Encoding BaseEncoding { get; set; } = System.Text.Encoding.ASCII;
36+
37+
/// <summary>
38+
/// Allows overriding the server certificate validation logic (e.g., verifying a specific self-signed certificate thumbprint).
39+
/// Note: This callback is only invoked when <see cref="IgnoreCertificateErrors"/> is set to <c>false</c>.
40+
/// When <see cref="IgnoreCertificateErrors"/> is <c>true</c> (the default), certificate errors are ignored and this callback is not used.
41+
/// </summary>
42+
public Func<X509Certificate, X509Chain, SslPolicyErrors, bool> ServerCertificateValidationCallback { get; set; }
43+
44+
/// <summary>
45+
/// Allows overriding the detected server file system / directory listing format.
46+
/// Useful when the server does not advertise MLSD in its FEAT response (causing a fallback to LIST),
47+
/// and the automatic detection of the LIST output format fails (e.g. force FtpFileSystemType.Windows).
48+
/// </summary>
49+
public FtpFileSystemType? ForceFileSystem { get; set; }
2950
}
3051
}

src/CoreFtp/Infrastructure/Stream/FtpControlStream.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,9 @@ private bool OnValidateCertificate(X509Certificate certificate, X509Chain chain,
469469
if (Configuration.IgnoreCertificateErrors)
470470
return true;
471471

472+
if (Configuration.ServerCertificateValidationCallback != null)
473+
return Configuration.ServerCertificateValidationCallback(certificate, chain, errors);
474+
472475
return errors == SslPolicyErrors.None;
473476
}
474477

0 commit comments

Comments
 (0)