FTPControlSocket.cs :  » Network-Clients » edtFTPnet » EnterpriseDT » Net » Ftp » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » Network Clients » edtFTPnet 
edtFTPnet » EnterpriseDT » Net » Ftp » FTPControlSocket.cs
// edtFTPnet
// 
// Copyright (C) 2004 Enterprise Distributed Technologies Ltd
// 
// www.enterprisedt.com
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
// 
// Bug fixes, suggestions and comments should posted on 
// http://www.enterprisedt.com/forums/index.php
// 
// Change Log:
// 
// $Log: FTPControlSocket.cs,v $
// Revision 1.52  2009-09-24 04:52:20  bruceb
// name resolution changes
//
// Revision 1.51  2009-04-02 04:42:16  bruceb
// fix nasty Kill() bug
//
// Revision 1.50  2009-03-29 23:52:55  bruceb
// add Kill()
//
// Revision 1.49  2009-02-16 06:11:07  bruceb
// ControlChannelIOException
//
// Revision 1.48  2008-12-02 07:47:15  bruceb
// use LF as a line marker
//
// Revision 1.47  2008-10-29 03:26:17  bruceb
// added readLine() to read only \r\n lines
//
// Revision 1.46  2008-08-21 00:44:23  hans
// MalformedReplyException now thrown when reply is corrupted.
//
// Revision 1.45  2008-06-19 23:38:16  bruceb
// remove timeout for CF
//
// Revision 1.44  2008-06-17 06:12:41  bruceb
// net cf changes
//
// Revision 1.43  2008-04-21 00:50:40  bruceb
// mutex changes
//
// Revision 1.42  2008-04-14 00:13:40  bruceb
// fixed UnauthorizedAccessException problem with mutex creation
//
// Revision 1.41  2008-03-27 05:21:11  bruceb
// SynchronizePassiveConnections
//
// Revision 1.40  2008-02-28 00:39:03  bruceb
// set StrictReturnCodes to false by default, add logging
//
// Revision 1.39  2008-02-14 05:32:00  bruceb
// print out encoding
//
// Revision 1.38  2008-02-04 20:21:04  bruceb
// FTPConnectionClosedException
//
// Revision 1.37  2007-06-26 01:36:32  bruceb
// CF changes
//
// Revision 1.36  2007/04/13 07:03:47  hans
// Added exception logging in SendCommand.
//
// Revision 1.35  2007/04/13 06:46:31  hans
// Fixed rethrows.
//
// Revision 1.34  2007/02/13 12:07:50  bruceb
// more info re expected reply codes
//
// Revision 1.33  2007/01/30 04:40:03  bruceb
// fixed comment & added LocalAddress
//
// Revision 1.32  2007/01/24 09:40:24  bruceb
// implement Connected
//
// Revision 1.31  2006/12/12 05:36:12  hans
// Replaced all validate-reply methods with a single ValidateReply method that uses the params keyword.
//
// Revision 1.30  2006/10/31 13:35:23  bruceb
// renamed logger
//
// Revision 1.29  2006/09/04 07:26:05  hans
// Added CommandError event.
//
// Revision 1.28  2006/07/28 14:33:52  bruceb
// regex used in PASV parsing
//
// Revision 1.27  2006/07/14 06:16:23  hans
// Tidied up comments.
//
// Revision 1.26  2006/07/06 13:00:26  bruceb
// changes to active port validation
//
// Revision 1.25  2006/06/29 18:52:28  bruceb
// some extra debug
//
// Revision 1.24  2006/06/28 22:15:05  hans
// Moved the FTPControlSocket.ValidateConnection call into FTPClient.Connect so that the the CommandSent and the ReplyReceived handlers could be added before it's called, hence including the Welcome message as an event.
// Set encoding to ASCII if it's passed in as null
//
// Revision 1.23  2006/06/14 10:37:27  hans
// Control channel encoding and .NET 2.0 compatibility
//
// Revision 1.22  2006/05/27 10:22:58  bruceb
// active port range default now uses 0 port
//
// Revision 1.21  2006/05/01 02:30:27  bruceb
// add retry for active mode ports re port range
//
// Revision 1.20  2006/04/13 04:32:03  bruceb
// increment ActivePortRange even if exception
//
// Revision 1.19  2006/02/09 10:35:28  hans
// Added a comment for controlPort
//
// Revision 1.18  2005/12/13 19:52:41  hans
// Added AutoPassiveIPSubstitution
//
// Revision 1.17  2005/09/30 06:34:41  bruceb
// allow 230 when initiate connection
//
// Revision 1.16  2005/09/20 10:24:23  bruceb
// check for null in reply
//
// Revision 1.15  2005/08/05 13:45:52  bruceb
// active mode port/ip address setting
//
// Revision 1.14  2005/08/04 21:57:43  bruceb
// throw change
//
// Revision 1.13  2005/06/10 15:48:13  bruceb
// error checking
//
// Revision 1.12  2005/04/08 12:05:32  bruceb
// Skip blank lines reading control socket replies
//
// Revision 1.11  2004/11/20 22:33:00  bruceb
// removed full classnames
//
// Revision 1.10  2004/11/15 23:27:03  hans
// *** empty log message ***
//
// Revision 1.9  2004/11/13 19:05:13  bruceb
// GetStream removed arg
//
// Revision 1.8  2004/11/06 11:10:02  bruceb
// tidied namespaces, changed IOException to SystemException
//
// Revision 1.7  2004/11/05 20:00:13  bruceb
// events added
//
// Revision 1.6  2004/11/04 22:32:26  bruceb
// made many protected methods internal
//
// Revision 1.5  2004/11/04 21:18:13  hans
// *** empty log message ***
//
// Revision 1.4  2004/10/29 14:30:31  bruceb
// BaseSocket changes
//
//

using System;
using System.IO;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;

using EnterpriseDT.Net;
using EnterpriseDT.Util.Debug;

namespace EnterpriseDT.Net.Ftp{    
    /// <summary>Supports client-side FTP operations</summary>
    /// <author>Bruce Blackshaw</author>
    /// <version>$Revision: 1.52 $</version>
    public class FTPControlSocket
    {
        private static string PASV_MUTEX_NAME = "Global\\edtFTPnet_SynchronizePassiveConnections";

        /// <summary>
        /// Event for notifying start of a transfer
        /// </summary> 
        internal event FTPMessageHandler CommandSent;
        
        /// <summary>
        /// Event for notifying start of a transfer
        /// </summary> 
        internal event FTPMessageHandler ReplyReceived;

        /// <summary>
        /// Occurs when there is an error while a command was being sent or
        /// a reply was being received.
        /// </summary>
        internal event FTPErrorEventHandler CommandError;

        /// <summary> 
        /// For cases where your FTP server does not properly manage PASV connections,
        /// it may be necessary to synchronize the creation of passive data sockets.
        /// It has been reported that some FTP servers (such as those at Akamai) 
        /// appear to get confused when multiple FTP clients from the same IP address
        /// attempt to connect at the same time.  For more details, please read
        /// the forum post http://www.enterprisedt.com/forums/viewtopic.php?t=2559
        /// The default value for SynchronizePassiveConnections is false.
        /// </summary>
        virtual internal bool SynchronizePassiveConnections
        {
            set
            {
                synchronizePassiveConnections = value;
            }
            get
            {
                return synchronizePassiveConnections;
            }
        }

        /// <summary> 
        /// Get/Set strict checking of FTP return codes. If strict 
        /// checking is on (the default) code must exactly match the expected 
        /// code. If strict checking is off, only the first digit must match.
        /// </summary>
        virtual internal bool StrictReturnCodes
        {
            set
            {
                log.Debug("StrictReturnCodes=" + value);
                this.strictReturnCodes = value;
            }
            get
            {
                return strictReturnCodes;
            }
            
        }
        /// <summary>   
        /// Get/Set the TCP timeout on the underlying control socket.
        /// </summary>
        virtual internal int Timeout
        {
            set
            {
                timeout = value;
                log.Debug("Setting socket timeout=" + value);
                if (controlSock == null)
                    throw new System.SystemException("Failed to set timeout - no control socket");
                SetSocketTimeout(controlSock, value);
            }
            get
            {
                return timeout;
            }
        }
                
        /// <summary>   Standard FTP end of line sequence</summary>
        internal const string EOL = "\r\n";

        /// <summary>
        /// Used for ASCII translation
        /// </summary>
        private const byte CARRIAGE_RETURN = 13;

        /// <summary>
        /// Used for ASCII translation
        /// </summary>
        private const byte LINE_FEED = 10;

        /// <summary>   Maximum number of auto retries in active mode</summary>
        internal const int MAX_ACTIVE_RETRY = 100;
        
        /// <summary>   The default and standard control port number for FTP</summary>
        public const int CONTROL_PORT = 21;
        
        /// <summary>   Used to flag messages</summary>
        private const string DEBUG_ARROW = "---> ";
        
        /// <summary>   Start of password message</summary>
        private static readonly string PASSWORD_MESSAGE = DEBUG_ARROW + "PASS";
        
        /// <summary> Logging object</summary>
        private Logger log = Logger.GetLogger("FTPControlSocket");

        /// <summary> Synchronize PASV socket connections if true (false by default)</summary>
        private bool synchronizePassiveConnections = false;
        
        /// <summary> Use strict return codes if true</summary>
        private bool strictReturnCodes = false;
        
        /// <summary>Address of the remote host</summary>
        protected string remoteHost = null;

        protected IPAddress remoteAddr = null;

    /// <summary>FTP port of the remote host</summary>
    protected int controlPort = -1;
        
        /// <summary>  The underlying socket.</summary>
        protected BaseSocket controlSock = null;
        
        /// <summary>  
        /// The timeout for the control socket
        /// </summary>
        protected int timeout = 0;
        
        /// <summary>  The write that writes to the control socket</summary>
        protected StreamWriter writer = null;
        
        /// <summary>  The reader that reads control data from the
        /// control socket
        /// </summary>
        protected StreamReader reader = null;

        private Encoding encoding;
                
        /// <summary>
        /// Port range for active mode
        /// </summary>
        private PortRange activePortRange = null;
        
        /// <summary>
        /// IP address to send with PORT command
        /// </summary>
        private IPAddress activeIPAddress = null;

        /// <summary>
        /// The next port number to use if activePortRange is set
        /// </summary>
        private int nextPort = 0;

    /// <summary>
    /// If true, uses the original host IP if an internal IP address
    /// is returned by the server in PASV mode.
    /// </summary>
    private bool autoPassiveIPSubstitution = false;
        
        /// <summary>
        /// Constructor. Performs TCP connection and
        /// sets up reader/writer. Allows different control
        /// port to be used
        /// </summary>
        /// <param name="remoteHost">      
        /// Remote inet address
        /// </param>
        /// <param name="controlPort">     
        /// port for control stream
        /// </param>
        /// <param name="timeout">          
        /// the length of the timeout, in milliseconds
        /// </param>
        /// <param name="encoding">          
        /// encoding to use for control channel
        /// </param>
        internal FTPControlSocket(string remoteHost, int controlPort, int timeout, Encoding encoding)            
        {
            if (activePortRange != null)
                activePortRange.ValidateRange();

            Initialize(
                new StandardSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp),
                remoteHost, controlPort, timeout, encoding);
        }

        /// <summary>   
        /// Default constructor
        /// </summary>
        internal FTPControlSocket()
        {
        }
        
        /// <summary>   
        /// Performs TCP connection and sets up reader/writer. 
        /// Allows different control port to be used
        /// </summary>
        /// <param name="sock">
        ///  Socket instance
        /// </param>
        /// <param name="remoteHost">     
        /// address of remote host
        /// </param>
        /// <param name="controlPort">     
        /// port for control stream
        /// </param>
        /// <param name="timeout">    
        /// the length of the timeout, in milliseconds      
        /// </param>
        /// <param name="encoding">          
        /// encoding to use for control channel
        /// </param>
        internal void Initialize(BaseSocket sock, string remoteHost, int controlPort, 
      int timeout, Encoding encoding)
        {
            this.remoteHost = remoteHost;
            this.controlPort = controlPort;
            this.timeout = timeout;
            
            // establish socket connection & set timeouts
            controlSock = sock;
            ConnectSocket(controlSock, remoteHost, controlPort);
            Timeout = timeout;
            
            InitStreams(encoding);
        }

        /// <summary>   
        /// Establishes the socket connection
        /// </summary>
        /// <param name="socket">
        ///  Socket instance
        /// </param>
        /// <param name="address">     
        /// IP address to connect to
        /// </param>
        /// <param name="port">    
        /// port to connect to     
        /// </param>
        internal virtual void ConnectSocket(BaseSocket socket, string address, int port)
        {
            remoteAddr = HostNameResolver.GetAddress(address);
            socket.Connect(new IPEndPoint(remoteAddr, port));
        }
        
        /// <summary>   Checks that the standard 220 reply is returned
        /// following the initiated connection. Allow 230 as well, some proxy
        /// servers return it.
        /// </summary>
        internal void ValidateConnection()
        {           
            FTPReply reply = ReadReply();
            ValidateReply(reply, "220", "230");
        } 
        
        
        /// <summary>  Obtain the reader/writer streams for this
        /// connection
        /// </summary>
        internal void InitStreams(Encoding encoding)
        {
            Stream stream = controlSock.GetStream();
            if (encoding == null)
                encoding = Encoding.ASCII;
            this.encoding = encoding;
            log.Debug("Command encoding=" + encoding.ToString());
            writer = new StreamWriter(stream, encoding);
            reader = new StreamReader(stream, encoding);
        }
        
        /// <summary>
        /// Set the port range to use in active mode
        /// </summary>
        /// <param name="portRange">port range to use</param>
        internal void SetActivePortRange(PortRange portRange)
        {
            portRange.ValidateRange();
            activePortRange = portRange;
            if (!portRange.UseOSAssignment)
            {
                Random rand = new Random();
                nextPort = rand.Next(activePortRange.LowPort,activePortRange.HighPort);
                log.Debug("SetActivePortRange("+ activePortRange.LowPort + "," + activePortRange.HighPort + "). NextPort=" + nextPort);
            }
        }
        
        /// <summary>
        /// Set an IP address to use for PORT commands
        /// </summary>
        /// <param name="address">IP address to use for PORT command</param>
        internal void SetActiveIPAddress(IPAddress address)
        {
            activeIPAddress = address;
        }

        internal void Kill()
        {
            try
            {
                if (controlSock != null)
                    controlSock.Close();
                controlSock = null;
            }
            catch (Exception e)
            {
                log.Debug("Killed socket", e);
            }
            log.Info("Killed control socket");
        }
        
        /// <summary>  
        /// Quit this FTP session and clean up.
        /// </summary>
        internal virtual void Logout()
        {
            
            SystemException ex = null;
            try
            {
                writer.Close();
            }
            catch (SystemException e)
            {
                ex = e;
            }
            try
            {
                reader.Close();
            }
            catch (SystemException e)
            {
                ex = e;
            }
            try
            {
                controlSock.Close();
                controlSock = null;
            }
            catch (SystemException e)
            {
                ex = e;
            }
            if (ex != null) 
            {
                log.Error("Caught exception", ex);
                throw ex;
            }
        }

        /// <summary>
        /// True if the control socket was connected at the last operation
        /// </summary>
        public bool Connected 
        {
            get 
            {
                return (controlSock == null ? false : controlSock.Connected);
            }
        }
        
        /// <summary>  
        /// Request a data socket be created on the
        /// server, connect to it and return our
        /// connected socket.
        /// </summary>
        /// <param name="connectMode">  
        /// The mode to connect in
        /// </param>
        /// <returns>  
        /// connected data socket
        /// </returns>
        internal virtual FTPDataSocket CreateDataSocket(FTPConnectMode connectMode)
        {            
            if (connectMode == FTPConnectMode.ACTIVE)
            {
                return CreateDataSocketActive();
            }
            else
            {
                // PASV
                return CreateDataSocketPASV();
            }
        }
        
        /// <summary>  
        /// Request a data socket be created on the Client
        /// client on any free port, do not connect it to yet.
        /// </summary>
        /// <returns>  
        /// not connected data socket
        /// </returns>
        internal virtual FTPDataSocket CreateDataSocketActive()
        {
            try 
            {
                int count = 0;
                int maxCount = MAX_ACTIVE_RETRY;
                if (activePortRange != null) 
                {
                    int range = activePortRange.HighPort-activePortRange.LowPort+1;
                    if (range < MAX_ACTIVE_RETRY)
                        maxCount = range;
                }
                while (count < maxCount)
                {
                    count++;
                    try
                    {
                        return NewActiveDataSocket(nextPort);
                    }
                    catch (SocketException) 
                    {
                        // check ok to retry
                        if (count < maxCount)
                        {
                            log.Warn("Detected socket in use - retrying and selecting new port");
                            SetNextAvailablePortFromRange();
                        }
                    }
                }
                throw new FTPException("Exhausted active port retry count - giving up");
                
            }
            finally // even if exception thrown, we want to increment the range
            {
                SetNextAvailablePortFromRange();
            }
        }

        /// <summary>
        /// Increment port number to use to next in range, or else recycle
        /// from lowPort again, making sure we avoid the current port
        /// </summary>
        private void SetNextAvailablePortFromRange() 
        {
            // keep using 0 if using OS ports
            if (activePortRange == null || activePortRange.UseOSAssignment)
                return;

            // need to set next port to random port in range if it is 0 and we
            // get to here - means the active port ranges have been changed
            if (nextPort == 0)
            {
                Random rand = new Random();
                nextPort = rand.Next(activePortRange.LowPort,activePortRange.HighPort);
            }
            else
                nextPort++;

            // if exceeded the high port drop to low
            if (nextPort > activePortRange.HighPort)
                nextPort = activePortRange.LowPort;

            log.Debug("Next active port will be: " + nextPort);
        }
                
        /// <summary>  
        /// Sets the data port on the server, i.e. sends a PORT
        /// command        
        /// </summary>
        /// <param name="ep">local endpoint
        /// </param>
        internal void SetDataPort(IPEndPoint ep)
        {
#if NET20
            byte[] hostBytes = ep.Address.GetAddressBytes();
#else
            byte[] hostBytes = BitConverter.GetBytes(ep.Address.Address);
#endif
            if (activeIPAddress != null)
            {
                log.Info("Forcing use of fixed IP for PORT command");
#if NET20
                hostBytes = activeIPAddress.GetAddressBytes();
#else
                hostBytes = BitConverter.GetBytes(activeIPAddress.Address);
#endif
            }
                            
            // This is a .NET 1.1 API
            // byte[] hostBytes = ep.Address.GetAddressBytes();
            
            byte[] portBytes = ToByteArray((ushort)ep.Port);
            
            // assemble the PORT command
            string cmd = new StringBuilder("PORT ").
                Append((short)hostBytes[0]).Append(",").
                Append((short)hostBytes[1]).Append(",").
                Append((short)hostBytes[2]).Append(",").
                Append((short)hostBytes[3]).Append(",").
                Append((short)portBytes[0]).Append(",").
                Append((short)portBytes[1]).ToString();
            
            // send command and check reply
            // CoreFTP returns 250 incorrectly
            FTPReply reply = SendCommand(cmd);
            ValidateReply(reply, "200", "250");
        }
        
        
        /// <summary>  
        /// Convert a short into a byte array
        /// </summary>
        /// <param name="val">  value to convert
        /// </param>
        /// <returns>  a byte array
        /// 
        /// </returns>
        internal byte[] ToByteArray(ushort val)
        {            
            byte[] bytes = new byte[2];
            bytes[0] = (byte) (val >> 8); // bits 1- 8
            bytes[1] = (byte) (val & 0x00FF); // bits 9-16
            return bytes;
        }                        
        
        /// <summary>  
        /// Request a data socket be created on the
        /// server, connect to it and return our
        /// connected socket.
        /// </summary>
        /// <returns>  connected data socket
        /// </returns>
        internal virtual FTPDataSocket CreateDataSocketPASV()
        {
            bool synchronizeConnection = SynchronizePassiveConnections;
            Mutex mutex = null;
            if (synchronizeConnection)
            {
                mutex = new Mutex(false, PASV_MUTEX_NAME);
                mutex.WaitOne();
            }

            try
            {
                // PASSIVE command - tells the server to listen for
                // a connection attempt rather than initiating it
                FTPReply replyObj = SendCommand("PASV");
                ValidateReply(replyObj, "227");
                string reply = replyObj.ReplyText;

                // The reply to PASV is in the form:
                // 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
                // where h1..h4 are the IP address to connect and
                // p1,p2 the port number
                // Example:
                // 227 Entering Passive Mode (128,3,122,1,15,87).
                // NOTE: Some FTP servers miss the brackets out completely
                Regex regEx = new Regex(@"(?<a0>\d{1,3}),(?<a1>\d{1,3}),(?<a2>\d{1,3}),(?<a3>\d{1,3}),(?<p0>\d{1,3}),(?<p1>\d{1,3})");
                Match m1 = regEx.Match(reply);

                // assemble the IP address
                // we try connecting, so we don't bother checking digits etc
                string ipAddress = m1.Groups["a0"].Value + "." + m1.Groups["a1"].Value + "." + m1.Groups["a2"].Value + "." + m1.Groups["a3"].Value;
                log.Debug("Server supplied address=" + ipAddress);

                // assemble the port number
                int[] portParts = new int[2];
                portParts[0] = Int32.Parse(m1.Groups["p0"].Value);
                portParts[1] = Int32.Parse(m1.Groups["p1"].Value);
                int port = (portParts[0] << 8) + portParts[1];
                log.Debug("Server supplied port=" + port);

                string hostIP = ipAddress;
                if (autoPassiveIPSubstitution && remoteAddr != null)
                {
                    hostIP = remoteAddr.ToString();
                    if (log.IsEnabledFor(Level.DEBUG))
                        log.Debug(string.Format("Substituting server supplied IP ({0}) with remote host IP ({1})",
                            ipAddress, hostIP));
                }

                // create the socket
                return NewPassiveDataSocket(hostIP, port);
            }
            finally
            {
                if (synchronizeConnection && mutex != null)
                {
                    mutex.ReleaseMutex();
                    mutex.Close();
                }
            }
        }
        
        /// <summary> Constructs a new <code>FTPDataSocket</code> object (client mode) and connect
        /// to the given remote host and port number.
        /// 
        /// </summary>
        /// <param name="ipAddress">IP Address to connect to.
        /// </param>
        /// <param name="port">Remote port to connect to.
        /// </param>
        /// <returns> A new <code>FTPDataSocket</code> object (client mode) which is
        /// connected to the given server.
        /// </returns>
        /// <throws>  SystemException Thrown if no TCP/IP connection could be made.  </throws>
        internal virtual FTPDataSocket NewPassiveDataSocket(string ipAddress, int port)
        {
            log.Debug("NewPassiveDataSocket(" + ipAddress + "," + port + ")");
            IPAddress ad = IPAddress.Parse(ipAddress);  
            IPEndPoint ipe = new IPEndPoint(ad, port); 
            BaseSocket sock = 
                new StandardSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                SetSocketTimeout(sock, timeout);
                sock.Connect(ipe);
            }
            catch (Exception ex)
            {
                log.Error("Failed to create connecting socket", ex);
                sock.Close();
                throw;
            }
            return new FTPPassiveDataSocket(sock);
        }

        internal IPAddress LocalAddress
        {
            get 
            {
                return ((IPEndPoint)controlSock.LocalEndPoint).Address;
            }
        }
        
        /// <summary> 
        /// Constructs a new <code>FTPDataSocket</code> object (server mode) which will
        /// listen on the given port number.
        /// </summary>
        /// <param name="port">Remote port to listen on.
        /// </param>
        /// <returns> A new <code>FTPDataSocket</code> object (server mode) which is
        /// configured to listen on the given port.
        /// </returns>
        /// <throws>  SystemException Thrown if an error occurred when creating the socket.  </throws>
        internal virtual FTPDataSocket NewActiveDataSocket(int port)
        {                        
            // create listening socket at a system allocated port
            BaseSocket sock = 
                new StandardSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                log.Debug("NewActiveDataSocket(" + port + ")");
                // choose specified port
                IPEndPoint endPoint = new IPEndPoint(((IPEndPoint)controlSock.LocalEndPoint).Address, port);
                sock.Bind(endPoint);     

                // queue up to 5 connections
                sock.Listen(5);
                
                // find out ip & port we are listening on            
                SetDataPort((IPEndPoint)sock.LocalEndPoint);
            }
            catch (Exception ex)
            {
                log.Error("Failed to create listening socket", ex);
                sock.Close();
                throw;
            }

            return new FTPActiveDataSocket(sock);
        }

        /// <summary>  Send a command to the FTP server and
        /// return the server's reply as a structured
        /// reply object
        /// </summary>
        /// <param name="command">  
        /// command to send
        /// </param>
        /// <returns>  reply to the supplied command
        /// </returns>
        public virtual FTPReply SendCommand(string command)
        {
            try
            {
                WriteCommand(command);

                // and read the result
                return ReadReply();
            }
            catch (Exception e)
            {
                log.Error("Exception in SendCommand", e);
                if (CommandError != null)
                    CommandError(this, new FTPErrorEventArgs(e));
                throw;
            }
        }
        
        /// <summary>  Send a command to the FTP server. Don't
        /// read the reply
        /// 
        /// </summary>
        /// <param name="command">  command to send
        /// </param>
        internal virtual void WriteCommand(string command)
        {            
            Log(DEBUG_ARROW + command, true);
            
            // send it
            writer.Write(command + EOL);
            writer.Flush();
        }

        /// <summary>
        /// Read a line, which means until a \r\n is reached
        /// </summary>
        /// <returns>line that is read</returns>
        private string ReadLine()
        {
            int current = 0;
            StringBuilder str = new StringBuilder();
            StringBuilder err = new StringBuilder();
            while (true)
            {
                try
                {
                    current = reader.Read();
                }
                catch (IOException ex)
                {
                    log.Error("Read failed ('" + err.ToString() + "' read so far)");
                    throw new ControlChannelIOException(ex.Message);
                }
                if (current < 0)
                {
                    string msg = "Control channel unexpectedly closed ('" + err.ToString() + "' read so far)";
                    log.Error(msg);
                    throw new ControlChannelIOException(msg);
                }
                if (current == LINE_FEED)
                    break;

                if (current != CARRIAGE_RETURN)
                {
                    str.Append((char)current);
                    err.Append((char)current);
                }
            }
            return str.ToString();
        }
        
        /// <summary>  Read the FTP server's reply to a previously
        /// issued command. RFC 959 states that a reply
        /// consists of the 3 digit code followed by text.
        /// The 3 digit code is followed by a hyphen if it
        /// is a muliline response, and the last line starts
        /// with the same 3 digit code.
        /// 
        /// </summary>
        /// <returns>  structured reply object
        /// </returns>
        internal virtual FTPReply ReadReply()
        {
            string line = ReadLine();
            while (line.Length == 0)
                line = ReadLine();
                
            Log(line, false);

            if (line.Length < 3)
            {
                String msg = "Short reply received (" + line + ")";
                log.Error(msg);
                throw new MalformedReplyException(msg);
            }
            
            string replyCode = line.Substring(0, 3);
            StringBuilder reply = new StringBuilder("");
            if (line.Length > 3)
                reply.Append(line.Substring(4));
            
            ArrayList dataLines = null;
            
            // check for multiline response and build up
            // the reply
            if (line[3] == '-')
            {
                dataLines = ArrayList.Synchronized(new ArrayList(10));
                bool complete = false;
                while (!complete)
                {
                    line = ReadLine();
                                       
                    if (line.Length == 0)
                        continue;
                    
                    Log(line, false);
                    
                    if (line.Length > 3 && line.Substring(0, (3) - (0)).Equals(replyCode) && 
                        line[3] == ' ')
                    {
                        reply.Append(line.Substring(3));
                        complete = true;
                    }
                    else
                    {
                        // not the last line.
                        reply.Append(" ").Append(line);
                        dataLines.Add(line);
                    }
                } // end while
            } // end if
            
            if (dataLines != null)
            {
                string[] data = new string[dataLines.Count];
                dataLines.CopyTo(data);
                return new FTPReply(replyCode, reply.ToString(), data);
            }
            else
            {
                return new FTPReply(replyCode, reply.ToString());
            }
        }
        
        
        /// <summary>  
        /// Validate the response the host has supplied against the
        /// expected reply. If we get an unexpected reply we throw an
        /// exception, setting the message to that returned by the
        /// FTP server
        /// </summary>
        /// <param name="reply">reply object</param>
        /// <param name="expectedReplyCodes"> array of expected replies</param>
        /// <returns>reply object</returns>
        public virtual FTPReply ValidateReply(FTPReply reply, params string[] expectedReplyCodes)
        {
            if ("421" == reply.ReplyCode)
            {
                log.Error("Received 421 - throwing exception");
                throw new FTPConnectionClosedException(reply.ReplyText);
            }
            foreach (string expectedReplyCode in expectedReplyCodes)
                if (strictReturnCodes)
                {
                    if (reply.ReplyCode==expectedReplyCode)
                        return reply;
                }
                else
                {
                    // non-strict - match first char
                    if (reply.ReplyCode[0] == expectedReplyCode[0])
                        return reply;
                }

            // got this far, not recognised
            StringBuilder buf = new StringBuilder("[");
            int i = 0;
            foreach (string expectedReplyCode in expectedReplyCodes)
            {
                buf.Append(expectedReplyCode);
                if (i+1 < expectedReplyCodes.Length)
                    buf.Append(",");
                i++;
            }
            buf.Append("]");
            log.Info("Expected reply codes = " + buf.ToString() + " (strict=" + strictReturnCodes + ")");
            
            // got this far, not recognised
            throw new FTPException(reply);
        }
                
        /// <summary>  
        /// Log a message, checking for passwords
        /// </summary>
        /// <param name="msg">
        /// message to log
        /// </param>
        /// <param name="command"> 
        /// true if a response, false otherwise
        /// </param>
        internal virtual void Log(string msg, bool command)
        {
            if (msg.StartsWith(PASSWORD_MESSAGE))
                msg = PASSWORD_MESSAGE + " ********";
            log.Debug(msg);
            if (command) 
            {
                if (CommandSent != null)
                    CommandSent(this, new FTPMessageEventArgs(msg));
            }
            else
            {
                if (ReplyReceived != null)
                    ReplyReceived(this, new FTPMessageEventArgs(msg)); 
            }
        }
        
        /// <summary>  
        /// Helper method to set a socket's timeout value
        /// </summary>
        /// <param name="sock">
        /// socket to set timeout for
        /// </param>
        /// <param name="timeout">
        /// timeout value to set
        /// </param>
        internal void SetSocketTimeout(BaseSocket sock, int timeout)
        {
            if (timeout > 0) 
            {
                try
                {
                    sock.SetSocketOption(SocketOptionLevel.Socket,
                        SocketOptionName.ReceiveTimeout, timeout);
                    sock.SetSocketOption(SocketOptionLevel.Socket,
                        SocketOptionName.SendTimeout, timeout);
                }
                catch (SocketException ex)
                {
                    log.Warn("Failed to set socket timeout: " + ex.Message);
                }
            }
        }

    /// <summary>
    /// Use <c>AutoPassiveIPSubstitution</c> to ensure that 
    /// data-socket connections are made to the same IP address
    /// that the control socket is connected to.
    /// </summary>
    /// <remarks>
    /// <c>AutoPassiveIPSubstitution</c> can be useful when connecting
    /// to FTP servers that request data connections be connected to an
    /// IP address other than the one to which the connection was 
    /// initially made.  This usually happens when an FTP server is behind
    /// a NAT router and has not been configured to reflect the fact that
    /// its internal (LAN) IP address is different from the address that
    /// external (Internet) machines connect to.
    /// </remarks>
    internal bool AutoPassiveIPSubstitution
    {
      get
      {
        return this.autoPassiveIPSubstitution;
      }
      set
      {
        this.autoPassiveIPSubstitution = value;
      }
    }
    }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.