Пример #1
0
        /// <summary>
        /// Produces a data socket using Passive (PASV) or Extended Passive (EPSV) mode
        /// </summary>
        /// <returns></returns>
        internal async Task <Stream> ConnectDataStreamAsync()
        {
            Logger?.LogTrace("[FtpClient] Connecting to a data socket");

            var epsvResult = await ControlStream.SendCommandAsync(FtpCommand.EPSV);

            int?passivePortNumber;

            if (epsvResult.FtpStatusCode == FtpStatusCode.EnteringExtendedPassive)
            {
                passivePortNumber = epsvResult.ResponseMessage.ExtractEpsvPortNumber();
            }
            else
            {
                // EPSV failed - try regular PASV
                var pasvResult = await ControlStream.SendCommandAsync(FtpCommand.PASV);

                if (pasvResult.FtpStatusCode != FtpStatusCode.EnteringPassive)
                {
                    throw new FtpException(pasvResult.ResponseMessage);
                }

                passivePortNumber = pasvResult.ResponseMessage.ExtractPasvPortNumber();
            }

            if (!passivePortNumber.HasValue)
            {
                throw new FtpException("Could not determine EPSV/PASV data port");
            }

            return(await ControlStream.OpenDataStreamAsync(Configuration.Host, passivePortNumber.Value, CancellationToken.None));
        }
Пример #2
0
        /// <summary>
        /// Changes the working directory to the given value for the current session
        /// </summary>
        /// <param name="directory"></param>
        /// <returns></returns>
        public async Task ChangeWorkingDirectoryAsync(string directory)
        {
            Logger?.LogTrace($"[FtpClient] changing directory to {directory}");
            if (directory.IsNullOrWhiteSpace() || directory.Equals("."))
            {
                throw new ArgumentOutOfRangeException(nameof(directory), "Directory supplied was incorrect");
            }

            EnsureLoggedIn();

            var response = await ControlStream.SendCommandAsync(new FtpCommandEnvelope
            {
                FtpCommand = FtpCommand.CWD,
                Data       = directory
            });

            if (!response.IsSuccess)
            {
                throw new FtpException(response.ResponseMessage);
            }

            var pwdResponse = await ControlStream.SendCommandAsync(FtpCommand.PWD);

            if (!response.IsSuccess)
            {
                throw new FtpException(response.ResponseMessage);
            }

            WorkingDirectory = pwdResponse.ResponseMessage.Split('"')[1];
        }
Пример #3
0
        /// <summary>
        /// Renames a file on the FTP server
        /// </summary>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <returns></returns>
        public async Task RenameAsync(string from, string to)
        {
            EnsureLoggedIn();
            Logger?.LogDebug($"[FtpClient] Renaming from {from}, to {to}");
            var renameFromResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope
            {
                FtpCommand = FtpCommand.RNFR,
                Data       = from
            });

            if (renameFromResponse.FtpStatusCode != FtpStatusCode.FileCommandPending)
            {
                throw new FtpException(renameFromResponse.ResponseMessage);
            }

            var renameToResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope
            {
                FtpCommand = FtpCommand.RNTO,
                Data       = to
            });

            if (renameToResponse.FtpStatusCode != FtpStatusCode.FileActionOK && renameToResponse.FtpStatusCode != FtpStatusCode.ClosingData)
            {
                throw new FtpException(renameFromResponse.ResponseMessage);
            }
        }
Пример #4
0
        /// <summary>
        ///     Attempts to log the user in to the FTP Server
        /// </summary>
        /// <returns></returns>
        public async Task LoginAsync()
        {
            if (IsConnected)
            {
                await LogOutAsync();
            }

            string username = Configuration.Username.IsNullOrWhiteSpace()
                ? Constants.ANONYMOUS_USER
                : Configuration.Username;

            await ControlStream.ConnectAsync();

            var usrResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope
            {
                FtpCommand = FtpCommand.USER,
                Data       = username
            });

            await BailIfResponseNotAsync(usrResponse, FtpStatusCode.SendUserCommand, FtpStatusCode.SendPasswordCommand, FtpStatusCode.LoggedInProceed);

            var passResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope
            {
                FtpCommand = FtpCommand.PASS,
                Data       = username != Constants.ANONYMOUS_USER ? Configuration.Password : string.Empty
            });

            await BailIfResponseNotAsync(passResponse, FtpStatusCode.LoggedInProceed);

            IsAuthenticated = true;

            if (ControlStream.IsEncrypted)
            {
                await ControlStream.SendCommandAsync(new FtpCommandEnvelope
                {
                    FtpCommand = FtpCommand.PBSZ,
                    Data       = "0"
                });

                await ControlStream.SendCommandAsync(new FtpCommandEnvelope
                {
                    FtpCommand = FtpCommand.PROT,
                    Data       = "P"
                });
            }

            Features = await DetermineFeaturesAsync();

            directoryProvider = DetermineDirectoryProvider();
            await EnableUTF8IfPossible();
            await SetTransferMode(Configuration.Mode, Configuration.ModeSecondType);

            if (Configuration.BaseDirectory != "/")
            {
                await CreateDirectoryAsync(Configuration.BaseDirectory);
            }

            await ChangeWorkingDirectoryAsync(Configuration.BaseDirectory);
        }
Пример #5
0
        /// <summary>
        /// Informs the FTP server of the client being used
        /// </summary>
        /// <param name="clientName"></param>
        /// <returns></returns>
        public async Task <FtpResponse> SetClientName(string clientName)
        {
            EnsureLoggedIn();
            Logger?.LogDebug($"[FtpClient] Setting client name to {clientName}");

            return(await ControlStream.SendCommandAsync(new FtpCommandEnvelope
            {
                FtpCommand = FtpCommand.CLNT,
                Data = clientName
            }));
        }
Пример #6
0
        /// <summary>
        /// Lists all directories in the current working directory
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public async Task DeleteFileAsync(string fileName)
        {
            EnsureLoggedIn();
            Logger?.LogDebug($"[FtpClient] Deleting file {fileName}");
            var response = await ControlStream.SendCommandAsync(new FtpCommandEnvelope
            {
                FtpCommand = FtpCommand.DELE,
                Data       = fileName
            });

            if (!response.IsSuccess)
            {
                throw new FtpException(response.ResponseMessage);
            }
        }
Пример #7
0
        /// <summary>
        ///     Attemps to log the user out asynchronously, sends the QUIT command and terminates the command socket.
        /// </summary>
        public async Task LogOutAsync()
        {
            await IgnoreStaleData();

            if (!IsConnected)
            {
                return;
            }

            Logger?.LogTrace("[FtpClient] Logging out");
            await ControlStream.SendCommandAsync(FtpCommand.QUIT);

            ControlStream.Disconnect();
            IsAuthenticated = false;
        }
Пример #8
0
        /// <summary>
        /// Determine if the FTP server supports UTF8 encoding, and set it to the default if possible
        /// </summary>
        /// <returns></returns>
        private async Task EnableUTF8IfPossible()
        {
            if (Equals(ControlStream.Encoding, Encoding.ASCII) && Features.Any(x => x == Constants.UTF8))
            {
                ControlStream.Encoding = Encoding.UTF8;
            }

            if (Equals(ControlStream.Encoding, Encoding.UTF8))
            {
                // If the server supports UTF8 it should already be enabled and this
                // command should not matter however there are conflicting drafts
                // about this so we'll just execute it to be safe.
                await ControlStream.SendCommandAsync("OPTS UTF8 ON");
            }
        }
Пример #9
0
        private async Task <IEnumerable <string> > DetermineFeaturesAsync()
        {
            EnsureLoggedIn();
            Logger?.LogTrace("[FTPClient] Determining features");
            var response = await ControlStream.SendCommandAsync(FTPCommand.FEAT);

            if (response.FTPStatusCode == FTPStatusCode.COMMANDSYNTAXERROR || response.FTPStatusCode == FTPStatusCode.COMMANDNOTIMPLEMENTED)
            {
                return(Enumerable.Empty <string>());
            }

            var features = response.Data.Where(x => !x.StartsWith(((int)FTPStatusCode.SYSTEMHELPREPLY).ToString()) && !x.IsNullOrWhiteSpace())
                           .Select(x => x.Replace(Constants.CARRIAGE_RETURN, string.Empty).Trim())
                           .ToList();

            return(features);
        }
Пример #10
0
        /// <summary>
        /// 暂不支持EPRT
        /// 1:ipV4;2 ipV6
        /// EPRT |1|132.235.1.2|6275|
        /// EPRT |2|1080::8:800:200C:417A|5282|
        ///
        /// PORT 192,168,191,11,206,97
        /// </summary>
        internal async Task <Stream> PortDataStreamAsync()
        {
            // 主动模式时,客户端必须告知服务器接收数据的端口号,PORT 命令格式为:PORT address
            //PORT 192,168,191,11,206,97
            // address参数的格式为i1、i2、i3、i4、p1、p2,其中i1、i2、i3、i4表示IP地址
            // 下面通过.字符串来组合这四个参数得到IP地址
            System.Net.Sockets.TcpListener dataListener;
            string portListener;
            string sendString = string.Empty;
            var    localip = (System.Net.IPAddress)LocalEndPoint;
            Random random = new Random();
            int    random1, random2;
            int    port;
            string ip = string.Empty;

            while (true)
            {
                // 随机生成一个端口进行数据传输
                random1 = random.Next(5, 200);
                random2 = random.Next(0, 200);
                // 生成的端口号控制>1024的随机端口
                // 下面这个运算算法只是为了得到一个大于1024的端口值
                port = random1 << 8 | random2;
                try
                {
                    dataListener = new System.Net.Sockets.TcpListener(localip, port);
                    dataListener.Start();
                }
                catch
                {
                    continue;
                }
                ip           = localip.ToString().Replace('.', ',');
                portListener = string.Format("{0},{1},{2}", ip, random1, random2);
                // 必须把端口号IP地址告诉客户端,客户端接收到响应命令后,
                // 再通过新的端口连接服务器的端口P,然后进行文件数据传输
                break;
            }
            var pasvResult = await ControlStream.SendCommandAsync(new FtpCommandEnvelope()
            {
                FtpCommand = FtpCommand.PORT,
                Data       = portListener
            });

            return(await ControlStream.AcceptDataStreamAsync(dataListener));
        }
Пример #11
0
        /// <summary>
        /// Determines the file size of the given file
        /// </summary>
        /// <param name="transferMode"></param>
        /// <param name="secondType"></param>
        /// <returns></returns>
        public async Task SetTransferMode(FtpTransferMode transferMode, char secondType = '\0')
        {
            EnsureLoggedIn();
            Logger?.LogTrace($"[FtpClient] Setting transfer mode {transferMode}, {secondType}");
            var response = await ControlStream.SendCommandAsync(new FtpCommandEnvelope
            {
                FtpCommand = FtpCommand.TYPE,
                Data       = secondType != '\0'
                    ? $"{(char) transferMode} {secondType}"
                    : $"{(char) transferMode}"
            });

            if (!response.IsSuccess)
            {
                throw new FtpException(response.ResponseMessage);
            }
        }
Пример #12
0
        private async Task <IEnumerable <string> > DetermineFeaturesAsync()
        {
            EnsureLoggedIn();
            Logger?.LogTrace("[FtpClient] Determining features");
            var response = await ControlStream.SendCommandAsync(FtpCommand.FEAT);

            if (response.FtpStatusCode == FtpStatusCode.CommandSyntaxError || response.FtpStatusCode == FtpStatusCode.CommandNotImplemented)
            {
                return(Enumerable.Empty <string>());
            }

            var features = response.Data.Where(x => !x.StartsWith(((int)FtpStatusCode.SystemHelpReply).ToString()) && !x.IsNullOrWhiteSpace())
                           .Select(x => x.Replace(Constants.CARRIAGE_RETURN, string.Empty).Trim())
                           .ToList();

            return(features);
        }
Пример #13
0
        /// <summary>
        ///     Attemps to log the user out asynchronously, sends the QUIT command and terminates the command socket.
        /// </summary>
        public async Task LogOutAsync()
        {
            await IgnoreStaleData();

            if (!IsConnected)
            {
                return;
            }

            LoggerHelper.Trace("[FtpClient] Logging out");
            await ControlStream.SendCommandAsync(FtpCommand.QUIT);

            ControlStream.Disconnect();
            if (LocalEndPoint != null)
            {
                LocalEndPoint = null;
            }
            IsAuthenticated = false;
        }
Пример #14
0
        /// <summary>
        /// Determines the file size of the given file
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public async Task <long> GetFileSizeAsync(string fileName)
        {
            EnsureLoggedIn();
            Logger?.LogDebug($"[FtpClient] Getting file size for {fileName}");
            var sizeResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope
            {
                FtpCommand = FtpCommand.SIZE,
                Data       = fileName
            });

            if (sizeResponse.FtpStatusCode != FtpStatusCode.FileStatus)
            {
                throw new FtpException(sizeResponse.ResponseMessage);
            }

            long fileSize = long.Parse(sizeResponse.ResponseMessage);

            return(fileSize);
        }
Пример #15
0
        /// <summary>
        /// Opens a filestream to the given filename
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="command"></param>
        /// <returns></returns>
        private async Task <Stream> OpenFileStreamAsync(string fileName, FTPCommand command)
        {
            EnsureLoggedIn();
            Logger?.LogDebug($"[FTPClient] Opening filestream for {fileName}, {command}");
            dataStream = await ConnectDataStreamAsync();

            var retrResponse = await ControlStream.SendCommandAsync(new FTPCommandEnvelope
            {
                FTPCommand = command,
                Data       = fileName
            });

            if ((retrResponse.FTPStatusCode != FTPStatusCode.DATAALREADYOPEN) &&
                (retrResponse.FTPStatusCode != FTPStatusCode.OPENINGDATA) &&
                (retrResponse.FTPStatusCode != FTPStatusCode.CLOSINGDATA))
            {
                throw new FTPException(retrResponse.ResponseMessage);
            }

            return(dataStream);
        }
Пример #16
0
        /// <summary>
        /// Opens a filestream to the given filename
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="command"></param>
        /// <returns></returns>
        private async Task <Stream> OpenFileStreamAsync(string fileName, FtpCommand command)
        {
            EnsureLoggedIn();
            Logger?.LogDebug($"[FtpClient] Opening filestream for {fileName}, {command}");
            dataStream = await ConnectDataStreamAsync();

            var retrResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope
            {
                FtpCommand = command,
                Data       = fileName
            });

            if ((retrResponse.FtpStatusCode != FtpStatusCode.DataAlreadyOpen) &&
                (retrResponse.FtpStatusCode != FtpStatusCode.OpeningData) &&
                (retrResponse.FtpStatusCode != FtpStatusCode.ClosingData))
            {
                throw new FtpException(retrResponse.ResponseMessage);
            }

            return(dataStream);
        }
Пример #17
0
        /// <summary>
        /// Deletes the given directory from the FTP server
        /// </summary>
        /// <param name="directory"></param>
        /// <returns></returns>
        public async Task DeleteDirectoryAsync(string directory)
        {
            if (directory.IsNullOrWhiteSpace() || directory.Equals("."))
            {
                throw new ArgumentOutOfRangeException(nameof(directory), "Directory supplied was not valid");
            }

            if (directory == "/")
            {
                return;
            }

            Logger?.LogDebug($"[FtpClient] Deleting directory {directory}");

            EnsureLoggedIn();

            var rmdResponse = await ControlStream.SendCommandAsync(new FtpCommandEnvelope
            {
                FtpCommand = FtpCommand.RMD,
                Data       = directory
            });

            switch (rmdResponse.FtpStatusCode)
            {
            case FtpStatusCode.CommandOK:
            case FtpStatusCode.FileActionOK:
                return;

            case FtpStatusCode.ActionNotTakenFileUnavailable:
                await DeleteNonEmptyDirectory(directory);

                return;

            default:
                throw new FtpException(rmdResponse.ResponseMessage);
            }
        }
Пример #18
0
 public async Task <FtpResponse> SendCommandAsync(string command, CancellationToken token = default(CancellationToken))
 {
     return(await ControlStream.SendCommandAsync(command, token));
 }
Пример #19
0
 public async Task <FtpResponse> SendCommandAsync(FtpCommandEnvelope envelope, CancellationToken token = default(CancellationToken))
 {
     return(await ControlStream.SendCommandAsync(envelope, token));
 }
Пример #20
0
        /// <summary>
        /// Creates a directory structure recursively given a path
        /// </summary>
        /// <param name="directories"></param>
        /// <param name="isRootedPath"></param>
        /// <returns></returns>
        private async Task CreateDirectoryStructureRecursively(IReadOnlyCollection <string> directories, bool isRootedPath)
        {
            Logger?.LogDebug($"[FtpClient] Creating directory structure recursively {string.Join( "/", directories )}");
            string originalPath = WorkingDirectory;

            if (isRootedPath && directories.Any())
            {
                await ChangeWorkingDirectoryAsync("/");
            }

            if (!directories.Any())
            {
                return;
            }

            if (directories.Count == 1)
            {
                await ControlStream.SendCommandAsync(new FtpCommandEnvelope
                {
                    FtpCommand = FtpCommand.MKD,
                    Data       = directories.First()
                });

                await ChangeWorkingDirectoryAsync(originalPath);

                return;
            }

            foreach (string directory in directories)
            {
                if (directory.IsNullOrWhiteSpace())
                {
                    continue;
                }

                var response = await ControlStream.SendCommandAsync(new FtpCommandEnvelope
                {
                    FtpCommand = FtpCommand.CWD,
                    Data       = directory
                });

                if (response.FtpStatusCode != FtpStatusCode.ActionNotTakenFileUnavailable)
                {
                    continue;
                }

                await ControlStream.SendCommandAsync(new FtpCommandEnvelope
                {
                    FtpCommand = FtpCommand.MKD,
                    Data       = directory
                });

                await ControlStream.SendCommandAsync(new FtpCommandEnvelope
                {
                    FtpCommand = FtpCommand.CWD,
                    Data       = directory
                });
            }

            await ChangeWorkingDirectoryAsync(originalPath);
        }