context: using System;
  using System.IO;
  using System.Net.Sockets;
  using System.Text;
  using System.Text.RegularExpressions;
  using System.Reflection;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Linq;
  using System.Security.Cryptography.X509Certificates;
  using System.Globalization;
  using System.Security.Authentication;
  using System.Net;
  using FluentFTP.Proxy;
  #if !CORE
  using System.Web;
  #endif
  
  #if (CORE || NETFX)
  using System.Threading;
  #endif
  #if ASYNC
  using System.Threading.Tasks;
  #endif
  
  namespace FluentFTP {
  
  	/// <summary>
  	/// FTP Control Connection. Speaks the FTP protocol with the server and
  	/// provides facilities for performing transactions.
  	/// 
  	/// Debugging problems with FTP transactions is much easier to do when
  	/// you can see exactly what is sent to the server and the reply 
  	/// FluentFTP gets in return. Please review the Debug example
  	/// below for information on how to add <see cref="System.Diagnostics.TraceListener"/>s for capturing
  	/// the conversation between FluentFTP and the server.
  	/// </summary>
  	/// <example>The following example illustrates how to assist in debugging
  	/// FluentFTP by getting a transaction log from the server.
  	/// <code source="..\Examples\Debug.cs" lang="cs" />
  	/// </example>
  	/// <example>The following example demonstrates adding a custom file
  	/// listing parser in the event that you encounter a list format
  	/// not already supported.
  	/// <code source="..\Examples\CustomParser.cs" lang="cs" />
  	/// </example>
  	/// <example>The following example demonstrates how to validate
  	/// a SSL certificate when using SSL/TLS.
  	/// <code source="..\Examples\ValidateCertificate.cs" lang="cs" />
  	/// </example>
  	/// <example>The following example demonstrates how to download a file.
  	/// <code source="..\Examples\OpenRead.cs" lang="cs" />
  	/// </example>
  	/// <example>The following example demonstrates how to download a file
  	/// using a URI object.
  	/// <code source="..\Examples\OpenReadURI.cs" lang="cs" />
  	/// </example>
  	/// <example>The following example demonstrates how to upload a file.
  	/// <code source="..\Examples\OpenWrite.cs" lang="cs" />
  	/// </example>
  	/// <example>The following example demonstrates how to upload a file
  	/// using a URI object.
  	/// <code source="..\Examples\OpenWriteURI.cs" lang="cs" />
  	/// </example>
  	/// <example>The following example demonstrates how to append to a file.
  	/// <code source="..\Examples\OpenAppend.cs" lang="cs" />
  	/// </example>
  	/// <example>The following example demonstrates how to append to a file
  	/// using a URI object.
  	/// <code source="..\Examples\OpenAppendURI.cs" lang="cs" />
  	/// </example>
  	/// <example>The following example demonstrates how to get a file
  	/// listing from the server.
  	/// <code source="..\Examples\GetListing.cs" lang="cs" />
  	/// </example>
  	public partial class FtpClient : IDisposable {
  
  		#region File Hashing - HASH
  
  		/// <summary>
  		/// Gets the currently selected hash algorithm for the HASH command.
  		/// </summary>
  		/// <remarks>
  		///  This feature is experimental. See this link for details:
  		/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
  		/// </remarks>
  		/// <returns>The <see cref="FtpHashAlgorithm"/> flag or <see cref="FtpHashAlgorithm.NONE"/> if there was a problem.</returns>
  		/// <example><code source="..\Examples\GetHashAlgorithm.cs" lang="cs" /></example>
  		public FtpHashAlgorithm GetHashAlgorithm() {
  			FtpReply reply;
  			FtpHashAlgorithm type = FtpHashAlgorithm.NONE;
  
  #if !CORE14
  			lock (m_lock) {
  #endif
  				if ((reply = Execute("OPTS HASH")).Success) {
  					switch (reply.Message) {
  						case "SHA-1":
  							type = FtpHashAlgorithm.SHA1;
  							break;
  						case "SHA-256":
  							type = FtpHashAlgorithm.SHA256;
  							break;
  						case "SHA-512":
  							type = FtpHashAlgorithm.SHA512;
  							break;
  						case "MD5":
  							type = FtpHashAlgorithm.MD5;
  							break;
  					}
  				}
  #if !CORE14
  			}
  #endif
  
  			return type;
  		}
  
  		delegate FtpHashAlgorithm AsyncGetHashAlgorithm();
  
  		/// <summary>
  		/// Begins an asynchronous operation to get the currently selected hash algorithm for the HASH command.
  		/// </summary>
  		/// <remarks>
  		///  This feature is experimental. See this link for details:
  		/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
  		/// </remarks>
  		/// <param name="callback">Async callback</param>
  		/// <param name="state">State object</param>
  		/// <returns>IAsyncResult</returns>
  		public IAsyncResult BeginGetHashAlgorithm(AsyncCallback callback, object state) {
  			AsyncGetHashAlgorithm func;
  			IAsyncResult ar;
  
  			ar = (func = new AsyncGetHashAlgorithm(GetHashAlgorithm)).BeginInvoke(callback, state);
  			lock (m_asyncmethods) {
  				m_asyncmethods.Add(ar, func);
  			}
  
  			return ar;
  		}
  
  		/// <summary>
  		/// Ends a call to <see cref="BeginGetHashAlgorithm"/>
  		/// </summary>
  		/// <param name="ar">IAsyncResult returned from <see cref="BeginGetHashAlgorithm"/></param>
  		/// <returns>The <see cref="FtpHashAlgorithm"/> flag or <see cref="FtpHashAlgorithm.NONE"/> if there was a problem.</returns>
  		public FtpHashAlgorithm EndGetHashAlgorithm(IAsyncResult ar) {
  			return GetAsyncDelegate<AsyncGetHashAlgorithm>(ar).EndInvoke(ar);
  		}
  
  #if ASYNC
  		/// <summary>
  		/// Gets the currently selected hash algorithm for the HASH command asynchronously.
  		/// </summary>
  		/// <remarks>
  		///  This feature is experimental. See this link for details:
  		/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
  		/// </remarks>
  		/// <returns>The <see cref="FtpHashAlgorithm"/> flag or <see cref="FtpHashAlgorithm.NONE"/> if there was a problem.</returns>
  		public async Task<FtpHashAlgorithm> GetHashAlgorithmAsync() {
  			//TODO:  Rewrite as true async method with cancellation support
  			return await Task.Factory.FromAsync<FtpHashAlgorithm>(
  				(ac, s) => BeginGetHashAlgorithm(ac, s),
  				ar => EndGetHashAlgorithm(ar), null);
  		}
  #endif
  
  		/// <summary>
  		/// Sets the hash algorithm on the server to use for the HASH command. 
  		/// </summary>
  		/// <remarks>
  		/// If you specify an algorithm not listed in <see cref="FtpClient.HashAlgorithms"/>
  		/// a <see cref="NotImplementedException"/> will be thrown
  		/// so be sure to query that list of Flags before
  		/// selecting a hash algorithm. Support for the
  		/// HASH command is experimental. Please see
  		/// the following link for more details:
  		/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
  		/// </remarks>
  		/// <param name="type">Hash Algorithm</param>
  		/// <exception cref="System.NotImplementedException">Thrown if the selected algorithm is not available on the server</exception>
  		/// <example><code source="..\Examples\SetHashAlgorithm.cs" lang="cs" /></example>
  		public void SetHashAlgorithm(FtpHashAlgorithm type) {
  			FtpReply reply;
  			string algorithm;
  
  #if !CORE14
  			lock (m_lock) {
  #endif
  				if ((HashAlgorithms & type) != type)
  					throw new NotImplementedException(("The hash algorithm " + type.ToString() + " was not advertised by the server."));
  
  				switch (type) {
  					case FtpHashAlgorithm.SHA1:
  						algorithm = "SHA-1";
  						break;
  					case FtpHashAlgorithm.SHA256:
  						algorithm = "SHA-256";
  						break;
  					case FtpHashAlgorithm.SHA512:
  						algorithm = "SHA-512";
  						break;
  					case FtpHashAlgorithm.MD5:
  						algorithm = "MD5";
  						break;
  					default:
  						algorithm = type.ToString();
  						break;
  				}
  
  				if (!(reply = Execute("OPTS HASH " + algorithm)).Success)
  					throw new FtpCommandException(reply);
  #if !CORE14
  			}
  #endif
  		}
  
  		delegate void AsyncSetHashAlgorithm(FtpHashAlgorithm type);
  
  		/// <summary>
  		/// Begins an asynchronous operation to set the hash algorithm on the server to use for the HASH command. 
  		/// </summary>
  		/// <remarks>
  		/// If you specify an algorithm not listed in <see cref="FtpClient.HashAlgorithms"/>
  		/// a <see cref="NotImplementedException"/> will be thrown
  		/// so be sure to query that list of Flags before
  		/// selecting a hash algorithm. Support for the
  		/// HASH command is experimental. Please see
  		/// the following link for more details:
  		/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
  		/// </remarks>
  		/// <param name="type">Hash algorithm to use</param>
  		/// <param name="callback">Async Callback</param>
  		/// <param name="state">State object</param>
  		/// <returns>IAsyncResult</returns>
  		public IAsyncResult BeginSetHashAlgorithm(FtpHashAlgorithm type, AsyncCallback callback, object state) {
  			AsyncSetHashAlgorithm func;
  			IAsyncResult ar;
  
  			ar = (func = new AsyncSetHashAlgorithm(SetHashAlgorithm)).BeginInvoke(type, callback, state);
  			lock (m_asyncmethods) {
  				m_asyncmethods.Add(ar, func);
  			}
  
  			return ar;
  		}
  
  		/// <summary>
  		/// Ends an asynchronous call to <see cref="BeginSetHashAlgorithm"/>
  		/// </summary>
  		/// <param name="ar">IAsyncResult returned from <see cref="BeginSetHashAlgorithm"/></param>
  		public void EndSetHashAlgorithm(IAsyncResult ar) {
  			GetAsyncDelegate<AsyncSetHashAlgorithm>(ar).EndInvoke(ar);
  		}
  
  #if ASYNC
  		/// <summary>
  		/// Sets the hash algorithm on the server to be used with the HASH command asynchronously.
  		/// </summary>
  		/// <param name="type">Hash algorithm to use</param>
  		/// <exception cref="System.NotImplementedException">Thrown if the selected algorithm is not available on the server</exception>
  		public async Task SetHashAlgorithmAsync(FtpHashAlgorithm type) {
  			//TODO:  Rewrite as true async method with cancellation support
  			await Task.Factory.FromAsync<FtpHashAlgorithm>(
  				(t, ac, s) => BeginSetHashAlgorithm(t, ac, s),
  				ar => EndSetHashAlgorithm(ar),
  				type, null);
  		}
  #endif
  
  		/// <summary>
  		/// Gets the hash of an object on the server using the currently selected hash algorithm. 
  		/// </summary>
  		/// <remarks>
  		/// Supported algorithms, if any, are available in the <see cref="HashAlgorithms"/>
  		/// property. You should confirm that it's not equal
  		/// to <see cref="FtpHashAlgorithm.NONE"/> before calling this method
  		/// otherwise the server trigger a <see cref="FtpCommandException"/>
  		/// due to a lack of support for the HASH command. You can
  		/// set the algorithm using the <see cref="SetHashAlgorithm"/> method and
  		/// you can query the server for the current hash algorithm
  		/// using the <see cref="GetHashAlgorithm"/> method.
  		/// 
  		/// This feature is experimental and based on the following draft:
  		/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
  		/// </remarks>
  		/// <param name="path">Full or relative path of the object to compute the hash for.</param>
  		/// <returns>The hash of the file.</returns>
  		/// <exception cref="FtpCommandException">
  		/// Thrown if the <see cref="HashAlgorithms"/> property is <see cref="FtpHashAlgorithm.NONE"/>, 
  		/// the remote path does not exist, or the command cannot be executed.
  		/// </exception>
  		/// <exception cref="ArgumentException">Path argument is null</exception>
  		/// <exception cref="NotImplementedException">Thrown when an unknown hash algorithm type is returned by the server</exception>
  		/// <example><code source="..\Examples\GetHash.cs" lang="cs" /></example>
  		public FtpHash GetHash(string path) {
  			FtpReply reply;
  			FtpHash hash = new FtpHash();
  			Match m;
  
  			if (path == null)
  				throw new ArgumentException("GetHash(path) argument can't be null");
  
  #if !CORE14
  			lock (m_lock) {
  #endif
  				if (!(reply = Execute("HASH " + path.GetFtpPath())).Success)
  					throw new FtpCommandException(reply);
  #if !CORE14
  			}
  #endif
  
  			// Current draft says the server should return this:
  			// SHA-256 0-49 169cd22282da7f147cb491e559e9dd filename.ext
  			if (!(m = Regex.Match(reply.Message,
  					@"(?<algorithm>.+)\s" +
  					@"(?<bytestart>\d+)-(?<byteend>\d+)\s" +
  					@"(?<hash>.+)\s" +
  					@"(?<filename>.+)")).Success) {
  
  				// Current version of FileZilla returns this:
  				// SHA-1 21c2ca15cf570582949eb59fb78038b9c27ffcaf 
  				m = Regex.Match(reply.Message, @"(?<algorithm>.+)\s(?<hash>.+)\s");
  			}
  
  			if (m != null && m.Success) {
  				switch (m.Groups["algorithm"].Value) {
  					case "SHA-1":
  						hash.Algorithm = FtpHashAlgorithm.SHA1;
  						break;
  					case "SHA-256":
  						hash.Algorithm = FtpHashAlgorithm.SHA256;
  						break;
  					case "SHA-512":
  						hash.Algorithm = FtpHashAlgorithm.SHA512;
  						break;
  					case "MD5":
  						hash.Algorithm = FtpHashAlgorithm.MD5;
  						break;
  					default:
  						throw new NotImplementedException("Unknown hash algorithm: " + m.Groups["algorithm"].Value);
  				}
  
  				hash.Value = m.Groups["hash"].Value;
  			} else {
  				FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to parse hash from: " + reply.Message);
  			}
  
  			return hash;
  		}
  
  #if !CORE
  		delegate FtpHash AsyncGetHash(string path);
  
  		/// <summary>
  		/// Begins an asynchronous operation to get the hash of an object on the server using the currently selected hash algorithm. 
  		/// </summary>
  		/// <remarks>
  		/// Supported algorithms, if any, are available in the <see cref="HashAlgorithms"/>
  		/// property. You should confirm that it's not equal
  		/// to <see cref="FtpHashAlgorithm.NONE"/> before calling this method
  		/// otherwise the server trigger a <see cref="FtpCommandException"/>
  		/// due to a lack of support for the HASH command. You can
  		/// set the algorithm using the <see cref="SetHashAlgorithm"/> method and
  		/// you can query the server for the current hash algorithm
  		/// using the <see cref="GetHashAlgorithm"/> method.
  		/// 
  		/// This feature is experimental and based on the following draft:
  		/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
  		/// </remarks>
  		/// <param name="path">The file you want the server to compute the hash for</param>
  		/// <param name="callback">AsyncCallback</param>
  		/// <param name="state">State object</param>
  		/// <returns>IAsyncResult</returns>
  		public IAsyncResult BeginGetHash(string path, AsyncCallback callback, object state) {
  			AsyncGetHash func;
  			IAsyncResult ar;
  
  			ar = (func = new AsyncGetHash(GetHash)).BeginInvoke(path, callback, state);
  			lock (m_asyncmethods) {
  				m_asyncmethods.Add(ar, func);
  			}
  
  			return ar;
  		}
  
  		/// <summary>
  		/// Ends an asynchronous call to <see cref="BeginGetHash"/>
  		/// </summary>
  		/// <param name="ar">IAsyncResult returned from <see cref="BeginGetHash"/></param>
  		public FtpHash EndGetHash(IAsyncResult ar) {
  			return GetAsyncDelegate<AsyncGetHash>(ar).EndInvoke(ar);
  		}
  
  #endif
  #if ASYNC
  		/// <summary>
  		/// Gets the hash of an object on the server using the currently selected hash algorithm asynchronously. 
  		/// </summary>
  		/// <remarks>
  		/// Supported algorithms, if any, are available in the <see cref="HashAlgorithms"/>
  		/// property. You should confirm that it's not equal
  		/// to <see cref="FtpHashAlgorithm.NONE"/> before calling this method
  		/// otherwise the server trigger a <see cref="FtpCommandException"/>
  		/// due to a lack of support for the HASH command. You can
  		/// set the algorithm using the <see cref="SetHashAlgorithm"/> method and
  		/// you can query the server for the current hash algorithm
  		/// using the <see cref="GetHashAlgorithm"/> method.
  		/// 
  		/// This feature is experimental and based on the following draft:
  		/// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02
  		/// </remarks>
  		/// <param name="path">The file you want the server to compute the hash for</param>
  		/// <exception cref="FtpCommandException">
  		/// Thrown if the <see cref="HashAlgorithms"/> property is <see cref="FtpHashAlgorithm.NONE"/>, 
  		/// the remote path does not exist, or the command cannot be executed.
  		/// </exception>
  		/// <exception cref="ArgumentException">Path argument is null</exception>
  		/// <exception cref="NotImplementedException">Thrown when an unknown hash algorithm type is returned by the server</exception>
  		/// <returns>The hash of the file.</returns>
  		public async Task<FtpHash> GetHashAsync(string path) {
  			//TODO:  Add cancellation support
  			FtpReply reply;
  			FtpHash hash = new FtpHash();
  			Match m;
  
  			if (path == null)
  				throw new ArgumentException("GetHash(path) argument can't be null");
  
  			if (!(reply = await ExecuteAsync("HASH " + path.GetFtpPath())).Success)
  				throw new FtpCommandException(reply);
  
  			// Current draft says the server should return this:
  			// SHA-256 0-49 169cd22282da7f147cb491e559e9dd filename.ext
  			if (!(m = Regex.Match(reply.Message,
  					@"(?<algorithm>.+)\s" +
  					@"(?<bytestart>\d+)-(?<byteend>\d+)\s" +
  					@"(?<hash>.+)\s" +
  					@"(?<filename>.+)")).Success)
  			{
  
  				// Current version of FileZilla returns this:
  				// SHA-1 21c2ca15cf570582949eb59fb78038b9c27ffcaf 
  				m = Regex.Match(reply.Message, @"(?<algorithm>.+)\s(?<hash>.+)\s");
  			}
  
  			if (m != null && m.Success)
  			{
  				switch (m.Groups["algorithm"].Value)
  				{
  					case "SHA-1":
  						hash.Algorithm = FtpHashAlgorithm.SHA1;
  						break;
  					case "SHA-256":
  						hash.Algorithm = FtpHashAlgorithm.SHA256;
  						break;
  					case "SHA-512":
  						hash.Algorithm = FtpHashAlgorithm.SHA512;
  						break;
  					case "MD5":
  						hash.Algorithm = FtpHashAlgorithm.MD5;
  						break;
  					default:
  						throw new NotImplementedException("Unknown hash algorithm: " + m.Groups["algorithm"].Value);
  				}
  
  				hash.Value = m.Groups["hash"].Value;
  			}
  			else
  			{
  				FtpTrace.WriteStatus(FtpTraceLevel.Warn, "Failed to parse hash from: " + reply.Message);
  			}
  
  			return hash;
  		}
  #endif
  
  		#endregion
  
  		#region File Checksum
  
  		/// <summary>
  		/// Retrieves a checksum of the given file using a checksum method that the server supports, if any. 
  		/// </summary>
  		/// <remarks>
  		/// The algorithm used goes in this order:
  		/// 1. HASH command; server preferred algorithm. See <see cref="FtpClient.SetHashAlgorithm"/>
  		/// 2. MD5 / XMD5 commands
  		/// 3. XSHA1 command
  		/// 4. XSHA256 command
  		/// 5. XSHA512 command
  		/// 6. XCRC command
  		/// </remarks>
  		/// <param name="path">Full or relative path of the file to checksum</param>
  		/// <returns><see cref="FtpHash"/> object containing the value and algorithm. Use the <see cref="FtpHash.IsValid"/> property to
  		/// determine if this command was successful. <see cref="FtpCommandException"/>s can be thrown from
  		/// the underlying calls.</returns>
  		/// <example><code source="..\Examples\GetChecksum.cs" lang="cs" /></example>
  		/// <exception cref="FtpCommandException">The command fails</exception>
  		public FtpHash GetChecksum(string path) {
  			if (HasFeature(FtpCapability.HASH)) {
  				return GetHash(path);
  			} else {
  				FtpHash res = new FtpHash();
  
  				if (HasFeature(FtpCapability.MD5)) {
  					res.Value = GetMD5(path);
  					res.Algorithm = FtpHashAlgorithm.MD5;
  				} else if (HasFeature(FtpCapability.XMD5)) {
  					res.Value = GetXMD5(path);
  					res.Algorithm = FtpHashAlgorithm.MD5;
  				} else if (HasFeature(FtpCapability.XSHA1)) {
  					res.Value = GetXSHA1(path);
  					res.Algorithm = FtpHashAlgorithm.SHA1;
  				} else if (HasFeature(FtpCapability.XSHA256)) {
  					res.Value = GetXSHA256(path);
  					res.Algorithm = FtpHashAlgorithm.SHA256;
  				} else if (HasFeature(FtpCapability.XSHA512)) {
  					res.Value = GetXSHA512(path);
  					res.Algorithm = FtpHashAlgorithm.SHA512;
  				} else if (HasFeature(FtpCapability.XCRC)) {
  					res.Value = GetXCRC(path);
  					res.Algorithm = FtpHashAlgorithm.CRC;
  				}
  
  				return res;
  			}
  		}
  
  #if !CORE
  		delegate FtpHash AsyncGetChecksum(string path);
  
  		/// <summary>
  		/// Begins an asynchronous operation to retrieve a checksum of the given file using a checksum method that the server supports, if any. 
  		/// </summary>
  		/// <remarks>
  		/// The algorithm used goes in this order:
  		/// 1. HASH command; server preferred algorithm. See <see cref="FtpClient.SetHashAlgorithm"/>
  		/// 2. MD5 / XMD5 commands
  		/// 3. XSHA1 command
  		/// 4. XSHA256 command
  		/// 5. XSHA512 command
  		/// 6. XCRC command
  		/// </remarks>
  		/// <param name="path">Full or relative path to remote file</param>
  		/// <param name="callback">AsyncCallback</param>
  		/// <param name="state">State Object</param>
  		/// <returns>IAsyncResult</returns>
  		public IAsyncResult BeginGetChecksum(string path, AsyncCallback callback,
  			object state) {
  			AsyncGetChecksum func = new AsyncGetChecksum(GetChecksum);
  			IAsyncResult ar = func.BeginInvoke(path, callback, state);
  			;
  
  
gt: 			lock (m_asyncmethods) {