using System; using System.Linq; using System.Collections.Generic; using System.IO; using Renci.SshNet.Sftp; using System.Text; using Renci.SshNet.Common; using System.Globalization; using System.Threading; using System.Diagnostics.CodeAnalysis; namespace Renci.SshNet { /// /// Implementation of the SSH File Transfer Protocol (SFTP) over SSH. /// public partial class SftpClient : BaseClient { /// /// Holds SftpSession instance that used to communicate to the SFTP server /// private SftpSession _sftpSession; private bool _disposeConnectionInfo; /// /// Gets or sets the operation timeout. /// /// The operation timeout. public TimeSpan OperationTimeout { get; set; } /// /// Gets or sets the size of the buffer. /// /// The size of the buffer. public uint BufferSize { get; set; } /// /// Gets remote working directory. /// public string WorkingDirectory { get { if (this._sftpSession == null) return null; return this._sftpSession.WorkingDirectory; } } /// /// Gets sftp protocol version. /// public int ProtocolVersion { get; private set; } #region Constructors /// /// Initializes a new instance of the class. /// /// The connection info. /// is null. public SftpClient(ConnectionInfo connectionInfo) : base(connectionInfo) { this.OperationTimeout = new TimeSpan(0, 0, 0, 0, -1); this.BufferSize = 1024 * 16; } /// /// Initializes a new instance of the class. /// /// Connection host. /// Connection port. /// Authentication username. /// Authentication password. /// is null. /// is invalid. -or- is null or contains whitespace characters. /// is not within and . [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")] public SftpClient(string host, int port, string username, string password) : this(new PasswordConnectionInfo(host, port, username, password)) { this._disposeConnectionInfo = true; } /// /// Initializes a new instance of the class. /// /// Connection host. /// Authentication username. /// Authentication password. /// is null. /// is invalid. -or- is null contains whitespace characters. public SftpClient(string host, string username, string password) : this(host, ConnectionInfo.DEFAULT_PORT, username, password) { } /// /// Initializes a new instance of the class. /// /// Connection host. /// Connection port. /// Authentication username. /// Authentication private key file(s) . /// is null. /// is invalid. -or- is nunullll or contains whitespace characters. /// is not within and . [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")] public SftpClient(string host, int port, string username, params PrivateKeyFile[] keyFiles) : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles)) { this._disposeConnectionInfo = true; } /// /// Initializes a new instance of the class. /// /// Connection host. /// Authentication username. /// Authentication private key file(s) . /// is null. /// is invalid. -or- is null or contains whitespace characters. public SftpClient(string host, string username, params PrivateKeyFile[] keyFiles) : this(host, ConnectionInfo.DEFAULT_PORT, username, keyFiles) { } #endregion /// /// Changes remote directory to path. /// /// New directory path. /// is null. /// Client is not connected. /// Permission to change directory denied by remote host. -or- A SSH command was denied by the server. /// The path in was not found on the remote host. /// A SSH error where is the message from the remote host. public void ChangeDirectory(string path) { if (path == null) throw new ArgumentNullException("path"); if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); this._sftpSession.ChangeDirectory(path); } /// /// Changes permissions of file(s) to specified mode. /// /// File(s) path, may match multiple files. /// The mode. /// is null. /// Client is not connected. /// Permission to change permission on the path(s) was denied by the remote host. -or- A SSH command was denied by the server. /// The path in was not found on the remote host. /// A SSH error where is the message from the remote host. public void ChangePermissions(string path, short mode) { var file = this.Get(path); file.SetPermissions(mode); } /// /// Creates remote directory specified by path. /// /// Directory path to create. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to create the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. public void CreateDirectory(string path) { if (path.IsNullOrWhiteSpace()) throw new ArgumentException(path); if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = this._sftpSession.GetCanonicalPath(path); this._sftpSession.RequestMkDir(fullPath); } /// /// Deletes remote directory specified by path. /// /// Directory to be deleted path. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to delete the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. public void DeleteDirectory(string path) { if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = this._sftpSession.GetCanonicalPath(path); this._sftpSession.RequestRmDir(fullPath); } /// /// Deletes remote file specified by path. /// /// File to be deleted path. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to delete the file was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. public void DeleteFile(string path) { if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = this._sftpSession.GetCanonicalPath(path); this._sftpSession.RequestRemove(fullPath); } /// /// Renames remote file from old path to new path. /// /// Path to the old file location. /// Path to the new file location. /// is null. -or- or is null. /// Client is not connected. /// Permission to rename the file was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. public void RenameFile(string oldPath, string newPath) { this.RenameFile(oldPath, newPath, false); } /// /// Renames remote file from old path to new path. /// /// Path to the old file location. /// Path to the new file location. /// if set to true then perform a posix rename. /// oldPath /// is null. -or- or is null. /// Client is not connected. /// Permission to rename the file was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. public void RenameFile(string oldPath, string newPath, bool isPosix) { if (oldPath == null) throw new ArgumentNullException("oldPath"); if (newPath == null) throw new ArgumentNullException("newPath"); if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var oldFullPath = this._sftpSession.GetCanonicalPath(oldPath); var newFullPath = this._sftpSession.GetCanonicalPath(newPath); if (isPosix) { this._sftpSession.RequestPosixRename(oldFullPath, newFullPath); } else { this._sftpSession.RequestRename(oldFullPath, newFullPath); } } /// /// Creates a symbolic link from old path to new path. /// /// The old path. /// The new path. /// is null. -or- is null or contains whitespace characters. /// Client is not connected. /// Permission to create the symbolic link was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. public void SymbolicLink(string path, string linkPath) { if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (linkPath.IsNullOrWhiteSpace()) throw new ArgumentException("linkPath"); if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = this._sftpSession.GetCanonicalPath(path); var linkFullPath = this._sftpSession.GetCanonicalPath(linkPath); this._sftpSession.RequestSymLink(fullPath, linkFullPath); } /// /// Retrieves list of files in remote directory. /// /// The path. /// The list callback. /// /// List of directory entries /// /// is null. /// Client is not connected. /// Permission to list the contents of the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. public IEnumerable ListDirectory(string path, Action listCallback = null) { return InternalListDirectory(path, listCallback); } /// /// Begins an asynchronous operation of retrieving list of files in remote directory. /// /// The path. /// The method to be called when the asynchronous write operation is completed. /// A user-provided object that distinguishes this particular asynchronous write request from other requests. /// The list callback. /// /// An that references the asynchronous operation. /// public IAsyncResult BeginListDirectory(string path, AsyncCallback asyncCallback, object state, Action listCallback = null) { var asyncResult = new SftpListDirectoryAsyncResult(asyncCallback, state); this.ExecuteThread(() => { try { var result = this.InternalListDirectory(path, (count) => { asyncResult.Update(count); if (listCallback != null) { listCallback(count); } }); asyncResult.SetAsCompleted(result, false); } catch (Exception exp) { asyncResult.SetAsCompleted(exp, false); } }); return asyncResult; } /// /// Ends an asynchronous operation of retrieving list of files in remote directory. /// /// The pending asynchronous SFTP request. /// /// List of files /// /// The IAsyncResult object () did not come from the corresponding async method on this type. -or- EndExecute was called multiple times with the same IAsyncResult. public IEnumerable EndListDirectory(IAsyncResult asyncResult) { var ar = asyncResult as SftpListDirectoryAsyncResult; if (ar == null || ar.EndInvokeCalled) throw new ArgumentException("Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult."); // Wait for operation to complete, then return result or throw exception return ar.EndInvoke(); } /// /// Gets reference to remote file or directory. /// /// The path. /// Reference to file object. /// path /// Client is not connected. /// is null. public SftpFile Get(string path) { if (path == null) throw new ArgumentNullException("path"); if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = this._sftpSession.GetCanonicalPath(path); var attributes = this._sftpSession.RequestLStat(fullPath); return new SftpFile(this._sftpSession, fullPath, attributes); } /// /// Checks whether file pr directory exists; /// /// The path. /// true if directory or file exists; otherwise false. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to perform the operation was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. public bool Exists(string path) { if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = this._sftpSession.GetFullRemotePath(path); if (this._sftpSession.RequestRealPath(fullPath, true) == null) { return false; } else { return true; } } /// /// Downloads remote file specified by the path into the stream. /// /// File to download. /// Stream to write the file into. /// The download callback. /// is null. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to perform the operation was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public void DownloadFile(string path, Stream output, Action downloadCallback = null) { this.InternalDownloadFile(path, output, null, downloadCallback); } /// /// Begins an asynchronous file downloading into the stream. /// /// The path. /// The output. /// /// An that references the asynchronous operation. /// /// path /// output /// is null. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to perform the operation was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public IAsyncResult BeginDownloadFile(string path, Stream output) { return this.BeginDownloadFile(path, output, null, null, null); } /// /// Begins an asynchronous file downloading into the stream. /// /// The path. /// The output. /// The method to be called when the asynchronous write operation is completed. /// /// An that references the asynchronous operation. /// /// path /// output /// is null. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to perform the operation was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public IAsyncResult BeginDownloadFile(string path, Stream output, AsyncCallback asyncCallback) { return this.BeginDownloadFile(path, output, asyncCallback, null, null); } /// /// Begins an asynchronous file downloading into the stream. /// /// The path. /// The output. /// The method to be called when the asynchronous write operation is completed. /// A user-provided object that distinguishes this particular asynchronous write request from other requests. /// The download callback. /// /// An that references the asynchronous operation. /// /// path /// output /// is null. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to perform the operation was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public IAsyncResult BeginDownloadFile(string path, Stream output, AsyncCallback asyncCallback, object state, Action downloadCallback = null) { if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (output == null) throw new ArgumentNullException("output"); var asyncResult = new SftpDownloadAsyncResult(asyncCallback, state); this.ExecuteThread(() => { try { this.InternalDownloadFile(path, output, asyncResult, (offset) => { asyncResult.Update(offset); if (downloadCallback != null) { downloadCallback(offset); } }); asyncResult.SetAsCompleted(null, false); } catch (Exception exp) { asyncResult.SetAsCompleted(exp, false); } }); return asyncResult; } /// /// Ends an asynchronous file downloading into the stream. /// /// The pending asynchronous SFTP request. /// The IAsyncResult object () did not come from the corresponding async method on this type. -or- EndExecute was called multiple times with the same IAsyncResult. public void EndDownloadFile(IAsyncResult asyncResult) { var ar = asyncResult as SftpDownloadAsyncResult; if (ar == null || ar.EndInvokeCalled) throw new ArgumentException("Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult."); // Wait for operation to complete, then return result or throw exception ar.EndInvoke(); } /// /// Uploads stream into remote file.. /// /// Data input stream. /// Remote file path. /// The upload callback. /// is null. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to upload the file was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public void UploadFile(Stream input, string path, Action uploadCallback = null) { this.UploadFile(input, path, true, uploadCallback); } /// /// Uploads stream into remote file.. /// /// Data input stream. /// Remote file path. /// if set to true then existing file will be overwritten. /// The upload callback. /// is null. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to upload the file was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public void UploadFile(Stream input, string path, bool canOverride, Action uploadCallback = null) { var flags = Flags.Write | Flags.Truncate; if (canOverride) flags |= Flags.CreateNewOrOpen; else flags |= Flags.CreateNew; this.InternalUploadFile(input, path, flags, null, uploadCallback); } /// /// Begins an asynchronous uploading the steam into remote file. /// /// Data input stream. /// Remote file path. /// /// An that references the asynchronous operation. /// /// is null. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to list the contents of the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public IAsyncResult BeginUploadFile(Stream input, string path) { return this.BeginUploadFile(input, path, true, null, null, null); } /// /// Begins an asynchronous uploading the steam into remote file. /// /// Data input stream. /// Remote file path. /// The method to be called when the asynchronous write operation is completed. /// /// An that references the asynchronous operation. /// /// is null. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to list the contents of the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public IAsyncResult BeginUploadFile(Stream input, string path, AsyncCallback asyncCallback) { return this.BeginUploadFile(input, path, true, asyncCallback, null, null); } /// /// Begins an asynchronous uploading the steam into remote file. /// /// Data input stream. /// Remote file path. /// The method to be called when the asynchronous write operation is completed. /// A user-provided object that distinguishes this particular asynchronous write request from other requests. /// The upload callback. /// /// An that references the asynchronous operation. /// /// is null. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to list the contents of the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public IAsyncResult BeginUploadFile(Stream input, string path, AsyncCallback asyncCallback, object state, Action uploadCallback = null) { return this.BeginUploadFile(input, path, true, asyncCallback, state, uploadCallback); } /// /// Begins an asynchronous uploading the steam into remote file. /// /// Data input stream. /// Remote file path. /// if set to true then existing file will be overwritten. /// The method to be called when the asynchronous write operation is completed. /// A user-provided object that distinguishes this particular asynchronous write request from other requests. /// The upload callback. /// /// An that references the asynchronous operation. /// /// input /// path /// is null. /// is null or contains whitespace characters. /// Client is not connected. /// Permission to list the contents of the directory was denied by the remote host. -or- A SSH command was denied by the server. /// A SSH error where is the message from the remote host. /// /// Method calls made by this method to , may under certain conditions result in exceptions thrown by the stream. /// public IAsyncResult BeginUploadFile(Stream input, string path, bool canOverride, AsyncCallback asyncCallback, object state, Action uploadCallback = null) { if (input == null) throw new ArgumentNullException("input"); if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); var flags = Flags.Write | Flags.Truncate; if (canOverride) flags |= Flags.CreateNewOrOpen; else flags |= Flags.CreateNew; var asyncResult = new SftpUploadAsyncResult(asyncCallback, state); this.ExecuteThread(() => { try { this.InternalUploadFile(input, path, flags, asyncResult, (offset) => { asyncResult.Update(offset); if (uploadCallback != null) { uploadCallback(offset); } }); asyncResult.SetAsCompleted(null, false); } catch (Exception exp) { asyncResult.SetAsCompleted(exp, false); } }); return asyncResult; } /// /// Ends an asynchronous uploading the steam into remote file. /// /// The pending asynchronous SFTP request. /// Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult. /// The IAsyncResult object () did not come from the corresponding async method on this type. -or- EndExecute was called multiple times with the same IAsyncResult. public void EndUploadFile(IAsyncResult asyncResult) { var ar = asyncResult as SftpUploadAsyncResult; if (ar == null || ar.EndInvokeCalled) throw new ArgumentException("Either the IAsyncResult object did not come from the corresponding async method on this type, or EndExecute was called multiple times with the same IAsyncResult."); // Wait for operation to complete, then return result or throw exception ar.EndInvoke(); } /// /// Gets status using statvfs@openssh.com request. /// /// The path. /// Reference to object that contains file status information. /// path /// Client is not connected. /// is null. public SftpFileSytemInformation GetStatus(string path) { if (path == null) throw new ArgumentNullException("path"); if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = this._sftpSession.GetCanonicalPath(path); return this._sftpSession.RequestStatVfs(fullPath); } #region File Methods /// /// Appends lines to a file, and then closes the file. /// /// The file to append the lines to. The file is created if it does not already exist. /// The lines to append to the file. /// isnull -or- is null. public void AppendAllLines(string path, IEnumerable contents) { if (contents == null) throw new ArgumentNullException("contents"); using (var stream = this.AppendText(path)) { foreach (var line in contents) { stream.WriteLine(line); } } } /// /// Appends lines to a file by using a specified encoding, and then closes the file. /// /// The file to append the lines to. The file is created if it does not already exist. /// The lines to append to the file. /// The character encoding to use. /// is null. -or- is null. -or- is null. public void AppendAllLines(string path, IEnumerable contents, Encoding encoding) { if (contents == null) throw new ArgumentNullException("contents"); using (var stream = this.AppendText(path, encoding)) { foreach (var line in contents) { stream.WriteLine(line); } } } /// /// Opens a file, appends the specified string to the file, and then closes the file. /// If the file does not exist, this method creates a file, writes the specified string to the file, then closes the file. /// /// The file to append the specified string to. /// The string to append to the file. /// is null. -or- is null. public void AppendAllText(string path, string contents) { using (var stream = this.AppendText(path)) { stream.Write(contents); } } /// /// Opens a file, appends the specified string to the file, and then closes the file. /// If the file does not exist, this method creates a file, writes the specified string to the file, then closes the file. /// /// The file to append the specified string to. /// The string to append to the file. /// The character encoding to use. /// is null. -or- is null. -or- is null. public void AppendAllText(string path, string contents, Encoding encoding) { using (var stream = this.AppendText(path, encoding)) { stream.Write(contents); } } /// /// Creates a that appends UTF-8 encoded text to an existing file. /// /// The path to the file to append to. /// A StreamWriter that appends UTF-8 encoded text to an existing file. /// is null. public StreamWriter AppendText(string path) { return this.AppendText(path, Encoding.UTF8); } /// /// Creates a that appends UTF-8 encoded text to an existing file. /// /// The path to the file to append to. /// The character encoding to use. /// /// A StreamWriter that appends UTF-8 encoded text to an existing file. /// /// is null. -or- is null. public StreamWriter AppendText(string path, Encoding encoding) { if (encoding == null) throw new ArgumentNullException("encoding"); return new StreamWriter(new SftpFileStream(this._sftpSession, path, FileMode.Append, FileAccess.Write), encoding); } /// /// Creates or overwrites a file in the specified path. /// /// The path and name of the file to create. /// A that provides read/write access to the file specified in path /// is null. public SftpFileStream Create(string path) { return new SftpFileStream(this._sftpSession, path, FileMode.Create, FileAccess.ReadWrite); } /// /// Creates or overwrites the specified file. /// /// The path and name of the file to create. /// The number of bytes buffered for reads and writes to the file. /// A that provides read/write access to the file specified in path /// is null. public SftpFileStream Create(string path, int bufferSize) { return new SftpFileStream(this._sftpSession, path, FileMode.Create, FileAccess.ReadWrite, bufferSize); } /// /// Creates or opens a file for writing UTF-8 encoded text. /// /// The file to be opened for writing. /// A that writes to the specified file using UTF-8 encoding. /// is null. public StreamWriter CreateText(string path) { return new StreamWriter(this.OpenWrite(path), Encoding.UTF8); } /// /// Creates or opens a file for writing UTF-8 encoded text. /// /// The file to be opened for writing. /// The character encoding to use. /// A that writes to the specified file using UTF-8 encoding. /// is null. public StreamWriter CreateText(string path, Encoding encoding) { return new StreamWriter(this.OpenWrite(path), encoding); } /// /// Deletes the specified file or directory. An exception is not thrown if the specified file does not exist. /// /// The name of the file or directory to be deleted. Wildcard characters are not supported. /// is null. /// Client is not connected. public void Delete(string path) { var file = this.Get(path); file.Delete(); } /// /// Returns the date and time the specified file or directory was last accessed. /// /// The file or directory for which to obtain access date and time information. /// A structure set to the date and time that the specified file or directory was last accessed. This value is expressed in local time. /// is null. /// Client is not connected. public DateTime GetLastAccessTime(string path) { var file = this.Get(path); return file.LastAccessTime; } /// /// Returns the date and time, in coordinated universal time (UTC), that the specified file or directory was last accessed. /// /// The file or directory for which to obtain access date and time information. /// A structure set to the date and time that the specified file or directory was last accessed. This value is expressed in UTC time. /// is null. /// Client is not connected. public DateTime GetLastAccessTimeUtc(string path) { var file = this.Get(path); return file.LastAccessTime.ToUniversalTime(); } /// /// Returns the date and time the specified file or directory was last written to. /// /// The file or directory for which to obtain write date and time information. /// A structure set to the date and time that the specified file or directory was last written to. This value is expressed in local time. /// is null. /// Client is not connected. public DateTime GetLastWriteTime(string path) { var file = this.Get(path); return file.LastWriteTime; } /// /// Returns the date and time, in coordinated universal time (UTC), that the specified file or directory was last written to. /// /// The file or directory for which to obtain write date and time information. /// A structure set to the date and time that the specified file or directory was last written to. This value is expressed in UTC time. /// is null. /// Client is not connected. public DateTime GetLastWriteTimeUtc(string path) { var file = this.Get(path); return file.LastWriteTime.ToUniversalTime(); } /// /// Opens a on the specified path with read/write access. /// /// The file to open. /// A value that specifies whether a file is created if one does not exist, and determines whether the contents of existing files are retained or overwritten. /// An unshared that provides access to the specified file, with the specified mode and access. /// is null. public SftpFileStream Open(string path, FileMode mode) { return new SftpFileStream(this._sftpSession, path, mode, FileAccess.ReadWrite); } /// /// Opens a on the specified path, with the specified mode and access. /// /// The file to open. /// A value that specifies whether a file is created if one does not exist, and determines whether the contents of existing files are retained or overwritten. /// A value that specifies the operations that can be performed on the file. /// An unshared that provides access to the specified file, with the specified mode and access. /// is null. public SftpFileStream Open(string path, FileMode mode, FileAccess access) { return new SftpFileStream(this._sftpSession, path, mode, access); } /// /// Opens an existing file for reading. /// /// The file to be opened for reading. /// A read-only System.IO.FileStream on the specified path. /// is null. public SftpFileStream OpenRead(string path) { return new SftpFileStream(this._sftpSession, path, FileMode.Open, FileAccess.Read); } /// /// Opens an existing UTF-8 encoded text file for reading. /// /// The file to be opened for reading. /// A on the specified path. /// is null. public StreamReader OpenText(string path) { return new StreamReader(this.OpenRead(path), Encoding.UTF8); } /// /// Opens an existing file for writing. /// /// The file to be opened for writing. /// An unshared object on the specified path with access. /// is null. public SftpFileStream OpenWrite(string path) { return new SftpFileStream(this._sftpSession, path, FileMode.OpenOrCreate, FileAccess.Write); } /// /// Opens a binary file, reads the contents of the file into a byte array, and then closes the file. /// /// The file to open for reading. /// A byte array containing the contents of the file. /// is null. public byte[] ReadAllBytes(string path) { using (var stream = this.OpenRead(path)) { var buffer = new byte[stream.Length]; stream.Read(buffer, 0, buffer.Length); return buffer; } } /// /// Opens a text file, reads all lines of the file, and then closes the file. /// /// The file to open for reading. /// A string array containing all lines of the file. /// is null. public string[] ReadAllLines(string path) { return this.ReadAllLines(path, Encoding.UTF8); } /// /// Opens a file, reads all lines of the file with the specified encoding, and then closes the file. /// /// The file to open for reading. /// The encoding applied to the contents of the file. /// A string array containing all lines of the file. /// is null. public string[] ReadAllLines(string path, Encoding encoding) { var lines = new List(); using (var stream = new StreamReader(this.OpenRead(path), encoding)) { while (!stream.EndOfStream) { lines.Add(stream.ReadLine()); } } return lines.ToArray(); } /// /// Opens a text file, reads all lines of the file, and then closes the file. /// /// The file to open for reading. /// A string containing all lines of the file. /// is null. public string ReadAllText(string path) { return this.ReadAllText(path, Encoding.UTF8); } /// /// Opens a file, reads all lines of the file with the specified encoding, and then closes the file. /// /// The file to open for reading. /// The encoding applied to the contents of the file. /// A string containing all lines of the file. /// is null. public string ReadAllText(string path, Encoding encoding) { var lines = new List(); using (var stream = new StreamReader(this.OpenRead(path), encoding)) { return stream.ReadToEnd(); } } /// /// Reads the lines of a file. /// /// The file to read. /// The lines of the file. /// is null. public IEnumerable ReadLines(string path) { return this.ReadAllLines(path); } /// /// Read the lines of a file that has a specified encoding. /// /// The file to read. /// The encoding that is applied to the contents of the file. /// The lines of the file. /// is null. public IEnumerable ReadLines(string path, Encoding encoding) { return this.ReadAllLines(path, encoding); } /// /// Sets the date and time the specified file was last accessed. /// /// The file for which to set the access date and time information. /// A containing the value to set for the last access date and time of path. This value is expressed in local time. [Obsolete("Note: This method currently throws NotImplementedException because it has not yet been implemented.")] public void SetLastAccessTime(string path, DateTime lastAccessTime) { throw new NotImplementedException(); } /// /// Sets the date and time, in coordinated universal time (UTC), that the specified file was last accessed. /// /// The file for which to set the access date and time information. /// A containing the value to set for the last access date and time of path. This value is expressed in UTC time. [Obsolete("Note: This method currently throws NotImplementedException because it has not yet been implemented.")] public void SetLastAccessTimeUtc(string path, DateTime lastAccessTimeUtc) { throw new NotImplementedException(); } /// /// Sets the date and time that the specified file was last written to. /// /// The file for which to set the date and time information. /// A System.DateTime containing the value to set for the last write date and time of path. This value is expressed in local time. [Obsolete("Note: This method currently throws NotImplementedException because it has not yet been implemented.")] public void SetLastWriteTime(string path, DateTime lastWriteTime) { throw new NotImplementedException(); } /// /// Sets the date and time, in coordinated universal time (UTC), that the specified file was last written to. /// /// The file for which to set the date and time information. /// A System.DateTime containing the value to set for the last write date and time of path. This value is expressed in UTC time. [Obsolete("Note: This method currently throws NotImplementedException because it has not yet been implemented.")] public void SetLastWriteTimeUtc(string path, DateTime lastWriteTimeUtc) { throw new NotImplementedException(); } /// /// Creates a new file, writes the specified byte array to the file, and then closes the file. If the target file already exists, it is overwritten. /// /// The file to write to. /// The bytes to write to the file. /// is null. public void WriteAllBytes(string path, byte[] bytes) { using (var stream = this.OpenWrite(path)) { stream.Write(bytes, 0, bytes.Length); } } /// /// Creates a new file, writes a collection of strings to the file, and then closes the file. /// /// The file to write to. /// The lines to write to the file. /// is null. public void WriteAllLines(string path, IEnumerable contents) { this.WriteAllLines(path, contents, Encoding.UTF8); } /// /// Creates a new file, write the specified string array to the file, and then closes the file. /// /// The file to write to. /// The string array to write to the file. /// is null. public void WriteAllLines(string path, string[] contents) { this.WriteAllLines(path, contents, Encoding.UTF8); } /// /// Creates a new file by using the specified encoding, writes a collection of strings to the file, and then closes the file. /// /// The file to write to. /// The lines to write to the file. /// The character encoding to use. /// is null. public void WriteAllLines(string path, IEnumerable contents, Encoding encoding) { using (var stream = this.CreateText(path, encoding)) { foreach (var line in contents) { stream.WriteLine(line); } } } /// /// Creates a new file, writes the specified string array to the file by using the specified encoding, and then closes the file. /// /// The file to write to. /// The string array to write to the file. /// An object that represents the character encoding applied to the string array. /// is null. public void WriteAllLines(string path, string[] contents, Encoding encoding) { using (var stream = this.CreateText(path, encoding)) { foreach (var line in contents) { stream.WriteLine(line); } } } /// /// Creates a new file, writes the specified string to the file, and then closes the file. If the target file already exists, it is overwritten. /// /// The file to write to. /// The string to write to the file. /// is null. public void WriteAllText(string path, string contents) { using (var stream = this.CreateText(path)) { stream.Write(contents); } } /// /// Creates a new file, writes the specified string to the file using the specified encoding, and then closes the file. If the target file already exists, it is overwritten. /// /// The file to write to. /// The string to write to the file. /// The encoding to apply to the string. /// is null. public void WriteAllText(string path, string contents, Encoding encoding) { using (var stream = this.CreateText(path, encoding)) { stream.Write(contents); } } /// /// Gets the of the file on the path. /// /// The path to the file. /// The of the file on the path. /// is null. public SftpFileAttributes GetAttributes(string path) { if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = this._sftpSession.GetCanonicalPath(path); return this._sftpSession.RequestLStat(fullPath); } /// /// Sets the specified of the file on the specified path. /// /// The path to the file. /// The desired . /// is null. public void SetAttributes(string path, SftpFileAttributes fileAttributes) { if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = this._sftpSession.GetCanonicalPath(path); this._sftpSession.RequestSetStat(fullPath, fileAttributes); } // Please don't forget this when you implement these methods: is null. //public FileSecurity GetAccessControl(string path); //public FileSecurity GetAccessControl(string path, AccessControlSections includeSections); //public DateTime GetCreationTime(string path); //public DateTime GetCreationTimeUtc(string path); //public void SetAccessControl(string path, FileSecurity fileSecurity); //public void SetCreationTime(string path, DateTime creationTime); //public void SetCreationTimeUtc(string path, DateTime creationTimeUtc); #endregion /// /// Internals the list directory. /// /// The path. /// The list callback. /// /// path /// is null. /// Client not connected. private IEnumerable InternalListDirectory(string path, Action listCallback) { if (path == null) throw new ArgumentNullException("path"); if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = this._sftpSession.GetCanonicalPath(path); var handle = this._sftpSession.RequestOpenDir(fullPath); var basePath = fullPath; if (!basePath.EndsWith("/")) basePath = string.Format("{0}/", fullPath); var result = new List(); var files = this._sftpSession.RequestReadDir(handle); while (files != null) { result.AddRange(from f in files select new SftpFile(this._sftpSession, string.Format(CultureInfo.InvariantCulture, "{0}{1}", basePath, f.Key), f.Value)); // Call callback to report number of files read if (listCallback != null) { // Execute callback on different thread this.ExecuteThread(() => { listCallback(result.Count); }); } files = this._sftpSession.RequestReadDir(handle); } this._sftpSession.RequestClose(handle); return result; } /// /// Internals the download file. /// /// The path. /// The output. /// The download callback. /// output /// path /// is null or contains whitespace. /// is null. /// Client not connected. private void InternalDownloadFile(string path, Stream output, SftpDownloadAsyncResult asyncResult, Action downloadCallback) { if (output == null) throw new ArgumentNullException("output"); if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = this._sftpSession.GetCanonicalPath(path); var handle = this._sftpSession.RequestOpen(fullPath, Flags.Read); ulong offset = 0; var data = this._sftpSession.RequestRead(handle, offset, this.BufferSize); // Read data while available while (data.Length > 0) { // Cancel download if (asyncResult != null && asyncResult.IsDownloadCanceled) break; output.Write(data, 0, data.Length); output.Flush(); offset += (ulong)data.Length; // Call callback to report number of bytes read if (downloadCallback != null) { // Execute callback on different thread this.ExecuteThread(() => { downloadCallback(offset); }); } data = this._sftpSession.RequestRead(handle, offset, this.BufferSize); } this._sftpSession.RequestClose(handle); } /// /// Internals the upload file. /// /// The input. /// The path. /// The flags. /// The upload callback. /// input /// path /// is null. /// is null or contains whitespace. /// Client not connected. private void InternalUploadFile(Stream input, string path, Flags flags, SftpUploadAsyncResult asyncResult, Action uploadCallback) { if (input == null) throw new ArgumentNullException("input"); if (path.IsNullOrWhiteSpace()) throw new ArgumentException("path"); if (this._sftpSession == null) throw new SshConnectionException("Client not connected."); var fullPath = this._sftpSession.GetCanonicalPath(path); var handle = this._sftpSession.RequestOpen(fullPath, flags); ulong offset = 0; var buffer = new byte[this.BufferSize]; var bytesRead = input.Read(buffer, 0, buffer.Length); var expectedResponses = 0; var responseReceivedWaitHandle = new AutoResetEvent(false); do { // Cancel upload if (asyncResult != null && asyncResult.IsUploadCanceled) break; if (bytesRead > 0) { if (bytesRead < this.BufferSize) { // Replace buffer for last chunk of data var data = new byte[bytesRead]; Buffer.BlockCopy(buffer, 0, data, 0, bytesRead); buffer = data; } var writtenBytes = offset + (ulong)buffer.Length; this._sftpSession.RequestWrite(handle, offset, buffer, null, (s) => { if (s.StatusCode == StatusCodes.Ok) { Interlocked.Decrement(ref expectedResponses); responseReceivedWaitHandle.Set(); // Call callback to report number of bytes written if (uploadCallback != null) { // Execute callback on different thread this.ExecuteThread(() => { uploadCallback(writtenBytes); }); } } }); Interlocked.Increment(ref expectedResponses); offset += (uint)bytesRead; bytesRead = input.Read(buffer, 0, buffer.Length); } else if (expectedResponses > 0) { // Wait for expectedResponses to change this._sftpSession.WaitHandle(responseReceivedWaitHandle, this.OperationTimeout); } } while (expectedResponses > 0 || bytesRead > 0); this._sftpSession.RequestClose(handle); } partial void ExecuteThread(Action action); /// /// Called when client is connected to the server. /// protected override void OnConnected() { base.OnConnected(); this._sftpSession = new SftpSession(this.Session, this.OperationTimeout, this.ConnectionInfo.Encoding); this._sftpSession.Connect(); // Resolve current running version this.ProtocolVersion = (int)this._sftpSession.ProtocolVersion; } /// /// Called when client is disconnecting from the server. /// protected override void OnDisconnecting() { base.OnDisconnecting(); this._sftpSession.Disconnect(); } /// /// Releases unmanaged and - optionally - managed resources /// /// true to release both managed and unmanaged resources; false to release only unmanaged ResourceMessages. protected override void Dispose(bool disposing) { if (this._sftpSession != null) { this._sftpSession.Dispose(); this._sftpSession = null; } if (this._disposeConnectionInfo) ((IDisposable)this.ConnectionInfo).Dispose(); base.Dispose(disposing); } } }