robinrodricks / FluentFTP

An FTP and FTPS client for .NET & .NET Standard, optimized for speed. Provides extensive FTP commands, File uploads/downloads, SSL/TLS connections, Automatic directory listing parsing, File hashing/checksums, File permissions/CHMOD, FTP proxies, FXP support, UTF-8 support, Async/await support, Powershell support and more. Written entirely in C#.
MIT License
3.14k stars 657 forks source link

FtpClient throws exception from log masking when Credentials.UserName is empty #1649

Closed ssg closed 2 months ago

ssg commented 2 months ago

FluentFTP Version: 51.1.0 Framework: Happens on both .NET Framework 4.8 and .NET 8.0

Summary

LogMaskModule tries to replace UserName instances with a fixed mask string, but if UserName is empty, then String.Replace throws an exception saying "System.ArgumentException: 'The value cannot be an empty string. (Parameter 'oldValue')'".

Applicability

FTP spec (RFC 959) doesn't allow an empty username, but there's nothing that prevents an FTP client from sending an empty USER command, and the server from accepting it.

Ideally, LogMaskModule should check for empty UserName and skip processing. FtpClient should reject an empty UserName directly in the constructor preferably with a manual override for exotic use cases.

Workarounds

Repro:

Here's a simple test program. Removing NetworkCredential parameter fixes this issue:

using System.Net;
using FluentFTP;

namespace FtpTest
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
            Directory.CreateDirectory(path);
            Console.WriteLine($"Server path: {path}");
            var server = new Zhaobang.FtpServer.FtpServer(new IPEndPoint(IPAddress.Loopback, 4444), path);
            var cancelTokenSource = new CancellationTokenSource();
            var cancelToken = cancelTokenSource.Token;
            var task = Task.Run(() => server.RunAsync(cancelToken));

            var client = new AsyncFtpClient("127.0.0.1", new NetworkCredential()
            {
                UserName = "",
                Password = "",
            },4444, new FtpConfig()
            {
                LogToConsole = true,                
            });
            if (!await client.DirectoryExists("test/mest"))
            {
                await client.CreateDirectory("test/mest", force: true);
            }
            await client.UploadFile("D:\\test.txt", "test/test.txt");
            Console.WriteLine("DONE! ");
            cancelTokenSource.Cancel();
            Console.WriteLine("CANCELLED TOO! ");
            await task;
        }
    }
}