FTPClient.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 » FTPClient.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

#region Change Log

// Change Log:
// 
// $Log: FTPClient.cs,v $
// Revision 1.111  2009-10-16 00:22:33  hans
// Made PortRange.LOW_PORT and PortRange.DEFAULT_HIGH_PORT internal.
//
// Revision 1.110  2009-09-24 04:52:20  bruceb
// name resolution changes
//
// Revision 1.109  2009-06-03 02:36:28  bruceb
// FTPTransferCancelledException fix
//
// Revision 1.108  2009-05-28 02:33:49  bruceb
// 426 etc fix
//
// Revision 1.107  2009-03-29 23:53:27  bruceb
// add CloseDataSocket(stream)
//
// Revision 1.106  2009-03-10 05:29:18  bruceb
// bandwidth throttling
//
// Revision 1.105  2009-02-27 06:55:10  bruceb
// fix mod time doco bug
//
// Revision 1.104  2008-12-04 23:59:28  bruceb
// cleaned up exception handling & fixed resume bug
//
// Revision 1.103  2008-11-07 01:49:04  bruceb
// removed semicolon
//
// Revision 1.102  2008-10-09 23:58:54  bruceb
// TransferNotifyListings & QuitImmediately fix
//
// Revision 1.101  2008-10-08 23:48:09  hans
// Fixed NRE in ValidateTransferOnError.
//
// Revision 1.100  2008-08-29 05:27:21  bruceb
// add internal commands for FXP usage
//
// Revision 1.99  2008-08-26 00:39:26  bruceb
// move resume/size to before data socket creation
//
// Revision 1.98  2008-08-21 00:42:28  hans
// Modified exception handling in download/upload methods so that (1) ValidateTransferOnError is only called for errors on the data-channel, and (2) the original exception is rethrown (and not a new exception from inside the exception handler.
//
// Revision 1.97  2008-06-19 23:49:55  bruceb
// remove timeout for CF
//
// Revision 1.96  2008-05-28 02:41:45  bruceb
// SetModTime now void
//
// Revision 1.95  2008-04-15 00:35:50  hans
// Added SetModTime
//
// Revision 1.94  2008-03-27 05:21:59  bruceb
// SynchronizePassiveConnections && Account
//
// Revision 1.93  2008-03-12 04:03:46  bruceb
// in QuitImmediately() kill the data socket if exists
//
// Revision 1.92  2008-02-28 00:38:22  bruceb
// fix bug re StrictReturnCodes
//
// Revision 1.91  2008-02-04 20:21:22  bruceb
// added 200
//
// Revision 1.90  2007-12-14 01:29:24  bruceb
// validate on error fixes + cancel listings
//
// Revision 1.89  2007-11-20 05:52:22  hans
// Now throws exception when transfer cancelled.
//
// Revision 1.88  2007-11-16 00:09:11  bruceb
// revert changes re closing filestreams as not required
//
// Revision 1.87  2007-11-15 01:14:31  bruceb
// fixed  bug re filestream closed before buffered stream owner
//
// Revision 1.86  2007-11-15 00:10:10  bruceb
// CF stream closing fixes
//
// Revision 1.85  2007-11-14 05:56:11  bruceb
// add comment
//
// Revision 1.84  2007-11-12 05:21:57  bruceb
// ShowHiddenFiles added
//
// Revision 1.83  2007-10-15 04:43:09  bruceb
// ensure files are absolutely closed by explicitly closing them rather than relying on owning streams
//
// Revision 1.82  2007-08-27 03:49:49  bruceb
// added 250 replies for Leitch media server
//
// Revision 1.81  2007-07-20 01:43:25  bruceb
// move logging
//
// Revision 1.80  2007-07-10 05:32:43  bruceb
// catch exception from Size() when resuming
//
// Revision 1.79  2007-06-26 01:36:32  bruceb
// CF changes
//
// Revision 1.78  2007-06-20 08:41:06  hans
// Made sure TimeDifference getter doesn't throw an exception if fileFactory is null.
//
// Revision 1.77  2007-06-07 23:58:15  hans
// Fixed modtimeFormats so that it supports 1 and 2 fractions as well as 3.
//
// Revision 1.76  2007-05-23 00:21:07  hans
// Added TimeDifference and TimeIncludesSeconds properties.  Added support for PropertyChanged events.
//
// Revision 1.75  2007-05-15 01:03:45  bruceb
// file factory change if syst doesn't work
//
// Revision 1.74  2007/04/25 04:32:40  bruceb
// extra check to see if dirname is blank
//
// Revision 1.73  2007/04/21 22:10:09  bruceb
// if no encoding set, skip non-printable chars in dir listings. Get rid of binary reader/writer. Add path to FTPFile in DirDetails
//
// Revision 1.72  2007/04/13 06:46:06  hans
// Fixed rethrows and added exception logging.
//
// Revision 1.71  2007/03/19 00:22:07  bruceb
// tweaks for locked local files (download)
//
// Revision 1.70  2007/03/16 04:59:19  bruceb
// ValidateTransferOnError() introduced, minor bug fixes
//
// Revision 1.69  2007/02/13 12:11:49  bruceb
// StrictReturnCodes off by default + check if localPath is a directory
//
// Revision 1.68  2007/02/02 01:43:22  bruceb
// fixed CheckConnection method
//
// Revision 1.67  2007/01/30 04:44:48  bruceb
// ServerStrings classes & enhance Exists
//
// Revision 1.66  2007/01/25 00:19:49  hans
// Added EditorBrowsable attributed to Obsolete methods
//
// Revision 1.65  2007/01/24 09:40:07  bruceb
// more detailed IsConnected
//
// Revision 1.64  2006/12/29 03:34:24  hans
// Added comments for Exists method.
//
// Revision 1.63  2006/12/12 05:35:10  hans
// Updated to use new ValidateReply method (using params)
//
// Revision 1.62  2006/12/05 23:11:01  hans
// Put in ifdef code for .NET using Int32.TryParse instead of Int32.Try to avoid unnecessary exceptions.
//
// Revision 1.61  2006/11/22 15:35:52  bruceb
// extra format to parse for modtime
//
// Revision 1.60  2006/10/17 14:15:10  bruceb
// added DataEncoding property
//
// Revision 1.59  2006/10/04 07:59:50  hans
// Put(Stream,string), Put(Stream,string,bool) now return a long which is the number of bytes transferred.
// Same with PutASCII and PutBinary.
// Fixed bug where resumeMarker wasn't being set to zero when it wasn't being used in a transfer.
// Added ResumeOffset argument to BytesTransferred event triggerings.
//
// Revision 1.58  2006/09/04 15:29:29  bruceb
// remove CheckConnection() in ServerWakeupInterval
//
// Revision 1.57  2006/08/23 13:50:05  bruceb
// now uses control encoding for dir listings
//
// Revision 1.56  2006/08/09 07:48:44  hans
// Downloaded files can be renamed in a Downloading event-handler.
//
// Revision 1.55  2006/08/02 10:40:13  hans
// Added getter for FTPFileFactory
//
// Revision 1.54  2006/07/14 06:14:16  hans
// Tidied up comments.
//
// Revision 1.53  2006/07/12 08:26:37  hans
// Fixed bug where parserCulture gets ignored if fileFactory is null when it is set.
//
// Revision 1.52  2006/07/11 21:46:54  bruceb
// close socket changes in order
//
// Revision 1.51  2006/07/11 09:59:39  hans
// Changed the way the data-socket is closed in PutBinary
//
// Revision 1.50  2006/07/10 16:59:23  bruceb
// fix bug where local files weren't being closed if CloseStreamsAfterTransfer was set to false
//
// Revision 1.49  2006/07/07 17:16:46  bruceb
// augment doco
//
// Revision 1.48  2006/07/07 10:51:28  hans
// Fixed BytesTransferred event when notify-interval equals buffer-size.
// Stopped BytesTransferred event firing after cancelled transfer.
//
// Revision 1.47  2006/07/06 13:00:26  bruceb
// changes to active port validation
//
// Revision 1.46  2006/06/29 18:58:38  bruceb
// remove "data.Timeout = timeout" as it is no longer necessary - we set the timeout  of the active socket elsewhere now
//
// Revision 1.45  2006/06/28 22:11:48  hans
// Visual Studio integration
// 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.
//
// Revision 1.44  2006/06/22 12:39:09  bruceb
// neaten up IsConnected
//
// Revision 1.43  2006/06/16 12:12:02  bruceb
// moved out some common types into another file, server wakeup
//
// Revision 1.42  2006/06/14 10:29:15  hans
// Made FTPClient implement IFileTransferClient
// Added ControlEncoding property
// Changed all <p> tags to <para>
//
// Revision 1.41  2006/06/14 10:08:41  bruceb
// moved types into separate file
//
// Revision 1.40  2006/05/27 10:23:38  bruceb
// change default port range to 1024->5000
//
// Revision 1.39  2006/05/25 05:43:02  hans
// Flush output-stream in stream-based puts.
//
// Revision 1.38  2006/04/18 07:16:53  hans
// - Changed PortRange so that its properties can be set after construction.
// - FTPClient.ActivePortRange now has a default object instead of null.  This was mainly done so that its properties could be viewed in FTPConnection.
//
// Revision 1.37  2006/03/16 22:27:46  hans
// Added stream and byte-array fields to TransferEventArgs.
// Improved comments.
// Added CloseStreamsAfterTransfer property.
// Moved TransferStarted event firing to before any transfer operations have taken place (prevents hangs in event-handlers with FTP operations in them).
//
// Revision 1.36  2006/02/09 10:30:27  hans
// Fixed bug in TransferEventArgs constructor
//
// Revision 1.35  2005/12/13 19:52:36  hans
// Added AutoPassiveIPSubstitution
//
// Revision 1.34  2005/11/28 21:20:28  hans
// Set culture to Invariant if value is null.
//
// Revision 1.32  2005/10/13 17:22:47  bruceb
// fixed TransferComplete events so they occur after 226 ack
//
// Revision 1.31  2005/09/30 18:04:22  bruceb
// fix re 226 being returned if no files
//
// Revision 1.30  2005/09/30 09:24:47  hans
// Added local file-name to TransferEventArgs
//
// Revision 1.29  2005/09/30 06:33:44  bruceb
// permit 350 return from STOR
//
// Revision 1.28  2005/09/20 11:12:01  bruceb
// data set compile fix
//
// Revision 1.27  2005/09/20 10:25:02  bruceb
// Restart() public, don't use abort in cancel, dir listing addition for empty dir
//
// Revision 1.26  2005/09/02 07:01:41  hans
// Check for remoteHost before connect
//
// Revision 1.25  2005/08/23 21:23:04  hans
// Added remoteFile to ByteTransferEventArgs.
//
// Revision 1.24  2005/08/05 13:45:51  bruceb
// active mode port/ip address setting
//
// Revision 1.23  2005/08/04 21:58:41  bruceb
// 550/450 changes
//
// Revision 1.22  2005/07/22 10:39:30  hans
// Added comments
//

#endregion

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections;
using System.Globalization;
using System.ComponentModel;
using System.Net.Sockets;

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


namespace EnterpriseDT.Net.Ftp{
    #region Types

    /// <summary>
    /// Specifies a TCP port range defining the lower and upper limits for
    /// data-channels.
    /// </summary>
    /// <remarks>
    /// The default is to let the operating system select
    /// the port number within the range 1024-5000.  If the range is set to
    /// anything other than the default then ports will be selected sequentially,
    /// increasing by one until the higher limit is reached and then wrapping around
    /// to the lower limit.
    /// </remarks>
    public class PortRange 
    {
        /// <summary>
        /// Lowest port number permitted.  This is also the default value for 
        /// <see cref="LowPort"/>.
        /// </summary>
        internal const int LOW_PORT = 1024;
        
        /// <summary>
        /// Default value for <see cref="HighPort"/>.
        /// </summary>
        internal const int DEFAULT_HIGH_PORT = 5000;
        
        /// <summary>
        /// Highest port number permitted.
        /// </summary>
        private const int HIGH_PORT = 65535;

        /// <summary>
        /// Used to notify of changed properties.
        /// </summary>
        private PropertyChangedEventHandler propertyChangeHandler = null;
        
    /// <summary>
    /// Default Constructor.
    /// </summary>
    internal PortRange()
    {
            this.low = LOW_PORT;
      this.high = DEFAULT_HIGH_PORT;
    }
        
    /// <summary>
    /// Constructor setting the lower and higher limits of the range.
    /// </summary>
    /// <param name="low">Lower limit of the port-range.</param>
        /// <param name="high">Higher limit of the port-range.</param>
    internal PortRange(int low, int high)
    {
      if (low < LOW_PORT || high > HIGH_PORT)
        throw new ArgumentException("Ports must be in range [" + LOW_PORT + "," + HIGH_PORT + "]");
      if (low >= high)
                throw new ArgumentException("Low port (" + low + ") must be smaller than high port (" + high + ")");
            this.low = low;
      this.high = high;
    }
        
        /// <summary>
        /// Lowest port number in range.
        /// </summary>
        /// <remarks>
        /// The default value is 1024.  If it is left at this value and <see cref="HighPort"/>
        /// is left at 5000 then the OS will select the port.  If it is set to
        /// anything other than 1024 then ports will be selected sequentially,
        /// increasing by one until the higher limit is reached and then wrapping around
        /// to the lower limit.
        /// </remarks>
        [Description("Lowest port number in range.")]
        [RefreshProperties(RefreshProperties.All)]
        [DefaultValue(LOW_PORT)]
        public int LowPort
        {
            get 
            {
                return low;
            }
      set
      {
        if (value > HIGH_PORT || value < LOW_PORT)
          throw new ArgumentException("Ports must be in range [" + LOW_PORT + "," + HIGH_PORT + "]");
                if (LowPort != value)
                {
                    low = value;
                    if (propertyChangeHandler != null)
                        propertyChangeHandler(this, new PropertyChangedEventArgs("LowPort"));
                }
      }
        }
        
        /// <summary>
        /// Highest port number in range.
        /// </summary>
        /// <remarks>
        /// The default value is 5000.  If it is left at this value and <see cref="LowPort"/>
        /// is left at 1024 then the OS will select the port.  If it is set to
        /// anything other than 5000 then ports will be selected sequentially,
        /// increasing by one until the higher limit is reached and then wrapping around
        /// to the lower limit.
        /// </remarks>
        [Description("Highest port number in range.")]
        [RefreshProperties(RefreshProperties.All)]
        [DefaultValue(DEFAULT_HIGH_PORT)]
        public int HighPort
        {
            get 
            {
                return high;
            }
      set
      {
                if (value > HIGH_PORT || value < LOW_PORT)
          throw new ArgumentException("Ports must be in range [" + LOW_PORT + "," + HIGH_PORT + "]");
                if (HighPort != value)
                {
                    high = value;
                    if (propertyChangeHandler != null)
                        propertyChangeHandler(this, new PropertyChangedEventArgs("HighPort"));
                }
      }
        }

        /// <summary>
        /// Determines if the operating system should select the ports within the range 1024-5000.
        /// </summary>
        /// <remarks>
        /// If <c>UseOSAssignment</c> is set to <c>true</c> then the OS will select data-channel
        /// ports within the range 1024-5000.  Otherwise ports will be selected sequentially,
        /// increasing by one until the higher limit is reached and then wrapping around
        /// to the lower limit.  Setting this flag will cause <see cref="LowPort"/> and
        /// <see cref="HighPort"/> to be set to 1024 and 5000, respectively.
        /// </remarks>
        [Description("Determines if the operating system should select the ports within the range 1024-5000.")]
        [RefreshProperties(RefreshProperties.All)]
        [DefaultValue(true)]
        public bool UseOSAssignment
        {
            get
            {
                return low == LOW_PORT && high == DEFAULT_HIGH_PORT;
            }
            set
            {
                LowPort = LOW_PORT;
                HighPort = DEFAULT_HIGH_PORT;
            }
        }

        /// <summary>
        /// Validate the port range, and throw an exception if incorrect.
        /// </summary>
        internal void ValidateRange()
        {
            if (low >= high)
                throw new FTPException("Low port (" + low + ") must be smaller than high port (" + high + ")");
        }

        /// <summary>
        /// Called when a property is changed.
        /// </summary>
        internal PropertyChangedEventHandler PropertyChangeHandler
        {
            get { return propertyChangeHandler; }
            set { propertyChangeHandler = value; }
        }

        /// <summary>
        /// Returns a <see cref="String"/> that represents the current <see cref="Object"/>. 
        /// </summary>
        /// <returns>A String that represents the current Object. </returns>
    public override string ToString()
    {
      return string.Format("{0} -> {1}", low, high);
    }

        /// <summary>
        /// Low port number in range
        /// </summary>
        private int low;
        
        /// <summary>
        /// High port number in range
        /// </summary>
        private int high;
    }

    /// <summary>
    /// Enumerates the connect modes that are possible, active and passive.
    /// </summary>
    /// <remarks>
    /// The mode describes the behaviour of the server. In active mode, the server
    /// actively connects to the client to establish a data connection. In passive mode
    /// the client connects to the server.
    /// </remarks>
    public enum FTPConnectMode 
    {
        /// <summary>   
        /// Represents active - PORT - connect mode. The server connects to the client
        /// for data transfers.
        /// </summary>
        ACTIVE = 1,

        /// <summary>   
        /// Represents passive - PASV - connect mode. The client connects to the server 
        /// for data transfers.
        /// </summary>
        PASV = 2
    }

    #endregion
    
    /// <summary>  
    /// Provides low-level access to FTP operations.  <see cref="FTPConnection"/>
    /// provides a superior interface and is recommended for general use.
    /// </summary>
    /// <author>Bruce Blackshaw</author>
    /// <version>$Revision: 1.111 $</version>
    public class FTPClient : IFileTransferClient
    {
    /// <summary>The version of edtFTPj.</summary>
    /// <value>An <c>int</c> array of <c>{major,middle,minor}</c> version numbers.</value>
    public static int[] Version
        {
            get
            {
                return version;
            }
        }

    /// <summary>The edtFTPj build timestamp.</summary>
    /// <value>
    /// Timestamp of when edtFTPj was build in the format <c>d-MMM-yyyy HH:mm:ss z</c>.
    /// </value>
    public static string BuildTimestamp
        {
            get
            {
                return buildTimestamp;
            }            
        }


        /// <summary>Controls whether or not checking of return codes is strict.</summary>
    /// <remarks>
    /// <para>
    /// Some servers return non-standard reply-codes.  Setting this property to <c>false</c>
    /// only the first digit of the reply-code is checked, thus decreasing the sensitivity
    /// of edtFTPj to non-standard reply-codes.  The default is <c>true</c> meaning that
    /// reply-codes must match exactly.
    /// </para>
    /// </remarks>
    /// <value>  
    /// <c>true</c> if strict return code checking, <c>false</c> if non-strict.
    /// </value>
    public bool StrictReturnCodes
        {
            get
            {
                return strictReturnCodes;
            }
            set
            {
                this.strictReturnCodes = value;
                if (control != null)
                    control.StrictReturnCodes = value;
            }
        }

        /// <summary> 
    /// TCP timeout on the underlying sockets, in milliseconds.
    /// </summary>
    virtual public int Timeout
        {
            get
            {
                return timeout;
            }
            set
            {                
                this.timeout = value;
                if (control != null)
                    control.Timeout = value;
            }        
        }

        /// <summary>  
        /// Is the client currently connected?
        /// </summary>
        public bool Connected
        {
            get
            {
                return control == null ? false : control.Connected;
            }
        }
      
    /// <summary>
    /// The connection-mode (passive or active) of data-channels.
    /// </summary>
    /// <remarks>
    /// <para>
    /// When the connection-mode is active, the server will initiate connections
    /// to the client, meaning that the client must open a socket and wait for the
    /// server to connect to it.  This often causes problems if the client is behind
    /// a firewall.
    /// </para>
    /// <para>
    /// When the connection-mode is passive, the client will initiates connections
    /// to the server, meaning that the client will connect to a particular socket
    /// on the server.  This is generally used if the client is behind a firewall.
    /// </para>
    /// </remarks>
    public FTPConnectMode ConnectMode
        {
            set
            {
                connectMode = value;
            }
            get
            {
                return connectMode;
            }
        }
        
    /// <summary>
    /// Indicates whether the client is currently connected with the server.
    /// </summary>
    public bool IsConnected
    {
      get
      {
        return Connected;
      }
    }
        /// <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>
        public bool SynchronizePassiveConnections
        {
            get
            {
                return synchronizePassiveConnections;
            }
            set
            {
                synchronizePassiveConnections = value;
                if (control != null)
                    control.SynchronizePassiveConnections = value;
            }
        }

        /// <summary>
        /// Include hidden files in operations that involve listing of directories,
        /// and if supported by the server.
        /// </summary>
        public bool ShowHiddenFiles
        {
            get
            {
                return showHiddenFiles;
            }
            set
            {
                showHiddenFiles = value;
            }
        }

    /// <summary>
    /// The number of bytes transferred between each notification of the
    /// <see cref="BytesTransferred"/> event.
    /// </summary>
    /// <remarks>
    /// Reduce this value to receive more frequent notifications of transfer progress.
    /// </remarks>
    public long TransferNotifyInterval
        {
            get
            {
                return monitorInterval;
            }
            set
            {
                monitorInterval = value;
            }
        }

    /// <summary>
    /// The size of the buffers (in bytes) used in writing to and reading from the data-sockets.
    /// </summary>
    public int TransferBufferSize
        {
            get
            {
                return transferBufferSize;
            }
            
            set
            {
        if (value<=0)
          throw new ArgumentException("TransferBufferSize must be greater than 0.");
                transferBufferSize = value;
            }
        }
             
    /// <summary>
    /// The domain-name or IP address of the FTP server.
    /// </summary>
    /// <remarks>
    /// <para>This property may only be set if not currently connected.</para>.
    /// </remarks>
    public virtual string RemoteHost
        {
            get
            {
                return remoteHost;
            }
            set
            {
                CheckConnection(false);
                remoteHost = value;
            }
        }    
        
    /// <summary>
    /// Controls whether or not a file is deleted when a failure occurs.
    /// </summary>
    /// <remarks>
    /// <para>
    /// If <c>true</c>, a partially downloaded file is deleted if there
    /// is a failure during the download.  For example, the connection
    /// to the FTP server might have failed. If <c>false</c>, the partially
    /// downloaded file remains on the client machine - and the download
    /// may be resumed, if it is a binary transfer.
    /// </para>
    /// <para>
    /// By default this flag is set to <c>true</c>.
    /// </para>
    /// </remarks>
    public bool DeleteOnFailure
        {
            get
            {
                return deleteOnFailure;
            }
            set
            {
                deleteOnFailure = value;
            }
        }    
        
    /// <summary>
    /// The port on the server to which to connect the control-channel. 
    /// </summary>
    /// <remarks>
    /// <para>Most FTP servers use port 21 (the default)</para>
    /// <para>This property may only be set if not currently connected.</para>
    /// </remarks>
    public int ControlPort
        {
            get
            {
                return controlPort;
            }
            set
            {
                CheckConnection(false);
                controlPort = value;
            }
        }    
        
    /// <summary>The culture for parsing file listings.</summary>
    /// <remarks>
    /// The <see cref="DirDetails(string)"/> method parses the file listings returned.  The names of the file
    /// can contain a wide variety of characters, so it is sometimes necessary to set this
    /// property to match the character-set used on the server.
    /// </remarks>
    public CultureInfo ParsingCulture
        {
            get
            {
                return parserCulture;
            }
            set
            {
        if (value==null)
          value = CultureInfo.InvariantCulture;
                this.parserCulture = value;
                if (fileFactory != null)
                    fileFactory.ParsingCulture = value;
            }            
        }

    /// <summary>
    /// The encoding to use when dealing with file and directory paths.
    /// </summary>
    public Encoding ControlEncoding
    {
      get
      {
        return controlEncoding;
      }
      set
      {
        controlEncoding = value;
      }
    }


        /// <summary>
        /// The encoding to use for data when transferring in ASCII mode.
        /// </summary>
        public Encoding DataEncoding
        {
            get
            {
                return dataEncoding;
            }
            set
            {
                dataEncoding = value;
            }
        }
                
    /// <summary>
    /// Override the chosen file factory with a user created one - meaning
    /// that a specific parser has been selected.
    /// </summary>
    public FTPFileFactory FTPFileFactory
        {
      get
      {
        return this.fileFactory;
      }
            set
            {
                this.fileFactory = value;
            }            
        }

        /// <summary>
        /// Time difference between server and client (relative to client).
        /// </summary>
        /// <remarks>
        /// The time-difference is relative to the server such that, for example, if the server is
        /// in New York and the client is in London then the difference would be -5 hours 
        /// (ignoring daylight savings differences).  This property only applies to FTP and FTPS.
        /// </remarks>
        public TimeSpan TimeDifference
        {
            get { return fileFactory!=null ? fileFactory.TimeDifference : new TimeSpan(); }
            set { fileFactory.TimeDifference = value; }
        }

        /// <summary>
        /// Indicates whether seconds were included in the most recent directoy listing.
        /// </summary>
        public bool TimeIncludesSeconds
        {
            get { return fileFactory.TimeIncludesSeconds; }
        }

    /// <summary>The latest valid reply from the server.</summary>
    /// <value>
    /// Reply object encapsulating last valid server response.
    /// </value>
    public FTPReply LastValidReply
        {
            get
            {
                return lastValidReply;
            }
        }

    /// <summary>The current file transfer type (BINARY or ASCII).</summary>
    /// <remarks>When the transfer-type is set to <c>BINARY</c> then files
    /// are transferred byte-for-byte such that the transferred file will
    /// be identical to the original.
    /// When the transfer-type is set to <c>BINARY</c> then end-of-line
    /// characters will be translated where necessary between Windows and
    /// UNIX formats.</remarks>
    public FTPTransferType TransferType
        {
            get
            {
                return transferType;
            }
            set
            {                
                CheckConnection(true);
                
                // determine the character to send
                string typeStr = ASCII_CHAR;
                if (value.Equals(FTPTransferType.BINARY))
                    typeStr = BINARY_CHAR;
                
                // send the command
                FTPReply reply = control.SendCommand("TYPE " + typeStr);
                lastValidReply = control.ValidateReply(reply, "200", "250"); // 250 for Leitch media server
                
                // record the type
                transferType = value;
            }            
        }
        
        /// <summary>
        /// Port range for active mode, used only if it is
        /// necessary to limit the ports to a narrow range specified
        /// in a firewall
        /// </summary>
        public PortRange ActivePortRange
        {
            get
            {
                return activePortRange;
            }
            
            set
            {               
                value.ValidateRange();
                activePortRange = value;
                if (control != null)
                    control.SetActivePortRange(value);
            }
        }
        
        /// <summary>
        /// Force the PORT command to send a fixed IP address, used only for
        /// certain firewalls
        /// </summary>
        public IPAddress ActiveIPAddress
        {
            get
            {
                return activeIPAddress;
            }
            set
            {
                activeIPAddress = value;
                if (control != null)
                    control.SetActiveIPAddress(value);
            }
        }
        
    /// <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>
    /// <para>
    /// <c>AutoPassiveIPSubstitution</c> is useful in passive mode when the 
    /// FTP server is supplying an incorrect IP address to the client for 
    /// use in creating data connections (directory listings and file 
    /// transfers), e.g. an internal IP address that is not accessible from 
    /// the client. Instead, the client will use the IP address obtained 
    /// from the FTP server's hostname.
    /// </para>
    /// <para>
    /// 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.
    /// </para>
    /// </remarks>
    public bool AutoPassiveIPSubstitution
    {
      get
      {
        return this.autoPassiveIPSubstitution;
      }
      set
      {
        this.autoPassiveIPSubstitution = value;
        if (control!=null)
          control.AutoPassiveIPSubstitution = value;
      }
    }

    /// <summary>
    /// If <c>true</c> then streams are closed after a transfer has completed.
    /// </summary>
    /// <remarks>The default is <c>true</c>.</remarks>
    public bool CloseStreamsAfterTransfer
    {
      get
      {
        return closeStreamsAfterTransfer;
      }
      set
      {
        closeStreamsAfterTransfer = value;
      }
    }

        /// <summary>
        /// The interval in seconds that the server is sent a wakeup message during
        /// large transfers.
        /// </summary>
        /// <remarks>During very large transfers some servers timeout, meaning
        /// that the transfer is not correctly completed. If this value is
        /// set to 0, no wakeup messages are sent. Note that many servers can't
        /// cope with a NOOP sent during a transfer, so only set this property if
        /// you are having timeout problems with very large transfers. It can result
        /// in receiving replies to NOOP with subsequent commands, so use with
        /// caution and check your log files.</remarks>
        public int ServerWakeupInterval
        {
            get 
            {
                return noOperationInterval;
            }
            set 
            {
                noOperationInterval = value;
            }
        } 

        /// <summary>
        /// By default the BytesTransferred event is not triggered during directory 
        /// listings - this property can be used to enable this behaviour.
        /// </summary>
        public bool TransferNotifyListings
        {
            get 
            {
                return transferNotifyListings;
            }
            set 
            {
                transferNotifyListings = value;
            }
        } 

        /// <summary>
        /// Holds fragments of server messages that indicate a directory
        /// is empty
        /// </summary>
        /// <remarks>
        /// The fragments are used when it is necessary to examine the message
        /// returned by a server to see if it is saying a directory is empty, which
        /// is normally used by DirDetails. If an FTP server is returning a different
        /// message that still clearly indicates a directory is empty, use this
        /// property to add a new server fragment to the repository via the Add method.
        /// It would be helpful to email support at enterprisedt dot com to inform 
        /// us of the message so it can be added to the next build.
        /// </remarks>
        public DirectoryEmptyStrings DirectoryEmptyMessages
        {
            get
            {
                return dirEmptyStrings;
            }
        }

        /// <summary>
        /// Holds fragments of server messages that indicate a file was not found
        /// </summary>
        /// <remarks>
        /// The fragments are used when it is necessary to examine the message
        /// returned by a server to see if it is saying a file was not found. 
        /// If an FTP server is returning a different message that still clearly 
        /// indicates a file was not found, use this property to add a new server 
        /// fragment to the repository via the Add method. It would be helpful to
        /// email support at enterprisedt dot com to inform us of the message so
        /// it can be added to the next build.
        /// </remarks>
        public FileNotFoundStrings FileNotFoundMessages
        {
            get
            {
                return fileNotFoundStrings;
            }
        }

        /// <summary>
        /// Holds fragments of server messages that indicate a transfer completed.
        /// </summary>
        /// <remarks>
        /// The fragments are used when it is necessary to examine the message
        /// returned by a server to see if it is saying a transfer completed.
        /// If an FTP server is returning a different message that still clearly 
        /// indicates the transfer complete, use this property to add a new server 
        /// fragment to the repository via the Add method. It would be helpful to
        /// email support at enterprisedt dot com to inform us of the message so
        /// it can be added to the next build.
        /// </remarks>
        public TransferCompleteStrings TransferCompleteMessages
        {
            get
            {
                return transferCompleteStrings;
            }
        }

    /// <summary>
    /// Notifies of the start of a transfer.
    /// </summary>
    [Obsolete("Use TransferStartedEx")]
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public virtual event EventHandler TransferStarted;
        
    /// <summary>
    /// Notifies of the start of a transfer, and supplies more details than <see cref="TransferStarted"/>
    /// </summary>
    public virtual event TransferHandler TransferStartedEx;
        
    /// <summary>
    /// Notifies of the completion of a transfer.
    /// </summary> 
    [Obsolete("Use TransferCompleteEx")]
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public virtual event EventHandler TransferComplete;
        
    /// <summary>
    /// Notifies of the completion of a transfer, and supplies more details than <see cref="TransferComplete"/>
    /// </summary> 
    public virtual event TransferHandler TransferCompleteEx;
            
    /// <summary>
    /// Event triggered every time <see cref="TransferNotifyInterval"/> bytes transferred.
    /// </summary> 
    public virtual event BytesTransferredHandler BytesTransferred;
        
    /// <summary>
    /// Triggered every time a command is sent to the server.
    /// </summary> 
    public virtual event FTPMessageHandler CommandSent;
        
    /// <summary>
    /// Triggered every time a reply is received from the server.
    /// </summary> 
    public virtual event FTPMessageHandler ReplyReceived;
        
        /// <summary> Default byte interval for transfer monitor</summary>
        private const int DEFAULT_MONITOR_INTERVAL = 4096;
        
        /// <summary> Default transfer buffer size</summary>
        private const int DEFAULT_BUFFER_SIZE = 4096;
        
        /// <summary> Major version (substituted by ant)</summary>
        private static string majorVersion = "2";
        
        /// <summary> Middle version (substituted by ant)</summary>
        private static string middleVersion = "0";
        
        /// <summary> Middle version (substituted by ant)</summary>
        private static string minorVersion = "1";
        
        /// <summary> Full version</summary>
        private static int[] version;
        
        /// <summary> Timestamp of build</summary>
        private static string buildTimestamp = "26-Oct-2009 13:44:34 EST";
        
        /// <summary>  
        /// The char sent to the server to set BINARY
        /// </summary>
        private static string BINARY_CHAR = "I";

        /// <summary>  
        /// The char sent to the server to set ASCII
        /// </summary>
        private static string ASCII_CHAR = "A";

        /// <summary>
        /// Short value for a timeout
        /// </summary>
        private static int SHORT_TIMEOUT = 500;
        
        /// <summary>
        /// Matcher for directory empty
        /// </summary>
        internal DirectoryEmptyStrings dirEmptyStrings = new DirectoryEmptyStrings();
    
        /// <summary>
        /// Matcher for transfer complete
        /// </summary>
        internal TransferCompleteStrings transferCompleteStrings = new TransferCompleteStrings();
    
        /// <summary>
        /// Matcher for file not found
        /// </summary>
        internal FileNotFoundStrings fileNotFoundStrings = new FileNotFoundStrings();

        /// <summary>
        /// Default time format.
        /// </summary>
        private const string DEFAULT_TIME_FORMAT = "yyyyMMddHHmmss";

        /// <summary>
        /// Four formats are provided because the fractional digits are optional.
        /// </summary>
        private string[] modtimeFormats = { DEFAULT_TIME_FORMAT, "yyyyMMddHHmmss'.'f", "yyyyMMddHHmmss'.'ff", "yyyyMMddHHmmss'.'fff" };
        
        /// <summary> Logging object</summary>
        private Logger log;
        
        /// <summary>  Socket responsible for controlling
        /// the connection
        /// </summary>
        internal FTPControlSocket control = null;
        
        /// <summary>  Socket responsible for transferring
        /// the data
        /// </summary>
        internal FTPDataSocket data = null;
        
        /// <summary>  Socket timeout for both data and control. In
        /// milliseconds
        /// </summary>
        internal int timeout = 120*1000;

        /// <summary>
        /// Interval for NOOP calls during large transfers in seconds
        /// </summary>
        protected int noOperationInterval = 0;
        
        /// <summary> Use strict return codes if true</summary>
        private bool strictReturnCodes = false;
        
        /// <summary>  Can be used to cancel a transfer</summary>
        private bool cancelTransfer = false;

        /// <summary>  Should BytesTransferred event be triggered in directory listings?</summary>
        private bool transferNotifyListings = false;
        
        /// <summary> If true, a file transfer is being resumed</summary>
        private bool resume = false;
        
        /// <summary>If a download to a file fails, delete the partial file</summary>
        private bool deleteOnFailure = true;   
             
        /// <summary>
        /// MDTM supported flag
        /// </summary>
        private bool mdtmSupported = true;
    
        /// <summary>
        /// SIZE supported flag
        /// </summary>
        private bool sizeSupported = true;
        
        /// <summary> Resume byte marker point</summary>
        private long resumeMarker = 0;

        /// <summary>
        /// Include hidden files in operations
        /// </summary>
        private bool showHiddenFiles = false;
        
        /// <summary> Bytes transferred in between monitor callbacks</summary>
        private long monitorInterval = DEFAULT_MONITOR_INTERVAL;
        
        /// <summary> Size of transfer buffers</summary>
        private int transferBufferSize = DEFAULT_BUFFER_SIZE;
        
        /// <summary>Culture used for parsing file details</summary>
        private CultureInfo parserCulture = CultureInfo.InvariantCulture;
        
        /// <summary> Parses LIST output</summary>
        private FTPFileFactory fileFactory = new FTPFileFactory();
                        
        /// <summary>  Record of the transfer type - make the default ASCII</summary>
        private FTPTransferType transferType = FTPTransferType.ASCII;
        
        /// <summary>  Record of the connect mode - make the default PASV (as this was
        /// the original mode supported)
        /// </summary>
        private FTPConnectMode connectMode = FTPConnectMode.PASV;

        /// <summary>
        /// Synchronize PASV socket connections if true (false by default)
        /// </summary>
        private bool synchronizePassiveConnections = false;

        /// <summary>
        /// Port range for active mode
        /// </summary>
        private PortRange activePortRange = new PortRange();
        
        /// <summary>
        /// IP address to send with active mode
        /// </summary>
        private IPAddress activeIPAddress = null;
        
        /// <summary>
        /// Holds the last valid reply from the server on the control socket
        /// </summary>
        internal FTPReply lastValidReply;
        
        /// <summary>
        /// Port on which we connect to the FTP server and messages are passed
        /// </summary>
        internal int controlPort = -1;        
        
        /// <summary>
        /// Remote host we are connecting to
        /// </summary>
        internal string remoteHost = null;
        
    /// <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>
    /// If <c>true</c> then streams are closed after a transfer has completed.
    /// </summary>
    /// <remarks>
    /// The default is <c>true</c>.
    /// </remarks>
    private bool closeStreamsAfterTransfer = true;

    /// <summary>
    /// The encoding to use when dealing with file and directory paths.
    /// </summary>
    private Encoding controlEncoding = null;

        /// <summary>
        /// The encoding to use for ASCII transfers.
        /// </summary>
        private Encoding dataEncoding = null;

        /// <summary>
        /// Threshold for throttling
        /// </summary>
        protected BandwidthThrottler throttler = null;

        #region Constructors
        
    /// <summary>Constructs an <c>FTPClient</c> instance and connects to the FTP server.</summary>
    /// <param name="remoteHost">The domain-name or IP address of the FTP server.</param>
    [Obsolete("This constructor is obsolete; use the default constructor and properties instead")]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public FTPClient(string remoteHost)
            :
            this(remoteHost, FTPControlSocket.CONTROL_PORT, 0)
        {
        }
        
    /// <summary>Constructs an <c>FTPClient</c> instance and connects to the FTP server.</summary>
    /// <param name="remoteHost">The domain-name or IP address of the FTP server.</param>
    /// <param name="controlPort">The port for control stream (-1 for default port).</param>
    [Obsolete("This constructor is obsolete; use the default constructor and properties instead")]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public FTPClient(string remoteHost, int controlPort)
            :
            this(remoteHost, controlPort, 0)
        {
        }
                
    /// <summary>Constructs an <c>FTPClient</c> instance and connects to the FTP server.</summary>
    /// <param name="remoteHost">The domain-name or IP address of the FTP server.</param>
    /// <param name="controlPort">The port for control stream (-1 for default port).</param>
    /// <param name="timeout">The length of the timeout in milliseconds (pass in 0 for no timeout)</param>
    [Obsolete("This constructor is obsolete; use the default constructor and properties instead")]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public FTPClient(string remoteHost, int controlPort, int timeout)
            :
            this(HostNameResolver.GetAddress(remoteHost), controlPort, timeout)
        {
            this.remoteHost = remoteHost;
        }
        
        
    /// <summary>Constructs an <c>FTPClient</c> instance and connects to the FTP server.</summary>
    /// <param name="remoteAddr">The address of the FTP server.</param>
    [Obsolete("This constructor is obsolete; use the default constructor and properties instead")]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public FTPClient(IPAddress remoteAddr)
            :
            this(remoteAddr, FTPControlSocket.CONTROL_PORT, 0)
        {
        }
        
        
    /// <summary>Constructs an <c>FTPClient</c> instance and connects to the FTP server.</summary>
    /// <param name="remoteAddr">The address of the FTP server.</param>
    /// <param name="controlPort">The port for control stream (-1 for default port).</param>
    [Obsolete("This constructor is obsolete; use the default constructor and properties instead")]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public FTPClient(IPAddress remoteAddr, int controlPort)
            :
            this(remoteAddr, controlPort, 0)
        {
        }
        
    /// <summary>Constructs an <c>FTPClient</c> instance and connects to the FTP server.</summary>
    /// <param name="remoteAddr">The address of the FTP server.</param>
    /// <param name="controlPort">The port for control stream (-1 for default port).</param>
    /// <param name="timeout">The length of the timeout in milliseconds (pass in 0 for no timeout)</param>
    [Obsolete("This constructor is obsolete; use the default constructor and properties instead")]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public FTPClient(IPAddress remoteAddr, int controlPort, int timeout)
        {
            InitBlock();
            remoteHost = remoteAddr.ToString();
            Connect(remoteAddr, controlPort, timeout);
        }
        
    /// <summary>Constructs an <c>FTPClient</c>.</summary>
    /// <remarks>
    /// The <c>FTPClient</c> will not connect to the FTP server until <see cref="Connect()"/> is called.
    /// </remarks>
    public FTPClient()
        {
            InitBlock();
        }
        
        #endregion
        
        /// <summary>  
        /// Instance initializer. Sets formatter to GMT.
        /// </summary>
        private void InitBlock()
        {
            log = Logger.GetLogger("FTPClient");
            transferType = FTPTransferType.ASCII;
            connectMode = FTPConnectMode.PASV;
            controlPort = FTPControlSocket.CONTROL_PORT;
        }
        
    /// <summary>Connect to the FTP server.</summary>
    /// <remarks>
    /// <para>
    /// The <see cref="RemoteHost"/> property must be set prior to calling this method.
    /// This method must be called before <see cref="Login(string,string)"/> or <see cref="User(string)"/>
    /// is called.
    /// </para>
    /// <para>
    /// This method will throw an <c>FTPException</c> if the client is already connected to the server.
    /// </para>
    /// </remarks>
    public virtual void Connect() 
        {
            CheckConnection(false);
      if (remoteHost==null)
        throw new FTPException("RemoteHost is not set.");
            Connect(remoteHost, controlPort, timeout);
        }

        internal virtual void Connect(IPAddress remoteHost, int controlPort, int timeout)
        {
            Connect(remoteHost.ToString(), controlPort, timeout);
        }
        
        internal virtual void Connect(string remoteHost, int controlPort, int timeout) 
        {
            if (controlPort < 0) 
            {
                log.Warn("Invalid control port supplied: " + controlPort + " Using default: " +
                    FTPControlSocket.CONTROL_PORT);
                controlPort = FTPControlSocket.CONTROL_PORT;
            }    
            this.controlPort = controlPort;
            if (log.DebugEnabled)
                log.Debug("Connecting to " + remoteHost + ":" + controlPort);
            Initialize(new FTPControlSocket(remoteHost, controlPort, timeout, controlEncoding));            
        }
        
        /// <summary>Set the control socket explicitly.</summary>
        /// <param name="control">Control socket reference.</param>
        internal void Initialize(FTPControlSocket control)
        {
            this.control = control;
      this.control.AutoPassiveIPSubstitution = autoPassiveIPSubstitution;
            this.control.SynchronizePassiveConnections = synchronizePassiveConnections;
            // set up the event handlers so they call back to this object - and can
            // then be passed on if required
            control.CommandSent += new FTPMessageHandler(CommandSentControl);
            control.ReplyReceived += new FTPMessageHandler(ReplyReceivedControl);
            if (activePortRange != null)
                control.SetActivePortRange(activePortRange);
            if (activeIPAddress != null)
                control.SetActiveIPAddress(activeIPAddress);
            control.StrictReturnCodes = strictReturnCodes;
            this.control.ValidateConnection();
        }

        
        /// <summary> 
        /// Checks if the client has connected to the server and throws an exception if it hasn't.
        /// This is only intended to be used by subclasses
        /// </summary>
        /// <throws>FTPException Thrown if the client has not connected to the server. </throws>
        internal void CheckConnection(bool shouldBeConnected)
        {
            if (shouldBeConnected && !Connected)
                throw new FTPException("The FTP client has not yet connected to the server.  " + 
                "The requested action cannot be performed until after a connection has been established.");
            else if (!shouldBeConnected && Connected)
                throw new FTPException("The FTP client has already been connected to the server.  " + 
                "The requested action must be performed before a connection is established.");
        }
        
        
        internal void CommandSentControl(object client, FTPMessageEventArgs message) 
        {
            if (CommandSent != null)
                CommandSent(this, message);            
        }
        
        
        internal void ReplyReceivedControl(object client, FTPMessageEventArgs message) 
        {
            if (ReplyReceived != null)
                ReplyReceived(this, message);
        }
        
        
    /// <summary>Switch debug of responses on or off</summary>
    /// <param name="on"><c>true</c> if you wish to have responses to
    /// the log stream, <c>false</c> otherwise.</param>
    /// <deprecated>
    /// Use the <see cref="EnterpriseDT.Util.Debug.Logger"/> class to 
    /// switch debugging on and off.
    /// </deprecated>
    public void DebugResponses(bool on)
        {
            if (on)
                Logger.CurrentLevel = Level.DEBUG;
            else
                Logger.CurrentLevel = Level.OFF;
        }
        
        
    /// <summary>Cancels the current transfer.</summary>
    /// <remarks>This method is generally called from a separate
    /// thread. Note that this may leave partially written files on the
    /// server or on local disk, and should not be used unless absolutely
    /// necessary. The server is not notified.</remarks>
    public virtual void CancelTransfer()
        {
            cancelTransfer = true;
        }
        
    /// <summary>Login into an account on the FTP server using the user-name and password provided.</summary>
    /// <remarks>This
    /// call completes the entire login process. Note that
    /// <see cref="Connect()"/> must be called first.</remarks>
    /// <param name="user">User-name.</param>
    /// <param name="password">Password.</param>
    public virtual void Login(string user, string password)
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("USER " + user);
            
            // we allow for a site with no password - 230 response
            lastValidReply = control.ValidateReply(reply, "230", "331");
            if (lastValidReply.ReplyCode.Equals("230"))
                return ;
            else
            {
                Password(password);
            }
        }
        
    /// <summary>
    /// Supply the user-name to log into an account on the FTP server. 
    /// Must be followed by the <see cref="Password(string)"/> method.
    /// Note that <see cref="Connect()"/> must be called first. 
    /// </summary>
    /// <param name="user">User-name.</param>
    public virtual void User(string user)
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("USER " + user);
            
            // we allow for a site with no password - 230 response
            lastValidReply = control.ValidateReply(reply, "230", "331");
        }
        
        
    /// <summary>
    /// Supplies the password for a previously supplied
    /// user-name to log into the FTP server. Must be
    /// preceeded by the <see cref="User(string)"/> method
    /// </summary>
    /// <param name="password">Password.</param>
    public virtual void Password(string password)
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("PASS " + password);
            
            // we allow for a site with no passwords (202)
            lastValidReply = control.ValidateReply(reply, "230", "202", "332");
        }

        /// <summary>
        /// Supply account information string to the server. 
        /// </summary>
        /// <remarks>
        /// This can be used for a variety of purposes - for example, the server could
        /// indicate that a password has expired (by sending 332 in reply to
        /// PASS) and a new password automatically supplied via ACCT. It
        /// is up to the server how it uses this string.
        /// </remarks>
        /// <param name="accountInfo">account information</param>
        public virtual void Account(string accountInfo)
        {
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("ACCT " + accountInfo);

            // ok or not implemented
            lastValidReply = control.ValidateReply(reply, "230", "202");
        }
        

    /// <summary>Issue arbitrary ftp commands to the FTP server.</summary>
    /// <param name="command">FTP command to be sent to server.</param>
    /// <param name="validCodes">Valid return codes for this command.</param>
    /// <returns>The text returned by the FTP server.</returns>
    public virtual string Quote(string command, string[] validCodes)
        {        
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand(command);
            
            // allow for no validation to be supplied
            if (validCodes != null)
            {
                lastValidReply = control.ValidateReply(reply, validCodes);
                
            }
            else // not doing any validation
            {
                lastValidReply = reply;
            }
            return lastValidReply.ReplyText;
        }

        /// <summary>
        /// Get the PASV address string (including port numbers)
        /// </summary>
        /// <param name="pasvReply">PASV reply sent by server</param>
        /// <returns></returns>
        internal string GetPASVAddress(string pasvReply)
        {
            int start = -1;
            int i = 0;
            while (i < pasvReply.Length)
            {
                if (Char.IsDigit(pasvReply[i]))
                {
                    start = i;
                    break;
                }
                i++;
            }
            int end = -1;
            i = pasvReply.Length - 1;
            while (i >= 0)
            {
                if (Char.IsDigit(pasvReply[i]))
                {
                    end = i;
                    break;
                }
                i--;
            }
            if (start < 0 || end < 0)
                return null;

            int len = end - start + 1;

            return pasvReply.Substring(start, len);
        }

        /// <summary>
        /// Send a command to the server and get the reply
        /// </summary>
        /// <param name="command">command</param>
        /// <returns>reply object</returns>
        internal FTPReply SendCommand(string command) 
        {
            return control.SendCommand(command);
        }
    
        /// <summary>
        /// Validate an FTPReply 
        /// </summary>
        /// <param name="reply">reply object</param>
        /// <param name="expectedReplyCode">expected code</param>
        internal void ValidateReply(FTPReply reply, string expectedReplyCode)
        {
            control.ValidateReply(reply, expectedReplyCode);
        }
    
        /// <summary>
        /// Validate an FTPReply 
        /// </summary>
        /// <param name="reply">reply object</param>
        /// <param name="expectedReplyCodes">expected codes</param>
        internal void ValidateReply(FTPReply reply, string[] expectedReplyCodes) 
        {
            control.ValidateReply(reply, expectedReplyCodes);
        }      
        
    /// <summary>
    /// Get the size of a remote file. 
    /// </summary>
    /// <remarks>
    /// This is not a standard FTP command, it is defined in "Extensions to FTP", a draft RFC 
    /// (draft-ietf-ftpext-mlst-16.txt).
    /// </remarks>
    /// <param name="remoteFile">Name or path of remote file in current directory.</param>
    /// <returns>Size of file in bytes.</returns>
    public virtual long Size(string remoteFile)
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("SIZE " + remoteFile);
            lastValidReply = control.ValidateReply(reply, "213");
            
            // parse the reply string .
            string replyText = lastValidReply.ReplyText;
            
            // trim off any trailing characters after a space, e.g. webstar
            // responds to SIZE with 213 55564 bytes
            int spacePos = replyText.IndexOf((System.Char) ' ');
            if (spacePos >= 0)
                replyText = replyText.Substring(0, (spacePos) - (0));
            
            // parse the reply
            try
            {
                return Int64.Parse(replyText);
            }
            catch (FormatException)
            {
                throw new FTPException("Failed to parse reply: " + replyText);
            }
        }
        
    /// <summary>Make the next file transfer (put or get) resume.</summary>
    /// <remarks>
    /// <para>
    /// For puts, the
    /// bytes already transferred are skipped over, while for gets, if 
    /// writing to a file, it is opened in append mode, and only the bytes
    /// required are transferred.
    /// </para>
    /// <para>
    /// Currently resume is only supported for BINARY transfers (which is
    /// generally what it is most useful for). 
    /// </para>
    /// </remarks>
    /// <throws>FTPException</throws>
    public virtual void Resume()
        {
            if (transferType.Equals(FTPTransferType.ASCII))
                throw new FTPException("Resume only supported for BINARY transfers");
            resume = true;
        }
        
        /// <summary> 
        /// Cancel the resume. Use this method if something goes wrong
        /// and the server is left in an inconsistent state
        /// </summary>
        /// <throws>  SystemException </throws>
        /// <throws>  FTPException </throws>
        public virtual void CancelResume()
        {
            Restart(0);
            resume = false;
        }
        
    /// <summary>Set the REST marker so that the next transfer doesn't start at the beginning of the remote file</summary>
    /// <remarks>
    /// Issue the RESTart command to the remote server. This indicates the byte
        /// position that REST is performed at. For put, bytes start at this point, while
        /// for get, bytes are fetched from this point.
    /// </remarks>
    /// <param name="size">the REST param, the mark at which the restart is performed on the remote file. 
    /// For STOR, this is retrieved by SIZE</param>
    /// <throws>SystemException </throws>
    /// <throws>FTPException </throws>
    public void Restart(long size)
        {
            FTPReply reply = control.SendCommand("REST " + size);
            lastValidReply = control.ValidateReply(reply, "350");
        }
        
    /// <summary>
    /// Put a local file onto the FTP server in the current directory.
    /// </summary>
    /// <param name="localPath">Path of the local file.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    public virtual void Put(string localPath, string remoteFile)
        {            
            Put(localPath, remoteFile, false);
        }
        
    /// <summary>
    /// Put a stream of data onto the FTP server in the current directory.
    /// </summary>
    /// <remarks>
    /// The stream is closed after the transfer is complete if
    /// <see cref="CloseStreamsAfterTransfer"/> is <c>true</c> (the default) and are left
    /// open otherwise.
    /// </remarks>
    /// <param name="srcStream">Input stream of data to put.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
        /// <returns>Number of bytes transferred.</returns>
    public virtual long Put(Stream srcStream, string remoteFile)
        {            
            return Put(srcStream, remoteFile, false);
        }
        
        
    /// <summary>
    /// Put a local file onto the FTP server in the current directory. Allows appending
    /// if current file exists.
    /// </summary>
    /// <param name="localPath">Path of the local file.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    /// <param name="append"><c>true</c> if appending, <c>false</c> otherwise</param>
    public virtual void Put(string localPath, string remoteFile, bool append)
        {            
      // events called before get in case event-handlers want to
      // do any FTP operations
      if (TransferStarted != null)
        TransferStarted(this, new EventArgs());
      if (TransferStartedEx != null)
        TransferStartedEx(this, new TransferEventArgs(localPath, remoteFile, TransferDirection.UPLOAD, transferType));

      // get according to set type
            try
            {
                if (transferType == FTPTransferType.ASCII)
                {
                    PutASCII(localPath, remoteFile, append);
                }
                else
                {
                    PutBinary(localPath, remoteFile, append);
                }
            }
            catch (SystemException ex)
            {
                log.Error("SystemException in Put(string,string,bool)", ex);
                ValidateTransferOnError();
                throw;
            }
            ValidateTransfer();
    
      if (TransferComplete != null)
        TransferComplete(this, new EventArgs());
      if (TransferCompleteEx != null)
        TransferCompleteEx(this, new TransferEventArgs(localPath, remoteFile, TransferDirection.UPLOAD, transferType));
    }
        
    /// <summary>
    /// Put a stream of data onto the FTP server in the current directory.  Allows appending
    /// if current file exists
    /// </summary>
    /// <remarks>
    /// The stream is closed after the transfer is complete if
    /// <see cref="CloseStreamsAfterTransfer"/> is <c>true</c> (the default) and are left
    /// open otherwise.
    /// </remarks>
    /// <param name="srcStream">Input stream of data to put.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    /// <param name="append"><c>true</c> if appending, <c>false</c> otherwise.</param>
        /// <returns>Number of bytes transferred.</returns>
        public virtual long Put(Stream srcStream, string remoteFile, bool append)
        {            
      // events called before get in case event-handlers want to
      // do any FTP operations
      if (TransferStarted != null)
        TransferStarted(this, new EventArgs());
      if (TransferStartedEx != null)
        TransferStartedEx(this, new TransferEventArgs(srcStream, remoteFile, TransferDirection.UPLOAD, transferType));

      // get according to set type
            long bytesTransferred = 0;
            try
            {
                if (transferType == FTPTransferType.ASCII)
                {
                    bytesTransferred = PutASCII(srcStream, remoteFile, append, false);
                }
                else
                {
                    bytesTransferred = PutBinary(srcStream, remoteFile, append, false);
                }
            }
            catch (SystemException ex)
            {
                log.Error("SystemException in Put(Stream,string,bool)", ex);
                ValidateTransferOnError();
                throw;
            }
            ValidateTransfer();

      if (TransferComplete != null)
        TransferComplete(this, new EventArgs());
      if (TransferCompleteEx != null)
        TransferCompleteEx(this, new TransferEventArgs(srcStream, remoteFile, TransferDirection.UPLOAD, transferType));

            return bytesTransferred;
    }
        
    /// <summary>Validate that the Put() or get() was successful.</summary>
    /// <remarks>This method is not for general use. If it is called explicitly after
        /// a transfer, the connection will hang.</remarks>
    public void ValidateTransfer()
        {            
            CheckConnection(true);
            
            // check the control response
            FTPReply reply = control.ReadReply();

            if (cancelTransfer)
            {
                lastValidReply = reply;
                log.Warn("Transfer cancelled");
                throw new FTPTransferCancelledException("Transfer cancelled.");
            }
   
            lastValidReply = control.ValidateReply(reply, "225", "226", "250");            
        }


        /// <summary>
        /// Validate a transfer when an error has occurred on the data channel.
        /// Set a very short transfer in case things have hung. Set it back
        /// at the end.
        /// </summary>
        protected void ValidateTransferOnError()
        {
            try
            {
                CheckConnection(true);
                if (control!=null)
                    control.Timeout = SHORT_TIMEOUT;
                ValidateTransfer();
            }
            catch (Exception e)
            {
                log.Error("Exception in ValidateTransferOnError())", e);
            }
            finally 
            {
                if (control != null)
                    control.Timeout = timeout;
            }
        }
        
    /// <summary>Close the data socket</summary>
    private void CloseDataSocket()
        {   
            if (data != null)
            {
                try
                {
                    data.Close();
                    data = null;
                }
                catch (SystemException ex)
                {
                    log.Warn("Caught exception closing data socket", ex);
                }
            }
        }


        protected void CloseDataSocket(Stream stream)
        {
            if (stream != null)
            {
                try
                {
                    stream.Close();
                }
                catch (IOException ex)
                {
                    log.Warn("Caught exception closing stream", ex);
                }
            }
            CloseDataSocket();
        }

        protected void CloseDataSocket(StreamReader stream)
        {
            if (stream != null)
            {
                try
                {
                    stream.Close();
                }
                catch (IOException ex)
                {
                    log.Warn("Caught exception closing stream", ex);
                }
            }
            CloseDataSocket();
        }

        protected void CloseDataSocket(StreamWriter stream)
        {
            if (stream != null)
            {
                try
                {
                    stream.Close();
                }
                catch (IOException ex)
                {
                    log.Warn("Caught exception closing stream", ex);
                }
            }
            CloseDataSocket();
        }

        /// <summary>
        /// If required, send a server wakeup message
        /// </summary>
        /// <remarks>A NOOP message is sent to the server</remarks>
        /// <param name="start">time the interval started</param>
        /// <returns>if a wakeup was sent, a new interval start time, 
        /// otherwise the one passed in</returns>
        private DateTime SendServerWakeup(DateTime start)
        {
            if (noOperationInterval == 0)
                return start;

            int elapsed = (int)((TimeSpan)(DateTime.Now - start)).TotalSeconds;
            if (elapsed >= noOperationInterval)
            {
                log.Info("Sending server wakeup message");
                control.WriteCommand("NOOP");
                return DateTime.Now;
            }
            return start;
        }

    /// <summary>Request the server to set up the put.</summary>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    /// <param name="append"><c>true</c> if appending, <c>false</c> otherwise.</param>
    private void InitPut(string remoteFile, bool append)
        {    
            CheckConnection(true);
            
            // reset the cancel flag
            cancelTransfer = false;
            
            bool close = false;
            data = null;
            try
            {
                resumeMarker = 0;

                // if resume is requested, we must obtain the size of the
                // remote file
                if (resume)
                {
                    if (transferType.Equals(FTPTransferType.ASCII))
                        throw new FTPException("Resume only supported for BINARY transfers");
                    try
                    {
                        resumeMarker = Size(remoteFile);
                    }
                    catch (FTPException ex)
                    {
                        log.Warn("Failed to find size of file '" + remoteFile + "' for resuming (" + ex.Message + ")");
                    }
                }

                // set up data channel
                data = control.CreateDataSocket(connectMode);
                data.Timeout = timeout;
                
                // issue REST
                if (resume)
                {
                    Restart(resumeMarker);
                }
                
                // send the command to store
                string cmd = append?"APPE ":"STOR ";
                FTPReply reply = control.SendCommand(cmd + remoteFile);
                
                // Can get a 125 or a 150, also allow 350 (for Global eXchange Services server)
                // JScape returns 151
                lastValidReply = control.ValidateReply(reply, "125", "150", "151", "350");
            }
            catch (SystemException)
            {
                close = true;
                throw;
            }
            catch (FTPException)
            {
                close = true;
                throw;
            }
            finally
            {
                if (close)
                {
                    resume = false;
                    CloseDataSocket();
                }
            }
        }
        
        
    /// <summary>
    /// Put as ASCII, i.e. read a line at a time and write
    /// inserting the correct FTP separator.
    /// </summary>
    /// <param name="localPath">Path of the local file.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    /// <param name="append"><c>true</c> if appending, <c>false</c> otherwise</param>
    private void PutASCII(string localPath, string remoteFile, bool append)
        {            
            // create an inputstream & pass to common method
            Stream srcStream = null;
            try 
            {
                srcStream = new FileStream(localPath, FileMode.Open, FileAccess.Read);
            }
            catch (Exception ex)
            {
                string msg = "Failed to open file '" + localPath + "'";
                log.Error(msg, ex);
                throw new FTPException(msg);
            }
            PutASCII(srcStream, remoteFile, append, true);
        }
        
    /// <summary>
    /// Put as ASCII, i.e. read a line at a time and write
    /// inserting the correct FTP separator.
    /// </summary>
    /// <remarks>
    /// The stream is closed after the transfer is complete if
    /// <see cref="CloseStreamsAfterTransfer"/> is <c>true</c> (the default) and are left
    /// open otherwise.
    /// </remarks>
    /// <param name="srcStream">Unput stream of data to put.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    /// <param name="append"><c>true</c> if appending, <c>false</c> otherwise</param>
    /// <param name="alwaysCloseStreams"><c>true if a local file is being put</c></param>
        /// <returns>Number of bytes transferred.</returns>
        private long PutASCII(Stream srcStream, string remoteFile, bool append, bool alwaysCloseStreams)
        {
            // need to read line by line ...
            StreamReader input = null;
            StreamWriter output = null;
            Exception storedEx = null;
            long size = 0;
            try
            {
        input = 
                    (dataEncoding == null ? new StreamReader(srcStream) : new StreamReader(srcStream, dataEncoding));
                                     
                InitPut(remoteFile, append);
                
                // get an character output stream to write to ... AFTER we
                // have the ok to go ahead AND AFTER we've successfully opened a
                // stream for the local file
                output =
                    (dataEncoding == null ? new StreamWriter(GetOutputStream()) : new StreamWriter(GetOutputStream(), dataEncoding));
                
                // write \r\n as required by RFC959 after each line
                long monitorCount = 0;
                string line = null;
                DateTime start = DateTime.Now;
                if (throttler != null)
                {
                    throttler.Reset();
                }

                while ((line = input.ReadLine()) != null && !cancelTransfer)
                {
                    size += line.Length+2;
                    monitorCount += line.Length+2;
                    output.Write(line);
                    output.Write(FTPControlSocket.EOL);

                    if (throttler != null)
                    {
                        throttler.ThrottleTransfer(size);
                    }

                    if (BytesTransferred != null && !cancelTransfer && monitorCount >= monitorInterval)
                    {
                        BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, 0));
                        monitorCount = 0;
                    }
                    start = SendServerWakeup(start);
                }
            }
            catch (Exception ex)
            {
                storedEx = ex;              
            }
            finally
            {
                try
                {
                    if ((alwaysCloseStreams || closeStreamsAfterTransfer))
                    {
                        log.Debug("Closing source stream");
                        srcStream.Close();
                        if (input != null)
                            input.Close();
                    }
                }
                catch (SystemException ex)
                {
                    log.Warn("Caught exception closing stream", ex);
                }
                
                try 
                {
                    if (output!=null)
                        output.Flush();
                }
                catch (SystemException ex)
                {
                    log.Warn("Caught exception flushing output-stream", ex);
                }
                CloseDataSocket(output);
            
                // if we did get an exception bail out now
                if (storedEx != null) {
                    log.Error("Caught exception", storedEx);
                    throw storedEx;
                }
                
                // notify the final transfer size
                if (BytesTransferred != null && !cancelTransfer)
                    BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, 0));
            }
            return size;
        }
       
    /// <summary>Put as binary, i.e. read and write raw bytes.</summary>
    /// <param name="localPath">Path of the local file.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    /// <param name="append"><c>true</c> if appending, <c>false</c> otherwise</param>
    private void PutBinary(string localPath, string remoteFile, bool append)
        {        
            // open input stream to read source file ... do this
            // BEFORE opening output stream to server, so if file not
            // found, an exception is thrown
            Stream srcStream = null;
            try 
            {
                srcStream = new FileStream(localPath, FileMode.Open, FileAccess.Read);
            }
            catch (Exception ex)
            {
                string msg = "Failed to open file '" + localPath + "'";
                log.Error(msg, ex);
                throw new FTPException(msg);
            }
            PutBinary(srcStream, remoteFile, append, true);
        }
        
    /// <summary>Put as binary, i.e. read and write raw bytes.</summary>
    /// <remarks>
    /// The stream is closed after the transfer is complete if
    /// <see cref="CloseStreamsAfterTransfer"/> is <c>true</c> (the default) and are left
    /// open otherwise.
    /// </remarks>
    /// <param name="srcStream">Input stream of data to put.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    /// <param name="append"><c>true</c> if appending, <c>false</c> otherwise</param>
        /// <param name="alwaysCloseStreams"><c>true if a local file is being put</c></param>
        /// <returns>Number of bytes transferred.</returns>
        private long PutBinary(Stream srcStream, string remoteFile, bool append, bool alwaysCloseStreams)
        {    
            BufferedStream input = null;
            BufferedStream output = null;
            Exception storedEx = null;
            long size = 0;
            try
            {
        input = new BufferedStream(srcStream);
                
                InitPut(remoteFile, append);
                
                // get an output stream
                output = new BufferedStream(GetOutputStream());
                
                // if resuming, we skip over the unwanted bytes
                if (resume)
                {
                    input.Seek(resumeMarker, SeekOrigin.Current);
                }
                else
                    resumeMarker = 0;

                byte[] buf = new byte[transferBufferSize];

                // read a chunk at a time and write to the data socket            
                long monitorCount = 0;
                int count = 0;
                DateTime start = DateTime.Now;
                if (throttler != null)
                {
                    throttler.Reset();
                }

                while ((count = input.Read(buf, 0, buf.Length)) > 0 && !cancelTransfer)
                {
                    output.Write(buf, 0, count);
                    size += count;
                    monitorCount += count;

                    if (throttler != null)
                    {
                        throttler.ThrottleTransfer(size);
                    }

                    if (BytesTransferred != null && !cancelTransfer && monitorCount >= monitorInterval)
                    {
                        BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, resumeMarker));
                        monitorCount = 0;
                    }
                    start = SendServerWakeup(start);
                }
            }
            catch (Exception ex)
            {
                storedEx = ex;              
            }
            finally
            {
                resume = false;
                try
                {
                    if ((alwaysCloseStreams || closeStreamsAfterTransfer))
                    {
                        log.Debug("Closing source stream");
                        if (input != null)
                            input.Close();
                    }
                }
                catch (SystemException ex)
                {
                    log.Warn("Caught exception closing stream", ex);
                }
                
        try 
        {
          if (output!=null)
                      output.Flush();
        }
        catch (SystemException ex)
        {
          log.Warn("Caught exception flushing output-stream", ex);
        }
                CloseDataSocket(output);
                
                // if we did get an exception bail out now
                if (storedEx != null) 
        {
                    log.Error("Caught exception", storedEx);
                    throw storedEx;
                }
                                
                // notify the final transfer size
                if (BytesTransferred != null && !cancelTransfer)
                    BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, resumeMarker));            

                // log bytes transferred
                log.Debug("Transferred " + size + " bytes to remote host");
            }
            return size;
        }
        
        
    /// <summary>
    /// Put data onto the FTP server in the current directory.
    /// </summary>
    /// <param name="bytes">Array of bytes to put.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    public virtual void Put(byte[] bytes, string remoteFile)
        {            
            Put(bytes, remoteFile, false);
        }
        
    /// <summary>
    /// Put data onto the FTP server in the current directory. Allows
    /// appending if current file exists.
    /// </summary>
    /// <param name="bytes">Array of bytes to put.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    /// <param name="append"><c>true</c> if appending, <c>false</c> otherwise.</param>
    public virtual void Put(byte[] bytes, string remoteFile, bool append)
        {            
            MemoryStream srcStream = new MemoryStream(bytes);
            Put(srcStream, remoteFile, append);
            // close probably for a second time - but we do this in case CloseStreamsAfterTransfer is
            // set to false, as we want to close this stream always
            srcStream.Close();
        }


        protected virtual Stream GetOutputStream()
        {
            return data.DataStream;
        }

        protected virtual Stream GetInputStream()
        {
            return data.DataStream;
        }
        
    /// <summary>
    /// Get data from the FTP server using the currently
    /// set transfer mode.
    /// </summary>
    /// <param name="localPath">Local file to put data in.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    public virtual void Get(string localPath, string remoteFile)
        {
            if (Directory.Exists(localPath))
            {
                localPath = Path.Combine(localPath, remoteFile);
                log.Debug("Setting local path to " + localPath);
            }

      // events called before get in case event-handlers want to
      // do any FTP operations
      if (TransferStarted != null)
        TransferStarted(this, new EventArgs());
      if (TransferStartedEx != null)
      {
        TransferEventArgs e = new TransferEventArgs(localPath, remoteFile, TransferDirection.DOWNLOAD, transferType);
        TransferStartedEx(this, e);
        localPath = e.LocalFilePath;
      }

      // get according to set type
            try 
            {
                if (transferType == FTPTransferType.ASCII)
                {
                    GetASCII(localPath, remoteFile);
                }
                else
                {
                    GetBinary(localPath, remoteFile);
                }
            }
            catch (SystemException ex)
            {
                ValidateTransferOnError();
                log.Error("SystemException in Get(string,string)", ex);
                throw;
            }
            ValidateTransfer();

      if (TransferComplete != null)
        TransferComplete(this, new EventArgs());
      if (TransferCompleteEx != null)
        TransferCompleteEx(this, new TransferEventArgs(localPath, remoteFile, TransferDirection.DOWNLOAD, transferType));
    }
        
    /// <summary>
    /// Get data from the FTP server, using the currently
    /// set transfer mode.
    /// </summary>
    /// <remarks>
    /// The stream is closed after the transfer is complete if
    /// <see cref="CloseStreamsAfterTransfer"/> is <c>true</c> (the default) and are left
    /// open otherwise.
    /// </remarks>
    /// <param name="destStream">Data stream to write data to.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    public virtual void Get(Stream destStream, string remoteFile)
        {            
      // events called before get in case event-handlers want to
      // do any FTP operations
      if (TransferStarted != null)
        TransferStarted(this, new EventArgs());
      if (TransferStartedEx != null)
        TransferStartedEx(this, new TransferEventArgs(destStream, remoteFile, TransferDirection.DOWNLOAD, transferType));

      // get according to set type
            try
            {
                if (transferType == FTPTransferType.ASCII)
                {
                    GetASCII(destStream, remoteFile);
                }
                else
                {
                    GetBinary(destStream, remoteFile);
                }
            }
            catch (SystemException ex)
            {
                ValidateTransferOnError();
                log.Error("SystemException in Get(Stream,string)", ex);
                throw;
            }
            ValidateTransfer();

      if (TransferComplete != null)
        TransferComplete(this, new EventArgs());
      if (TransferCompleteEx != null)
        TransferCompleteEx(this, new TransferEventArgs(destStream, remoteFile, TransferDirection.DOWNLOAD, transferType));
    }
        
    /// <summary>Request to the server that the get is set up.</summary>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    private void InitGet(string remoteFile)
        {
            CheckConnection(true);
            
            // reset the cancel flag
            cancelTransfer = false;
            
            bool close = false;
            data = null;
            try
            {
                // set up data channel
                data = control.CreateDataSocket(connectMode);
                data.Timeout = timeout;
                
                // if resume is requested, we must issue REST
                if (resume)
                {
                    if (transferType.Equals(FTPTransferType.ASCII))
                        throw new FTPException("Resume only supported for BINARY transfers");
                    Restart(resumeMarker);
                }
                else
                    resumeMarker = 0;

                // send the retrieve command
                FTPReply reply = control.SendCommand("RETR " + remoteFile);
                
                // Can get a 125 or a 150
                lastValidReply = control.ValidateReply(reply, "125", "150");
            }
            catch (SystemException)
            {
                close = true;
                throw;
            }
            catch (FTPException)
            {
                close = true;
                throw;
            }
            finally
            {
                if (close)
                {
                    resume = false;
                    CloseDataSocket();
                }
            }
        }
        
        
    /// <summary>
    /// Get as ASCII, i.e. read a line at a time and write
    /// using the correct newline separator for the OS.
    /// </summary>
    /// <param name="localPath">Local file to put data in.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    private void GetASCII(string localPath, string remoteFile)
        {               
            // Need to store the local file name so the file can be
            // deleted if necessary.
            FileInfo localFile = new FileInfo(localPath);

            StreamWriter output = null;

            // check it is writable
            if (localFile.Exists)
            {
                if ((localFile.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                    throw new FTPException(localPath + " is readonly - cannot write");

                // try opening existing file - see if it is locked
                output = (dataEncoding == null ? new StreamWriter(localPath) : new StreamWriter(localPath, false, dataEncoding));
            }
            if (output == null)
                output = (dataEncoding == null ? new StreamWriter(localPath) : 
                                                    new StreamWriter(localPath, false, dataEncoding));
            
            Exception storedEx = null;
            long size = 0;
            StreamReader input = null;
            try
            {
                InitGet(remoteFile);

                input =
                    (dataEncoding == null ? new StreamReader(GetInputStream()) : new StreamReader(GetInputStream(), dataEncoding));

                // output a new line after each received newline
                long monitorCount = 0;
                string line = null;
                DateTime start = DateTime.Now;
                if (throttler != null)
                {
                    throttler.Reset();
                }

                while ((line = ReadLine(input)) != null && !cancelTransfer)
                {
                    size += line.Length + 2;
                    monitorCount += line.Length + 2;
                    output.WriteLine(line);

                    if (throttler != null)
                    {
                        throttler.ThrottleTransfer(size);
                    }

                    if (BytesTransferred != null && !cancelTransfer && monitorCount >= monitorInterval)
                    {
                        BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, 0));
                        monitorCount = 0;
                    }
                    start = SendServerWakeup(start);
                }
                // if asked to cancel, abort
                //if (cancelTransfer)
                //    Abort();
            }
            catch (Exception ex)
            {
                storedEx = ex;
            }
            finally
            {
                try
                {
                    output.Close();
                }
                catch (SystemException ex)
                {
                    log.Warn("Caught exception closing output stream", ex);
                }

                CloseDataSocket(input);

                // if we failed to write the file, rethrow the exception
                if (storedEx != null)
                {
                    // delete the partial file if failure occurred
                    if (deleteOnFailure)
                        localFile.Delete();

                    log.Error("Caught exception", storedEx);
                    throw storedEx;
                }

                if (BytesTransferred != null && !cancelTransfer)
                    BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, 0));
            }
        }
        
    /// <summary>
    /// Get as ASCII, i.e. read a line at a time and write
    /// using the correct newline separator for the OS.
    /// </summary>
    /// <remarks>
    /// The stream is closed after the transfer is complete if
    /// <see cref="CloseStreamsAfterTransfer"/> is <c>true</c> (the default) and are left
    /// open otherwise.
    /// </remarks>
    /// <param name="destStream">Data stream to write data to.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    private void GetASCII(Stream destStream, string remoteFile)
        {            
      InitGet(remoteFile);
            
            StreamWriter output = 
                (dataEncoding == null ? new StreamWriter(destStream) : new StreamWriter(destStream, dataEncoding));
            StreamReader input = null;
            Exception storedEx = null;
            long size = 0;
            try
            {
                input =
                    (dataEncoding == null ? new StreamReader(GetInputStream()) : new StreamReader(GetInputStream(), dataEncoding));

                // output a new line after each received newline
                long monitorCount = 0;
                string line = null;
                DateTime start = DateTime.Now;
                if (throttler != null)
                {
                    throttler.Reset();
                }

                while ((line = ReadLine(input)) != null && !cancelTransfer)
                {
                    size += line.Length + 2;
                    monitorCount += line.Length + 2;
                    output.WriteLine(line);

                    if (throttler != null)
                    {
                        throttler.ThrottleTransfer(size);
                    }

                    if (BytesTransferred != null && !cancelTransfer && monitorCount >= monitorInterval)
                    {
                        BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, 0));
                        monitorCount = 0;
                    }
                    start = SendServerWakeup(start);
                }
                output.Flush();
                // if asked to transfer, abort
                //if (cancelTransfer)
                // Abort();
            }
            catch (Exception ex)
            {
                storedEx = ex;
            }
            finally
            {
                try
                {
                    if (closeStreamsAfterTransfer)
                        output.Close();
                }
                catch (SystemException ex)
                {
                    log.Warn("Caught exception closing output stream", ex);
                }

                CloseDataSocket(input);

                // if we failed to write the file, rethrow the exception
                if (storedEx != null)
                {
                    log.Error("Caught exception", storedEx);
                    throw storedEx;
                }

                if (BytesTransferred != null && !cancelTransfer)
                    BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, 0));
            }
        }
        
    /// <summary>Get as binary file, i.e. straight transfer of data.</summary>
    /// <param name="localPath">Local file to put data in.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    private void GetBinary(string localPath, string remoteFile)
        {            
            // B. McKeown: Need to store the local file name so the file can be
            // deleted if necessary.
            FileInfo localFile = new FileInfo(localPath);
            
            // if resuming, we must find the marker
            FileMode mode = resume ? FileMode.Append : FileMode.Create;
            BufferedStream output = null;
            if (localFile.Exists)
            {
                if ((localFile.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                    throw new FTPException(localPath + " is readonly - cannot write");
                if (resume)
                    resumeMarker = localFile.Length;
                else
                    resumeMarker = 0;
                // open stream here and test if file locked
                output = new BufferedStream(new FileStream(localPath, mode));
            }
            if (output == null)
                output = new BufferedStream(new FileStream(localPath, mode));
               
            BufferedStream input = null;
            long size = 0;
            Exception storedEx = null;
            try
            {
                InitGet(remoteFile);

                input = new BufferedStream(GetInputStream());

                // do the retrieving
                long monitorCount = 0;
                byte[] chunk = new byte[transferBufferSize];
                int count;
                DateTime start = DateTime.Now;
                if (throttler != null)
                {
                    throttler.Reset();
                }

                // read from socket & write to file in chunks
                while ((count = ReadChunk(input, chunk, transferBufferSize)) > 0 && !cancelTransfer)
                {
                    output.Write(chunk, 0, count);
                    size += count;
                    monitorCount += count;

                    if (throttler != null)
                    {
                        throttler.ThrottleTransfer(size);
                    }

                    if (BytesTransferred != null && !cancelTransfer && monitorCount >= monitorInterval)
                    {
                        BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, resumeMarker));
                        monitorCount = 0;
                    }
                    start = SendServerWakeup(start);
                }
                // if asked to transfer, abort
                //if (cancelTransfer)
                // Abort();               
            }
            catch (Exception ex)
            {
                storedEx = ex;
            }
            finally
            {
                resume = false;
                try
                {
                    output.Close();
                }
                catch (SystemException ex)
                {
                    log.Warn("Caught exception closing output stream", ex);
                }

                CloseDataSocket(input);

                // if we failed to write the file, rethrow the exception
                if (storedEx != null)
                {
                    // delete the partial file if failure occurred
                    if (deleteOnFailure)
                        localFile.Delete();
                    log.Error("Caught exception", storedEx);
                    throw storedEx;
                }

                if (BytesTransferred != null && !cancelTransfer)
                    BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, resumeMarker));

                // log bytes transferred
                log.Debug("Transferred " + size + " bytes from remote host");
            }
        }
        
    /// <summary>Get as binary file, i.e. straight transfer of data.</summary>
    /// <remarks>
    /// The stream is closed after the transfer is complete if
    /// <see cref="CloseStreamsAfterTransfer"/> is <c>true</c> (the default) and are left
    /// open otherwise.
    /// </remarks>
    /// <param name="destStream">Stream to write to.</param>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    private void GetBinary(Stream destStream, string remoteFile)
        {            
      InitGet(remoteFile);
            
            // get an input stream to read data from ... AFTER we have
            // the ok to go ahead AND AFTER we've successfully opened a
            // stream for the local file
            BufferedStream output = new BufferedStream(destStream);
            BufferedStream input = null;
            long size = 0;
            Exception storedEx = null;
            try
            {
                input = new BufferedStream(GetInputStream());

                // do the retrieving
                long monitorCount = 0;
                byte[] chunk = new byte[transferBufferSize];
                int count;
                DateTime start = DateTime.Now;
                if (throttler != null)
                {
                    throttler.Reset();
                }

                // read from socket & write to file in chunks
                while ((count = ReadChunk(input, chunk, transferBufferSize)) > 0 && !cancelTransfer)
                {
                    output.Write(chunk, 0, count);
                    size += count;
                    monitorCount += count;

                    if (throttler != null)
                    {
                        throttler.ThrottleTransfer(size);
                    }

                    if (BytesTransferred != null && !cancelTransfer && monitorCount >= monitorInterval)
                    {
                        BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, resumeMarker));
                        monitorCount = 0;
                    }
                    start = SendServerWakeup(start);
                }
                output.Flush();
                // if asked to transfer, abort
                //if (cancelTransfer)
                //    Abort();
            }
            catch (Exception ex)
            {
                storedEx = ex;
            }
            finally
            {
                try
                {
                    if (closeStreamsAfterTransfer)
                    {
                        output.Close();
                    }
                }
                catch (SystemException ex)
                {
                    log.Warn("Caught exception closing output stream", ex);
                }

                CloseDataSocket(input);

                // if we failed to write to the stream, rethrow the exception
                if (storedEx != null)
                {
                    log.Error("Caught exception", storedEx);
                    throw storedEx;
                }

                if (BytesTransferred != null && !cancelTransfer)
                    BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, resumeMarker));

                // log bytes transferred
                log.Debug("Transferred " + size + " bytes from remote host");
            }
        }
        
    /// <summary>Get data from the FTP server.</summary>
    /// <remarks>
    /// Transfers in whatever mode we are in. Retrieve as a byte array. Note
    /// that we may experience memory limitations as the
    /// entire file must be held in memory at one time.
    /// </remarks>
    /// <param name="remoteFile">Name of remote file in current directory.</param>
    public virtual byte[] Get(string remoteFile)
        {            
      // events called before get in case event-handlers want to
      // do any FTP operations
      if (TransferStarted != null)
        TransferStarted(this, new EventArgs());  
      if (TransferStartedEx != null)
        TransferStartedEx(this, new TransferEventArgs(new byte[0], remoteFile, TransferDirection.DOWNLOAD, transferType));

      InitGet(remoteFile);
            
            // get an input stream to read data from
            BufferedStream input = null;
            long size = 0;
            Exception storedEx = null;
            MemoryStream memStr = null;
            byte[] buffer = null;
            try
            {
                input = new BufferedStream(GetInputStream());

                // do the retrieving
                long monitorCount = 0;
                byte[] chunk = new byte[transferBufferSize]; // read chunks into
                memStr = new MemoryStream(transferBufferSize); // temp swap buffer
                int count; // size of chunk read
                DateTime start = DateTime.Now;
                if (throttler != null)
                {
                    throttler.Reset();
                }

                // read from socket & write to file
                while ((count = ReadChunk(input, chunk, transferBufferSize)) > 0 && !cancelTransfer)
                {
                    memStr.Write(chunk, 0, count);
                    size += count;
                    monitorCount += count;

                    if (throttler != null)
                    {
                        throttler.ThrottleTransfer(size);
                    }

                    if (BytesTransferred != null && !cancelTransfer && monitorCount >= monitorInterval)
                    {
                        BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, resumeMarker));
                        monitorCount = 0;
                    }
                    start = SendServerWakeup(start);
                }
                // if asked to transfer, abort
                //if (cancelTransfer)
                // Abort();
            }
            catch (Exception ex)
            {
                storedEx = ex;
            }
            finally
            {
                try
                {
                    if (memStr != null)
                        memStr.Close();
                }
                catch (SystemException ex)
                {
                    log.Warn("Caught exception closing stream", ex);
                }

                CloseDataSocket(input);

                // if we failed to write to the stream, rethrow the exception
                if (storedEx != null)
                {
                    log.Error("Caught exception", storedEx);
                    throw storedEx;
                }

                // notify final transfer size
                if (BytesTransferred != null && !cancelTransfer)
                    BytesTransferred(this, new BytesTransferredEventArgs(remoteFile, size, resumeMarker));

                ValidateTransfer();

                buffer = memStr == null ? null : memStr.ToArray();

                if (TransferComplete != null)
                    TransferComplete(this, new EventArgs());
                if (TransferCompleteEx != null)
                    TransferCompleteEx(this, new TransferEventArgs(buffer, remoteFile, TransferDirection.UPLOAD, transferType));          
            }
            return buffer;
        }
        
        
    /// <summary>Run a site-specific command on the server.</summary>
    /// <remarks>
    /// Support for commands is dependent on the server.
    /// </remarks>
    /// <param name="command">The site command to run</param>
    /// <returns><c>true</c> if command ok, <c>false</c> if command not implemented.</returns>
    public virtual bool Site(string command)
        {            
            CheckConnection(true);
            
            // send the retrieve command
            FTPReply reply = control.SendCommand("SITE " + command);
            
            // Can get a 200 (ok) or 202 (not impl). Some
            // FTP servers return 502 (not impl)
            lastValidReply = control.ValidateReply(reply, "200", "202", "250", "502"); // 250 for leitch media server
            
            // return true or false? 200 is ok, 202/502 not
            // implemented
            if (reply.ReplyCode.Equals("200"))
                return true;
            else
                return false;
        }
        
    /// <summary>
    /// List the current directory's contents as an array of FTPFile objects.
    /// </summary>
    /// <remarks>
    /// This works for Windows and most Unix FTP servers.  Please inform EDT
    /// about unusual formats (support@enterprisedt.com).
    /// </remarks>
    /// <returns>An array of <see cref="FTPFile"/> objects.</returns>
    public virtual FTPFile[] DirDetails()
        {
            return DirDetails(null);
        }        
        
    /// <summary>
    /// List a directory's contents as an array of FTPFile objects.
    /// </summary>
    /// <remarks>
    /// This works for Windows and most Unix FTP servers.  Please inform EDT
    /// about unusual formats (support@enterprisedt.com). Note that for some
    /// servers, this will not work from the parent directory of dirname. You
    /// need to ChDir() into dirname and use DirDetails() (with no arguments).
    /// </remarks>
    /// <param name="dirname">Name of directory OR filemask (if supported by the server).</param>
    /// <returns>An array of <see cref="FTPFile"/> objects.</returns>
    public virtual FTPFile[] DirDetails(string dirname)
        {
            // create the factory
            if (fileFactory == null)
                fileFactory = new FTPFileFactory();

            // initialize if not yet done
            if (!fileFactory.ParserSetExplicitly && fileFactory.System == null)
            {
                try
                {
                    fileFactory.System = GetSystem();
                }
                catch (FTPException ex)
                {
                    log.Warn("SYST command failed - setting Unix as default parser", ex);
                    fileFactory.System = FTPFileFactory.UNIX_STR;
                }
            }
            if (parserCulture != null)
                fileFactory.ParsingCulture = parserCulture;

            string path = Pwd();

            // add dirname to path if it looks like a directory name
            // and has no obvious wildcards
            if (dirname != null && dirname.Length > 0 && dirname.IndexOf('*') < 0 
                && dirname.IndexOf('?') < 0)
            {
                path = Path.Combine(path, dirname);
            }
            
            // get the details and parse. Set the directory for each file
            FTPFile[] result = fileFactory.Parse(Dir(dirname, true));
            for (int i = 0; i < result.Length; i++) 
                result[i].Path = path + (path.EndsWith("/") ? "" : "/") + result[i].Name;
                
            return result;
        }
        
    /// <summary>
    /// List current directory's contents as an array of strings of
    /// filenames.
    /// </summary>
    /// <returns>An array of current directory listing strings.</returns>
    public virtual string[] Dir()
        {            
            return Dir(null, false);
        } 
        
    /// <summary>
    /// List a directory's contents as an array of strings of filenames.
    /// </summary>
    /// <param name="dirname">Name of directory OR filemask.</param>
    /// <returns>An array of directory listing strings.</returns>
    public virtual string[] Dir(string dirname)
        {            
            return Dir(dirname, false);
        }

    /// <summary>
    /// List a directory's contents as an array of strings.
    /// </summary>
    /// <remarks>
    /// If <c>full</c> is <c>true</c> then a detailed
    /// listing if returned (if available), otherwise just filenames are provided.
    /// The detailed listing varies in details depending on OS and
    /// FTP server. Note that a full listing can be used on a file
    /// name to obtain information about a file. The <c>ShowHiddenFiles</c> flag
        /// can be used to request that hidden files be returned in the listing. Servers may
        /// or may not support this.
    /// </remarks> 
    /// <param name="dirname">Name of directory OR filemask.</param>
    /// <param name="full"><c>true</c> if detailed listing required, <c>false</c> otherwise.</param>
    /// <returns>An array of directory listing strings.</returns>
    public virtual string[] Dir(string dirname, bool full)
        {
            CheckConnection(true);
            
            try
            {
                // set up data channel
                data = control.CreateDataSocket(connectMode);
                data.Timeout = timeout;
                
                // send the retrieve command
                string command = full ? "LIST ":"NLST ";
                if (showHiddenFiles)
                    command += "-a ";
                if (dirname != null)
                    command += dirname;
                
                // some FTP servers bomb out if NLST has whitespace appended
                command = command.Trim();
                FTPReply reply = control.SendCommand(command);
                
                // check the control response. wu-ftp returns 550 if the
                // directory is empty, so we handle 550 appropriately. Similarly
                // proFTPD returns 450. If dir is empty, some servers return 226 Transfer complete
                lastValidReply = control.ValidateReply(reply, "125", "226", "150", "450", "550");
                
                // an empty array of files for 450/550
                string[] result = new string[0];
                
                // a normal reply ... extract the file list
                string replyCode = lastValidReply.ReplyCode;
                if (!replyCode.Equals("450") && !replyCode.Equals("550") && !replyCode.Equals("226"))
                {
                    // get a character input stream to read data from
                    Encoding enc = controlEncoding == null ? Encoding.ASCII : controlEncoding;
                    ArrayList lines = null;
                    // reset the cancel flag
                    cancelTransfer = false;
                    try
                    {
                        if (enc.Equals(Encoding.ASCII))
                        {
                            lines = ReadASCIIListingData(dirname);
                        }
                        else
                        {
                            lines = ReadListingData(dirname, enc);
                        }

                        // check the control response
                        reply = control.ReadReply();
                        lastValidReply = control.ValidateReply(reply, "226", "250");
                    }
                    catch (SystemException ex)
                    {
                        ValidateTransferOnError();
                        log.Error("SystemException in directory listing", ex);
                        throw;
                    }
                    
                    // empty array is default
                    if (!(lines.Count == 0))
                    {
                        log.Debug("Found " + lines.Count + " listing lines");
                        result = new string[lines.Count];
                        lines.CopyTo(result);
                    }
                    else
                        log.Debug("No listing data found");
                }
                else { // throw exception if not a "no files" message or transfer complete
          string replyText = lastValidReply.ReplyText.ToUpper();
                    if (!dirEmptyStrings.Matches(replyText)
                        && !transferCompleteStrings.Matches(replyText))
                        throw new FTPException(reply);
                }
                return result;
            }
            finally
            {
                CloseDataSocket();
            }
        }

        /// <summary>
        /// Reads the listing data for a particular encoding
        /// </summary>
        /// <param name="enc">encoding</param>
        /// <returns>array of listing lines</returns>
        private ArrayList ReadListingData(string dirname, Encoding enc) 
        {
            StreamReader input = new StreamReader(GetInputStream(), enc);
                    
            // read a line at a time
            ArrayList lines = new ArrayList(10);
            string line = null;
            long size = 0;
            long monitorCount = 0;
            try
            {
                while ((line = ReadLine(input)) != null && !cancelTransfer)
                {
                    size += line.Length;
                    monitorCount += line.Length;
                    lines.Add(line);
                    if (transferNotifyListings && BytesTransferred != null && 
                        !cancelTransfer && monitorCount >= monitorInterval)
                    {
                        BytesTransferred(this, new BytesTransferredEventArgs(dirname, size, 0));
                        monitorCount = 0;
                    }
                    log.Debug("-->" + line);
                }
                if (transferNotifyListings && BytesTransferred != null)
                    BytesTransferred(this, new BytesTransferredEventArgs(dirname, size, 0));
                return lines;
            }
            finally
            {
                CloseDataSocket(input); // need to close here
            }
        }

        /// <summary>
        /// Reads the listing data for ASCII encoding.
        /// </summary>
        /// <remarks>Skips non-ASCII chars found in the stream</remarks>
        /// <returns>array of listing lines</returns>
        private ArrayList ReadASCIIListingData(string dirname) 
        {
            log.Debug("Reading ASCII listing data");
            BufferedStream bstr = new BufferedStream(GetInputStream());
            MemoryStream mstr = new MemoryStream(TransferBufferSize*2);
            int b;
            long size = 0;
            long monitorCount = 0;
            try 
            {
                while ((b = bstr.ReadByte()) != -1 && !cancelTransfer) 
                {
                    // strip out non-printable chars (except for CRLF)
                    if ((b < 0x20 && b != 0xA && b != 0xD) || b > 0x7f)
                        continue;
                    size++;
                    monitorCount++;
                    mstr.WriteByte((byte)b);
                    if (transferNotifyListings && BytesTransferred != null && 
                        !cancelTransfer && monitorCount >= monitorInterval)
                    {
                        BytesTransferred(this, new BytesTransferredEventArgs(dirname, size, 0));
                        monitorCount = 0;
                    }       
                }
                if (transferNotifyListings && BytesTransferred != null)
                    BytesTransferred(this, new BytesTransferredEventArgs(dirname, size, 0));
            }
            finally
            {
                CloseDataSocket(bstr); // need to close here
            }

            // go back to the start
            mstr.Seek(0, SeekOrigin.Begin);

            StreamReader input = new StreamReader(mstr, Encoding.ASCII);
                    
            // read a line at a time
            ArrayList lines = new ArrayList(10);
            string line = null;
            while ((line = ReadLine(input)) != null)
            {
                lines.Add(line);
                log.Debug("-->" + line);
            }
            input.Close();
            mstr.Close();
            return lines;   
        }
        
    /// <summary>
    /// Attempts to read a specified number of bytes from the given 
    /// <code>BufferedStream</code> and place it in the given byte-array.
    /// </summary>
    /// <remarks>
    /// The purpose of this method is to permit subclasses to execute
    /// any additional code necessary when performing this operation. 
    /// </remarks>
    /// <param name="input">The <code>Stream</code> to read from.</param>
    /// <param name="chunk">The byte-array to place read bytes in.</param>
    /// <param name="chunksize">Number of bytes to read.</param>
    /// <returns>Number of bytes actually read.</returns>
    /// <throws>SystemException Thrown if there was an error while reading. </throws>
    internal virtual int ReadChunk(Stream input, byte[] chunk, int chunksize)
        {
            return input.Read(chunk, 0, chunksize);
        }
        
    /// <summary>Attempts to read a single character from the given <code>StreamReader</code>.</summary>
    /// <remarks>
    /// The purpose of this method is to permit subclasses to execute
    /// any additional code necessary when performing this operation. 
    /// </remarks>
    /// <param name="input">The <code>StreamReader</code> to read from.</param>
    /// <returns>The character read.</returns>
    /// <throws>SystemException Thrown if there was an error while reading. </throws>
    internal virtual int ReadChar(StreamReader input)
        {
            return input.Read();
        }
        
    /// <summary>
    /// Attempts to read a single line from the given <code>StreamReader</code>. 
    /// </summary>
    /// <remarks>
    /// The purpose of this method is to permit subclasses to execute
    /// any additional code necessary when performing this operation. 
    /// </remarks>
    /// <param name="input">The <code>StreamReader</code> to read from.</param>
    /// <returns>The string read.</returns>
    /// <throws>SystemException Thrown if there was an error while reading. </throws>
    internal virtual string ReadLine(StreamReader input)
        {
            return input.ReadLine();
        }
                
    /// <summary>Delete the specified remote file.</summary>
    /// <param name="remoteFile">Name of remote file to delete.</param>
    public virtual void Delete(string remoteFile)
        {            
            CheckConnection(true);
            FTPReply reply = control.SendCommand("DELE " + remoteFile);
            lastValidReply = control.ValidateReply(reply, "200", "250");
        }
        
        
    /// <summary>Rename a file or directory.</summary>
    /// <param name="from">Name of file or directory to rename.</param>
    /// <param name="to">Intended name.</param>
    public virtual void Rename(string from, string to)
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("RNFR " + from);
            lastValidReply = control.ValidateReply(reply, "350");
            
            reply = control.SendCommand("RNTO " + to);
            lastValidReply = control.ValidateReply(reply, "250");
        }
        
        
    /// <summary>Delete the specified remote working directory.</summary>
    /// <param name="dir">Name of remote directory to delete.</param>
    public virtual void RmDir(string dir)
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("RMD " + dir);
            
            // some servers return 200,257, technically incorrect but
            // we cater for it ...
            lastValidReply = control.ValidateReply(reply, "200", "250", "257");
        }
        
        
    /// <summary>Create the specified remote working directory.</summary>
    /// <param name="dir">Name of remote directory to create.</param>
    public virtual void MkDir(string dir)
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("MKD " + dir);
            
            // some servers return 200,257, technically incorrect but
            // we cater for it ...
            lastValidReply = control.ValidateReply(reply, "200", "250", "257");
        }
        
        
    /// <summary>Change the remote working directory to that supplied.</summary>
    /// <param name="dir">Name of remote directory to change to.</param>
    public virtual void ChDir(string dir)
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("CWD " + dir);
            lastValidReply = control.ValidateReply(reply, "200", "250");
        }

        
    /// <summary>Change the remote working directory to the parent directory.</summary>
    public virtual void CdUp()
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("CDUP");
            lastValidReply = control.ValidateReply(reply, "200", "250");
        }

        /// <summary>
        /// Checks for the existence of a file on the server.
        /// </summary>
        /// <param name="remoteFile">Path of file.</param>
        /// <returns><c>true</c> if the file exists and <c>false</c> otherwise.</returns>
        public virtual bool Exists(string remoteFile)
        {
            CheckConnection(true);

            FTPReply reply = null;
            char ch;

            // first try the SIZE command
            if (sizeSupported)
            {
                reply = control.SendCommand("SIZE " + remoteFile);
                ch = reply.ReplyCode[0];
                if (ch == '2')
                    return true;
                if (ch == '5' && fileNotFoundStrings.Matches(reply.ReplyText))
                    return false;
                
                sizeSupported = false;
                log.Debug("SIZE not supported");
            }

            // then try the MDTM command
            if (mdtmSupported)
            {
                reply = control.SendCommand("MDTM " + remoteFile);
                ch = reply.ReplyCode[0];
                if (ch == '2')
                    return true;
                if (ch == '5' && fileNotFoundStrings.Matches(reply.ReplyText))
                    return false;
             
                mdtmSupported = false;
                log.Debug("MDTM not supported");
            }

            // ok, now try RETR since nothing else is supported
            // get a port
            Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint endPoint = new IPEndPoint(control.LocalAddress, 0);
            sock.Bind(endPoint);
            try
            {
                control.SetDataPort((IPEndPoint)sock.LocalEndPoint);
            }
            finally
            {
                sock.Close();
            }
        
            // send the retrieve command
            reply = control.SendCommand("RETR " + remoteFile);
            ch = reply.ReplyCode[0];
        
            // normally return 125 etc. But could return 425 unable to create data
            // connection, which means the file exists but can't connect to our (non-
            // existent) server socket
            if (ch == '1' || ch == '2' || ch == '4')
                return true;
            if (ch == '5' && fileNotFoundStrings.Matches(reply.ReplyText))
                return false;
        
            string msg = "Unable to determine if file '" + remoteFile + "' exists.";
            log.Warn(msg);
            throw new FTPException(msg);
        }
        
    /// <summary>Get modification time for a remote file.</summary>
    /// <param name="remoteFile">Name of remote file.</param>
    /// <returns>Modification time of file as a <c>DateTime</c>.</returns>
    public virtual DateTime ModTime(string remoteFile)
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("MDTM " + remoteFile);
            lastValidReply = control.ValidateReply(reply, "213");
            
            // parse the reply string, which returns UTC
            DateTime ts = 
                DateTime.ParseExact(lastValidReply.ReplyText, modtimeFormats, 
                                    null, DateTimeStyles.None);
            
            // return the equivalent in local time
            return TimeZone.CurrentTimeZone.ToLocalTime(ts);
        }

        /// <summary>Sets the modification time of a remote file.</summary>
        /// <remarks>
        /// Although times are passed to the server with second precision, some
        /// servers may ignore seconds and only provide minute precision.  
        /// May not be supported by some FTP servers.
        /// </remarks>
        /// <param name="remoteFile">Name of remote file.</param>
        /// <param name="modTime">Desired modification-time to set in local time.</param>
        public virtual void SetModTime(string remoteFile, DateTime modTime)
        {
            CheckConnection(true);

            DateTime univTime = TimeZone.CurrentTimeZone.ToUniversalTime(modTime);
            string timeStr = univTime.ToString(DEFAULT_TIME_FORMAT);
            FTPReply reply = control.SendCommand("MFMT " + timeStr + " " + remoteFile);
            lastValidReply = control.ValidateReply(reply, "213");
        }
        
    /// <summary>Get the current remote working directory.</summary>
    /// <returns>The current working directory.</returns>
    public virtual string Pwd()
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("PWD");
            lastValidReply = control.ValidateReply(reply, "257");
            
            // get the reply text and extract the dir
            // listed in quotes, if we can find it. Otherwise
            // just return the whole reply string
            string text = lastValidReply.ReplyText;
            int start = text.IndexOf((System.Char) '"');
            int end = text.LastIndexOf((System.Char) '"');
            if (start >= 0 && end > start)
                return text.Substring(start + 1, (end) - (start + 1));
            else
                return text;
        }
        
        
    /// <summary>Get the server supplied features.</summary>
    /// <returns>
    /// <c>string</c>-array containing server features, or <c>null</c> if no features or not supported.
    /// </returns>
    public virtual string[] Features()
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("FEAT");
            lastValidReply = control.ValidateReply(reply, "211", "500", "502");
            if (lastValidReply.ReplyCode=="211")
                return lastValidReply.ReplyData;
            else
                throw new FTPException(reply);
        }
        
    /// <summary>Get the type of the OS at the server.</summary>
    /// <returns>The type of server OS.</returns>
    public virtual string GetSystem()
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("SYST");
            lastValidReply = control.ValidateReply(reply, "200", "213", "215", "250"); // added 250 for leitch
            return lastValidReply.ReplyText;
        }

        /// <summary>  
        /// Send a "no operation" message that does nothing, which can
        /// be called periodically to prevent the connection timing out.
        /// </summary>
        public void NoOperation()
        {
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("NOOP");
            lastValidReply = control.ValidateReply(reply, "200", "250"); // added 250 for leitch
        }
          
        /// <summary>  Get the help text for the specified command
        /// 
        /// </summary>
        /// <param name="command"> name of the command to get help on
        /// </param>
        /// <returns> help text from the server for the supplied command
        /// </returns>
        public virtual string Help(string command)
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("HELP " + command);
            lastValidReply = control.ValidateReply(reply, "211", "214");
            return lastValidReply.ReplyText;
        }
        
    /// <summary>Abort the current action.</summary>
    /// <remarks>
    /// This does not close the FTP session.
    /// </remarks>
    protected virtual void Abort()
        {            
            CheckConnection(true);
            
            FTPReply reply = control.SendCommand("ABOR");
            lastValidReply = control.ValidateReply(reply, "426", "226");
        }
        
    /// <summary>Quit the FTP session by sending a <c>QUIT</c> command before closing the socket.</summary>
    public virtual void Quit()
        {            
            CheckConnection(true);
            
            if (fileFactory != null)
                fileFactory.System = null;
            try
            {
                FTPReply reply = control.SendCommand("QUIT");
                lastValidReply = control.ValidateReply(reply, "221", "226");
            }
            finally
            {
                if (data != null)
                    data.Close();
                data = null;

                // ensure we clean up the connection
                control.Logout();
                control = null;      
            }
        }
        
    /// <summary>
    /// Quit the FTP session immediately by closing the control socket
    /// without sending the <c>QUIT</c> command.
    /// </summary>
    public virtual void QuitImmediately() 
        {         
            if (fileFactory != null)
                fileFactory.System = null;

            try
            {
                if (data != null)
                    data.Close();
            }
            finally
            {
                if (control != null)
                    control.Kill();
                control = null;
                data = null;
            }
        }
        
        
        /// <summary>Work out the version array.</summary>
        static FTPClient()
        {
            try
            {
                version = new int[3];
#if !NET20
                version[0] = Int32.Parse(majorVersion);
                version[1] = Int32.Parse(middleVersion);
                version[2] = Int32.Parse(minorVersion);
#else
                if (!Int32.TryParse(majorVersion, out version[0]))
                    System.Console.Error.WriteLine("Error: Could not parse major version number string, " + version[0]);
                if (!Int32.TryParse(middleVersion, out version[1]))
                    System.Console.Error.WriteLine("Error: Could not parse middle version number string, " + version[1]);
                if (!Int32.TryParse(minorVersion, out version[2]))
                    System.Console.Error.WriteLine("Error: Could not parse minor version number string, " + version[2]);
#endif
            }
            catch (FormatException ex)
            {
                System.Console.Error.WriteLine("Failed to calculate version: " + ex.Message);
            }
        }
    }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.