www.pudn.com > SharpDownload(FTP¼°WEBÏÂÔØ).zip > FtpClient.cs


using System; 
using System.Net; 
using System.IO; 
using System.Text; 
using System.Net.Sockets; 
using System.Diagnostics; 
using System.Runtime.Remoting; 
using System.Runtime.Remoting.Messaging; 
 
/* 
 * FTP Client library in C# 
 * Author: Jaimon Mathew 
 * mailto:jaimonmathew@rediffmail.com 
 * http://www.csharphelp.com/archives/archive9.html 
 *  
 * Adapted for use by Dan Glass 07/03/03 
 *  
 * Further mods Gavin McKay 17/07/03 
 *  
 * FTP Reference: http://www.w3.org/Protocols/rfc959/ 
 * Resume reference: http://www.codeproject.com/useritems/pauseresume.asp 
 */ 
 
#region FTP Reply Codes 
/* 
	110 Restart marker reply. 
        In this case, the text is exact and not left to the 
        particular implementation; it must read: 
            MARK yyyy = mmmm 
        Where yyyy is User-process data stream marker, and mmmm 
        server's equivalent marker (note the spaces between markers 
        and "="). 
    120 Service ready in nnn minutes. 
    125 Data connection already open; transfer starting. 
    150 File status okay; about to open data connection. 
    200 Command okay. 
    202 Command not implemented, superfluous at this site. 
    211 System status, or system help reply. 
    212 Directory status. 
    213 File status. 
    214 Help message. 
        On how to use the server or the meaning of a particular 
        non-standard command.  This reply is useful only to the 
        human user. 
    215 NAME system type. 
        Where NAME is an official system name from the list in the 
        Assigned Numbers document. 
    220 Service ready for new user. 
    221 Service closing control connection. 
        Logged out if appropriate. 
    225 Data connection open; no transfer in progress. 
    226 Closing data connection. 
        Requested file action successful (for example, file 
        transfer or file abort). 
    227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). 
    230 User logged in, proceed. 
    250 Requested file action okay, completed. 
    257 "PATHNAME" created. 
     
    331 User name okay, need password. 
    332 Need account for login. 
    350 Requested file action pending further information. 
     
    421 Service not available, closing control connection. 
        This may be a reply to any command if the service knows it 
        must shut down. 
    425 Can't open data connection. 
    426 Connection closed; transfer aborted. 
    450 Requested file action not taken. 
        File unavailable (e.g., file busy). 
    451 Requested action aborted: local error in processing. 
    452 Requested action not taken. 
        Insufficient storage space in system. 
    500 Syntax error, command unrecognized. 
        This may include errors such as command line too long. 
    501 Syntax error in parameters or arguments. 
    502 Command not implemented. 
    503 Bad sequence of commands. 
    504 Command not implemented for that parameter. 
    530 Not logged in. 
    532 Need account for storing files. 
    550 Requested action not taken. 
        File unavailable (e.g., file not found, no access). 
    551 Requested action aborted: page type unknown. 
    552 Requested file action aborted. 
        Exceeded storage allocation (for current directory or 
        dataset). 
    553 Requested action not taken. 
        File name not allowed. 
 
 */ 
#endregion 
 
namespace SharpDownload 
{ 
 
	///  
	/// FtpClient implementation of ProtocolClient 
	/// Provides FTP-specific download functionality 
	///  
	public class FtpClient : ProtocolClient 
	{ 
 
		private FileDownload.FileDownloadCommandEnum command = FileDownload.FileDownloadCommandEnum.NoCommand; 
 
		///  
		/// Ftp exception class 
		///  
		public class FtpException : Exception 
		{ 
			///  
			/// Public constructor - inherits base constructor 
			///  
			/// Error message 
			public FtpException(string message) : base(message){} 
			///  
			/// Public constructor - inherits base constructor 
			///  
			/// Error message 
			/// Inner exception that caused the error 
			public FtpException(string message, Exception innerException) : base(message,innerException){} 
		} 
 
		private string MODULE_NAME = "FtpClient"; 
 
		///  
		/// Overloaded constructor for anonymous ftp access 
		///  
		public FtpClient() 
		{ 
			this.Username = "anonymous"; 
			this.Password = "anonymous@anonymous.net"; 
			this.Port = 21; 
		} 
 
		///  
		/// If the value of mode is true, set binary mode for downloads, else, Ascii mode. 
		///  
		public bool BinaryMode 
		{ 
			get 
			{ 
				return this.binMode; 
			} 
			set 
			{ 
				if ( this.binMode == value ) return; 
 
				if ( value ) 
					sendCommand("TYPE I"); 
 
				else 
					sendCommand("TYPE A"); 
 
				if ( this.resultCode != 200 ) throw new FtpException(result.Substring(4)); 
			} 
		} 
		///  
		/// Login to the remote server. 
		///  
		public override void Login() 
		{ 
			if ( this.loggedin ) this.Close(); 
 
			Debug.WriteLine("Opening connection to " + this.server, MODULE_NAME); 
			 
			this.clientSocket = base.createDataSocket(); 
 
			this.readResponse(); 
 
			if(this.resultCode != 220) 
			{ 
				this.Close(); 
				throw new FtpException(this.result.Substring(4)); 
			} 
 
			// Send the username to the server 
			this.sendCommand( "USER " + username ); 
 
			if( !(this.resultCode == 331 || this.resultCode == 230) ) 
			{ 
				this.cleanup(); 
				throw new FtpException(this.result.Substring(4)); 
			} 
 
			// Check to see if the username was accepted 
			if( this.resultCode != 230 ) 
			{ 
				// Send the password to the server 
				this.sendCommand( "PASS " + password ); 
 
				if( !(this.resultCode == 230 || this.resultCode == 202) ) 
				{ 
					this.cleanup(); 
					throw new FtpException(this.result.Substring(4)); 
				} 
			} 
 
			this.loggedin = true; 
 
			Debug.WriteLine( "Connected to " + this.server, MODULE_NAME); 
 
			this.ChangeDir(this.remotePath); 
		} 
		 
		///  
		/// Close the FTP connection. 
		///  
		public override void Close() 
		{ 
			Debug.WriteLine("Closing connection to " + this.server, MODULE_NAME ); 
 
			if( this.clientSocket != null ) 
			{ 
				this.sendCommand("QUIT"); 
			} 
 
			this.cleanup(); 
		} 
 
		///  
		/// Return the size of a file. 
		///  
		///  
		///  
		public override long GetFileSize(string fileName) 
		{ 
			if ( !this.loggedin ) this.Login(); 
 
			this.sendCommand("TYPE i"); 
			if ( this.resultCode != 200 ) 
				throw new FtpException(this.result.Substring(4)); 
 
			this.sendCommand("SIZE " + fileName); 
			long size=0; 
 
			if ( this.resultCode == 213 ) 
				size = long.Parse(this.result.Substring(4)); 
 
			else 
				throw new FtpException(this.result.Substring(4)); 
 
			Debug.WriteLine("File size is: " + size.ToString() + " bytes", MODULE_NAME); 
 
			return size; 
		} 
	 
		///  
		/// Stops the current download 
		///  
		public override void StopDownload() 
		{ 
		} 
		 
		///  
		/// Download a remote file to the Assembly's local directory, 
		/// keeping the same file name. 
		///  
		///  
		public override DownloadResult Download(string remFileName) 
		{ 
			return this.Download(remFileName,"",false); 
		} 
 
		///  
		/// Download a remote file to the Assembly's local directory, 
		/// keeping the same file name, and set the resume flag. 
		///  
		///  
		///  
		public override DownloadResult Download(string remFileName, Boolean resume) 
		{ 
			return this.Download(remFileName, "", resume); 
		} 
		 
		///  
		/// Download a remote file to a local file name which can include 
		/// a path. The local file name will be created or overwritten, 
		/// but the path must exist. 
		///  
		///  
		///  
		public override DownloadResult Download(string remFileName, string locFileName) 
		{ 
			return this.Download(remFileName, locFileName, false); 
		} 
 
		///  
		/// Download a remote file to a local file name.  Specifies whether to attempt to resume. 
		///  
		///  
		///  
		///  
		public override DownloadResult Download(string remFileName,string locFileName, Boolean resume) 
		{ 
			return this.Download(remFileName, locFileName, resume, 0); 
		} 
 
		///  
		/// Download a remote file to local file.  Specifies whether to resume, and the byte offset to resume at. 
		///  
		///  
		///  
		///  
		///  
		public override DownloadResult Download(string remFileName,string locFileName, Boolean resume, long byteOffset) 
		{ 
			return this.Download(remFileName, locFileName, resume, byteOffset, 0); 
		} 
 
		///  
		/// Download a remote file to a local file.  Specifies to resume, where to start the resume, and when to stop the resume for partial downloads. 
		///  
		///  
		///  
		///  
		///  
		///  
		public override DownloadResult Download(string remFileName,string locFileName, Boolean resume, long byteOffset, long byteStopDownload) 
		{ 
			return this.Download(remFileName, locFileName, resume, byteOffset, byteStopDownload, 0); 
		} 
 
		///  
		/// Download a remote file to a local file.  Specifies to resume, where to start the resume, and when to stop the resume for partial downloads. 
		///  
		///  
		///  
		///  
		///  
		///  
		public override DownloadResult Download(string remFileName,string locFileName, Boolean resume, long byteOffset, long byteStopDownload, long totalFileSize) 
		{ 
			return this.Download(remFileName, locFileName, resume, byteOffset, byteStopDownload, totalFileSize, false); 
		} 
 
		///  
		/// Download a remote file to a local file name which can include 
		/// a path, and set the resume flag. The local file name will be 
		/// created or overwritten, but the path must exist. 
		///  
		/// Remote file name on the server 
		/// Local file name to save to 
		/// true attempts to resume the download 
		/// Offset to start download 
		/// Point to stop download for partial 
		/// Is this the last part in the list? 
		public override DownloadResult Download(string remFileName, string locFileName, Boolean resume, long byteOffset, long byteStopDownload, long totalFileSize, bool isLastPart) 
		{ 
 
			DateTime timeout;	// Timeout value 
			bool stopPointReached = false; 
			long locFileOffset = 0; 
 
			// Raise a download started event 
			OnDownloadStartedEvent(new DownloadStartedEventArgs()); 
			 
			// Check if already logged in 
			if ( !this.loggedin ) this.Login(); 
 
			// Set mode to binary 
			this.sendCommand("TYPE i"); 
			if ( this.resultCode != 200 ) 
				throw new FtpException(this.result.Substring(4)); 
			this.BinaryMode = true; 
 
			// Get an output stream 
			FileStream output = GetLocalFileStream(locFileName, remFileName); 
 
			Socket cSocket = createDataSocket(); 
 
			// Check if resume requested 
			if ( resume ) 
			{ 
				// Set the byte offset for the local file to end of file 
				locFileOffset = output.Length; 
				if (locFileOffset>0) 
				{ 
					// Some data already received prevously 
					DataReceivedEventArgs eDataReceived = new DataReceivedEventArgs(output.Length); 
					OnDataReceivedEvent(eDataReceived); 
				} 
				if ( locFileOffset > 0 | byteOffset > 0 ) 
				{ 
					// Send a RESTart command to the FTP server 
					this.sendCommand( "REST " + (locFileOffset + byteOffset)); 
					if ( this.resultCode != 350 ) 
					{ 
						// Server doesn't support resuming 
						OnResumeSupportedEvent(new ResumeSupportedEventArgs(false)); 
						locFileOffset = 0; 
						Debug.WriteLine("Resuming not supported:" + result.Substring(4), MODULE_NAME ); 
					} 
					else 
					{ 
						// Server supports resuming 
						OnResumeSupportedEvent(new ResumeSupportedEventArgs(true)); 
						Debug.WriteLine("Resuming at offset " + locFileOffset, MODULE_NAME ); 
						output.Seek( locFileOffset, SeekOrigin.Begin ); 
					} 
				} 
			} 
			 
			// Send ftp RETRieve command 
			this.sendCommand("RETR " + remFileName); 
 
			// Check the return code 
			if ( this.resultCode != 150 && this.resultCode != 125 ) 
			{ 
				throw new FtpException(this.result.Substring(4)); 
			} 
 
			do 
			{ 
				// Reset the timeout value for the next read 
				// NOTE: Need this so that each time a socket receive is successful, 
				//		 we wait an additional "timeout" value for the next receive 
				timeout = DateTime.Now.AddSeconds(this.timeoutSeconds); 
 
				// Get the data received 
				this.bytes = cSocket.Receive(buffer, buffer.Length, 0); 
 
				// Check if any data received 
				if ( this.bytes <= 0) 
				{ 
					// No data, so the file has finished downloading 
					Debug.WriteLine("No data received - assumed end of stream", MODULE_NAME); 
					stopPointReached = true; 
				} 
 
				// Check if we have reached stop download point, including data  
				// we have just received 
				if (!stopPointReached && byteStopDownload!=0 && (output.Length+buffer.Length>=byteStopDownload)) 
				{ 
					Debug.WriteLine("Stop download point reached", MODULE_NAME); 
					// Write the data to the output file UP TO the stop download point 
					Byte[] smallBuffer = CopyToSmallArray(this.buffer, (int)(byteStopDownload-output.Length+1)); 
					output.Write(smallBuffer, 0, smallBuffer.Length); 
					stopPointReached = true; 
				} 
				else 
				{ 
					// Write the data to the output file 
					output.Write(this.buffer, 0, this.bytes); 
				} 
 
				// Raise a data received event 
				try 
				{ 
					DataReceivedEventArgs eDataReceived = new DataReceivedEventArgs(output.Length); 
					OnDataReceivedEvent(eDataReceived); 
				} 
				catch (Exception eDataReceivedError) 
				{ 
					// Ignore event raise errors 
					// NOTE: I got errors here when the event CONSUMER raises an error - why? 
					Debug.WriteLine("An error occurred raising the Data Received event. [" + eDataReceivedError.Message + "]", MODULE_NAME); 
				} 
 
				// Check if the stop point was reached 
				if (stopPointReached) break; 
 
			} while ( timeout > DateTime.Now ); 
 
			output.Close(); 
 
			if ( cSocket.Connected ) cSocket.Close(); 
 
			this.readResponse(); 
 
			Debug.WriteLine("Download completed, response code is: " + this.resultCode, MODULE_NAME); 
 
			// Check if result code is ok 
			// EXCEPT if we manually stopped at a certain point (byteStopDownload) 
			if( this.resultCode != 226 && this.resultCode != 250 && byteStopDownload==0) 
				throw new FtpException(this.result.Substring(4)); 
 
			return DownloadResult.Finished; 
		} 
 
 
		///  
		/// Change the current working directory on the remote FTP server. 
		///  
		///  
		public void ChangeDir(string dirName) 
		{ 
			if( dirName == null || dirName.Equals(".") || dirName.Length == 0 ) 
			{ 
				return; 
			} 
 
			if ( !this.loggedin ) this.Login(); 
 
			this.sendCommand( "CWD " + dirName ); 
 
			if ( this.resultCode != 250 ) throw new FtpException(result.Substring(4)); 
 
			this.sendCommand( "PWD" ); 
 
			if ( this.resultCode != 257 ) throw new FtpException(result.Substring(4)); 
 
			// gonna have to do better than this.... 
			this.remotePath = this.message.Split('"')[1]; 
 
			Debug.WriteLine( "Current directory is " + this.remotePath, MODULE_NAME ); 
		} 
 
		///  
		/// Read the response from the server 
		///  
		private void readResponse() 
		{ 
			this.message = ""; 
			this.result = this.readLine(); 
 
			if ( this.result.Length > 3 ) 
				this.resultCode = int.Parse( this.result.Substring(0,3) ); 
			else 
				this.result = null; 
		} 
 
		///  
		/// Reads lines from the buffer and appends to the return message 
		///  
		///  
		private string readLine() 
		{ 
			while(true) 
			{ 
				this.bytes = clientSocket.Receive( this.buffer, this.buffer.Length, 0 ); 
				this.message += ASCII.GetString( this.buffer, 0, this.bytes ); 
 
				if ( this.bytes < this.buffer.Length ) 
				{ 
					break; 
				} 
			} 
 
			string[] msg = this.message.Split('\n'); 
 
			if ( this.message.Length > 2 ) 
				this.message = msg[ msg.Length - 2 ]; 
 
			else 
				this.message = msg[0]; 
 
 
			if ( this.message.Length > 4 && !this.message.Substring(3,1).Equals(" ") ) return this.readLine(); 
 
			if ( this.verboseDebugging ) 
			{ 
				for(int i = 0; i < msg.Length - 1; i++) 
				{ 
					Debug.Write( msg[i], MODULE_NAME ); 
				} 
			} 
 
			return message; 
		} 
 
		///  
		/// Sends a command to the server 
		///  
		///  
		private void sendCommand(String command) 
		{ 
			if ( this.verboseDebugging ) Debug.WriteLine(command, MODULE_NAME); 
 
			Byte[] cmdBytes = Encoding.ASCII.GetBytes( ( command + "\r\n" ).ToCharArray() ); 
			clientSocket.Send( cmdBytes, cmdBytes.Length, 0); 
			this.readResponse(); 
		} 
 
		///  
		/// When doing data transfers, we need to open another socket for it. 
		///  
		/// Connected socket 
		private new Socket createDataSocket() 
		{ 
			this.sendCommand("PASV"); 
 
			if ( this.resultCode != 227 ) throw new FtpException(this.result.Substring(4)); 
 
			int index1 = this.result.IndexOf('('); 
			int index2 = this.result.IndexOf(')'); 
 
			string ipData = this.result.Substring(index1+1,index2-index1-1); 
 
			int[] parts = new int[6]; 
 
			int len = ipData.Length; 
			int partCount = 0; 
			string buf=""; 
 
			for (int i = 0; i < len && partCount <= 6; i++) 
			{ 
				char ch = char.Parse( ipData.Substring(i,1) ); 
 
				if ( char.IsDigit(ch) ) 
					buf+=ch; 
 
				else if (ch != ',') 
					throw new FtpException("Malformed PASV result: " + result); 
 
				if ( ch == ',' || i+1 == len ) 
				{ 
					try 
					{ 
						parts[partCount++] = int.Parse(buf); 
						buf = ""; 
					} 
					catch (Exception ex) 
					{ 
						throw new FtpException("Malformed PASV result (not supported?): " + this.result, ex); 
					} 
				} 
			} 
 
			string ipAddress = parts[0] + "."+ parts[1]+ "." + parts[2] + "." + parts[3]; 
			int port = (parts[4] << 8) + parts[5]; 
 
			Socket socket = base.createDataSocket(ipAddress, port); 
			return socket; 
		} 
		 
		///  
		/// Always release those sockets. 
		///  
		private void cleanup() 
		{ 
			if ( this.clientSocket!=null ) 
			{ 
				this.clientSocket.Close(); 
				this.clientSocket = null; 
			} 
			this.loggedin = false; 
		} 
 
		///  
		/// Overridden function returns if resume is supported 
		///  
		/// true if resume is supported 
		public override bool CanResume() 
		{ 
			// Check if the server supports the RESTart command 
			this.sendCommand( "REST 0"); 
			if ( this.resultCode == 350 ) 
			{ 
				// Server can resume 
				Debug.WriteLine("Resume supported", MODULE_NAME); 
				return true; 
			} 
			else 
			{ 
				// Server doesn't support resume 
				Debug.WriteLine("Resume NOT supported", MODULE_NAME); 
				return false; 
			} 
		} 
 
		///  
		/// Overridden function checks if resume is supported by the server 
		///  
		/// remote filename 
		/// true if resume is supported 
		public override bool CanResume(string remFileName) 
		{ 
			return CanResume(); 
		} 
 
		public override FileDownload.FileDownloadCommandEnum Command 
		{ 
			get 
			{ 
				return command; 
			} 
			set 
			{ 
				command = value; 
			} 
		} 
 
		///  
		/// Destuctor 
		///  
		~FtpClient() 
		{ 
			this.cleanup(); 
		} 
 
	} 
}