FTPS

using System;
using System.Net;
using System.IO;
using System.Text;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Threading;
namespace yang.ftp
{
    /// <summary>
    /// Base FTP exception class.
    /// </summary>
    public class FTPException : Exception
    {
        protected FTPException()
        {
        }

        public FTPException(string Message)
            : base(Message)
        {
        }

        public FTPException(string Message, Exception innerException)
            : base(Message, innerException)
        {
        }
    }

    public class FTPReplyParseException : FTPException
    {
        private string replyText;

        public string ReplyText
        {
            get { return replyText; }
        }

        public FTPReplyParseException(string replyText)
            : base("Invalid server reply: " + replyText)
        {
            this.replyText = replyText;
        }
    }

    public class FTPProtocolException : FTPException
    {
        FTPReply reply;

        public FTPReply Reply
        {
            get { return reply; }
        }

        public FTPProtocolException(FTPReply reply)
            : base("Invalid FTP protocol reply: " + reply.ToString())
        {
            this.reply = reply;
        }
    }

    /// <summary>
    /// Exception indicating that a command or set of commands have been cancelled by the caller, via a callback method or event.
    /// </summary>
    public class FTPOperationCancelledException : FTPException
    {
        public FTPOperationCancelledException(string Message)
            : base(Message)
        {
        }
    }

    /// <summary>
    /// FTP exception generated by a command with a return code >= 400, as stated in RFC 959.
    /// </summary>
    public class FTPCommandException : FTPException
    {
        int errorCode;

        public int ErrorCode
        {
            get { return errorCode; }
        }

        public FTPCommandException(string Message)
            : base(Message)
        {
        }

        public FTPCommandException(string Message, Exception innerException)
            : base(Message, innerException)
        {
        }

        public FTPCommandException(FTPReply reply)
            : base(reply.Message)
        {
            this.errorCode = reply.Code;
        }
    }

    /// <summary>
    /// FTP exception related to the SSL/TLS support
    /// </summary>
    public class FTPSslException : FTPException
    {
        public FTPSslException(string Message)
            : base(Message)
        {
        }

        public FTPSslException(string Message, Exception innerException)
            : base(Message, innerException)
        {
        }
    }

    /// <summary>
    /// The SSL/TLS support requested or required for a connection.
    /// </summary>
    [Flags]
    public enum ESSLSupportMode
    {
        /// <summary>
        /// No SSL/TLS support. Used for standard FTP connections.
        /// </summary>
        ClearText = 0,

        /// <summary>
        /// Requests a SSL/TLS connection during authentication. 
        /// Authentication is performed using <see cref="ClearText"/> if SSL/TLS is not supported by the server.
        /// Reverts to <see cref="ClearText"/> after authetication if the CCC command is supported by the server.
        /// </summary>
        CredentialsRequested = 1,

        /// <summary>
        /// Requires a SSL/TLS connection during authentication. 
        /// Reverts to <see cref="ClearText"/> after authetication if the CCC command is supported by the server.
        /// </summary>
        CredentialsRequired = 2 | CredentialsRequested,

        /// <summary>
        /// Requests a SSL/TLS connection on the control channel. 
        /// </summary>
        /// <remarks>
        /// Acts like <see cref="CredentialsRequested"/> but does not revert to <see cref="ClearText"/> after authentication.
        /// </remarks>
        ControlChannelRequested = 4 | CredentialsRequested,

        /// <summary>
        /// Requires a SSL/TLS connection on the control channel. 
        /// </summary>
        /// <remarks>
        /// Acts like <see cref="CredentialsRequired"/> but does not revert to <see cref="ClearText"/> after authentication.
        /// </remarks>
        ControlChannelRequired = CredentialsRequired | ControlChannelRequested,

        /// <summary>
        /// Requests a SSL/TLS connection on the data channel, implies <see cref="CredentialsRequested"/>.
        /// Data transfers are not encrypted is not supported by the server.
        /// </summary>
        DataChannelRequested = 8 | CredentialsRequested,

        /// <summary>
        /// Requires a SSL/TLS connection on the data channel, implies <see cref="CredentialsRequired"/>.
        /// </summary>
        DataChannelRequired = 16 | DataChannelRequested | CredentialsRequired,

        /// <summary>
        /// Requests a SSL/TLS connection on both control and data channels, implies <see cref="ControlChannelRequested"/> and <see cref="DataChannelRequested"/>.
        /// Control channel commands and data transfers are not encrypted is not supported by the server.
        /// </summary>
        ControlAndDataChannelsRequested = ControlChannelRequested | DataChannelRequested,

        /// <summary>
        /// Requires a SSL/TLS connection on both control and data channels, implies <see cref="ControlChannelRequired"/> and <see cref="DataChannelRequired"/>.
        /// </summary>
        ControlAndDataChannelsRequired = ControlChannelRequired | DataChannelRequired,

        /// <summary>
        /// An alias for <see cref="ControlAndDataChannelsRequired"/>
        /// </summary>
        All = ControlAndDataChannelsRequired,

        /// <summary>
        /// Implicit SSL/TLS, not supported by RFC 4217. Both control channel and data channel are always encrypted.
        /// </summary>
        Implicit = 32 | ControlAndDataChannelsRequired
    }

    /// <summary>
    /// Possible actions occurring during a file transfer.
    /// </summary>
    public enum ETransferActions
    {
        LocalDirectoryCreated, RemoteDirectoryCreated,
        FileUploaded, FileUploadingStatus,
        FileDownloaded, FileDownloadingStatus
    }

    /// <summary>
    /// File pattern style used in <see cref="FTPSClient.GetFiles"/> and  <see cref="FTPSClient.PutFiles"/>.
    /// </summary>
    public enum EPatternStyle
    {
        /// <summary>
        /// Interpret as is.
        /// </summary>
        Verbatim,
        /// <summary>
        /// Interpret as wildcard, where <c>*</c> means 0 or more chars having any value and <c>?</c> means one char having any value. 
        /// </summary>
        Wildcard,
        /// <summary>
        /// Interpret as a regular expression.
        /// </summary>
        Regex
    }

    /// <summary>
    /// Trasfer mode used in connections
    /// </summary>
    public enum EDataConnectionMode
    {
        Active,
        Passive
    }
    /// <summary>
    /// Encapsulates the SSL/TLS algorithms connection information.
    /// </summary>
    public class SslInfo
    {
        SslProtocols sslProtocol;

        CipherAlgorithmType cipherAlgorithm;
        int cipherStrength;

        HashAlgorithmType hashAlgorithm;
        int hashStrength;

        ExchangeAlgorithmType keyExchangeAlgorithm;
        int keyExchangeStrength;

        public SslProtocols SslProtocol
        {
            get { return sslProtocol; }
            set { sslProtocol = value; }
        }

        public CipherAlgorithmType CipherAlgorithm
        {
            get { return cipherAlgorithm; }
            set { cipherAlgorithm = value; }
        }

        public int CipherStrength
        {
            get { return cipherStrength; }
            set { cipherStrength = value; }
        }

        public HashAlgorithmType HashAlgorithm
        {
            get { return hashAlgorithm; }
            set { hashAlgorithm = value; }
        }

        public int HashStrength
        {
            get { return hashStrength; }
            set { hashStrength = value; }
        }

        public ExchangeAlgorithmType KeyExchangeAlgorithm
        {
            get { return keyExchangeAlgorithm; }
            set { keyExchangeAlgorithm = value; }
        }

        public int KeyExchangeStrength
        {
            get { return keyExchangeStrength; }
            set { keyExchangeStrength = value; }
        }

        public override string ToString()
        {
            return SslProtocol.ToString() + ", " +
                   CipherAlgorithm.ToString() + " (" + cipherStrength.ToString() + " bit), " +
                   KeyExchangeAlgorithm.ToString() + " (" + keyExchangeStrength.ToString() + " bit), " +
                   HashAlgorithm.ToString() + " (" + hashStrength.ToString() + " bit)";
        }
    }


    public class LogCommandEventArgs : EventArgs
    {
        public LogCommandEventArgs(string commandText)
            : base()
        {
            this.CommandText = commandText;
        }

        public string CommandText { get; private set; }
    }

    public class LogServerReplyEventArgs : EventArgs
    {
        public LogServerReplyEventArgs(FTPReply serverReply)
            : base()
        {
            this.ServerReply = serverReply;
        }

        public FTPReply ServerReply { get; private set; }
    }
    public delegate void LogCommandEventHandler(object sender, LogCommandEventArgs args);
    public delegate void LogServerReplyEventHandler(object sender, LogServerReplyEventArgs args);
    /// <summary>
    /// FTP Client
    /// </summary>
    public class FTPClient
    {
        Encoding encoding = Encoding.GetEncoding("gb2312") ;
        public event System.EventHandler Datareceived;
        public event System.EventHandler Filereceived;
        public event System.EventHandler Filesend;
        public event System.EventHandler Datasend;
        private long receivedbyte;

        public long Receivedbyte
        {
            get { return receivedbyte; }
            set { receivedbyte = value; }
        }

        #region 构造函数
        /// <summary>
        /// 缺省构造函数
        /// </summary>
        public FTPClient()
        {
            strRemoteHost = "";
            strRemotePath = "";
            strRemoteUser = "";
            strRemotePass = "";
            strRemotePort = 2222;
            bConnected = false;
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="remoteHost"></param>
        /// <param name="remotePath"></param>
        /// <param name="remoteUser"></param>
        /// <param name="remotePass"></param>
        /// <param name="remotePort"></param>
        public FTPClient(string remoteHost, string remotePath, string remoteUser, string remotePass, int remotePort)
        {
            strRemoteHost = remoteHost;
            strRemotePath = remotePath;
            strRemoteUser = remoteUser;
            strRemotePass = remotePass;
            strRemotePort = remotePort;
            Connect();
        }
        #endregion

        #region 登陆
        /// <summary>
        /// FTP服务器IP地址
        /// </summary>
        private string strRemoteHost;
        public string RemoteHost
        {
            get
            {
                return strRemoteHost;
            }
            set
            {
                strRemoteHost = value;
            }
        }
        /// <summary>
        /// FTP服务器端口
        /// </summary>
        private int strRemotePort;
        public int RemotePort
        {
            get
            {
                return strRemotePort;
            }
            set
            {
                strRemotePort = value;
            }
        }
        /// <summary>
        /// 当前服务器目录
        /// </summary>
        private string strRemotePath;
        public string RemotePath
        {
            get
            {
                return strRemotePath;
            }
            set
            {
                strRemotePath = value;
            }
        }
        /// <summary>
        /// 登录用户账号
        /// </summary>
        private string strRemoteUser;
        public string RemoteUser
        {
            set
            {
                strRemoteUser = value;
            }
        }
        /// <summary>
        /// 用户登录密码
        /// </summary>
        private string strRemotePass;
        public string RemotePass
        {
            set
            {
                strRemotePass = value;
            }
        }

        /// <summary>
        /// 是否登录
        /// </summary>
        private Boolean bConnected;
        public bool Connected
        {
            get
            {
                return bConnected;
            }
        }
        #endregion
        public string t()
        {
            return "ddss";
        }


        #region Private Enums

        enum EProtCode { C, S, E, P }
        enum EAuthMechanism { TLS }
        enum ERepType { A, E, I, L }

        #endregion

        #region Private Fields

        TcpClient ctrlClient = null;
        StreamReader ctrlSr;
        StreamWriter ctrlSw;
        SslStream ctrlSslStream;

        TcpClient dataClient = null;
        SslStream dataSslStream;

        EDataConnectionMode dataConnectionMode = EDataConnectionMode.Passive;

        /// <summary>
        /// <c>true</c> to ignore the address returned by PASV
        /// </summary>
        bool useCtrlEndPointAddressForData = true;

        bool waitingCompletionReply = false;

        //string hostname;

        //const string anonUsername = "anonymous";
        //const string anonPassword = "anonymous@FTPSClient.org"; // dummy password

        //const string clntName = "AlexFTPS";

        const ESSLSupportMode defaultSSLSupportMode = ESSLSupportMode.CredentialsRequired | ESSLSupportMode.DataChannelRequested;

        ESSLSupportMode sslSupportRequestedMode;
        ESSLSupportMode sslSupportCurrentMode;

        X509Certificate sslServerCert;
        X509Certificate sslClientCert;

        SslInfo sslInfo;

        /// <summary>
        /// 0 means no check
        /// </summary>
        int sslMinKeyExchangeAlgStrength = 0;
        int sslMinCipherAlgStrength = 0;
        int sslMinHashAlgStrength = 0;

        bool sslCheckCertRevocation = true;

        RemoteCertificateValidationCallback userValidateServerCertificate;

        int timeout = 120000; //ms

        //IList<string> features = null;

        //ETransferMode transferMode = ETransferMode.ASCII;
        //ETextEncoding textEncoding = ETextEncoding.ASCII;

        //string welcomeMessage = null;

        //string bannerMessage = null;

        //Stack<string> currDirStack = new Stack<string>();

        //TcpListener activeDataConnListener;

        //Thread keepAliveThread = null;
        //volatile bool keepAlive = true;
        //int keepAliveTimeout = 20000; // ms

        #endregion
        #region Public Events

        public event LogCommandEventHandler LogCommand;
        public event LogServerReplyEventHandler LogServerReply;

        #endregion
        private void SetupCtrlStreamReaderAndWriter(Stream s)
        {
            if (ctrlSw != null)
                ctrlSw.Flush();

            // SreamWriter's doc states that the default encoding is UTF8 without BOM.
            // Mono 2.0 seems to have a BOM anyway. Create it explicitly as a workaround
            //Encoding encoding = new UTF8Encoding(false);
            // encoding = Encoding.GetEncoding("gb2312");
            ctrlSr = new StreamReader(s, encoding);
            ctrlSw = new StreamWriter(s, encoding);
            ctrlSw.NewLine = "\r\n";
        }
        /// <summary>
        /// Copies the protocol information form the given stream.
        /// </summary>
        /// <param name="sslStream"></param>
        private void SetSslInfo(SslStream sslStream)
        {
            sslInfo = new SslInfo()
            {
                SslProtocol = sslStream.SslProtocol,
                CipherAlgorithm = sslStream.CipherAlgorithm,
                CipherStrength = sslStream.CipherStrength,
                HashAlgorithm = sslStream.HashAlgorithm,
                HashStrength = sslStream.HashStrength,
                KeyExchangeAlgorithm = sslStream.KeyExchangeAlgorithm,
                KeyExchangeStrength = sslStream.KeyExchangeStrength
            };
        }
        private SslStream CreateSSlStream(Stream s, bool leaveInnerStreamOpen)
        {
            SslStream sslStream = new SslStream(s, leaveInnerStreamOpen,
                new RemoteCertificateValidationCallback(ValidateServerCertificate),
                null //new LocalCertificateSelectionCallback(ValidateClientCertificate)
                );

            sslStream.ReadTimeout = timeout;
            sslStream.WriteTimeout = timeout;

            X509CertificateCollection clientCertColl = new X509CertificateCollection();
            if (sslClientCert != null)
                clientCertColl.Add(sslClientCert);

            //sslStream.AuthenticateAsClient(hostname);
            sslStream.AuthenticateAsClient(RemoteHost, clientCertColl, SslProtocols.Default, sslCheckCertRevocation);

            CheckSslAlgorithmsStrength(sslStream);

            return sslStream;
        }

        private void CheckSslAlgorithmsStrength(SslStream sslStream)
        {
            // Check algorithms length
            if (sslMinKeyExchangeAlgStrength > 0 && sslStream.KeyExchangeStrength < sslMinKeyExchangeAlgStrength)
                throw new FTPSslException("The SSL/TSL key exchange algorithm strength does not fulfill the requirements: " + sslStream.KeyExchangeStrength.ToString());

            if (sslMinCipherAlgStrength > 0 && sslStream.CipherStrength < sslMinCipherAlgStrength)
                throw new FTPSslException("The SSL/TSL cipher algorithm strength does not fulfill the requirements: " + sslStream.CipherStrength.ToString());

            if (sslMinHashAlgStrength > 0 && sslStream.HashStrength < sslMinHashAlgStrength)
                throw new FTPSslException("The SSL/TSL hash algorithm strength does not fulfill the requirements: " + sslStream.HashStrength.ToString());

        }

        private bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            bool certOk = true;

            // Validate only the first time or if the certificate changes
            if (this.sslServerCert == null || !sslServerCert.Equals(certificate))
            {
                if (userValidateServerCertificate != null)
                    certOk = userValidateServerCertificate(this, certificate, chain, sslPolicyErrors);
                else if (sslPolicyErrors != SslPolicyErrors.None)
                    certOk = false;

                if (certOk)
                    this.sslServerCert = new X509Certificate(certificate.Export(X509ContentType.Cert));
            }

            return certOk;
        }
        #region 链接
        /// <summary>
        /// 建立连接 
        /// </summary>
        public void Connect()
        {
            //DisConnect();

            socketControl = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ep = new IPEndPoint(IPAddress.Parse(RemoteHost), strRemotePort);
            // 链接
            try
            {
                socketControl.Connect(ep);
            }
            catch (Exception)
            {
                throw new IOException("不能连接到FTP服务器");
            }
            this.userValidateServerCertificate = new RemoteCertificateValidationCallback((a, b, c, d) => { return true; });
            this.sslClientCert = null;

            Stream s = new NetworkStream(socketControl);
            s.ReadTimeout = timeout;
            s.WriteTimeout = timeout;

            // SreamWriter's doc states that the default encoding is UTF8 without BOM.
            // Mono 2.0 seems to have a BOM anyway. Create it explicitly as a workaround
            //Encoding encoding = new UTF8Encoding(false);
            //encoding = Encoding.GetEncoding("gb2312");
            ctrlSr = new StreamReader(s, encoding);
            ctrlSw = new StreamWriter(s, encoding);
            ctrlSw.NewLine = "\r\n";

            ctrlSslStream = CreateSSlStream(s, true);

            SetupCtrlStreamReaderAndWriter(ctrlSslStream);

            SetSslInfo(ctrlSslStream);

            // Wait fot server message
            string bannerMessage = GetReply().Message;

            获取应答码
            //ReadReply();
            //if (iReplyCode != 220)
            //{
            //    DisConnect();
            //    throw new IOException(strReply.Substring(4));
            //}

            // 登陆
            SendCommand("USER " + strRemoteUser);
            if (!(iReplyCode == 331 || iReplyCode == 230))
            {
                CloseSocketConnect();//关闭连接
                throw new IOException(strReply.Substring(4));
            }
            if (iReplyCode != 230)
            {
                SendCommand("PASS " + strRemotePass);
                if (!(iReplyCode == 230 || iReplyCode == 202))
                {
                    CloseSocketConnect();//关闭连接
                    throw new IOException(strReply.Substring(4));
                }
            }
            bConnected = true;

            //PBSZ 0
            //PROT P
            //SendCommand("PBSZ 0");
            //SendCommand("PROT P");
            //SendCommand("CLNT FTPS");
            //SendCommand("OPTS UTF8 ON");
            //SendCommand("TYPE I");

            // 切换到目录
            ChDir(strRemotePath);
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        private FTPReply GetReply()
        {
            try
            {
                FTPReply reply = new FTPReply();
                bool replyDone = false;

                do
                {
                    string replyLine = ctrlSr.ReadLine();

                    Match m = Regex.Match(replyLine, @"^([0-9]{3})([\s\-])(.*)$");

                    if (m.Success)
                    {
                        int code = int.Parse(m.Groups[1].Value);
                        string messageLine = m.Groups[3].Value;
                        replyDone = (m.Groups[2].Value == " ");

                        if (reply.Code == 0)
                        {
                            reply.Code = code;
                            reply.Message = messageLine;
                        }
                        else // Multiline message
                        {
                            if (reply.Code != code)
                                throw new FTPReplyParseException(replyLine);

                            reply.Message += "\r\n" + messageLine;
                        }
                    }
                    else // Multiline message
                    {
                        if (reply.Code == 0)
                            throw new FTPReplyParseException(replyLine);

                        reply.Message += "\r\n" + replyLine.TrimStart();
                    }
                }
                while (!replyDone);

                waitingCompletionReply = (reply.Code < 200);

                if (LogServerReply != null)
                    LogServerReply(this, new LogServerReplyEventArgs(reply));

                if (reply.Code >= 400)
                    throw new FTPCommandException(reply);

                return reply;
            }
            catch (Exception)
            {
                waitingCompletionReply = false;
                throw;
            }
        }
        /// <summary>
        /// 关闭连接
        /// </summary>
        public void DisConnect()
        {
            if (socketControl != null)
            {
                SendCommand("QUIT");
            }
            CloseSocketConnect();
        }

        #endregion

        #region 传输模式

        /// <summary>
        /// 传输模式:二进制类型、ASCII类型
        /// </summary>
        public enum TransferType { Binary, ASCII };

        /// <summary>
        /// 设置传输模式
        /// </summary>
        /// <param name="ttType">传输模式</param>
        public void SetTransferType(TransferType ttType)
        {
            if (ttType == TransferType.Binary)
            {
                SendCommand("TYPE I");//binary类型传输
            }
            else
            {
                SendCommand("TYPE A");//ASCII类型传输
            }
            if (iReplyCode != 200)
            {
                throw new IOException(strReply.Substring(4));
            }
            else
            {
                trType = ttType;
            }
        }


        /// <summary>
        /// 获得传输模式
        /// </summary>
        /// <returns>传输模式</returns>
        public TransferType GetTransferType()
        {
            return trType;
        }

        #endregion

        #region 文件操作
        /// <summary>
        /// 获得文件列表
        /// </summary>
        /// <param name="strMask">文件名的匹配字符串</param>
        /// <returns></returns>
        public string[] Dir(string strMask)
        {
            // 建立链接
            if (!bConnected)
            {

                Connect();
            }

            //建立进行数据连接的socket
            Socket socketData = CreateDataSocket();

            //传送命令
            SendCommand("NLST " + strMask);

            //分析应答代码
            if (!(iReplyCode == 150 || iReplyCode == 125 || iReplyCode == 226))
            {
                throw new IOException(strReply.Substring(4));
            }
            Stream s = new NetworkStream(socketData);

            s = CreateSSlStream(s, false);
            s.ReadTimeout = timeout;
            s.WriteTimeout = timeout;
            //获得结果
            strMsg = "";

            //while (true)
            //{
            //    int iBytes = socketData.Receive(buffer, buffer.Length, 0);

            //    strMsg += Encoding.Default.GetString(buffer, 0, iBytes);
            //    if (iBytes < buffer.Length)
            //    {
            //        break;
            //    }
            //}


            StringBuilder data = new StringBuilder();

            byte[] buf = new byte[1024];
            int n = 0;
            do
            {
                n = s.Read(buf, 0, buf.Length);
                data.Append(encoding.GetString(buf, 0, n));
            }
            while (n != 0);

            strMsg = data.ToString();

            char[] seperator = { '\n' };
            string[] strsFileList = strMsg.Split(seperator);
            socketData.Close();//数据socket关闭时也会有返回码
            if (iReplyCode != 226)
            {
                ReadReply();
                if (iReplyCode != 226)
                {
                    throw new IOException(strReply.Substring(4));
                }
            }
            return strsFileList;
        }


        /// <summary>
        /// 获取文件大小
        /// </summary>
        /// <param name="strFileName">文件名</param>
        /// <returns>文件大小</returns>
        public long GetFileSize(string strFileName)
        {
            if (!bConnected)
            {
                Connect();
            }
            SendCommand("SIZE " + Path.GetFileName(strFileName));
            long lSize = 0;
            if (iReplyCode == 213)
            {
                lSize = Int64.Parse(strReply.Substring(4));
            }
            else
            {
                throw new IOException(strReply.Substring(4));
            }
            return lSize;
        }


        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="strFileName">待删除文件名</param>
        public void Delete(string strFileName)
        {
            if (!bConnected)
            {
                Connect();
            }
            SendCommand("DELE " + strFileName);
            if (iReplyCode != 250)
            {
                throw new IOException(strReply.Substring(4));
            }
        }


        /// <summary>
        /// 重命名(如果新文件名与已有文件重名,将覆盖已有文件)
        /// </summary>
        /// <param name="strOldFileName">旧文件名</param>
        /// <param name="strNewFileName">新文件名</param>
        public void Rename(string strOldFileName, string strNewFileName)
        {
            if (!bConnected)
            {
                Connect();
            }
            SendCommand("RNFR " + strOldFileName);
            if (iReplyCode != 350)
            {
                throw new IOException(strReply.Substring(4));
            }
            //  如果新文件名与原有文件重名,将覆盖原有文件
            SendCommand("RNTO " + strNewFileName);
            if (iReplyCode != 250)
            {
                throw new IOException(strReply.Substring(4));
            }
        }
        #endregion

        #region 上传和下载
        /// <summary>
        /// 下载一批文件
        /// </summary>
        /// <param name="strFileNameMask">文件名的匹配字符串</param>
        /// <param name="strFolder">本地目录(不得以\结束)</param>
        public void Get(string strFileNameMask, string strFolder)
        {
            if (!bConnected)
            {
                Connect();
            }
            string[] strFiles = Dir(strFileNameMask);
            foreach (string strFile in strFiles)
            {
                if (!strFile.Equals(""))//一般来说strFiles的最后一个元素可能是空字符串
                {
                    Get(strFile, strFolder, strFile);
                }
            }
        }


        /// <summary>
        /// 下载一个文件
        /// </summary>
        /// <param name="strRemoteFileName">要下载的文件名</param>
        /// <param name="strFolder">本地目录(不得以\结束)</param>
        /// <param name="strLocalFileName">保存在本地时的文件名</param>
        public void Get(string strRemoteFileName, string strFolder, string strLocalFileName)
        {
            if (!bConnected)
            {
                try
                {
                    Connect();
                }
                catch (Exception oe)
                {

                    throw oe;
                }

            }
            SetTransferType(TransferType.Binary);
            if (strLocalFileName.Equals(""))
            {
                strLocalFileName = strRemoteFileName;
            }
            if (!File.Exists(strLocalFileName))
            {
                Stream st = File.Create(strLocalFileName);
                st.Close();
            }
            FileStream output = new
            FileStream(strFolder + "\\" + strLocalFileName, FileMode.Create);
            Socket socketData = CreateDataSocket();
            SendCommand("RETR " + strRemoteFileName);
            if (!(iReplyCode == 150 || iReplyCode == 125
            || iReplyCode == 226 || iReplyCode == 250))
            {
                throw new IOException(strReply.Substring(4));
            }
            receivedbyte = 0;

            Stream s = new NetworkStream(socketData);

            s = CreateSSlStream(s, false);
            s.ReadTimeout = timeout;
            s.WriteTimeout = timeout;


            while (true)
            {
                int iBytes = s.Read(buffer, 0, buffer.Length);
                //int iBytes = socketData.Receive(buffer, buffer.Length, 0);
                output.Write(buffer, 0, iBytes);
                //yang 加入收到数据
                receivedbyte += iBytes;
                //yang 加入事件收到数据
                if (Datareceived != null)
                    Datareceived(null, null);
                if (iBytes <= 0)
                {
                    break;
                }
            }

            output.Close();
            if (socketData.Connected)
            {
                socketData.Close();
            }
            if (!(iReplyCode == 226 || iReplyCode == 250))
            {
                ReadReply();
                if (!(iReplyCode == 226 || iReplyCode == 250))
                {
                    throw new IOException(strReply.Substring(4));
                }
            }
            //yang 加入数据接收完成
            if (Filereceived != null)
                Filereceived(null, null);
        }


        /// <summary>
        /// 上传一批文件
        /// </summary>
        /// <param name="strFolder">本地目录(不得以\结束)</param>
        /// <param name="strFileNameMask">文件名匹配字符(可以包含*和?)</param>
        public void Put(string strFolder, string strFileNameMask)
        {
            string[] strFiles = Directory.GetFiles(strFolder, strFileNameMask);
            foreach (string strFile in strFiles)
            {
                //strFile是完整的文件名(包含路径)
                Put(strFile);
            }
        }


        /// <summary>
        /// 上传一个文件
        /// </summary>
        /// <param name="strFileName">本地文件名</param>
        public void Put(string strFileName)
        {
            if (!bConnected)
            {
                Connect();
            }
            Socket socketData = CreateDataSocket();

            SendCommand("STOR " + Path.GetFileName(strFileName));
            if (!(iReplyCode == 125 || iReplyCode == 150))
            {
                throw new IOException(strReply.Substring(4));
            }
            //FileStream input = new
            //FileStream(strFileName, FileMode.Open);
            int iBytes = 0;
            receivedbyte = 0;
            //FTPS
            Stream s = new NetworkStream(socketData);
            s = CreateSSlStream(s, false);
            s.ReadTimeout = timeout;
            s.WriteTimeout = timeout;


            using (FileStream fs = File.OpenRead(strFileName))
            {
                byte[] buf = new byte[1024];
                int n = 0;
                do
                {
                    n = fs.Read(buf, 0, buf.Length);
                    if (n > 0)
                    {
                        s.Write(buf, 0, n);
                    }
                    if (Datasend != null)
                        Datasend(null, null);
                }
                while (n > 0);

                fs.Close();
            }
            s.Close();


            //while ((iBytes = input.Read(buffer, 0, buffer.Length)) > 0)
            //{
            //    //FTPS
            //    s.Write(buffer, 0, buffer.Length);
            //    //socketData.Send(buffer, iBytes, 0);
            //    //yang 加入上传数据
            //    receivedbyte += iBytes;
            //    //yang 加入事件上传数据
            //    if (Datasend != null)
            //        Datasend(null, null);
            //}
            //input.Close();

            if (socketData.Connected)
            {
                socketData.Close();
            }
            if (!(iReplyCode == 226 || iReplyCode == 250))
            {
                ReadReply();
                if (!(iReplyCode == 226 || iReplyCode == 250))
                {
                    throw new IOException(strReply.Substring(4));
                }
            }
            //yang 加入数据上传完成
            if (Filesend != null)
                Filesend(null, null);
        }

        #endregion

        #region 目录操作
        /// <summary>
        /// 创建目录
        /// </summary>
        /// <param name="strDirName">目录名</param>
        public void MkDir(string strDirName)
        {
            if (!bConnected)
            {
                Connect();
            }
            SendCommand("MKD " + strDirName);
            if (iReplyCode != 257)
            {
                throw new IOException(strReply.Substring(4));
            }
        }


        /// <summary>
        /// 删除目录
        /// </summary>
        /// <param name="strDirName">目录名</param>
        public void RmDir(string strDirName)
        {
            if (!bConnected)
            {
                Connect();
            }
            SendCommand("RMD " + strDirName);
            if (iReplyCode != 250)
            {
                throw new IOException(strReply.Substring(4));
            }
        }


        /// <summary>
        /// 改变目录
        /// </summary>
        /// <param name="strDirName">新的工作目录名</param>
        public void ChDir(string strDirName)
        {
            if (strDirName.Equals(".") || strDirName.Equals(""))
            {
                return;
            }
            if (!bConnected)
            {
                Connect();
            }
            SendCommand("CWD " + strDirName);
            if (iReplyCode != 250)
            {
                throw new IOException(strReply.Substring(4));
            }
            this.strRemotePath = strDirName;
        }

        #endregion

        #region 内部变量
        /// <summary>
        /// 服务器返回的应答信息(包含应答码)
        /// </summary>
        private string strMsg;
        /// <summary>
        /// 服务器返回的应答信息(包含应答码)
        /// </summary>
        private string strReply;
        /// <summary>
        /// 服务器返回的应答码
        /// </summary>
        private int iReplyCode;
        /// <summary>
        /// 进行控制连接的socket
        /// </summary>
        private Socket socketControl;
        /// <summary>
        /// 传输模式
        /// </summary>
        private TransferType trType;
        /// <summary>
        /// 接收和发送数据的缓冲区
        /// </summary>
        public static int BLOCK_SIZE = 512;
        Byte[] buffer = new Byte[BLOCK_SIZE];
        /// <summary>
        /// 编码方式
        /// </summary>
        Encoding ASCII = Encoding.ASCII;
        #endregion

        #region 内部函数
        /// <summary>
        /// 将一行应答字符串记录在strReply和strMsg
        /// 应答码记录在iReplyCode
        /// </summary>
        private void ReadReply()
        {
            strMsg = "";
            strReply = ctrlSr.ReadLine();// ReadLine();
            iReplyCode = Int32.Parse(strReply.Substring(0, 3));
        }

        /// <summary>
        /// 建立进行数据连接的socket
        /// </summary>
        /// <returns>数据连接socket</returns>
        private Socket CreateDataSocket()
        {
            SendCommand("PASV");
            if (iReplyCode != 227)
            {
                throw new IOException(strReply.Substring(4));
            }
            int index1 = strReply.IndexOf('(');
            int index2 = strReply.IndexOf(')');
            string ipData =
             strReply.Substring(index1 + 1, index2 - index1 - 1);
            int[] parts = new int[6];
            int len = ipData.Length;
            int partCount = 0;
            string buf = "";
            for (int i = 0; i < len && partCount <= 6; i++)
            {
                char ch = Char.Parse(ipData.Substring(i, 1));
                if (Char.IsDigit(ch))
                    buf += ch;
                else if (ch != ',')
                {
                    throw new IOException("Malformed PASV strReply: " +
                     strReply);
                }
                if (ch == ',' || i + 1 == len)
                {
                    try
                    {
                        parts[partCount++] = Int32.Parse(buf);
                        buf = "";
                    }
                    catch (Exception)
                    {
                        throw new IOException("Malformed PASV strReply: " +
                         strReply);
                    }
                }
            }
            string ipAddress = parts[0] + "." + parts[1] + "." +
             parts[2] + "." + parts[3];
            int port = (parts[4] << 8) + parts[5];
            Socket s = new
             Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint ep = new
             IPEndPoint(IPAddress.Parse(RemoteHost), port);
            try
            {
                s.Connect(ep);
            }
            catch (Exception)
            {
                throw new IOException("不能连接到FTP服务器");
            }
            return s;
        }


        /// <summary>
        /// 关闭socket连接(用于登录以前)
        /// </summary>
        private void CloseSocketConnect()
        {
            if (socketControl != null)
            {
                socketControl.Close();
                socketControl = null;
            }
            bConnected = false;
        }

        / <summary>
        / 读取Socket返回的所有字符串
        / </summary>
        / <returns>包含应答码的字符串行</returns>
        //private string ReadLine()
        //{
        //    while (true)
        //    {
        //        int iBytes = socketControl.Receive(buffer, buffer.Length, 0);
        //        strMsg += ASCII.GetString(buffer, 0, iBytes);
        //        if (iBytes < buffer.Length)
        //        {
        //            break;
        //        }
        //    }
        //    char[] seperator = { '\n' };
        //    string[] mess = strMsg.Split(seperator);
        //    if (strMsg.Length > 2)
        //    {
        //        strMsg = mess[mess.Length - 2];
        //        //seperator[0]是10,换行符是由13和0组成的,分隔后10后面虽没有字符串,
        //        //但也会分配为空字符串给后面(也是最后一个)字符串数组,
        //        //所以最后一个mess是没用的空字符串
        //        //但为什么不直接取mess[0],因为只有最后一行字符串应答码与信息之间有空格
        //        //ggggggggggggg
        //    }
        //    else
        //    {
        //        strMsg = mess[0];
        //    }
        //    if (!strMsg.Substring(3, 1).Equals(" "))//返回字符串正确的是以应答码(如220开头,后面接一空格,再接问候字符串)
        //    {
        //        return ReadLine();
        //    }
        //    return strMsg;
        //}


        /// <summary>
        /// 发送命令并获取应答码和最后一行应答字符串
        /// </summary>
        /// <param name="strCommand">命令</param>
        private void SendCommand(String strCommand)
        {
            ctrlSw.WriteLine(strCommand);
            ctrlSw.Flush();

            ReadReply();
            //Byte[] cmdBytes =
            //Encoding.Default.GetBytes((strCommand + "\r\n").ToCharArray());
            //socketControl.Send(cmdBytes, cmdBytes.Length, 0);
            //ReadReply();
        }

        #endregion
    }
    public class FTPReply
    {
        private int code;
        private string message;

        public int Code
        {
            get { return code; }
            set { code = value; }
        }

        public string Message
        {
            get { return message; }
            set { message = value; }
        }

        public override string ToString()
        {
            return string.Format("{0} {1}", Code, Message);
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值