// edtFTPnet
//
// Copyright (C) 2004 Enterprise Distributed Technologies Ltd
//
// www.enterprisedt.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Bug fixes, suggestions and comments should posted on
// http://www.enterprisedt.com/forums/index.php
//
// Change Log:
//
// $Log: FTPClient.cs,v $
// Revision 1.14 2004/11/20 22:34:54 bruceb
// abort() added re resume, fixed resume append bug
//
// Revision 1.13 2004/11/15 23:26:40 hans
// *** empty log message ***
//
// Revision 1.12 2004/11/13 22:25:32 hans
// *** empty log message ***
//
// Revision 1.11 2004/11/13 19:14:01 bruceb
// exception restructuring etc
//
// Revision 1.10 2004/11/11 22:14:37 hans
// *** empty log message ***
//
// Revision 1.9 2004/11/06 22:38:13 bruceb
// renamed property
//
// Revision 1.8 2004/11/06 11:10:01 bruceb
// tidied namespaces, changed IOException to SystemException
//
// Revision 1.7 2004/11/05 20:00:13 bruceb
// events added
//
// Revision 1.6 2004/11/04 21:18:11 hans
// *** empty log message ***
//
// Revision 1.5 2004/11/03 21:32:04 bruceb
// fixed getbinary bug
//
// Revision 1.4 2004/10/29 14:30:31 bruceb
// BaseSocket changes
//
//
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Collections;
using System.Globalization;
using System.ComponentModel;
using Level = EnterpriseDT.Util.Debug.Level;
using Logger = EnterpriseDT.Util.Debug.Logger;
namespace EnterpriseDT.Net.Ftp
{
#region Types
///
/// Event args for BytesTransferred event
///
public class BytesTransferredEventArgs : EventArgs
{
///
/// Constructor
///
/// The current count of bytes transferred
///
///
public BytesTransferredEventArgs(long byteCount)
{
this.byteCount = byteCount;
}
///
/// Gets the byte count
///
public long ByteCount
{
get
{
return byteCount;
}
}
private long byteCount;
}
///
/// Event args for ReplyReceived and CommandSent events
///
public class FTPMessageEventArgs : EventArgs
{
///
/// Constructor
///
/// The message sent to or from the remote host
///
///
public FTPMessageEventArgs(string message)
{
this.message = message;
}
///
/// Gets the message
///
public string Message
{
get
{
return message;
}
}
private string message;
}
///
/// Delegate used for the BytesTransferred event
///
public delegate void BytesTransferredHandler(
object ftpClient,
BytesTransferredEventArgs bytesTransferred
);
///
/// Delegate used for ReplyReceived and CommandSent events
///
public delegate void FTPMessageHandler(
object ftpClient,
FTPMessageEventArgs message
);
///
/// Enumerates the connect modes that are possible, active and passiv e
///
public enum FTPConnectMode
{
///
/// Represents active - PORT - connect mode
///
ACTIVE = 1,
///
/// Represents passive - PASV - connect mode
///
PASV = 2
}
///
/// Enumerates the transfer types possible. We support only the two common types,
/// ASCII and Image (often called binary).
///
public enum FTPTransferType
{
///
/// Represents ASCII transfer type
///
ASCII = 1,
///
/// Represents Image (or binary) transfer type
///
BINARY = 2
}
#endregion
///
/// Supports client-side FTP. Most common
/// FTP operations are present in this class.
///
/// Bruce Blackshaw
///
/// $LastChangedRevision$
///
public class FTPClient
{
/// Get the version of edtFTPj
///
///
/// int array of {major,middle,minor} version numbers
///
public static int[] Version
{
get
{
return version;
}
}
/// Get the build timestamp
///
///
/// d-MMM-yyyy HH:mm:ss z build timestamp
///
public static string BuildTimestamp
{
get
{
return buildTimestamp;
}
}
///
/// Strict checking of return codes. If it is on
/// (the default), all return codes must exactly match the expected code.
/// If strict checking is off, only the first digit must match
///
///
/// true if strict return code checking, false if non-strict.
///
virtual public bool StrictReturnCodes
{
get
{
return strictReturnCodes;
}
set
{
this.strictReturnCodes = value;
if (control != null)
control.StrictReturnCodes = value;
}
}
///
/// Set the TCP timeout on the underlying socket.
///
virtual public int Timeout
{
get
{
return timeout;
}
set
{
this.timeout = value;
if (control != null)
control.Timeout = value;
}
}
///
/// Get/Set the connect mode
///
virtual public FTPConnectMode ConnectMode
{
set
{
connectMode = value;
}
get
{
return connectMode;
}
}
///
/// Get the bytes transferred between each notification of the
/// BytesTransferred event. Reduce this value to receive more
/// frequent notifications of transfer progress
///
public long TransferNotifyInterval
{
get
{
return monitorInterval;
}
set
{
monitorInterval = value;
}
}
///
/// Get/set the size of the buffers used in writing to and reading from
/// the data sockets
///
public int TransferBufferSize
{
get
{
return transferBufferSize;
}
set
{
transferBufferSize = value;
}
}
///
/// Get/set the name of the remote host.
///
///
/// Can only be set if not currently connected.
///
public virtual string RemoteHost
{
get
{
return remoteHost;
}
set
{
CheckConnection(false);
remoteHost = value;
}
}
///
/// Get/set the delete on failure flag
///
///
/// If true, 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 false, the partially
/// downloaded file remains on the client machine - and the download
/// may be resumed, if it is a binary transfer. By default this flag is set to true.
///
public bool DeleteOnFailure
{
get
{
return deleteOnFailure;
}
set
{
deleteOnFailure = value;
}
}
///
/// Get/set the controlPort. Can only be set
/// if not currently connected
///
public int ControlPort
{
get
{
return controlPort;
}
set
{
CheckConnection(false);
controlPort = value;
}
}
///
/// Override the chosen file factory with a user created one - meaning
/// that a specific parser has been selected
///
public FTPFileFactory FTPFileFactory
{
set
{
this.fileFactory = value;
}
}
///
/// Gets the latest valid reply from the server
///
/// reply object encapsulating last valid server response
///
public FTPReply LastValidReply
{
get
{
return lastValidReply;
}
}
///
/// Get or set the current transfer type
///
///
/// the current type of the transfer, i.e. BINARY or ASCII
///
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");
// record the type
transferType = value;
}
}
///
/// Event for notifying start of a transfer
///
public event EventHandler TransferStarted;
///
/// Event for notifying start of a transfer
///
public event EventHandler TransferComplete;
///
/// Event for notifying start of a transfer
///
public event BytesTransferredHandler BytesTransferred;
///
/// Event for notifying start of a transfer
///
public event FTPMessageHandler CommandSent;
///
/// Event for notifying start of a transfer
///
public event FTPMessageHandler ReplyReceived;
/// Default byte interval for transfer monitor
private const int DEFAULT_MONITOR_INTERVAL = 4096;
/// Default transfer buffer size
private const int DEFAULT_BUFFER_SIZE = 4096;
/// Major version (substituted by ant)
private static string majorVersion = "1";
/// Middle version (substituted by ant)
//TODO: uncomment
private static string middleVersion = "0";
/// Middle version (substituted by ant)
private static string minorVersion = "0";
/// Full version
private static int[] version;
/// Timestamp of build
private static string buildTimestamp = "1/1/2000";
///
/// The char sent to the server to set BINARY
///
private static string BINARY_CHAR = "I";
///
/// The char sent to the server to set ASCII
///
private static string ASCII_CHAR = "A";
/// Date format
private static readonly string tsFormat = "yyyyMMddHHmmss";
/// Logging object
private Logger log;
/// Socket responsible for controlling
/// the connection
///
internal FTPControlSocket control = null;
/// Socket responsible for transferring
/// the data
///
internal FTPDataSocket data = null;
/// Socket timeout for both data and control. In
/// milliseconds
///
internal int timeout = 0;
/// Use strict return codes if true
private bool strictReturnCodes = true;
/// Can be used to cancel a transfer
private bool cancelTransfer = false;
/// If true, a file transfer is being resumed
private bool resume = false;
/// If a download to a file fails, delete the partial file
private bool deleteOnFailure = true;
/// Resume byte marker point
private long resumeMarker = 0;
/// Bytes transferred in between monitor callbacks
private long monitorInterval = DEFAULT_MONITOR_INTERVAL;
/// Size of transfer buffers
private int transferBufferSize = DEFAULT_BUFFER_SIZE;
/// Parses LIST output
private FTPFileFactory fileFactory = null;
/// Record of the transfer type - make the default ASCII
private FTPTransferType transferType = FTPTransferType.ASCII;
/// Record of the connect mode - make the default PASV (as this was
/// the original mode supported)
///
private FTPConnectMode connectMode = FTPConnectMode.PASV;
///
/// Holds the last valid reply from the server on the control socket
///
internal FTPReply lastValidReply;
///
/// Port on which we connect to the FTP server and messages are passed
///
internal int controlPort = -1;
///
/// Remote host we are connecting to
///
internal string remoteHost = null;
#region Constructors
///
/// Constructor. Creates the control socket
///
/// the remote hostname
///
public FTPClient(string remoteHost):
this(remoteHost, FTPControlSocket.CONTROL_PORT, 0)
{
}
///
/// Constructor. Creates the control socket
///
/// the remote hostname
///
/// port for control stream (-1 for default port)
///
public FTPClient(string remoteHost, int controlPort):
this(remoteHost, controlPort, 0)
{
}
///
/// Constructor. Creates the control socket
///
/// the remote hostname
///
/// port for control stream (use -1 for the default port)
///
/// the length of the timeout, in milliseconds
/// (pass in 0 for no timeout)
///
public FTPClient(string remoteHost, int controlPort, int timeout):
this(Dns.Resolve(remoteHost).AddressList[0], controlPort, timeout)
{
this.remoteHost = remoteHost;
}
/// Constructor. Creates the control
/// socket
///
///
/// the address of the
/// remote host
///
public FTPClient(IPAddress remoteAddr):
this(remoteAddr, FTPControlSocket.CONTROL_PORT, 0)
{
}
///
/// Constructor. Creates the control
/// socket. Allows setting of control port (normally
/// set by default to 21).
///
///
/// the address of the
/// remote host
///
/// port for control stream
///
public FTPClient(IPAddress remoteAddr, int controlPort):
this(remoteAddr, controlPort, 0)
{
}
///
/// Constructor. Creates the control
/// socket. Allows setting of control port (normally
/// set by default to 21).
///
/// the address of the
/// remote host
///
/// port for control stream (-1 for default port)
///
/// the length of the timeout, in milliseconds
/// (pass in 0 for no timeout)
///
public FTPClient(IPAddress remoteAddr, int controlPort, int timeout)
{
InitBlock();
remoteHost = remoteAddr.ToString();
Connect(remoteAddr, controlPort, timeout);
}
///
/// Default constructor for use by subclasses. Does not connect
/// to the remote host
///
public FTPClient()
{
InitBlock();
}
#endregion
///
/// Instance initializer. Sets formatter to GMT.
///
private void InitBlock()
{
log = Logger.GetLogger(typeof(FTPClient));
transferType = FTPTransferType.ASCII;
connectMode = FTPConnectMode.PASV;
controlPort = FTPControlSocket.CONTROL_PORT;
}
///
/// Connect to the remote host. Cannot be currently connected. RemoteHost
/// property must be set
///
public virtual void Connect()
{
CheckConnection(false);
Connect(Dns.Resolve(remoteHost).AddressList[0], controlPort, timeout);
}
internal virtual void Connect(IPAddress remoteAddr, 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;
log.Debug("Connecting to " + remoteAddr.ToString() + ":" + controlPort);
Initialize(new FTPControlSocket(remoteAddr, controlPort, timeout));
}
///
/// Set the control socket explicitly
///
/// control socket reference
///
internal void Initialize(FTPControlSocket control)
{
this.control = control;
// 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);
}
///
/// 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
///
/// FTPException Thrown if the client has not connected to the server.
internal virtual void CheckConnection(bool shouldBeConnected)
{
if (shouldBeConnected && control == null)
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 && control != null)
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);
}
///
/// Switch Debug of responses on or off
///
/// true if you wish to have responses to
/// the log stream, false otherwise
///
/// use the Logger class to switch Debugging on and off
///
public void DebugResponses(bool on)
{
if (on)
Logger.CurrentLevel = Level.DEBUG;
else
Logger.CurrentLevel = Level.OFF;
}
/// Cancels the current transfer. 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
///
public virtual void CancelTransfer()
{
cancelTransfer = true;
}
///
/// Login into an account on the FTP server. This
/// call completes the entire login process
///
/// user name
///
/// user's password
///
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
string[] validCodes = new string[]{"230", "331"};
lastValidReply = control.ValidateReply(reply, validCodes);
if (lastValidReply.ReplyCode.Equals("230"))
return ;
else
{
Password(password);
}
}
/// Supply the user name to log into an account
/// on the FTP server. Must be followed by the
/// password() method - but we allow for
///
///
/// user name
///
public virtual void User(string user)
{
CheckConnection(true);
FTPReply reply = control.SendCommand("USER " + user);
// we allow for a site with no password - 230 response
string[] validCodes = new string[]{"230", "331"};
lastValidReply = control.ValidateReply(reply, validCodes);
}
///
/// Supplies the password for a previously supplied
/// username to log into the FTP server. Must be
/// preceeded by the user() method
///
/// The password.
///
public virtual void Password(string password)
{
CheckConnection(true);
FTPReply reply = control.SendCommand("PASS " + password);
// we allow for a site with no passwords (202)
string[] validCodes = new string[]{"230", "202"};
lastValidReply = control.ValidateReply(reply, validCodes);
}
/// Issue arbitrary ftp commands to the FTP server.
///
///
/// ftp command to be sent to server
///
/// valid return codes for this command
///
///
/// the text returned by the FTP server
///
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 && validCodes.Length > 0)
{
lastValidReply = control.ValidateReply(reply, validCodes);
return lastValidReply.ReplyText;
}
else
{
throw new FTPException("Valid reply code must be supplied");
}
}
///
/// Get the size of a remote file. This is not a standard FTP command, it
/// is defined in "Extensions to FTP", a draft RFC
/// (draft-ietf-ftpext-mlst-16.txt)
///
/// name or path of remote file in current directory
///
/// size of file in bytes
///
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);
}
}
///
/// Make the next file transfer (put or get) resume. 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.
///
/// Currently resume is only supported for BINARY transfers (which is
/// generally what it is most useful for).
///
/// FTPException
public virtual void Resume()
{
if (transferType.Equals(FTPTransferType.ASCII))
throw new FTPException("Resume only supported for BINARY transfers");
resume = true;
}
///
/// Cancel the resume. Use this method if something goes wrong
/// and the server is left in an inconsistent state
///
/// SystemException
/// FTPException
public virtual void CancelResume()
{
Restart(0);
resume = false;
}
///
/// Issue the RESTart command to the remote server
///
/// the REST param, the mark at which the restart is
/// performed on the remote file. For STOR, this is retrieved
/// by SIZE
///
/// SystemException
/// FTPException
private void Restart(long size)
{
FTPReply reply = control.SendCommand("REST " + size);
lastValidReply = control.ValidateReply(reply, "350");
}
///
/// Put a local file onto the FTP server. It
/// is placed in the current directory.
///
/// path of the local file
///
/// name of remote file in
/// current directory
///
public virtual void Put(string localPath, string remoteFile)
{
Put(localPath, remoteFile, false);
}
///
/// Put a stream of data onto the FTP server. It
/// is placed in the current directory.
///
/// input stream of data to put
///
/// name of remote file in
/// current directory
///
public virtual void Put(Stream srcStream, string remoteFile)
{
Put(srcStream, remoteFile, false);
}
/// Put a local file onto the FTP server. It
/// is placed in the current directory. Allows appending
/// if current file exists
///
///
/// path of the local file
///
/// name of remote file in
/// current directory
///
/// true if appending, false otherwise
///
public virtual void Put(string localPath, string remoteFile, bool append)
{
// get according to set type
if (transferType == FTPTransferType.ASCII)
{
PutASCII(localPath, remoteFile, append);
}
else
{
PutBinary(localPath, remoteFile, append);
}
ValidateTransfer();
}
///
/// Put a stream of data onto the FTP server. It
/// is placed in the current directory. Allows appending
/// if current file exists
///
/// input stream of data to put
///
/// name of remote file in
/// current directory
///
/// true if appending, false otherwise
///
public virtual void Put(Stream srcStream, string remoteFile, bool append)
{
// get according to set type
if (transferType == FTPTransferType.ASCII)
{
PutASCII(srcStream, remoteFile, append);
}
else
{
PutBinary(srcStream, remoteFile, append);
}
ValidateTransfer();
}
///
/// Validate that the Put() or get() was successful. This method is not
/// for general use.
///
public virtual void ValidateTransfer()
{
CheckConnection(true);
// check the control response
string[] validCodes = new string[]{"225", "226", "250", "426", "450"};
FTPReply reply = control.ReadReply();
// permit 426/450 error if we cancelled the transfer, otherwise
// throw an exception
string code = reply.ReplyCode;
if ((code.Equals("426") || code.Equals("450")) && !cancelTransfer)
throw new FTPException(reply);
lastValidReply = control.ValidateReply(reply, validCodes);
}
///
/// Close the data socket
///
///
/// stream to close
///
private void CloseDataSocket(Stream stream)
{
if (stream != null) {
try {
stream.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing data socket", ex);
}
}
CloseDataSocket();
}
///
/// Close the data socket
///
private void CloseDataSocket()
{
if (data != null)
{
try
{
data.Close();
data = null;
}
catch (SystemException ex)
{
log.Warn("Caught exception closing data socket", ex);
}
}
}
///
/// Request the server to set up the put
///
/// name of remote file in
/// current directory
///
/// true if appending, false otherwise
///
private void InitPut(string remoteFile, bool append)
{
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 obtain the size of the
// remote file and issue REST
if (resume)
{
if (transferType.Equals(FTPTransferType.ASCII))
throw new FTPException("Resume only supported for BINARY transfers");
resumeMarker = Size(remoteFile);
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
string[] validCodes = new string[]{"125", "150"};
lastValidReply = control.ValidateReply(reply, validCodes);
}
catch (SystemException ex)
{
close = true;
throw ex;
}
catch (FTPException ex)
{
close = true;
throw ex;
}
finally
{
if (close)
{
resume = false;
CloseDataSocket();
}
}
}
///
/// Put as ASCII, i.e. read a line at a time and write
/// inserting the correct FTP separator
///
/// full path of local file to read from
///
/// name of remote file we are writing to
///
/// true if appending, false otherwise
///
private void PutASCII(string localPath, string remoteFile, bool append)
{
// create an inputstream & pass to common method
Stream srcStream = new FileStream(localPath, FileMode.Open, FileAccess.Read);
PutASCII(srcStream, remoteFile, append);
}
///
/// Put as ASCII, i.e. read a line at a time and write
/// inserting the correct FTP separator
///
/// input stream of data to put
///
/// name of remote file we are writing to
///
/// true if appending, false otherwise
///
private void PutASCII(Stream srcStream, string remoteFile, bool append)
{
// need to read line by line ...
StreamReader input = null;
StreamWriter output = null;
SystemException storedEx = null;
long size = 0;
try
{
input = new StreamReader(srcStream);
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 = new StreamWriter(data.DataStream);
if (TransferStarted != null)
TransferStarted(this, new EventArgs());
// write \r\n as required by RFC959 after each line
long monitorCount = 0;
string line = null;
while ((line = input.ReadLine()) != null && !cancelTransfer)
{
size += line.Length;
monitorCount += line.Length;
output.Write(line);
output.Write(FTPControlSocket.EOL);
if (BytesTransferred != null && monitorCount > monitorInterval)
{
BytesTransferred(this, new BytesTransferredEventArgs(size));
monitorCount = 0;
}
}
}
catch (SystemException ex)
{
storedEx = ex;
}
finally
{
try
{
if (input != null)
input.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing stream", ex);
}
try
{
if (output != null)
output.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing data socket", ex);
}
// if we did get an exception bail out now
if (storedEx != null)
throw storedEx;
// notify the final transfer size
if (BytesTransferred != null)
BytesTransferred(this, new BytesTransferredEventArgs(size));
if (TransferComplete != null)
TransferComplete(this, new EventArgs());
}
}
///
/// Put as binary, i.e. read and write raw bytes
///
///
/// full path of local file to read from
///
///
/// name of remote file we are writing to
///
///
/// true if appending, false otherwise
///
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 = new FileStream(localPath, FileMode.Open, FileAccess.Read);
PutBinary(srcStream, remoteFile, append);
}
///
/// Put as binary, i.e. read and write raw bytes
///
/// input stream of data to put
///
/// name of remote file we are writing to
///
/// true if appending, false otherwise
///
private void PutBinary(Stream srcStream, string remoteFile, bool append)
{
BufferedStream input = null;
BinaryWriter output = null;
SystemException storedEx = null;
long size = 0;
try
{
input = new BufferedStream(srcStream);
InitPut(remoteFile, append);
// get an output stream
output = new BinaryWriter(data.DataStream);
// if resuming, we skip over the unwanted bytes
if (resume)
{
input.Seek(resumeMarker, SeekOrigin.Current);
}
byte[] buf = new byte[transferBufferSize];
if (TransferStarted != null)
TransferStarted(this, new EventArgs());
// read a chunk at a time and write to the data socket
long monitorCount = 0;
int count = 0;
while ((count = input.Read(buf, 0, buf.Length)) > 0 && !cancelTransfer)
{
output.Write(buf, 0, count);
size += count;
monitorCount += count;
if (BytesTransferred != null && monitorCount > monitorInterval)
{
BytesTransferred(this, new BytesTransferredEventArgs(size));
monitorCount = 0;
}
}
}
catch (SystemException ex)
{
storedEx = ex;
}
finally
{
resume = false;
try
{
if (input != null)
input.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing stream", ex);
}
try
{
if (output != null)
output.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing data socket", ex);
}
// if we did get an exception bail out now
if (storedEx != null)
throw storedEx;
// notify the final transfer size
if (BytesTransferred != null)
BytesTransferred(this, new BytesTransferredEventArgs(size));
if (TransferComplete != null)
TransferComplete(this, new EventArgs());
// log bytes transferred
log.Debug("Transferred " + size + " bytes to remote host");
}
}
///
/// Put data onto the FTP server. It
/// is placed in the current directory.
///
/// array of bytes
///
/// name of remote file in
/// current directory
///
public virtual void Put(byte[] bytes, string remoteFile)
{
Put(bytes, remoteFile, false);
}
///
/// Put data onto the FTP server. It
/// is placed in the current directory. Allows
/// appending if current file exists
///
/// array of bytes
///
/// name of remote file in
/// current directory
///
/// true if appending, false otherwise
///
public virtual void Put(byte[] bytes, string remoteFile, bool append)
{
InitPut(remoteFile, append);
// get an output stream
BinaryWriter output = new BinaryWriter(data.DataStream);
try
{
// write array
output.Write(bytes, 0, bytes.Length);
}
finally
{
try
{
output.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing data socket", ex);
}
}
ValidateTransfer();
}
///
/// Get data from the FTP server. Uses the currently
/// set transfer mode.
///
/// local file to put data in
///
/// name of remote file in
/// current directory
///
public virtual void Get(string localPath, string remoteFile)
{
// get according to set type
if (transferType == FTPTransferType.ASCII)
{
GetASCII(localPath, remoteFile);
}
else
{
GetBinary(localPath, remoteFile);
}
ValidateTransfer();
}
///
/// Get data from the FTP server, using the currently
/// set transfer mode.
///
/// data stream to write data to
///
/// name of remote file in
/// current directory
///
public virtual void Get(Stream destStream, string remoteFile)
{
// get according to set type
if (transferType == FTPTransferType.ASCII)
{
GetASCII(destStream, remoteFile);
}
else
{
GetBinary(destStream, remoteFile);
}
ValidateTransfer();
}
///
/// Request to the server that the get is set up
///
/// name of remote file
///
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);
}
// send the retrieve command
FTPReply reply = control.SendCommand("RETR " + remoteFile);
// Can get a 125 or a 150
string[] validCodes1 = new string[]{"125", "150"};
lastValidReply = control.ValidateReply(reply, validCodes1);
}
catch (SystemException ex)
{
close = true;
throw ex;
}
catch (FTPException ex)
{
close = true;
throw ex;
}
finally
{
if (close)
{
resume = false;
CloseDataSocket();
}
}
}
///
/// Get as ASCII, i.e. read a line at a time and write
/// using the correct newline separator for the OS
///
/// full path of local file to write to
///
/// name of remote file
///
private void GetASCII(string localPath, string remoteFile)
{
// Call InitGet() before creating the FileOutputStream.
// This will prevent being left with an empty file if a FTPException
// is thrown by InitGet().
InitGet(remoteFile);
SystemException storedEx = null;
long size = 0;
// Need to store the local file name so the file can be
// deleted if necessary.
FileInfo localFile = new FileInfo(localPath);
// create the buffered stream for writing
StreamWriter output = new StreamWriter(localPath);
// get an character 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
StreamReader input = null;
try
{
input = new StreamReader(data.DataStream);
// If we are in active mode we have to set the timeout of the passive
// socket. We can achieve this by setting Timeout again.
// If we are in passive mode then we are merely setting the value twice
// which does no harm anyway. Doing this simplifies any logic changes.
data.Timeout = timeout;
if (TransferStarted != null)
TransferStarted(this, new EventArgs());
// output a new line after each received newline
long monitorCount = 0;
string line = null;
while ((line = ReadLine(input)) != null && !cancelTransfer)
{
size += line.Length;
monitorCount += line.Length;
output.WriteLine(line);
if (BytesTransferred != null && monitorCount > monitorInterval)
{
BytesTransferred(this, new BytesTransferredEventArgs(size));
monitorCount = 0;
}
}
// if asked to transfer, abort
if (cancelTransfer)
Abort();
}
catch (SystemException ex)
{
storedEx = ex;
}
try
{
output.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing output stream", ex);
}
try {
if (input != null)
input.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing data socket", ex);
}
// if we failed to write the file, rethrow the exception
if (storedEx != null)
{
// delete the partial file if failure occurred
if (deleteOnFailure)
localFile.Delete();
throw storedEx;
}
if (BytesTransferred != null)
BytesTransferred(this, new BytesTransferredEventArgs(size));
if (TransferComplete != null)
TransferComplete(this, new EventArgs());
}
///
/// Get as ASCII, i.e. read a line at a time and write
/// using the correct newline separator for the OS
///
/// data stream to write data to
///
/// name of remote file
///
private void GetASCII(Stream destStream, string remoteFile)
{
InitGet(remoteFile);
// create the buffered stream for writing
StreamWriter output = new StreamWriter(destStream);
// get an character input stream to read data from ... AFTER we
// have the ok to go ahead
StreamReader input = null;
SystemException storedEx = null;
long size = 0;
try
{
input = new StreamReader(data.DataStream);
// B. McKeown:
// If we are in active mode we have to set the timeout of the passive
// socket. We can achieve this by setting Timeout again.
// If we are in passive mode then we are merely setting the value twice
// which does no harm anyway. Doing this simplifies any logic changes.
data.Timeout = timeout;
if (TransferStarted != null)
TransferStarted(this, new EventArgs());
// output a new line after each received newline
long monitorCount = 0;
string line = null;
while ((line = ReadLine(input)) != null && !cancelTransfer)
{
size += line.Length;
monitorCount += line.Length;
output.WriteLine(line);
if (BytesTransferred != null && monitorCount > monitorInterval)
{
BytesTransferred(this, new BytesTransferredEventArgs(size));
monitorCount = 0;
}
}
// if asked to transfer, abort
if (cancelTransfer)
Abort();
}
catch (SystemException ex)
{
storedEx = ex;
}
try {
output.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing data socket", ex);
}
try {
if (input != null)
input.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing data socket", ex);
}
// if we failed to write the file, rethrow the exception
if (storedEx != null)
throw storedEx;
if (BytesTransferred != null)
BytesTransferred(this, new BytesTransferredEventArgs(size));
if (TransferComplete != null)
TransferComplete(this, new EventArgs());
}
///
/// Get as binary file, i.e. straight transfer of data
///
/// full path of local file to write to
///
/// name of remote file
///
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
if (localFile.Exists && resume)
resumeMarker = localFile.Length;
// B.McKeown:
// Call InitGet() before creating the FileOutputStream.
// This will prevent being left with an empty file if a FTPException
// is thrown by InitGet().
InitGet(remoteFile);
// create the output stream for writing the file
FileMode mode = resume ? FileMode.Append : FileMode.Create;
BinaryWriter output = new BinaryWriter(new FileStream(localPath, mode));
// 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
BinaryReader input = null;
long size = 0;
SystemException storedEx = null;
try
{
input = new BinaryReader(data.DataStream);
// B. McKeown:
// If we are in active mode we have to set the timeout of the passive
// socket. We can achieve this by calling setTimeout() again.
// If we are in passive mode then we are merely setting the value twice
// which does no harm anyway. Doing this simplifies any logic changes.
data.Timeout = timeout;
if (TransferStarted != null)
TransferStarted(this, new EventArgs());
// do the retrieving
long monitorCount = 0;
byte[] chunk = new byte[transferBufferSize];
int count;
// 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 (BytesTransferred != null && monitorCount > monitorInterval)
{
BytesTransferred(this, new BytesTransferredEventArgs(size));
monitorCount = 0;
}
}
// if asked to transfer, abort
if (cancelTransfer)
Abort();
}
catch (SystemException ex)
{
storedEx = ex;
}
resume = false;
try {
output.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing stream", ex);
}
try {
if (input != null)
input.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing data socket", ex);
}
// if we failed to write the file, rethrow the exception
if (storedEx != null)
{
// delete the partial file if failure occurred
if (deleteOnFailure)
localFile.Delete();
throw storedEx;
}
if (BytesTransferred != null)
BytesTransferred(this, new BytesTransferredEventArgs(size));
if (TransferComplete != null)
TransferComplete(this, new EventArgs());
// log bytes transferred
log.Debug("Transferred " + size + " bytes from remote host");
}
///
/// Get as binary file, i.e. straight transfer of data
///
/// stream to write to
///
/// name of remote file
///
private void GetBinary(Stream destStream, string remoteFile)
{
InitGet(remoteFile);
// create the buffered output stream for writing the file
BufferedStream output = new BufferedStream(destStream);
// 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
BinaryReader input = null;
long size = 0;
SystemException storedEx = null;
try
{
input = new BinaryReader(data.DataStream);
// B. McKeown:
// If we are in active mode we have to set the timeout of the passive
// socket. We can achieve this by calling setTimeout() again.
// If we are in passive mode then we are merely setting the value twice
// which does no harm anyway. Doing this simplifies any logic changes.
data.Timeout = timeout;
if (TransferStarted != null)
TransferStarted(this, new EventArgs());
// do the retrieving
long monitorCount = 0;
byte[] chunk = new byte[transferBufferSize];
int count;
// 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 (BytesTransferred != null && monitorCount > monitorInterval)
{
BytesTransferred(this, new BytesTransferredEventArgs(size));
monitorCount = 0;
}
}
// if asked to transfer, abort
if (cancelTransfer)
Abort();
}
catch (SystemException ex)
{
storedEx = ex;
}
try {
output.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing stream", ex);
}
try {
if (input != null)
input.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing data socket", ex);
}
// if we failed to write to the stream, rethrow the exception
if (storedEx != null)
throw storedEx;
if (BytesTransferred != null)
BytesTransferred(this, new BytesTransferredEventArgs(size));
if (TransferComplete != null)
TransferComplete(this, new EventArgs());
// log bytes transferred
log.Debug("Transferred " + size + " bytes from remote host");
}
///
/// Get data from the FTP server.
///
///
/// 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.
///
/// name of remote file in
/// current directory
///
public virtual byte[] Get(string remoteFile)
{
InitGet(remoteFile);
// get an input stream to read data from
BinaryReader input = new BinaryReader(data.DataStream);
long size = 0;
SystemException storedEx = null;
MemoryStream temp = null;
try
{
// B. McKeown:
// If we are in active mode we have to set the timeout of the passive
// socket. We can achieve this by calling setTimeout() again.
// If we are in passive mode then we are merely setting the value twice
// which does no harm anyway. Doing this simplifies any logic changes.
data.Timeout = timeout;
// do the retrieving
long monitorCount = 0;
byte[] chunk = new byte[transferBufferSize]; // read chunks into
temp = new MemoryStream(transferBufferSize); // temp swap buffer
int count; // size of chunk read
if (TransferStarted != null)
TransferStarted(this, new EventArgs());
// read from socket & write to file
while ((count = ReadChunk(input, chunk, transferBufferSize)) > 0 && !cancelTransfer)
{
temp.Write(chunk, 0, count);
size += count;
monitorCount += count;
if (BytesTransferred != null && monitorCount > monitorInterval)
{
BytesTransferred(this, new BytesTransferredEventArgs(size));
monitorCount = 0;
}
}
// if asked to transfer, abort
if (cancelTransfer)
Abort();
}
catch (SystemException ex)
{
storedEx = ex;
}
try
{
if (temp != null)
temp.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing stream", ex);
}
try
{
input.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing data socket", ex);
}
// if we failed to write to the stream, rethrow the exception
if (storedEx != null)
throw storedEx;
// notify final transfer size
if (BytesTransferred != null)
BytesTransferred(this, new BytesTransferredEventArgs(size));
if (TransferComplete != null)
TransferComplete(this, new EventArgs());
ValidateTransfer();
return temp.ToArray();;
}
///
/// Run a site-specific command on the
/// server. Support for commands is dependent
/// on the server
///
/// the site command to run
///
/// true if command ok, false if
/// command not implemented
///
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)
string[] validCodes = new string[]{"200", "202", "502"};
lastValidReply = control.ValidateReply(reply, validCodes);
// return true or false? 200 is ok, 202/502 not
// implemented
if (reply.ReplyCode.Equals("200"))
return true;
else
return false;
}
///
/// List a directory's contents as an array of FTPFile objects.
/// Should work for Windows and most Unix FTP servers - let us know
/// about unusual formats (support@enterprisedt.com)
///
/// name of directory OR filemask
///
/// an array of FTPFile objects
///
public virtual FTPFile[] DirDetails(string dirname)
{
// create the factory
if (fileFactory == null)
fileFactory = new FTPFileFactory(GetSystem());
// get the details and parse
return fileFactory.Parse(Dir(dirname, true));
}
///
/// List current directory's contents as an array of strings of
/// filenames.
///
/// an array of current directory listing strings
///
public virtual string[] Dir()
{
return Dir(null, false);
}
///
/// List a directory's contents as an array of strings of filenames.
///
/// name of directory OR filemask
///
/// an array of directory listing strings
///
public virtual string[] Dir(string dirname)
{
return Dir(dirname, false);
}
///
/// List a directory's contents as an array of strings. A detailed
/// listing is 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
///
/// name of directory OR filemask
///
/// true if detailed listing required
/// false otherwise
///
/// an array of directory listing strings
///
public virtual string[] Dir(string dirname, bool full)
{
CheckConnection(true);
// set up data channel
data = control.CreateDataSocket(connectMode);
data.Timeout = timeout;
// send the retrieve command
string command = full?"LIST ":"NLST ";
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
string[] validCodes1 = new string[]{"125", "150", "450", "550"};
lastValidReply = control.ValidateReply(reply, validCodes1);
// 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"))
{
// get a character input stream to read data from .
StreamReader input = new StreamReader(data.DataStream);
// read a line at a time
ArrayList lines = new ArrayList(10);
string line = null;
while ((line = ReadLine(input)) != null)
{
lines.Add(line);
}
try {
input.Close();
}
catch (SystemException ex)
{
log.Warn("Caught exception closing data socket", ex);
}
CloseDataSocket();
// check the control response
string[] validCodes2 = new string[]{"226", "250"};
reply = control.ReadReply();
lastValidReply = control.ValidateReply(reply, validCodes2);
// empty array is default
if (!(lines.Count == 0))
{
result = new string[lines.Count];
lines.CopyTo(result);
}
}
else
{
// 450 or 550 - still need to close data socket
CloseDataSocket();
}
return result;
}
///
/// Attempts to read a specified number of bytes from the given
/// BufferedStream
and place it in the given byte-array.
/// The purpose of this method is to permit subclasses to execute
/// any additional code necessary when performing this operation.
///
/// The BinaryReader
to read from.
///
/// The byte-array to place read bytes in.
///
/// Number of bytes to read.
///
/// Number of bytes actually read.
///
/// SystemException Thrown if there was an error while reading.
internal virtual int ReadChunk(BinaryReader input, byte[] chunk, int chunksize)
{
return input.Read(chunk, 0, chunksize);
}
///
/// Attempts to read a single character from the given StreamReader
.
/// The purpose of this method is to permit subclasses to execute
/// any additional code necessary when performing this operation.
///
/// The StreamReader
to read from.
///
/// The character read.
///
/// SystemException Thrown if there was an error while reading.
internal virtual int ReadChar(StreamReader input)
{
return input.Read();
}
///
/// Attempts to read a single line from the given StreamReader
.
/// The purpose of this method is to permit subclasses to execute
/// any additional code necessary when performing this operation.
///
/// The StreamReader
to read from.
///
/// The string read.
///
///
/// SystemException Thrown if there was an error while reading.
///
internal virtual string ReadLine(StreamReader input)
{
return input.ReadLine();
}
///
/// Delete the specified remote file
///
/// name of remote file to
/// delete
///
public virtual void Delete(string remoteFile)
{
CheckConnection(true);
string[] validCodes = new string[]{"200", "250"};
FTPReply reply = control.SendCommand("DELE " + remoteFile);
lastValidReply = control.ValidateReply(reply, validCodes);
}
///
/// Rename a file or directory
///
/// name of file or directory to rename
///
/// intended name
///
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");
}
///
/// Delete the specified remote working directory
///
/// name of remote directory to
/// delete
///
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 ...
string[] validCodes = new string[]{"200", "250", "257"};
lastValidReply = control.ValidateReply(reply, validCodes);
}
///
/// Create the specified remote working directory
///
/// name of remote directory to
/// create
///
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 ...
string[] validCodes = new string[]{"200", "250", "257"};
lastValidReply = control.ValidateReply(reply, validCodes);
}
///
/// Change the remote working directory to that supplied
///
/// name of remote directory to
/// change to
///
public virtual void ChDir(string dir)
{
CheckConnection(true);
FTPReply reply = control.SendCommand("CWD " + dir);
lastValidReply = control.ValidateReply(reply, "250");
}
///
/// Get modification time for a remote file
///
/// name of remote file
///
///
/// modification time of file as a date
///
public virtual DateTime ModTime(string remoteFile)
{
CheckConnection(true);
FTPReply reply = control.SendCommand("MDTM " + remoteFile);
lastValidReply = control.ValidateReply(reply, "213");
// parse the reply string ...
DateTime ts = DateTime.ParseExact(lastValidReply.ReplyText,
tsFormat,
CultureInfo.CurrentCulture.DateTimeFormat);
return ts.ToUniversalTime();
}
///
/// Get the current remote working directory
///
/// the current working directory
///
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;
}
///
/// Get the server supplied features
///
///
/// string containing server features, or null if no features or not
/// supported
///
public virtual string[] Features()
{
CheckConnection(true);
FTPReply reply = control.SendCommand("FEAT");
string[] validCodes = new string[]{"211", "500", "502"};
lastValidReply = control.ValidateReply(reply, validCodes);
if (lastValidReply.ReplyCode.Equals("211"))
return lastValidReply.ReplyData;
else
throw new FTPException(reply);
}
///
/// Get the type of the OS at the server
///
/// the type of server OS
///
public virtual string GetSystem()
{
CheckConnection(true);
FTPReply reply = control.SendCommand("SYST");
lastValidReply = control.ValidateReply(reply, "215");
return lastValidReply.ReplyText;
}
/// Get the help text for the specified command
///
///
/// name of the command to get help on
///
/// help text from the server for the supplied command
///
public virtual string Help(string command)
{
CheckConnection(true);
FTPReply reply = control.SendCommand("HELP " + command);
string[] validCodes = new string[]{"211", "214"};
lastValidReply = control.ValidateReply(reply, validCodes);
return lastValidReply.ReplyText;
}
///
/// Abort the current action
///
protected virtual void Abort()
{
CheckConnection(true);
FTPReply reply = control.SendCommand("ABOR");
string[] validCodes = new string[]{"426", "226"};
lastValidReply = control.ValidateReply(reply, validCodes);
}
///
/// Quit the FTP session
///
public virtual void Quit()
{
CheckConnection(true);
fileFactory = null;
try
{
FTPReply reply = control.SendCommand("QUIT");
string[] validCodes = new string[]{"221", "226"};
lastValidReply = control.ValidateReply(reply, validCodes);
}
finally
{
// ensure we clean up the connection
control.Logout();
control = null;
}
}
///
/// Quit the FTP session immediately by closing the control socket
/// without sending the QUIT command
///
public virtual void QuitImmediately()
{
CheckConnection(true);
fileFactory = null;
control.Logout();
control = null;
}
///
/// Work out the version array
///
static FTPClient()
{
{
try
{
version = new int[3];
version[0] = Int32.Parse(majorVersion);
version[1] = Int32.Parse(middleVersion);
version[2] = Int32.Parse(minorVersion);
}
catch (FormatException ex)
{
System.Console.Error.WriteLine("Failed to calculate version: " + ex.Message);
}
}
}
}
}