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 { /// /// 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 s for capturing /// the conversation between FluentFTP and the server. /// /// The following example illustrates how to assist in debugging /// FluentFTP by getting a transaction log from the server. /// /// /// The following example demonstrates adding a custom file /// listing parser in the event that you encounter a list format /// not already supported. /// /// /// The following example demonstrates how to validate /// a SSL certificate when using SSL/TLS. /// /// /// The following example demonstrates how to download a file. /// /// /// The following example demonstrates how to download a file /// using a URI object. /// /// /// The following example demonstrates how to upload a file. /// /// /// The following example demonstrates how to upload a file /// using a URI object. /// /// /// The following example demonstrates how to append to a file. /// /// /// The following example demonstrates how to append to a file /// using a URI object. /// /// /// The following example demonstrates how to get a file /// listing from the server. /// /// public partial class FtpClient : IDisposable { #region Active/Passive Streams /// /// Opens the specified type of passive data stream /// /// Type of passive data stream to open /// The command to execute that requires a data stream /// Restart location in bytes for file transfer /// A data stream ready to be used FtpDataStream OpenPassiveDataStream(FtpDataConnectionType type, string command, long restart) { FtpTrace.WriteFunc("OpenPassiveDataStream", new object[] { type, command, restart }); FtpDataStream stream = null; FtpReply reply; Match m; string host = null; int port = 0; if (m_stream == null) throw new InvalidOperationException("The control connection stream is null! Generally this means there is no connection to the server. Cannot open a passive data stream."); if (type == FtpDataConnectionType.EPSV || type == FtpDataConnectionType.AutoPassive) { if (!(reply = Execute("EPSV")).Success) { // if we're connected with IPv4 and data channel type is AutoPassive then fallback to IPv4 if (reply.Type == FtpResponseType.PermanentNegativeCompletion && type == FtpDataConnectionType.AutoPassive && m_stream != null && m_stream.LocalEndPoint.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) return OpenPassiveDataStream(FtpDataConnectionType.PASV, command, restart); throw new FtpCommandException(reply); } m = Regex.Match(reply.Message, @"\(\|\|\|(?\d+)\|\)"); if (!m.Success) { throw new FtpException("Failed to get the EPSV port from: " + reply.Message); } host = m_host; port = int.Parse(m.Groups["port"].Value); } else { if (m_stream.LocalEndPoint.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) throw new FtpException("Only IPv4 is supported by the PASV command. Use EPSV instead."); if (!(reply = Execute("PASV")).Success) throw new FtpCommandException(reply); m = Regex.Match(reply.Message, @"(?\d+)," + @"(?\d+)," + @"(?\d+)," + @"(?\d+)," + @"(?\d+)," + @"(?\d+)"); if (!m.Success || m.Groups.Count != 7) throw new FtpException(("Malformed PASV response: " + reply.Message)); // PASVEX mode ignores the host supplied in the PASV response if (type == FtpDataConnectionType.PASVEX) host = m_host; else host = (m.Groups["quad1"].Value + "." + m.Groups["quad2"].Value + "." + m.Groups["quad3"].Value + "." + m.Groups["quad4"].Value); port = (int.Parse(m.Groups["port1"].Value) << 8) + int.Parse(m.Groups["port2"].Value); } stream = new FtpDataStream(this); stream.ConnectTimeout = DataConnectionConnectTimeout; stream.ReadTimeout = DataConnectionReadTimeout; Connect(stream, host, port, InternetProtocolVersions); stream.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.KeepAlive, m_keepAlive); if (restart > 0) { if (!(reply = Execute("REST " + restart)).Success) throw new FtpCommandException(reply); } if (!(reply = Execute(command)).Success) { stream.Close(); throw new FtpCommandException(reply); } // the command status is used to determine // if a reply needs to be read from the server // when the stream is closed so always set it // otherwise things can get out of sync. stream.CommandStatus = reply; #if !NO_SSL // this needs to take place after the command is executed if (m_dataConnectionEncryption && m_encryptionmode != FtpEncryptionMode.None) { stream.ActivateEncryption(m_host, this.ClientCertificates.Count > 0 ? this.ClientCertificates : null, m_SslProtocols); } #endif return stream; } #if ASYNC /// /// Opens the specified type of passive data stream /// /// Type of passive data stream to open /// The command to execute that requires a data stream /// Restart location in bytes for file transfer /// A data stream ready to be used async Task OpenPassiveDataStreamAsync(FtpDataConnectionType type, string command, long restart) { FtpTrace.WriteFunc(nameof(OpenPassiveDataStreamAsync), new object[] { type, command, restart }); FtpDataStream stream = null; FtpReply reply; Match m; string host = null; int port = 0; if (m_stream == null) throw new InvalidOperationException("The control connection stream is null! Generally this means there is no connection to the server. Cannot open a passive data stream."); if (type == FtpDataConnectionType.EPSV || type == FtpDataConnectionType.AutoPassive) { if (!(reply = await ExecuteAsync("EPSV")).Success) { // if we're connected with IPv4 and data channel type is AutoPassive then fallback to IPv4 if (reply.Type == FtpResponseType.PermanentNegativeCompletion && type == FtpDataConnectionType.AutoPassive && m_stream != null && m_stream.LocalEndPoint.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) return await OpenPassiveDataStreamAsync(FtpDataConnectionType.PASV, command, restart); throw new FtpCommandException(reply); } m = Regex.Match(reply.Message, @"\(\|\|\|(?\d+)\|\)"); if (!m.Success) { throw new FtpException("Failed to get the EPSV port from: " + reply.Message); } host = m_host; port = int.Parse(m.Groups["port"].Value); } else { if (m_stream.LocalEndPoint.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) throw new FtpException("Only IPv4 is supported by the PASV command. Use EPSV instead."); if (!(reply = await ExecuteAsync("PASV")).Success) throw new FtpCommandException(reply); m = Regex.Match(reply.Message, @"(?\d+)," + @"(?\d+)," + @"(?\d+)," + @"(?\d+)," + @"(?\d+)," + @"(?\d+)"); if (!m.Success || m.Groups.Count != 7) throw new FtpException(("Malformed PASV response: " + reply.Message)); // PASVEX mode ignores the host supplied in the PASV response if (type == FtpDataConnectionType.PASVEX) host = m_host; else host = (m.Groups["quad1"].Value + "." + m.Groups["quad2"].Value + "." + m.Groups["quad3"].Value + "." + m.Groups["quad4"].Value); port = (int.Parse(m.Groups["port1"].Value) << 8) + int.Parse(m.Groups["port2"].Value); } stream = new FtpDataStream(this); stream.ConnectTimeout = DataConnectionConnectTimeout; stream.ReadTimeout = DataConnectionReadTimeout; await ConnectAsync(stream, host, port, InternetProtocolVersions); stream.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.KeepAlive, m_keepAlive); if (restart > 0) { if (!(reply = await ExecuteAsync("REST " + restart)).Success) throw new FtpCommandException(reply); } if (!(reply = await ExecuteAsync(command)).Success) { stream.Close(); throw new FtpCommandException(reply); } // the command status is used to determine // if a reply needs to be read from the server // when the stream is closed so always set it // otherwise things can get out of sync. stream.CommandStatus = reply; #if !NO_SSL // this needs to take place after the command is executed if (m_dataConnectionEncryption && m_encryptionmode != FtpEncryptionMode.None) { await stream.ActivateEncryptionAsync(m_host, this.ClientCertificates.Count > 0 ? this.ClientCertificates : null, m_SslProtocols); } #endif return stream; } #endif /// /// Returns the ip address to be sent to the server for the active connection /// /// /// string GetLocalAddress(IPAddress ip) { // Use resolver if (m_AddressResolver != null) { return m_Address ?? (m_Address = m_AddressResolver()); } // Use supplied ip return ip.ToString(); } /// /// Opens the specified type of active data stream /// /// Type of passive data stream to open /// The command to execute that requires a data stream /// Restart location in bytes for file transfer /// A data stream ready to be used FtpDataStream OpenActiveDataStream(FtpDataConnectionType type, string command, long restart) { FtpTrace.WriteFunc("OpenActiveDataStream", new object[] { type, command, restart }); FtpDataStream stream = new FtpDataStream(this); FtpReply reply; #if !CORE IAsyncResult ar; #endif if (m_stream == null) throw new InvalidOperationException("The control connection stream is null! Generally this means there is no connection to the server. Cannot open an active data stream."); if (m_ActivePorts == null || !m_ActivePorts.Any()) { // Use random port stream.Listen(m_stream.LocalEndPoint.Address, 0); } else { var success = false; // Use one of the specified ports foreach (var port in m_ActivePorts) { try { stream.Listen(m_stream.LocalEndPoint.Address, port); success = true; } catch (SocketException) { #if NETFX // Already in use if (se.ErrorCode != 10048) throw; #else throw; #endif } } // No usable port found if (!success) throw new Exception("No valid active data port available!"); } #if !CORE ar = stream.BeginAccept(null, null); #endif if (type == FtpDataConnectionType.EPRT || type == FtpDataConnectionType.AutoActive) { int ipver = 0; switch (stream.LocalEndPoint.AddressFamily) { case System.Net.Sockets.AddressFamily.InterNetwork: ipver = 1; // IPv4 break; case System.Net.Sockets.AddressFamily.InterNetworkV6: ipver = 2; // IPv6 break; default: throw new InvalidOperationException("The IP protocol being used is not supported."); } if (!(reply = Execute("EPRT |" + ipver + "|" + GetLocalAddress(stream.LocalEndPoint.Address) + "|" + stream.LocalEndPoint.Port + "|")).Success) { // if we're connected with IPv4 and the data channel type is AutoActive then try to fall back to the PORT command if (reply.Type == FtpResponseType.PermanentNegativeCompletion && type == FtpDataConnectionType.AutoActive && m_stream != null && m_stream.LocalEndPoint.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { stream.ControlConnection = null; // we don't want this failed EPRT attempt to close our control connection when the stream is closed so clear out the reference. stream.Close(); return OpenActiveDataStream(FtpDataConnectionType.PORT, command, restart); } else { stream.Close(); throw new FtpCommandException(reply); } } } else { if (m_stream.LocalEndPoint.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) throw new FtpException("Only IPv4 is supported by the PORT command. Use EPRT instead."); if (!(reply = Execute("PORT " + GetLocalAddress(stream.LocalEndPoint.Address).Replace('.', ',') + "," + stream.LocalEndPoint.Port / 256 + "," + stream.LocalEndPoint.Port % 256)).Success) { stream.Close(); throw new FtpCommandException(reply); } } if (restart > 0) { if (!(reply = Execute("REST " + restart)).Success) throw new FtpCommandException(reply); } if (!(reply = Execute(command)).Success) { stream.Close(); throw new FtpCommandException(reply); } // the command status is used to determine // if a reply needs to be read from the server // when the stream is closed so always set it // otherwise things can get out of sync. stream.CommandStatus = reply; #if CORE stream.AcceptAsync().Wait(); #else ar.AsyncWaitHandle.WaitOne(m_dataConnectionConnectTimeout); if (!ar.IsCompleted) { stream.Close(); throw new TimeoutException("Timed out waiting for the server to connect to the active data socket."); } stream.EndAccept(ar); #endif #if !NO_SSL if (m_dataConnectionEncryption && m_encryptionmode != FtpEncryptionMode.None) { stream.ActivateEncryption(m_host, this.ClientCertificates.Count > 0 ? this.ClientCertificates : null, m_SslProtocols); } #endif stream.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.KeepAlive, m_keepAlive); stream.ReadTimeout = m_dataConnectionReadTimeout; return stream; } #if ASYNC /// /// Opens the specified type of active data stream /// /// Type of passive data stream to open /// The command to execute that requires a data stream /// Restart location in bytes for file transfer /// A data stream ready to be used async Task OpenActiveDataStreamAsync(FtpDataConnectionType type, string command, long restart) { FtpTrace.WriteFunc(nameof(OpenActiveDataStreamAsync), new object[] { type, command, restart }); FtpDataStream stream = new FtpDataStream(this); FtpReply reply; if (m_stream == null) throw new InvalidOperationException("The control connection stream is null! Generally this means there is no connection to the server. Cannot open an active data stream."); if (m_ActivePorts == null || !m_ActivePorts.Any()) { // Use random port stream.Listen(m_stream.LocalEndPoint.Address, 0); } else { var success = false; // Use one of the specified ports foreach (var port in m_ActivePorts) { try { stream.Listen(m_stream.LocalEndPoint.Address, port); success = true; } catch (SocketException) { #if NETFX // Already in use if (se.ErrorCode != 10048) throw; #else throw; #endif } } // No usable port found if (!success) throw new Exception("No valid active data port available!"); } var result = stream.AcceptAsync(); if (type == FtpDataConnectionType.EPRT || type == FtpDataConnectionType.AutoActive) { int ipver = 0; switch (stream.LocalEndPoint.AddressFamily) { case System.Net.Sockets.AddressFamily.InterNetwork: ipver = 1; // IPv4 break; case System.Net.Sockets.AddressFamily.InterNetworkV6: ipver = 2; // IPv6 break; default: throw new InvalidOperationException("The IP protocol being used is not supported."); } if (!(reply = await ExecuteAsync("EPRT |" + ipver + "|" + GetLocalAddress(stream.LocalEndPoint.Address) + "|" + stream.LocalEndPoint.Port + "|")).Success) { // if we're connected with IPv4 and the data channel type is AutoActive then try to fall back to the PORT command if (reply.Type == FtpResponseType.PermanentNegativeCompletion && type == FtpDataConnectionType.AutoActive && m_stream != null && m_stream.LocalEndPoint.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { stream.ControlConnection = null; // we don't want this failed EPRT attempt to close our control connection when the stream is closed so clear out the reference. stream.Close(); return await OpenActiveDataStreamAsync(FtpDataConnectionType.PORT, command, restart); } else { stream.Close(); throw new FtpCommandException(reply); } } } else { if (m_stream.LocalEndPoint.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) throw new FtpException("Only IPv4 is supported by the PORT command. Use EPRT instead."); if (!(reply = await ExecuteAsync("PORT " + GetLocalAddress(stream.LocalEndPoint.Address).Replace('.', ',') + "," + stream.LocalEndPoint.Port / 256 + "," + stream.LocalEndPoint.Port % 256)).Success) { stream.Close(); throw new FtpCommandException(reply); } } if (restart > 0) { if (!(reply = await ExecuteAsync("REST " + restart)).Success) throw new FtpCommandException(reply); } if (!(reply = await ExecuteAsync(command)).Success) { stream.Close(); throw new FtpCommandException(reply); } // the command status is used to determine // if a reply needs to be read from the server // when the stream is closed so always set it // otherwise things can get out of sync. stream.CommandStatus = reply; await result; #if !NO_SSL if (m_dataConnectionEncryption && m_encryptionmode != FtpEncryptionMode.None) { await stream.ActivateEncryptionAsync(m_host, this.ClientCertificates.Count > 0 ? this.ClientCertificates : null, m_SslProtocols); } #endif stream.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.KeepAlive, m_keepAlive); stream.ReadTimeout = m_dataConnectionReadTimeout; return stream; } #endif /// /// Opens a data stream. /// /// The command to execute that requires a data stream /// Restart location in bytes for file transfer /// The data stream. FtpDataStream OpenDataStream(string command, long restart) { FtpDataConnectionType type = m_dataConnectionType; FtpDataStream stream = null; #if !CORE14 lock (m_lock) { #endif if (!IsConnected) Connect(); // The PORT and PASV commands do not work with IPv6 so // if either one of those types are set change them // to EPSV or EPRT appropriately. if (m_stream.LocalEndPoint.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) { switch (type) { case FtpDataConnectionType.PORT: type = FtpDataConnectionType.EPRT; FtpTrace.WriteLine(FtpTraceLevel.Info, "Changed data connection type to EPRT because we are connected with IPv6."); break; case FtpDataConnectionType.PASV: case FtpDataConnectionType.PASVEX: type = FtpDataConnectionType.EPSV; FtpTrace.WriteLine(FtpTraceLevel.Info, "Changed data connection type to EPSV because we are connected with IPv6."); break; } } switch (type) { case FtpDataConnectionType.AutoPassive: case FtpDataConnectionType.EPSV: case FtpDataConnectionType.PASV: case FtpDataConnectionType.PASVEX: stream = OpenPassiveDataStream(type, command, restart); break; case FtpDataConnectionType.AutoActive: case FtpDataConnectionType.EPRT: case FtpDataConnectionType.PORT: stream = OpenActiveDataStream(type, command, restart); break; } if (stream == null) throw new InvalidOperationException("The specified data channel type is not implemented."); #if !CORE14 } #endif return stream; } #if ASYNC /// /// Opens a data stream. /// /// The command to execute that requires a data stream /// Restart location in bytes for file transfer /// The data stream. async Task OpenDataStreamAsync(string command, long restart) { FtpDataConnectionType type = m_dataConnectionType; FtpDataStream stream = null; if (!IsConnected) await ConnectAsync(); // The PORT and PASV commands do not work with IPv6 so // if either one of those types are set change them // to EPSV or EPRT appropriately. if (m_stream.LocalEndPoint.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) { switch (type) { case FtpDataConnectionType.PORT: type = FtpDataConnectionType.EPRT; FtpTrace.WriteLine(FtpTraceLevel.Info, "Changed data connection type to EPRT because we are connected with IPv6."); break; case FtpDataConnectionType.PASV: case FtpDataConnectionType.PASVEX: type = FtpDataConnectionType.EPSV; FtpTrace.WriteLine(FtpTraceLevel.Info, "Changed data connection type to EPSV because we are connected with IPv6."); break; } } switch (type) { case FtpDataConnectionType.AutoPassive: case FtpDataConnectionType.EPSV: case FtpDataConnectionType.PASV: case FtpDataConnectionType.PASVEX: stream = await OpenPassiveDataStreamAsync(type, command, restart); break; case FtpDataConnectionType.AutoActive: case FtpDataConnectionType.EPRT: case FtpDataConnectionType.PORT: stream = await OpenActiveDataStreamAsync(type, command, restart); break; } if (stream == null) throw new InvalidOperationException("The specified data channel type is not implemented."); return stream; } #endif /// /// Disconnects a data stream /// /// The data stream to close internal FtpReply CloseDataStream(FtpDataStream stream) { FtpTrace.WriteFunc("CloseDataStream"); FtpReply reply = new FtpReply(); if (stream == null) throw new ArgumentException("The data stream parameter was null"); #if !CORE14 lock (m_lock) { #endif try { if (IsConnected) { // if the command that required the data connection was // not successful then there will be no reply from // the server, however if the command was successful // the server will send a reply when the data connection // is closed. if (stream.CommandStatus.Type == FtpResponseType.PositivePreliminary) { if (!(reply = GetReply()).Success) { throw new FtpCommandException(reply); } } } } finally { // if this is a clone of the original control // connection we should Dispose() if (IsClone) { Disconnect(); Dispose(); } } #if !CORE14 } #endif return reply; } #endregion #region Open Read /// /// Opens the specified file for reading /// /// The full or relative path of the file /// A stream for reading the file on the server /// public Stream OpenRead(string path) { return OpenRead(path, FtpDataType.Binary, 0, true); } /// /// Opens the specified file for reading /// /// The full or relative path of the file /// ASCII/Binary /// A stream for reading the file on the server /// public Stream OpenRead(string path, FtpDataType type) { return OpenRead(path, type, 0, true); } /// /// Opens the specified file for reading /// /// The full or relative path of the file /// ASCII/Binary /// Only set this to false if you are SURE that the file does not exist. If true, it reads the file size and saves it into the stream length. /// A stream for reading the file on the server /// public Stream OpenRead(string path, FtpDataType type, bool checkIfFileExists) { return OpenRead(path, type, 0, checkIfFileExists); } /// /// Opens the specified file for reading /// /// The full or relative path of the file /// ASCII/Binary /// Resume location /// A stream for reading the file on the server /// public virtual Stream OpenRead(string path, FtpDataType type, long restart) { return OpenRead(path, type, restart, true); } /// /// Opens the specified file for reading /// /// The full or relative path of the file /// Resume location /// A stream for reading the file on the server /// public Stream OpenRead(string path, long restart) { return OpenRead(path, FtpDataType.Binary, restart, true); } /// /// Opens the specified file for reading /// /// The full or relative path of the file /// Resume location /// Only set this to false if you are SURE that the file does not exist. If true, it reads the file size and saves it into the stream length. /// A stream for reading the file on the server /// public Stream OpenRead(string path, long restart, bool checkIfFileExists) { return OpenRead(path, FtpDataType.Binary, restart, checkIfFileExists); } /// /// Opens the specified file for reading /// /// The full or relative path of the file /// ASCII/Binary /// Resume location /// Only set this to false if you are SURE that the file does not exist. If true, it reads the file size and saves it into the stream length. /// A stream for reading the file on the server /// public virtual Stream OpenRead(string path, FtpDataType type, long restart, bool checkIfFileExists) { // verify args if (path.IsBlank()) throw new ArgumentException("Required parameter is null or blank.", "path"); FtpTrace.WriteFunc("OpenRead", new object[] { path, type, restart }); FtpClient client = null; FtpDataStream stream = null; long length = 0; #if !CORE14 lock (m_lock) { #endif this.SetDataType(type); if (m_threadSafeDataChannels) { client = CloneConnection(); client.Connect(); client.SetWorkingDirectory(GetWorkingDirectory()); } else { client = this; } length = checkIfFileExists ? client.GetFileSize(path) : 0; stream = client.OpenDataStream(("RETR " + path.GetFtpPath()), restart); #if !CORE14 } #endif if (stream != null) { if (length > 0) stream.SetLength(length); if (restart > 0) stream.SetPosition(restart); } return stream; } #if !CORE /// /// Begins an asynchronous operation to open the specified file for reading /// /// The full or relative path of the file /// Async Callback /// State object /// IAsyncResult /// public IAsyncResult BeginOpenRead(string path, AsyncCallback callback, object state) { return BeginOpenRead(path, FtpDataType.Binary, 0, callback, state); } /// /// Opens the specified file for reading /// /// The full or relative path of the file /// ASCII/Binary /// Async Callback /// State object /// IAsyncResult /// public IAsyncResult BeginOpenRead(string path, FtpDataType type, AsyncCallback callback, object state) { return BeginOpenRead(path, type, 0, callback, state); } /// /// Begins an asynchronous operation to open the specified file for reading /// /// The full or relative path of the file /// Resume location /// Async Callback /// State object /// IAsyncResult /// public IAsyncResult BeginOpenRead(string path, long restart, AsyncCallback callback, object state) { return BeginOpenRead(path, FtpDataType.Binary, restart, callback, state); } delegate Stream AsyncOpenRead(string path, FtpDataType type, long restart); /// /// Begins an asynchronous operation to open the specified file for reading /// /// The full or relative path of the file /// ASCII/Binary /// Resume location /// Async Callback /// State object /// IAsyncResult /// public IAsyncResult BeginOpenRead(string path, FtpDataType type, long restart, AsyncCallback callback, object state) { AsyncOpenRead func; IAsyncResult ar; ar = (func = new AsyncOpenRead(OpenRead)).BeginInvoke(path, type, restart, callback, state); lock (m_asyncmethods) { m_asyncmethods.Add(ar, func); } return ar; } /// /// Ends a call to /// /// returned from /// A readable stream of the remote file /// public Stream EndOpenRead(IAsyncResult ar) { return GetAsyncDelegate(ar).EndInvoke(ar); } #endif #if ASYNC /// /// Opens the specified file for reading asynchronously /// /// The full or relative path of the file /// ASCII/Binary /// Resume location /// Only set this to false if you are SURE that the file does not exist. If true, it reads the file size and saves it into the stream length. /// A stream for reading the file on the server public virtual async Task OpenReadAsync(string path, FtpDataType type, long restart, bool checkIfFileExists) { // TODO: Add cancellation support // verify args if (path.IsBlank()) throw new ArgumentException("Required parameter is null or blank.", "path"); FtpTrace.WriteFunc(nameof(OpenReadAsync), new object[] { path, type, restart }); FtpClient client = null; FtpDataStream stream = null; long length = 0; if (m_threadSafeDataChannels) { client = CloneConnection(); await client.ConnectAsync(); await client.SetWorkingDirectoryAsync(await GetWorkingDirectoryAsync()); } else { client = this; } await client.SetDataTypeAsync(type); length = checkIfFileExists ? await client.GetFileSizeAsync(path) : 0; stream = await client.OpenDataStreamAsync(("RETR " + path.GetFtpPath()), restart); if (stream != null) { if (length > 0) stream.SetLength(length); if (restart > 0) stream.SetPosition(restart); } return stream; } /// /// Opens the specified file for reading asynchronously /// /// The full or relative path of the file /// ASCII/Binary /// Resume location /// A readable stream of the remote file public Task OpenReadAsync(string path, FtpDataType type, long restart) { //TODO: Add cancellation support return OpenReadAsync(path, type, restart, true); } /// /// Opens the specified file for reading asynchronously /// /// The full or relative path of the file /// ASCII/Binary /// A readable stream of the remote file public Task OpenReadAsync(string path, FtpDataType type) { //TODO: Add cancellation support return OpenReadAsync(path, type, 0, true); } /// /// Opens the specified file for reading asynchronously /// /// The full or relative path of the file /// Resume location /// A readable stream of the remote file public Task OpenReadAsync(string path, long restart) { //TODO: Add cancellation support return OpenReadAsync(path, FtpDataType.Binary, restart, true); } /// /// Opens the specified file for reading asynchronously /// /// The full or relative path of the file /// A readable stream of the remote file public Task OpenReadAsync(string path) { //TODO: Add cancellation support return OpenReadAsync(path, FtpDataType.Binary, 0, true); } #endif #endregion #region Open Write /// /// Opens the specified file for writing. Please call GetReply() after you have successfully transfered the file to read the "OK" command sent by the server and prevent stale data on the socket. /// /// Full or relative path of the file /// A stream for writing to the file on the server /// public Stream OpenWrite(string path) { return OpenWrite(path, FtpDataType.Binary, true); } /// /// Opens the specified file for writing. Please call GetReply() after you have successfully transfered the file to read the "OK" command sent by the server and prevent stale data on the socket. /// /// Full or relative path of the file /// ASCII/Binary /// A stream for writing to the file on the server /// public virtual Stream OpenWrite(string path, FtpDataType type) { return OpenWrite(path, type, true); } /// /// Opens the specified file for writing. Please call GetReply() after you have successfully transfered the file to read the "OK" command sent by the server and prevent stale data on the socket. /// /// Full or relative path of the file /// ASCII/Binary /// Only set this to false if you are SURE that the file does not exist. If true, it reads the file size and saves it into the stream length. /// A stream for writing to the file on the server /// public virtual Stream OpenWrite(string path, FtpDataType type, bool checkIfFileExists) { // verify args if (path.IsBlank()) throw new ArgumentException("Required parameter is null or blank.", "path"); FtpTrace.WriteFunc("OpenWrite", new object[] { path, type }); FtpClient client = null; FtpDataStream stream = null; long length = 0; #if !CORE14 lock (m_lock) { #endif if (m_threadSafeDataChannels) { client = CloneConnection(); client.Connect(); client.SetWorkingDirectory(GetWorkingDirectory()); } else { client = this; } client.SetDataType(type); length = checkIfFileExists ? client.GetFileSize(path) : 0; stream = client.OpenDataStream(("STOR " + path.GetFtpPath()), 0); if (length > 0 && stream != null) stream.SetLength(length); #if !CORE14 } #endif return stream; } #if !CORE /// /// Begins an asynchronous operation to open the specified file for writing /// /// Full or relative path of the file /// Async callback /// State object /// IAsyncResult /// public IAsyncResult BeginOpenWrite(string path, AsyncCallback callback, object state) { return BeginOpenWrite(path, FtpDataType.Binary, callback, state); } delegate Stream AsyncOpenWrite(string path, FtpDataType type); /// /// Begins an asynchronous operation to open the specified file for writing /// /// Full or relative path of the file /// ASCII/Binary /// Async callback /// State object /// IAsyncResult /// public IAsyncResult BeginOpenWrite(string path, FtpDataType type, AsyncCallback callback, object state) { AsyncOpenWrite func; IAsyncResult ar; ar = (func = new AsyncOpenWrite(OpenWrite)).BeginInvoke(path, type, callback, state); lock (m_asyncmethods) { m_asyncmethods.Add(ar, func); } return ar; } /// /// Ends a call to /// /// returned from /// A writable stream /// public Stream EndOpenWrite(IAsyncResult ar) { return GetAsyncDelegate(ar).EndInvoke(ar); } #endif #if ASYNC /// /// Opens the specified file for writing. Please call GetReply() after you have successfully transfered the file to read the "OK" command sent by the server and prevent stale data on the socket. /// /// Full or relative path of the file /// ASCII/Binary /// Only set this to false if you are SURE that the file does not exist. If true, it reads the file size and saves it into the stream length. /// A stream for writing to the file on the server public virtual async Task OpenWriteAsync(string path, FtpDataType type, bool checkIfFileExists) { // verify args if (path.IsBlank()) throw new ArgumentException("Required parameter is null or blank.", "path"); FtpTrace.WriteFunc(nameof(OpenWriteAsync), new object[] { path, type }); FtpClient client = null; FtpDataStream stream = null; long length = 0; if (m_threadSafeDataChannels) { client = CloneConnection(); await client.ConnectAsync(); await client.SetWorkingDirectoryAsync(await GetWorkingDirectoryAsync()); } else { client = this; } await client.SetDataTypeAsync(type); length = checkIfFileExists ? await client.GetFileSizeAsync(path) : 0; stream = await client.OpenDataStreamAsync(("STOR " + path.GetFtpPath()), 0); if (length > 0 && stream != null) stream.SetLength(length); return stream; } /// /// Opens the specified file for writing. Please call GetReply() after you have successfully transfered the file to read the "OK" command sent by the server and prevent stale data on the socket. asynchronously /// /// Full or relative path of the file /// ASCII/Binary /// A stream for writing to the file on the server public Task OpenWriteAsync(string path, FtpDataType type) { //TODO: Add cancellation support return OpenWriteAsync(path, type, true); } /// /// Opens the specified file for writing. Please call GetReply() after you have successfully transfered the file to read the "OK" command sent by the server and prevent stale data on the socket. asynchronously /// /// Full or relative path of the file /// A stream for writing to the file on the server public Task OpenWriteAsync(string path) { //TODO: Add cancellation support return OpenWriteAsync(path, FtpDataType.Binary, true); } #endif #endregion #region Open Append /// /// Opens the specified file for appending. Please call GetReply() after you have successfully transfered the file to read the "OK" command sent by the server and prevent stale data on the socket. /// /// The full or relative path to the file to be opened /// A stream for writing to the file on the server /// public Stream OpenAppend(string path) { return OpenAppend(path, FtpDataType.Binary, true); } /// /// Opens the specified file for appending. Please call GetReply() after you have successfully transfered the file to read the "OK" command sent by the server and prevent stale data on the socket. /// /// The full or relative path to the file to be opened /// ASCII/Binary /// Only set this to false if you are SURE that the file does not exist. If true, it reads the file size and saves it into the stream length. /// A stream for writing to the file on the server /// public virtual Stream OpenAppend(string path, FtpDataType type) { return OpenAppend(path, type, true); } /// /// Opens the specified file for appending. Please call GetReply() after you have successfully transfered the file to read the "OK" command sent by the server and prevent stale data on the socket. /// /// The full or relative path to the file to be opened /// ASCII/Binary /// Only set this to false if you are SURE that the file does not exist. If true, it reads the file size and saves it into the stream length. /// A stream for writing to the file on the server /// public virtual Stream OpenAppend(string path, FtpDataType type, bool checkIfFileExists) { // verify args if (path.IsBlank()) throw new ArgumentException("Required parameter is null or blank.", "path"); FtpTrace.WriteFunc("OpenAppend", new object[] { path, type }); FtpClient client = null; FtpDataStream stream = null; long length = 0; #if !CORE14 lock (m_lock) { #endif if (m_threadSafeDataChannels) { client = CloneConnection(); client.Connect(); client.SetWorkingDirectory(GetWorkingDirectory()); } else { client = this; } client.SetDataType(type); length = checkIfFileExists ? client.GetFileSize(path) : 0; stream = client.OpenDataStream(("APPE " + path.GetFtpPath()), 0); if (length > 0 && stream != null) { stream.SetLength(length); stream.SetPosition(length); } #if !CORE14 } #endif return stream; } #if !CORE /// /// Begins an asynchronous operation to open the specified file for appending /// /// Full or relative path of the file /// Async callback /// State object /// IAsyncResult /// public IAsyncResult BeginOpenAppend(string path, AsyncCallback callback, object state) { return BeginOpenAppend(path, FtpDataType.Binary, callback, state); } delegate Stream AsyncOpenAppend(string path, FtpDataType type); /// /// Begins an asynchronous operation to open the specified file for appending /// /// Full or relative path of the file /// ASCII/Binary /// Async callback /// State object /// IAsyncResult /// public IAsyncResult BeginOpenAppend(string path, FtpDataType type, AsyncCallback callback, object state) { IAsyncResult ar; AsyncOpenAppend func; ar = (func = new AsyncOpenAppend(OpenAppend)).BeginInvoke(path, type, callback, state); lock (m_asyncmethods) { m_asyncmethods.Add(ar, func); } return ar; } /// /// Ends a call to /// /// returned from /// A writable stream /// public Stream EndOpenAppend(IAsyncResult ar) { return GetAsyncDelegate(ar).EndInvoke(ar); } #endif #if ASYNC /// /// Opens the specified file to be appended asynchronously /// /// Full or relative path of the file /// ASCII/Binary /// Only set this to false if you are SURE that the file does not exist. If true, it reads the file size and saves it into the stream length. /// A stream for writing to the file on the server public virtual async Task OpenAppendAsync(string path, FtpDataType type, bool checkIfFileExists) { // TODO: Add cancellation support // verify args if (path.IsBlank()) throw new ArgumentException("Required parameter is null or blank.", "path"); FtpTrace.WriteFunc(nameof(OpenAppendAsync), new object[] { path, type }); FtpClient client = null; FtpDataStream stream = null; long length = 0; if (m_threadSafeDataChannels) { client = CloneConnection(); await client.ConnectAsync(); await client.SetWorkingDirectoryAsync(await GetWorkingDirectoryAsync()); } else { client = this; } await client.SetDataTypeAsync(type); length = checkIfFileExists ? await client.GetFileSizeAsync(path) : 0; stream = await client.OpenDataStreamAsync(("APPE " + path.GetFtpPath()), 0); if (length > 0 && stream != null) { stream.SetLength(length); stream.SetPosition(length); } return stream; } /// /// Opens the specified file to be appended asynchronously /// /// Full or relative path of the file /// ASCII/Binary /// A stream for writing to the file on the server public Task OpenAppendAsync(string path, FtpDataType type) { //TODO: Add cancellation support return OpenAppendAsync(path, type, true); } /// /// Opens the specified file to be appended asynchronously /// /// Full or relative path of the file /// A stream for writing to the file on the server public Task OpenAppendAsync(string path) { //TODO: Add cancellation support return OpenAppendAsync(path, FtpDataType.Binary, true); } #endif #endregion #region Set Data Type /// /// Sets the data type of information sent over the data stream /// /// ASCII/Binary protected void SetDataType(FtpDataType type) { #if !CORE14 lock (m_lock) { #endif this.SetDataTypeInternal(type); #if !CORE14 } #endif CurrentDataType = type; } /// Internal method that handles actually setting the data type. /// Thrown when a FTP Command error condition occurs. /// Thrown when a FTP error condition occurs. /// ASCII/Binary. /// This method doesn't do any locking to prevent recursive lock scenarios. Callers must do their own locking. private void SetDataTypeInternal(FtpDataType type) { FtpReply reply; switch (type) { case FtpDataType.ASCII: if (!(reply = Execute("TYPE A")).Success) throw new FtpCommandException(reply); /*if (!(reply = Execute("STRU R")).Success) FtpTrace.WriteLine(reply.Message);*/ break; case FtpDataType.Binary: if (!(reply = Execute("TYPE I")).Success) throw new FtpCommandException(reply); /*if (!(reply = Execute("STRU F")).Success) FtpTrace.WriteLine(reply.Message);*/ break; default: throw new FtpException("Unsupported data type: " + type.ToString()); } } #if !CORE delegate void AsyncSetDataType(FtpDataType type); /// /// Begins an asynchronous operation to set the data type of information sent over the data stream /// /// ASCII/Binary /// Async callback /// State object /// IAsyncResult protected IAsyncResult BeginSetDataType(FtpDataType type, AsyncCallback callback, object state) { IAsyncResult ar; AsyncSetDataType func; ar = (func = new AsyncSetDataType(SetDataType)).BeginInvoke(type, callback, state); lock (m_asyncmethods) { m_asyncmethods.Add(ar, func); } return ar; } /// /// Ends a call to /// /// IAsyncResult returned from protected void EndSetDataType(IAsyncResult ar) { GetAsyncDelegate(ar).EndInvoke(ar); } #endif #if ASYNC /// /// Sets the data type of information sent over the data stream asynchronously /// /// ASCII/Binary protected async Task SetDataTypeAsync(FtpDataType type) { //TODO: Add cancellation support FtpReply reply; switch (type) { case FtpDataType.ASCII: if (!(reply = await ExecuteAsync("TYPE A")).Success) throw new FtpCommandException(reply); break; case FtpDataType.Binary: if (!(reply = await ExecuteAsync("TYPE I")).Success) throw new FtpCommandException(reply); break; default: throw new FtpException("Unsupported data type: " + type.ToString()); } CurrentDataType = type; } #endif #endregion } }