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 Properties #if !CORE14 /// /// Used for internally synchronizing access to this /// object from multiple threads /// readonly Object m_lock = new Object(); /// /// For usage by FTP proxies only /// protected Object Lock { get { return m_lock; } } #endif /// /// A list of asynchronous methods that are in progress /// readonly Dictionary m_asyncmethods = new Dictionary(); /// /// Control connection socket stream /// FtpSocketStream m_stream = null; bool m_isDisposed = false; /// /// Gets a value indicating if this object has already been disposed. /// public bool IsDisposed { get { return m_isDisposed; } private set { m_isDisposed = value; } } /// /// Gets the base stream for talking to the server via /// the control connection. /// protected Stream BaseStream { get { return m_stream; } } FtpIpVersion m_ipVersions = FtpIpVersion.ANY; /// /// Flags specifying which versions of the internet protocol to /// support when making a connection. All addresses returned during /// name resolution are tried until a successful connection is made. /// You can fine tune which versions of the internet protocol to use /// by adding or removing flags here. I.e., setting this property /// to FtpIpVersion.IPv4 will cause the connection process to /// ignore IPv6 addresses. The default value is ANY version. /// public FtpIpVersion InternetProtocolVersions { get { return m_ipVersions; } set { m_ipVersions = value; } } int m_socketPollInterval = 15000; /// /// Gets or sets the length of time in milliseconds /// that must pass since the last socket activity /// before calling /// on the socket to test for connectivity. /// Setting this interval too low will /// have a negative impact on performance. Setting this /// interval to 0 disables Polling all together. /// The default value is 15 seconds. /// public int SocketPollInterval { get { return m_socketPollInterval; } set { m_socketPollInterval = value; if (m_stream != null) m_stream.SocketPollInterval = value; } } bool m_staleDataTest = true; /// /// Gets or sets a value indicating whether a test should be performed to /// see if there is stale (unrequested data) sitting on the socket. In some /// cases the control connection may time out but before the server closes /// the connection it might send a 4xx response that was unexpected and /// can cause synchronization errors with transactions. To avoid this /// problem the method checks to see if there is any data /// available on the socket before executing a command. On Azure hosting /// platforms this check can cause an exception to be thrown. In order /// to work around the exception you can set this property to false /// which will skip the test entirely however doing so eliminates the /// best effort attempt of detecting such scenarios. See this thread /// for more details about the Azure problem: /// https://netftp.codeplex.com/discussions/535879 /// public bool StaleDataCheck { get { return m_staleDataTest; } set { m_staleDataTest = value; } } /// /// Gets a value indicating if the connection is alive /// public bool IsConnected { get { if (m_stream != null) return m_stream.IsConnected; return false; } } bool m_threadSafeDataChannels = false; /// /// When this value is set to true (default) the control connection /// is cloned and a new connection the server is established for the /// data channel operation. This is a thread safe approach to make /// asynchronous operations on a single control connection transparent /// to the developer. /// public bool EnableThreadSafeDataConnections { get { return m_threadSafeDataChannels; } set { m_threadSafeDataChannels = value; } } bool m_isClone = false; /// /// Gets a value indicating if this control connection is a clone. This property /// is used with data streams to determine if the connection should be closed /// when the stream is closed. Servers typically only allow 1 data connection /// per control connection. If you try to open multiple data connections this /// object will be cloned for 2 or more resulting in N new connections to the /// server. /// internal bool IsClone { get { return m_isClone; } private set { m_isClone = value; } } Encoding m_textEncoding = Encoding.ASCII; bool m_textEncodingAutoUTF = true; /// /// Gets or sets the text encoding being used when talking with the server. The default /// value is however upon connection, the client checks /// for UTF8 support and if it's there this property is switched over to /// . Manually setting this value overrides automatic detection /// based on the FEAT list; if you change this value it's always used /// regardless of what the server advertises, if anything. /// public Encoding Encoding { get { return m_textEncoding; } set { #if !CORE14 lock (m_lock) { #endif m_textEncoding = value; m_textEncodingAutoUTF = false; #if !CORE14 } #endif } } string m_host = null; /// /// The server to connect to /// public string Host { get { return m_host; } set { // remove unwanted prefix/postfix if (value.StartsWith("ftp://")) { value = value.Substring(value.IndexOf("ftp://") + ("ftp://").Length); } if (value.EndsWith("/")) { value = value.Replace("/", ""); } m_host = value; } } int m_port = 0; /// /// The port to connect to. If this value is set to 0 (Default) the port used /// will be determined by the type of SSL used or if no SSL is to be used it /// will automatically connect to port 21. /// public int Port { get { // automatically determine port // when m_port is 0. if (m_port == 0) { switch (EncryptionMode) { case FtpEncryptionMode.None: case FtpEncryptionMode.Explicit: return 21; case FtpEncryptionMode.Implicit: return 990; } } return m_port; } set { m_port = value; } } NetworkCredential m_credentials = new NetworkCredential("anonymous", "anonymous"); /// /// Credentials used for authentication /// public NetworkCredential Credentials { get { return m_credentials; } set { m_credentials = value; } } int m_maxDerefCount = 20; /// /// Gets or sets a value that controls the maximum depth /// of recursion that will follow symbolic /// links before giving up. You can also specify the value /// to be used as one of the overloaded parameters to the /// method. The default value is 20. Specifying /// -1 here means indefinitely try to resolve a link. This is /// not recommended for obvious reasons (stack overflow). /// public int MaximumDereferenceCount { get { return m_maxDerefCount; } set { m_maxDerefCount = value; } } X509CertificateCollection m_clientCerts = new X509CertificateCollection(); /// /// Client certificates to be used in SSL authentication process /// public X509CertificateCollection ClientCertificates { get { return m_clientCerts; } protected set { m_clientCerts = value; } } // Holds the cached resolved address string m_Address; Func m_AddressResolver; /// /// Delegate used for resolving local address, used for active data connections /// This can be used in case you're behind a router, but port forwarding is configured to forward the /// ports from your router to your internal IP. In that case, we need to send the router's IP instead of our internal IP. /// See example: FtpClient.GetPublicIP -> This uses Ipify api to find external IP /// public Func AddressResolver { get { return m_AddressResolver; } set { m_AddressResolver = value; } } IEnumerable m_ActivePorts; /// /// Ports used for Active Data Connection /// public IEnumerable ActivePorts { get { return m_ActivePorts; } set { m_ActivePorts = value; } } FtpDataConnectionType m_dataConnectionType = FtpDataConnectionType.AutoPassive; /// /// Data connection type, default is AutoPassive which tries /// a connection with EPSV first and if it fails then tries /// PASV before giving up. If you know exactly which kind of /// connection you need you can slightly increase performance /// by defining a specific type of passive or active data /// connection here. /// public FtpDataConnectionType DataConnectionType { get { return m_dataConnectionType; } set { m_dataConnectionType = value; } } bool m_ungracefullDisconnect = false; /// /// Disconnect from the server without sending QUIT. This helps /// work around IOExceptions caused by buggy connection resets /// when closing the control connection. /// public bool UngracefullDisconnection { get { return m_ungracefullDisconnect; } set { m_ungracefullDisconnect = value; } } int m_connectTimeout = 15000; /// /// Gets or sets the length of time in milliseconds to wait for a connection /// attempt to succeed before giving up. Default is 15000 (15 seconds). /// public int ConnectTimeout { get { return m_connectTimeout; } set { m_connectTimeout = value; } } int m_readTimeout = 15000; /// /// Gets or sets the length of time wait in milliseconds for data to be /// read from the underlying stream. The default value is 15000 (15 seconds). /// public int ReadTimeout { get { return m_readTimeout; } set { m_readTimeout = value; } } int m_dataConnectionConnectTimeout = 15000; /// /// Gets or sets the length of time in milliseconds for a data connection /// to be established before giving up. Default is 15000 (15 seconds). /// public int DataConnectionConnectTimeout { get { return m_dataConnectionConnectTimeout; } set { m_dataConnectionConnectTimeout = value; } } int m_dataConnectionReadTimeout = 15000; /// /// Gets or sets the length of time in milliseconds the data channel /// should wait for the server to send data. Default value is /// 15000 (15 seconds). /// public int DataConnectionReadTimeout { get { return m_dataConnectionReadTimeout; } set { m_dataConnectionReadTimeout = value; } } bool m_keepAlive = false; /// /// Gets or sets a value indicating if should be set on /// the underlying stream's socket. If the connection is alive, the option is /// adjusted in real-time. The value is stored and the KeepAlive option is set /// accordingly upon any new connections. The value set here is also applied to /// all future data streams. It has no affect on cloned control connections or /// data connections already in progress. The default value is false. /// public bool SocketKeepAlive { get { return m_keepAlive; } set { m_keepAlive = value; if (m_stream != null) m_stream.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.KeepAlive, value); } } FtpCapability m_caps = FtpCapability.NONE; /// /// Gets the server capabilities represented by flags /// public FtpCapability Capabilities { get { if (m_stream == null || !m_stream.IsConnected) { Connect(); } return m_caps; } protected set { m_caps = value; } } FtpHashAlgorithm m_hashAlgorithms = FtpHashAlgorithm.NONE; /// /// Get the hash types supported by the server, if any. This /// is a recent extension to the protocol that is not fully /// standardized and is not guaranteed to work. See here for /// more details: /// http://tools.ietf.org/html/draft-bryan-ftpext-hash-02 /// public FtpHashAlgorithm HashAlgorithms { get { if (m_stream == null || !m_stream.IsConnected) { Connect(); } return m_hashAlgorithms; } private set { m_hashAlgorithms = value; } } FtpEncryptionMode m_encryptionmode = FtpEncryptionMode.None; /// /// Type of SSL to use, or none. Default is none. Explicit is TLS, Implicit is SSL. /// public FtpEncryptionMode EncryptionMode { get { return m_encryptionmode; } set { m_encryptionmode = value; } } bool m_dataConnectionEncryption = true; /// /// Indicates if data channel transfers should be encrypted. Only valid if /// property is not equal to . /// public bool DataConnectionEncryption { get { return m_dataConnectionEncryption; } set { m_dataConnectionEncryption = value; } } #if !CORE bool m_plainTextEncryption = false; /// /// Indicates if the encryption should be disabled immediately after connecting using a CCC command. /// This is useful when you have a FTP firewall that requires plaintext FTP, but your server mandates FTPS connections. /// public bool PlainTextEncryption { get { return m_plainTextEncryption; } set { m_plainTextEncryption = value; } } #endif #if CORE private SslProtocols m_SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; #else private SslProtocols m_SslProtocols = SslProtocols.Default; #endif /// /// Encryption protocols to use. Only valid if EncryptionMode property is not equal to . /// Default value is .NET Framework defaults from the class. /// public SslProtocols SslProtocols { get { return m_SslProtocols; } set { m_SslProtocols = value; } } FtpSslValidation m_sslvalidate = null; /// /// Event is fired to validate SSL certificates. If this event is /// not handled and there are errors validating the certificate /// the connection will be aborted. /// /// public event FtpSslValidation ValidateCertificate { add { m_sslvalidate += value; } remove { m_sslvalidate -= value; } } private string m_systemType = "UNKNOWN"; /// /// Gets the type of system/server that we're /// connected to. /// public string SystemType { get { return m_systemType; } } private string m_connectionType = "Default"; /// Gets the connection type public string ConnectionType { get { return m_connectionType; } protected set { m_connectionType = value; } } #endregion #region Constructor / Destructor /// /// Creates a new instance of an FTP Client. /// public FtpClient() { m_listParser = new FtpListParser(this); } /// /// Creates a new instance of an FTP Client, with the given host. /// public FtpClient(string host) { Host = host; m_listParser = new FtpListParser(this); } /// /// Creates a new instance of an FTP Client, with the given host and credentials. /// public FtpClient(string host, NetworkCredential credentials) { Host = host; Credentials = credentials; m_listParser = new FtpListParser(this); } /// /// Creates a new instance of an FTP Client, with the given host, port and credentials. /// public FtpClient(string host, int port, NetworkCredential credentials) { Host = host; Port = port; Credentials = credentials; m_listParser = new FtpListParser(this); } /// /// Creates a new instance of an FTP Client, with the given host, username and password. /// public FtpClient(string host, string user, string pass) { Host = host; Credentials = new NetworkCredential(user, pass); m_listParser = new FtpListParser(this); } /// /// Creates a new instance of an FTP Client, with the given host, port, username and password. /// public FtpClient(string host, int port, string user, string pass) { Host = host; Port = port; Credentials = new NetworkCredential(user, pass); m_listParser = new FtpListParser(this); } /// /// Creates a new instance of this class. Useful in FTP proxy classes. /// /// protected virtual FtpClient Create() { return new FtpClient(); } /// /// Disconnects from the server, releases resources held by this /// object. /// public void Dispose() { #if !CORE14 lock (m_lock) { #endif if (IsDisposed) return; FtpTrace.WriteFunc("Dispose"); FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "Disposing FtpClient object..."); try { if (IsConnected) { Disconnect(); } } catch (Exception ex) { FtpTrace.WriteLine(FtpTraceLevel.Warn, "FtpClient.Dispose(): Caught and discarded an exception while disconnecting from host: " + ex.ToString()); } if (m_stream != null) { try { m_stream.Dispose(); } catch (Exception ex) { FtpTrace.WriteLine(FtpTraceLevel.Warn, "FtpClient.Dispose(): Caught and discarded an exception while disposing FtpStream object: " + ex.ToString()); } finally { m_stream = null; } } m_credentials = null; m_textEncoding = null; m_host = null; m_asyncmethods.Clear(); IsDisposed = true; GC.SuppressFinalize(this); #if !CORE14 } #endif } /// /// Finalizer /// ~FtpClient() { Dispose(); } #endregion #region Clone /// /// Clones the control connection for opening multiple data streams /// /// A new control connection with the same property settings as this one /// protected FtpClient CloneConnection() { FtpClient conn = Create(); conn.m_isClone = true; // configure new connection as clone of self conn.InternetProtocolVersions = InternetProtocolVersions; conn.SocketPollInterval = SocketPollInterval; conn.StaleDataCheck = StaleDataCheck; conn.EnableThreadSafeDataConnections = EnableThreadSafeDataConnections; conn.Encoding = Encoding; conn.Host = Host; conn.Port = Port; conn.Credentials = Credentials; conn.MaximumDereferenceCount = MaximumDereferenceCount; conn.ClientCertificates = ClientCertificates; conn.DataConnectionType = DataConnectionType; conn.UngracefullDisconnection = UngracefullDisconnection; conn.ConnectTimeout = ConnectTimeout; conn.ReadTimeout = ReadTimeout; conn.DataConnectionConnectTimeout = DataConnectionConnectTimeout; conn.DataConnectionReadTimeout = DataConnectionReadTimeout; conn.SocketKeepAlive = SocketKeepAlive; conn.Capabilities = Capabilities; conn.EncryptionMode = EncryptionMode; conn.DataConnectionEncryption = DataConnectionEncryption; conn.SslProtocols = SslProtocols; conn.TransferChunkSize = TransferChunkSize; conn.ListingParser = ListingParser; conn.ListingCulture = ListingCulture; conn.TimeOffset = TimeOffset; conn.RetryAttempts = RetryAttempts; conn.UploadRateLimit = UploadRateLimit; conn.DownloadRateLimit = DownloadRateLimit; conn.RecursiveList = RecursiveList; conn.DownloadDataType = DownloadDataType; conn.UploadDataType = UploadDataType; #if !CORE conn.PlainTextEncryption = PlainTextEncryption; #endif // copy props using attributes (slower, not .NET core compatible) /*foreach (PropertyInfo prop in GetType().GetProperties()) { object[] attributes = prop.GetCustomAttributes(typeof(FtpControlConnectionClone), true); if (attributes.Length > 0) { prop.SetValue(conn, prop.GetValue(this, null), null); } }*/ // always accept certificate no matter what because if code execution ever // gets here it means the certificate on the control connection object being // cloned was already accepted. conn.ValidateCertificate += new FtpSslValidation( delegate (FtpClient obj, FtpSslValidationEventArgs e) { e.Accept = true; }); return conn; } #endregion #region Execute Command /// /// Executes a command /// /// The command to execute /// The servers reply to the command /// public FtpReply Execute(string command) { FtpReply reply; #if !CORE14 lock (m_lock) { #endif if (StaleDataCheck) { ReadStaleData(true, false, true); } if (!IsConnected) { if (command == "QUIT") { FtpTrace.WriteStatus(FtpTraceLevel.Info, "Not sending QUIT because the connection has already been closed."); return new FtpReply() { Code = "200", Message = "Connection already closed." }; } Connect(); } // hide sensitive data from logs string commandTxt = command; if (!FtpTrace.LogUserName && command.StartsWith("USER", StringComparison.Ordinal)) { commandTxt = "USER ***"; } if (!FtpTrace.LogPassword && command.StartsWith("PASS", StringComparison.Ordinal)) { commandTxt = "PASS ***"; } FtpTrace.WriteLine(FtpTraceLevel.Info, "Command: " + commandTxt); // send command to FTP server m_stream.WriteLine(m_textEncoding, command); reply = GetReply(); #if !CORE14 } #endif return reply; } #if !CORE delegate FtpReply AsyncExecute(string command); /// /// Performs execution of the specified command asynchronously /// /// The command to execute /// The method /// State object /// IAsyncResult /// public IAsyncResult BeginExecute(string command, AsyncCallback callback, object state) { AsyncExecute func; IAsyncResult ar; ar = (func = new AsyncExecute(Execute)).BeginInvoke(command, callback, state); lock (m_asyncmethods) { m_asyncmethods.Add(ar, func); } return ar; } /// /// Ends an asynchronous command /// /// IAsyncResult returned from BeginExecute /// FtpReply object (never null). /// public FtpReply EndExecute(IAsyncResult ar) { return GetAsyncDelegate(ar).EndInvoke(ar); } #endif #if ASYNC // TODO: Add cencellation support /// /// Performs an asynchronous execution of the specified command /// /// The command to execute /// The servers reply to the command public async Task ExecuteAsync(string command) { FtpReply reply; if (StaleDataCheck) { #if CORE await ReadStaleDataAsync(true, false, true); #else ReadStaleData(true, false, true); #endif } if (!IsConnected) { if (command == "QUIT") { FtpTrace.WriteStatus(FtpTraceLevel.Info, "Not sending QUIT because the connection has already been closed."); return new FtpReply() { Code = "200", Message = "Connection already closed." }; } await ConnectAsync(); } // hide sensitive data from logs string commandTxt = command; if (!FtpTrace.LogUserName && command.StartsWith("USER", StringComparison.Ordinal)) { commandTxt = "USER ***"; } if (!FtpTrace.LogPassword && command.StartsWith("PASS", StringComparison.Ordinal)) { commandTxt = "PASS ***"; } FtpTrace.WriteLine(FtpTraceLevel.Info, "Command: " + commandTxt); // send command to FTP server await m_stream.WriteLineAsync(m_textEncoding, command); reply = await GetReplyAsync(); return reply; } #endif #endregion #region Get Reply /// /// Retrieves a reply from the server. Do not execute this method /// unless you are sure that a reply has been sent, i.e., you /// executed a command. Doing so will cause the code to hang /// indefinitely waiting for a server reply that is never coming. /// /// FtpReply representing the response from the server /// public FtpReply GetReply() { FtpReply reply = new FtpReply(); string buf; #if !CORE14 lock (m_lock) { #endif if (!IsConnected) throw new InvalidOperationException("No connection to the server has been established."); m_stream.ReadTimeout = m_readTimeout; while ((buf = m_stream.ReadLine(Encoding)) != null) { Match m; if ((m = Regex.Match(buf, "^(?[0-9]{3}) (?.*)$")).Success) { reply.Code = m.Groups["code"].Value; reply.Message = m.Groups["message"].Value; break; } reply.InfoMessages += (buf + "\n"); } // if reply received if (reply.Code != null) { // hide sensitive data from logs string logMsg = reply.Message; if (!FtpTrace.LogUserName && reply.Code == "331" && logMsg.StartsWith("User ", StringComparison.Ordinal) && logMsg.Contains(" OK")) { logMsg = logMsg.Replace(Credentials.UserName, "***"); } // log response code + message FtpTrace.WriteLine(FtpTraceLevel.Info, "Response: " + reply.Code + " " + logMsg); } // log multiline response messages if (reply.InfoMessages != null) { reply.InfoMessages = reply.InfoMessages.Trim(); } if (!string.IsNullOrEmpty(reply.InfoMessages)) { //FtpTrace.WriteLine(FtpTraceLevel.Verbose, "+---------------------------------------+"); FtpTrace.WriteLine(FtpTraceLevel.Verbose, reply.InfoMessages.Split('\n').AddPrefix("Response: ", true).Join("\n")); //FtpTrace.WriteLine(FtpTraceLevel.Verbose, "-----------------------------------------"); } #if !CORE14 } #endif return reply; } #if ASYNC // TODO: add example /// /// Retrieves a reply from the server. Do not execute this method /// unless you are sure that a reply has been sent, i.e., you /// executed a command. Doing so will cause the code to hang /// indefinitely waiting for a server reply that is never coming. /// /// FtpReply representing the response from the server /// public async Task GetReplyAsync() { FtpReply reply = new FtpReply(); string buf; if (!IsConnected) throw new InvalidOperationException("No connection to the server has been established."); m_stream.ReadTimeout = m_readTimeout; while ((buf = await m_stream.ReadLineAsync(Encoding)) != null) { Match m; if ((m = Regex.Match(buf, "^(?[0-9]{3}) (?.*)$")).Success) { reply.Code = m.Groups["code"].Value; reply.Message = m.Groups["message"].Value; break; } reply.InfoMessages += (buf + "\n"); } // if reply received if (reply.Code != null) { // hide sensitive data from logs string logMsg = reply.Message; if (!FtpTrace.LogUserName && reply.Code == "331" && logMsg.StartsWith("User ", StringComparison.Ordinal) && logMsg.Contains(" OK")) { logMsg = logMsg.Replace(Credentials.UserName, "***"); } // log response code + message FtpTrace.WriteLine(FtpTraceLevel.Info, "Response: " + reply.Code + " " + logMsg); } // log multiline response messages if (reply.InfoMessages != null) { reply.InfoMessages = reply.InfoMessages.Trim(); } if (!string.IsNullOrEmpty(reply.InfoMessages)) { //FtpTrace.WriteLine(FtpTraceLevel.Verbose, "+---------------------------------------+"); FtpTrace.WriteLine(FtpTraceLevel.Verbose, reply.InfoMessages.Split('\n').AddPrefix("Response: ", true).Join("\n")); //FtpTrace.WriteLine(FtpTraceLevel.Verbose, "-----------------------------------------"); } return reply; } #endif #endregion #region Connect private FtpListParser m_listParser; /// /// Connect to the server /// /// Thrown if this object has been disposed. /// public virtual void Connect() { FtpReply reply; #if !CORE14 lock (m_lock) { #endif FtpTrace.WriteFunc("Connect"); if (IsDisposed) throw new ObjectDisposedException("This FtpClient object has been disposed. It is no longer accessible."); if (m_stream == null) { m_stream = new FtpSocketStream(m_SslProtocols); m_stream.ValidateCertificate += new FtpSocketStreamSslValidation(FireValidateCertficate); } else { if (IsConnected) { Disconnect(); } } if (Host == null) { throw new FtpException("No host has been specified"); } if (!IsClone) { m_caps = FtpCapability.NONE; } m_hashAlgorithms = FtpHashAlgorithm.NONE; m_stream.ConnectTimeout = m_connectTimeout; m_stream.SocketPollInterval = m_socketPollInterval; Connect(m_stream); m_stream.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.KeepAlive, m_keepAlive); #if !NO_SSL if (EncryptionMode == FtpEncryptionMode.Implicit) { m_stream.ActivateEncryption(Host, m_clientCerts.Count > 0 ? m_clientCerts : null, m_SslProtocols); } #endif Handshake(); #if !NO_SSL if (EncryptionMode == FtpEncryptionMode.Explicit) { if (!(reply = Execute("AUTH TLS")).Success) { throw new FtpSecurityNotAvailableException("AUTH TLS command failed."); } m_stream.ActivateEncryption(Host, m_clientCerts.Count > 0 ? m_clientCerts : null, m_SslProtocols); } #endif if (m_credentials != null) { Authenticate(); } if (m_stream.IsEncrypted && DataConnectionEncryption) { if (!(reply = Execute("PBSZ 0")).Success) throw new FtpCommandException(reply); if (!(reply = Execute("PROT P")).Success) throw new FtpCommandException(reply); } // if this is a clone these values should have already been loaded // so save some bandwidth and CPU time and skip executing this again. if (!IsClone) { if ((reply = Execute("FEAT")).Success && reply.InfoMessages != null) { GetFeatures(reply); } } // Enable UTF8 if the encoding is ASCII and UTF8 is supported if (m_textEncodingAutoUTF && m_textEncoding == Encoding.ASCII && HasFeature(FtpCapability.UTF8)) { m_textEncoding = Encoding.UTF8; } FtpTrace.WriteStatus(FtpTraceLevel.Info, "Text encoding: " + m_textEncoding.ToString()); if (m_textEncoding == Encoding.UTF8) { // If the server supports UTF8 it should already be enabled and this // command should not matter however there are conflicting drafts // about this so we'll just execute it to be safe. Execute("OPTS UTF8 ON"); } // Get the system type - Needed to auto-detect file listing parser if ((reply = Execute("SYST")).Success) { m_systemType = reply.Message; } #if !NO_SSL && !CORE if (m_stream.IsEncrypted && PlainTextEncryption) { if (!(reply = Execute("CCC")).Success) { throw new FtpSecurityNotAvailableException("Failed to disable encryption with CCC command. Perhaps your server does not support it or is not configured to allow it."); } else { // close the SslStream and send close_notify command to server m_stream.DeactivateEncryption(); // read stale data (server's reply?) ReadStaleData(false, true, false); } } #endif // Create the parser even if the auto-OS detection failed m_listParser.Init(m_systemType); #if !CORE14 } #endif } #if ASYNC // TODO: add example /// /// Connect to the server /// /// Thrown if this object has been disposed. /// public virtual async Task ConnectAsync() { FtpReply reply; FtpTrace.WriteFunc(nameof(ConnectAsync)); if (IsDisposed) throw new ObjectDisposedException("This FtpClient object has been disposed. It is no longer accessible."); if (m_stream == null) { m_stream = new FtpSocketStream(m_SslProtocols); m_stream.ValidateCertificate += new FtpSocketStreamSslValidation(FireValidateCertficate); } else { if (IsConnected) { Disconnect(); } } if (Host == null) { throw new FtpException("No host has been specified"); } if (!IsClone) { m_caps = FtpCapability.NONE; } m_hashAlgorithms = FtpHashAlgorithm.NONE; m_stream.ConnectTimeout = m_connectTimeout; m_stream.SocketPollInterval = m_socketPollInterval; await ConnectAsync(m_stream); m_stream.SetSocketOption(System.Net.Sockets.SocketOptionLevel.Socket, System.Net.Sockets.SocketOptionName.KeepAlive, m_keepAlive); #if !NO_SSL if (EncryptionMode == FtpEncryptionMode.Implicit) { await m_stream.ActivateEncryptionAsync(Host, m_clientCerts.Count > 0 ? m_clientCerts : null, m_SslProtocols); } #endif await HandshakeAsync(); #if !NO_SSL if (EncryptionMode == FtpEncryptionMode.Explicit) { if (!(reply = await ExecuteAsync("AUTH TLS")).Success) { throw new FtpSecurityNotAvailableException("AUTH TLS command failed."); } await m_stream.ActivateEncryptionAsync(Host, m_clientCerts.Count > 0 ? m_clientCerts : null, m_SslProtocols); } #endif if (m_credentials != null) { await AuthenticateAsync(); } if (m_stream.IsEncrypted && DataConnectionEncryption) { if (!(reply = await ExecuteAsync("PBSZ 0")).Success) throw new FtpCommandException(reply); if (!(reply = await ExecuteAsync("PROT P")).Success) throw new FtpCommandException(reply); } // if this is a clone these values should have already been loaded // so save some bandwidth and CPU time and skip executing this again. if (!IsClone) { if ((reply = await ExecuteAsync("FEAT")).Success && reply.InfoMessages != null) { GetFeatures(reply); } } // Enable UTF8 if the encoding is ASCII and UTF8 is supported if (m_textEncodingAutoUTF && m_textEncoding == Encoding.ASCII && HasFeature(FtpCapability.UTF8)) { m_textEncoding = Encoding.UTF8; } FtpTrace.WriteStatus(FtpTraceLevel.Info, "Text encoding: " + m_textEncoding.ToString()); if (m_textEncoding == Encoding.UTF8) { // If the server supports UTF8 it should already be enabled and this // command should not matter however there are conflicting drafts // about this so we'll just execute it to be safe. await ExecuteAsync("OPTS UTF8 ON"); } // Get the system type - Needed to auto-detect file listing parser if ((reply = await ExecuteAsync("SYST")).Success) { m_systemType = reply.Message; } #if !NO_SSL && !CORE if (m_stream.IsEncrypted && PlainTextEncryption) { if (!(reply = await ExecuteAsync("CCC")).Success) { throw new FtpSecurityNotAvailableException("Failed to disable encryption with CCC command. Perhaps your server does not support it or is not configured to allow it."); } else { // close the SslStream and send close_notify command to server m_stream.DeactivateEncryption(); // read stale data (server's reply?) await ReadStaleDataAsync(false, true, false); } } #endif // Create the parser even if the auto-OS detection failed m_listParser.Init(m_systemType); } #endif /// /// Connect to the FTP server. Overwritten in proxy classes. /// /// protected virtual void Connect(FtpSocketStream stream) { stream.Connect(Host, Port, InternetProtocolVersions); } #if ASYNC /// /// Connect to the FTP server. Overwritten in proxy classes. /// /// protected virtual async Task ConnectAsync(FtpSocketStream stream) { await stream.ConnectAsync(Host, Port, InternetProtocolVersions); } #endif /// /// Connect to the FTP server. Overwritten in proxy classes. /// protected virtual void Connect(FtpSocketStream stream, string host, int port, FtpIpVersion ipVersions) { stream.Connect(host, port, ipVersions); } #if ASYNC /// /// Connect to the FTP server. Overwritten in proxy classes. /// protected virtual Task ConnectAsync(FtpSocketStream stream, string host, int port, FtpIpVersion ipVersions) { return stream.ConnectAsync(host, port, ipVersions); } #endif /// /// Called during Connect(). Typically extended by FTP proxies. /// protected virtual void Handshake() { FtpReply reply; if (!(reply = GetReply()).Success) { if (reply.Code == null) { throw new IOException("The connection was terminated before a greeting could be read."); } else { throw new FtpCommandException(reply); } } } #if ASYNC /// /// Called during . Typically extended by FTP proxies. /// protected virtual async Task HandshakeAsync() { FtpReply reply; if (!(reply = await GetReplyAsync()).Success) { if (reply.Code == null) { throw new IOException("The connection was terminated before a greeting could be read."); } else { throw new FtpCommandException(reply); } } } #endif /// /// Populates the capabilities flags based on capabilities /// supported by this server. This method is overridable /// so that new features can be supported /// /// The reply object from the FEAT command. The InfoMessages property will /// contain a list of the features the server supported delimited by a new line '\n' character. protected virtual void GetFeatures(FtpReply reply) { foreach (string feat in reply.InfoMessages.Split('\n')) { if (feat.ToUpper().Trim().StartsWith("MLST") || feat.ToUpper().Trim().StartsWith("MLSD")) m_caps |= FtpCapability.MLSD; else if (feat.ToUpper().Trim().StartsWith("MDTM")) m_caps |= FtpCapability.MDTM; else if (feat.ToUpper().Trim().StartsWith("REST STREAM")) m_caps |= FtpCapability.REST; else if (feat.ToUpper().Trim().StartsWith("SIZE")) m_caps |= FtpCapability.SIZE; else if (feat.ToUpper().Trim().StartsWith("UTF8")) m_caps |= FtpCapability.UTF8; else if (feat.ToUpper().Trim().StartsWith("PRET")) m_caps |= FtpCapability.PRET; else if (feat.ToUpper().Trim().StartsWith("MFMT")) m_caps |= FtpCapability.MFMT; else if (feat.ToUpper().Trim().StartsWith("MFCT")) m_caps |= FtpCapability.MFCT; else if (feat.ToUpper().Trim().StartsWith("MFF")) m_caps |= FtpCapability.MFF; else if (feat.ToUpper().Trim().StartsWith("MD5")) m_caps |= FtpCapability.MD5; else if (feat.ToUpper().Trim().StartsWith("XMD5")) m_caps |= FtpCapability.XMD5; else if (feat.ToUpper().Trim().StartsWith("XCRC")) m_caps |= FtpCapability.XCRC; else if (feat.ToUpper().Trim().StartsWith("XSHA1")) m_caps |= FtpCapability.XSHA1; else if (feat.ToUpper().Trim().StartsWith("XSHA256")) m_caps |= FtpCapability.XSHA256; else if (feat.ToUpper().Trim().StartsWith("XSHA512")) m_caps |= FtpCapability.XSHA512; else if (feat.ToUpper().Trim().StartsWith("HASH")) { Match m; m_caps |= FtpCapability.HASH; if ((m = Regex.Match(feat.ToUpper().Trim(), @"^HASH\s+(?.*)$")).Success) { foreach (string type in m.Groups["types"].Value.Split(';')) { switch (type.ToUpper().Trim()) { case "SHA-1": case "SHA-1*": m_hashAlgorithms |= FtpHashAlgorithm.SHA1; break; case "SHA-256": case "SHA-256*": m_hashAlgorithms |= FtpHashAlgorithm.SHA256; break; case "SHA-512": case "SHA-512*": m_hashAlgorithms |= FtpHashAlgorithm.SHA512; break; case "MD5": case "MD5*": m_hashAlgorithms |= FtpHashAlgorithm.MD5; break; case "CRC": case "CRC*": m_hashAlgorithms |= FtpHashAlgorithm.CRC; break; } } } } } } #if !CORE delegate void AsyncConnect(); /// /// Initiates a connection to the server /// /// AsyncCallback method /// State object /// IAsyncResult /// public IAsyncResult BeginConnect(AsyncCallback callback, object state) { AsyncConnect func; IAsyncResult ar; ar = (func = new AsyncConnect(Connect)).BeginInvoke(callback, state); lock (m_asyncmethods) { m_asyncmethods.Add(ar, func); } return ar; } /// /// Ends an asynchronous connection attempt to the server from /// /// returned from /// public void EndConnect(IAsyncResult ar) { GetAsyncDelegate(ar).EndInvoke(ar); } #endif #endregion #region Login /// /// Performs a login on the server. This method is overridable so /// that the login procedure can be changed to support, for example, /// a FTP proxy. /// protected virtual void Authenticate() { Authenticate(Credentials.UserName, Credentials.Password); } #if ASYNC /// /// Performs a login on the server. This method is overridable so /// that the login procedure can be changed to support, for example, /// a FTP proxy. /// protected virtual async Task AuthenticateAsync() { await AuthenticateAsync(Credentials.UserName, Credentials.Password); } #endif /// /// Performs a login on the server. This method is overridable so /// that the login procedure can be changed to support, for example, /// a FTP proxy. /// protected virtual void Authenticate(string userName, string password) { FtpReply reply; if (!(reply = Execute("USER " + userName)).Success) throw new FtpCommandException(reply); if (reply.Type == FtpResponseType.PositiveIntermediate && !(reply = Execute("PASS " + password)).Success) throw new FtpCommandException(reply); } #if ASYNC /// /// Performs a login on the server. This method is overridable so /// that the login procedure can be changed to support, for example, /// a FTP proxy. /// protected virtual async Task AuthenticateAsync(string userName, string password) { FtpReply reply; if (!(reply = await ExecuteAsync("USER " + userName)).Success) throw new FtpCommandException(reply); if (reply.Type == FtpResponseType.PositiveIntermediate && !(reply = await ExecuteAsync("PASS " + password)).Success) throw new FtpCommandException(reply); } #endif #endregion #region Disconnect /// /// Disconnects from the server /// public virtual void Disconnect() { #if !CORE14 lock (m_lock) { #endif if (m_stream != null && m_stream.IsConnected) { try { if (!UngracefullDisconnection) { Execute("QUIT"); } } catch (SocketException sockex) { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "FtpClient.Disconnect(): SocketException caught and discarded while closing control connection: " + sockex.ToString()); } catch (IOException ioex) { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "FtpClient.Disconnect(): IOException caught and discarded while closing control connection: " + ioex.ToString()); } catch (FtpCommandException cmdex) { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "FtpClient.Disconnect(): FtpCommandException caught and discarded while closing control connection: " + cmdex.ToString()); } catch (FtpException ftpex) { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "FtpClient.Disconnect(): FtpException caught and discarded while closing control connection: " + ftpex.ToString()); } finally { m_stream.Close(); } } #if !CORE14 } #endif } #if !CORE delegate void AsyncDisconnect(); /// /// Initiates a disconnection on the server /// /// method /// State object /// IAsyncResult /// public IAsyncResult BeginDisconnect(AsyncCallback callback, object state) { IAsyncResult ar; AsyncDisconnect func; ar = (func = new AsyncDisconnect(Disconnect)).BeginInvoke(callback, state); lock (m_asyncmethods) { m_asyncmethods.Add(ar, func); } return ar; } /// /// Ends a call to /// /// returned from /// public void EndDisconnect(IAsyncResult ar) { GetAsyncDelegate(ar).EndInvoke(ar); } #endif #if ASYNC /// /// Disconnects from the server asynchronously /// public async Task DisconnectAsync() { //TODO: Add cancellation support if (m_stream != null && m_stream.IsConnected) { try { if (!UngracefullDisconnection) { await ExecuteAsync("QUIT"); } } catch (SocketException sockex) { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "FtpClient.Disconnect(): SocketException caught and discarded while closing control connection: " + sockex.ToString()); } catch (IOException ioex) { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "FtpClient.Disconnect(): IOException caught and discarded while closing control connection: " + ioex.ToString()); } catch (FtpCommandException cmdex) { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "FtpClient.Disconnect(): FtpCommandException caught and discarded while closing control connection: " + cmdex.ToString()); } catch (FtpException ftpex) { FtpTrace.WriteStatus(FtpTraceLevel.Warn, "FtpClient.Disconnect(): FtpException caught and discarded while closing control connection: " + ftpex.ToString()); } finally { m_stream.Close(); } } } #endif #endregion #region FTPS /// /// Catches the socket stream ssl validation event and fires the event handlers /// attached to this object for validating SSL certificates /// /// The stream that fired the event /// The event args used to validate the certificate void FireValidateCertficate(FtpSocketStream stream, FtpSslValidationEventArgs e) { OnValidateCertficate(e); } /// /// Fires the SSL validation event /// /// Event Args void OnValidateCertficate(FtpSslValidationEventArgs e) { FtpSslValidation evt; evt = m_sslvalidate; if (evt != null) evt(this, e); } #endregion #region Utils /// /// Performs a bitwise and to check if the specified /// flag is set on the property. /// /// The to check for /// True if the feature was found, false otherwise public bool HasFeature(FtpCapability cap) { return ((this.Capabilities & cap) == cap); } /// /// Retrieves the delegate for the specified IAsyncResult and removes /// it from the m_asyncmethods collection if the operation is successful /// /// Type of delegate to retrieve /// The IAsyncResult to retrieve the delegate for /// The delegate that generated the specified IAsyncResult protected T GetAsyncDelegate(IAsyncResult ar) { T func; lock (m_asyncmethods) { if (m_isDisposed) { throw new ObjectDisposedException("This connection object has already been disposed."); } if (!m_asyncmethods.ContainsKey(ar)) throw new InvalidOperationException("The specified IAsyncResult could not be located."); if (!(m_asyncmethods[ar] is T)) { #if CORE throw new InvalidCastException("The AsyncResult cannot be matched to the specified delegate. "); #else StackTrace st = new StackTrace(1); throw new InvalidCastException("The AsyncResult cannot be matched to the specified delegate. " + ("Are you sure you meant to call " + st.GetFrame(0).GetMethod().Name + " and not another method?") ); #endif } func = (T)m_asyncmethods[ar]; m_asyncmethods.Remove(ar); } return func; } /// /// Ensure a relative path is absolute by appending the working dir /// private string GetAbsolutePath(string path) { if (path == null || path.Trim().Length == 0) { // if path not given, then use working dir string pwd = GetWorkingDirectory(); if (pwd != null && pwd.Trim().Length > 0) path = pwd; else path = "./"; } else if (!path.StartsWith("/")) { // if relative path given then add working dir to calc full path string pwd = GetWorkingDirectory(); if (pwd != null && pwd.Trim().Length > 0) { if (path.StartsWith("./")) path = path.Remove(0, 2); path = (pwd + "/" + path).GetFtpPath(); } } return path; } #if ASYNC /// /// Ensure a relative path is absolute by appending the working dir /// private async Task GetAbsolutePathAsync(string path) { if (path == null || path.Trim().Length == 0) { // if path not given, then use working dir string pwd = await GetWorkingDirectoryAsync(); if (pwd != null && pwd.Trim().Length > 0) path = pwd; else path = "./"; } else if (!path.StartsWith("/")) { // if relative path given then add working dir to calc full path string pwd = await GetWorkingDirectoryAsync(); if (pwd != null && pwd.Trim().Length > 0) { if (path.StartsWith("./")) path = path.Remove(0, 2); path = (pwd + "/" + path).GetFtpPath(); } } return path; } #endif private static string DecodeUrl(string url) { #if CORE return WebUtility.UrlDecode(url); #else return HttpUtility.UrlDecode(url); #endif } private static byte[] ReadToEnd(Stream stream, long maxLength, int chunkLen) { int read = 1; byte[] buffer = new byte[chunkLen]; using (var mem = new MemoryStream()) { do { long length = maxLength == 0 ? buffer.Length : Math.Min(maxLength - (int)mem.Length, buffer.Length); read = stream.Read(buffer, 0, (int)length); mem.Write(buffer, 0, read); if (maxLength > 0 && mem.Length == maxLength) break; } while (read > 0); return mem.ToArray(); } } /// /// Disables UTF8 support and changes the Encoding property /// back to ASCII. If the server returns an error when trying /// to turn UTF8 off a FtpCommandException will be thrown. /// public void DisableUTF8() { FtpReply reply; #if !CORE14 lock (m_lock) { #endif if (!(reply = Execute("OPTS UTF8 OFF")).Success) throw new FtpCommandException(reply); m_textEncoding = Encoding.ASCII; m_textEncodingAutoUTF = false; #if !CORE14 } #endif } /// /// Data shouldn't be on the socket, if it is it probably /// means we've been disconnected. Read and discard /// whatever is there and close the connection (optional). /// /// close the connection? /// even read encrypted data? /// trace data to logs? private void ReadStaleData(bool closeStream, bool evenEncrypted, bool traceData) { if (m_stream != null && m_stream.SocketDataAvailable > 0) { if (traceData) { FtpTrace.WriteStatus(FtpTraceLevel.Info, "There is stale data on the socket, maybe our connection timed out or you did not call GetReply(). Re-connecting..."); } if (m_stream.IsConnected && (!m_stream.IsEncrypted || evenEncrypted)) { byte[] buf = new byte[m_stream.SocketDataAvailable]; m_stream.RawSocketRead(buf); if (traceData) { FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "The stale data was: " + Encoding.GetString(buf).TrimEnd('\r', '\n')); } } if (closeStream) { m_stream.Close(); } } } #if ASYNC /// /// Data shouldn't be on the socket, if it is it probably /// means we've been disconnected. Read and discard /// whatever is there and close the connection (optional). /// /// close the connection? /// even read encrypted data? /// trace data to logs? private async Task ReadStaleDataAsync(bool closeStream, bool evenEncrypted, bool traceData) { if (m_stream != null && m_stream.SocketDataAvailable > 0) { if (traceData) { FtpTrace.WriteStatus(FtpTraceLevel.Info, "There is stale data on the socket, maybe our connection timed out or you did not call GetReply(). Re-connecting..."); } if (m_stream.IsConnected && (!m_stream.IsEncrypted || evenEncrypted)) { byte[] buf = new byte[m_stream.SocketDataAvailable]; await m_stream.RawSocketReadAsync(buf); if (traceData) { FtpTrace.WriteStatus(FtpTraceLevel.Verbose, "The stale data was: " + Encoding.GetString(buf).TrimEnd('\r', '\n')); } } if (closeStream) { m_stream.Close(); } } } #endif private bool IsProxy() { return (this is FtpClientProxy); } private static string[] fileNotFoundStrings = new string[] { "can't check for file existence", "does not exist", "failed to open file", "not found", "no such file", "cannot find the file", "cannot find", "could not get file", "not a regular file", "file unavailable", "file is unavailable", "file not unavailable", "file is not available", "no files found", "no file found" }; private bool IsKnownError(string reply, string[] strings) { reply = reply.ToLower(); foreach (string msg in strings) { if (reply.Contains(msg)) { return true; } } return false; } #endregion #region Static API /// /// Calculate the CHMOD integer value given a set of permissions. /// public static int CalcChmod(FtpPermission owner, FtpPermission group, FtpPermission other) { int chmod = 0; if (HasPermission(owner, FtpPermission.Read)) { chmod += 400; } if (HasPermission(owner, FtpPermission.Write)) { chmod += 200; } if (HasPermission(owner, FtpPermission.Execute)) { chmod += 100; } if (HasPermission(group, FtpPermission.Read)) { chmod += 40; } if (HasPermission(group, FtpPermission.Write)) { chmod += 20; } if (HasPermission(group, FtpPermission.Execute)) { chmod += 10; } if (HasPermission(other, FtpPermission.Read)) { chmod += 4; } if (HasPermission(other, FtpPermission.Write)) { chmod += 2; } if (HasPermission(other, FtpPermission.Execute)) { chmod += 1; } return chmod; } private static bool HasPermission(FtpPermission owner, FtpPermission flag) { return (owner & flag) == flag; } //TODO: Create async versions of static methods /// /// Connects to the specified URI. If the path specified by the URI ends with a /// / then the working directory is changed to the path specified. /// /// The URI to parse /// Indicates if a ssl certificate should be validated when using FTPS schemes /// FtpClient object public static FtpClient Connect(Uri uri, bool checkcertificate) { FtpClient cl = new FtpClient(); if (uri == null) throw new ArgumentException("Invalid URI object"); switch (uri.Scheme.ToLower()) { case "ftp": case "ftps": break; default: throw new UriFormatException("The specified URI scheme is not supported. Please use ftp:// or ftps://"); } cl.Host = uri.Host; cl.Port = uri.Port; if (uri.UserInfo != null && uri.UserInfo.Length > 0) { if (uri.UserInfo.Contains(":")) { string[] parts = uri.UserInfo.Split(':'); if (parts.Length != 2) throw new UriFormatException("The user info portion of the URI contains more than 1 colon. The username and password portion of the URI should be URL encoded."); cl.Credentials = new NetworkCredential(DecodeUrl(parts[0]), DecodeUrl(parts[1])); } else cl.Credentials = new NetworkCredential(DecodeUrl(uri.UserInfo), ""); } else { // if no credentials were supplied just make up // some for anonymous authentication. cl.Credentials = new NetworkCredential("ftp", "ftp"); } cl.ValidateCertificate += new FtpSslValidation(delegate (FtpClient control, FtpSslValidationEventArgs e) { if (e.PolicyErrors != System.Net.Security.SslPolicyErrors.None && checkcertificate) e.Accept = false; else e.Accept = true; }); cl.Connect(); if (uri.PathAndQuery != null && uri.PathAndQuery.EndsWith("/")) cl.SetWorkingDirectory(uri.PathAndQuery); return cl; } /// /// Connects to the specified URI. If the path specified by the URI ends with a /// / then the working directory is changed to the path specified. /// /// The URI to parse /// FtpClient object public static FtpClient Connect(Uri uri) { return Connect(uri, true); } /// /// Opens a stream to the file specified by the URI /// /// FTP/FTPS URI pointing at a file /// Indicates if a ssl certificate should be validated when using FTPS schemes /// ASCII/Binary mode /// Restart location /// Stream object /// public static Stream OpenRead(Uri uri, bool checkcertificate, FtpDataType datatype, long restart) { FtpClient cl = null; CheckURI(uri); cl = Connect(uri, checkcertificate); cl.EnableThreadSafeDataConnections = false; return cl.OpenRead(uri.PathAndQuery, datatype, restart); } /// /// Opens a stream to the file specified by the URI /// /// FTP/FTPS URI pointing at a file /// Indicates if a ssl certificate should be validated when using FTPS schemes /// ASCII/Binary mode /// Stream object /// public static Stream OpenRead(Uri uri, bool checkcertificate, FtpDataType datatype) { return OpenRead(uri, checkcertificate, datatype, 0); } /// /// Opens a stream to the file specified by the URI /// /// FTP/FTPS URI pointing at a file /// Indicates if a ssl certificate should be validated when using FTPS schemes /// Stream object /// public static Stream OpenRead(Uri uri, bool checkcertificate) { return OpenRead(uri, checkcertificate, FtpDataType.Binary, 0); } /// /// Opens a stream to the file specified by the URI /// /// FTP/FTPS URI pointing at a file /// Stream object /// public static Stream OpenRead(Uri uri) { return OpenRead(uri, true, FtpDataType.Binary, 0); } /// /// Opens a stream to the file specified by the URI /// /// FTP/FTPS URI pointing at a file /// Indicates if a ssl certificate should be validated when using FTPS schemes /// ASCII/Binary mode /// Stream object /// public static Stream OpenWrite(Uri uri, bool checkcertificate, FtpDataType datatype) { FtpClient cl = null; CheckURI(uri); cl = Connect(uri, checkcertificate); cl.EnableThreadSafeDataConnections = false; return cl.OpenWrite(uri.PathAndQuery, datatype); } /// /// Opens a stream to the file specified by the URI /// /// FTP/FTPS URI pointing at a file /// Indicates if a ssl certificate should be validated when using FTPS schemes /// Stream object /// public static Stream OpenWrite(Uri uri, bool checkcertificate) { return OpenWrite(uri, checkcertificate, FtpDataType.Binary); } /// /// Opens a stream to the file specified by the URI /// /// FTP/FTPS URI pointing at a file /// Stream object /// public static Stream OpenWrite(Uri uri) { return OpenWrite(uri, true, FtpDataType.Binary); } /// /// Opens a stream to the file specified by the URI /// /// FTP/FTPS URI pointing at a file /// Indicates if a ssl certificate should be validated when using FTPS schemes /// ASCII/Binary mode /// Stream object /// public static Stream OpenAppend(Uri uri, bool checkcertificate, FtpDataType datatype) { FtpClient cl = null; CheckURI(uri); cl = Connect(uri, checkcertificate); cl.EnableThreadSafeDataConnections = false; return cl.OpenAppend(uri.PathAndQuery, datatype); } /// /// Opens a stream to the file specified by the URI /// /// FTP/FTPS URI pointing at a file /// Indicates if a ssl certificate should be validated when using FTPS schemes /// Stream object /// public static Stream OpenAppend(Uri uri, bool checkcertificate) { return OpenAppend(uri, checkcertificate, FtpDataType.Binary); } /// /// Opens a stream to the file specified by the URI /// /// FTP/FTPS URI pointing at a file /// Stream object /// public static Stream OpenAppend(Uri uri) { return OpenAppend(uri, true, FtpDataType.Binary); } private static void CheckURI(Uri uri) { if (string.IsNullOrEmpty(uri.PathAndQuery)) { throw new UriFormatException("The supplied URI does not contain a valid path."); } if (uri.PathAndQuery.EndsWith("/")) { throw new UriFormatException("The supplied URI points at a directory."); } } /// /// Calculate you public internet IP using the ipify service. Returns null if cannot be calculated. /// /// Public IP Address public static string GetPublicIP() { #if NETFX try { var request = WebRequest.Create("https://api.ipify.org/"); request.Method = "GET"; using (var response = request.GetResponse()) { using (var stream = new StreamReader(response.GetResponseStream())) { return stream.ReadToEnd(); } } } catch (Exception) { } #endif return null; } #endregion } }