using System; using Renci.SshNet.Channels; using System.IO; using Renci.SshNet.Common; using System.Text.RegularExpressions; namespace Renci.SshNet { /// /// Provides SCP client functionality. /// public partial class ScpClient { private static readonly Regex _directoryInfoRe = new Regex(@"D(?\d{4}) (?\d+) (?.+)"); private static readonly Regex _timestampRe = new Regex(@"T(?\d+) 0 (?\d+) 0"); /// /// Uploads the specified file to the remote host. /// /// The file system info. /// The path. /// is null. /// is null or empty. public void Upload(FileInfo fileInfo, string path) { if (fileInfo == null) throw new ArgumentNullException("fileInfo"); if (string.IsNullOrEmpty(path)) throw new ArgumentException("path"); using (var input = new PipeStream()) using (var channel = this.Session.CreateClientChannel()) { channel.DataReceived += delegate(object sender, ChannelDataEventArgs e) { input.Write(e.Data, 0, e.Data.Length); input.Flush(); }; channel.Open(); // Send channel command request channel.SendExecRequest(string.Format("scp -t \"{0}\"", path)); this.CheckReturnCode(input); this.InternalUpload(channel, input, fileInfo, fileInfo.Name); channel.Close(); } } /// /// Uploads the specified directory to the remote host. /// /// The directory info. /// The path. /// fileSystemInfo /// is null or empty. public void Upload(DirectoryInfo directoryInfo, string path) { if (directoryInfo == null) throw new ArgumentNullException("directoryInfo"); if (string.IsNullOrEmpty(path)) throw new ArgumentException("path"); using (var input = new PipeStream()) using (var channel = this.Session.CreateClientChannel()) { channel.DataReceived += delegate(object sender, ChannelDataEventArgs e) { input.Write(e.Data, 0, e.Data.Length); input.Flush(); }; channel.Open(); // Send channel command request channel.SendExecRequest(string.Format("scp -rt \"{0}\"", path)); this.CheckReturnCode(input); this.InternalSetTimestamp(channel, input, directoryInfo.LastWriteTimeUtc, directoryInfo.LastAccessTimeUtc); this.SendData(channel, string.Format("D0755 0 {0}\n", Path.GetFileName(path))); this.CheckReturnCode(input); this.InternalUpload(channel, input, directoryInfo); this.SendData(channel, "E\n"); this.CheckReturnCode(input); channel.Close(); } } /// /// Downloads the specified file from the remote host to local file. /// /// Remote host file name. /// Local file information. /// is null. /// is null or empty. public void Download(string filename, FileInfo fileInfo) { if (string.IsNullOrEmpty(filename)) throw new ArgumentException("filename"); if (fileInfo == null) throw new ArgumentNullException("fileInfo"); using (var input = new PipeStream()) using (var channel = this.Session.CreateClientChannel()) { channel.DataReceived += delegate(object sender, ChannelDataEventArgs e) { input.Write(e.Data, 0, e.Data.Length); input.Flush(); }; channel.Open(); // Send channel command request channel.SendExecRequest(string.Format("scp -pf \"{0}\"", filename)); this.SendConfirmation(channel); // Send reply this.InternalDownload(channel, input, fileInfo); channel.Close(); } } /// /// Downloads the specified directory from the remote host to local directory. /// /// Remote host directory name. /// Local directory information. /// is null or empty. /// is null. public void Download(string directoryName, DirectoryInfo directoryInfo) { if (string.IsNullOrEmpty(directoryName)) throw new ArgumentException("directoryName"); if (directoryInfo == null) throw new ArgumentNullException("directoryInfo"); using (var input = new PipeStream()) using (var channel = this.Session.CreateClientChannel()) { channel.DataReceived += delegate(object sender, ChannelDataEventArgs e) { input.Write(e.Data, 0, e.Data.Length); input.Flush(); }; channel.Open(); // Send channel command request channel.SendExecRequest(string.Format("scp -prf \"{0}\"", directoryName)); this.SendConfirmation(channel); // Send reply this.InternalDownload(channel, input, directoryInfo); channel.Close(); } } private void InternalUpload(ChannelSession channel, Stream input, FileInfo fileInfo, string filename) { this.InternalSetTimestamp(channel, input, fileInfo.LastWriteTimeUtc, fileInfo.LastAccessTimeUtc); using (var source = fileInfo.OpenRead()) { this.InternalUpload(channel, input, source, filename); } } private void InternalUpload(ChannelSession channel, Stream input, DirectoryInfo directoryInfo) { // Upload files var files = directoryInfo.GetFiles(); foreach (var file in files) { this.InternalUpload(channel, input, file, file.Name); } // Upload directories var directories = directoryInfo.GetDirectories(); foreach (var directory in directories) { this.InternalSetTimestamp(channel, input, directoryInfo.LastWriteTimeUtc, directoryInfo.LastAccessTimeUtc); this.SendData(channel, string.Format("D0755 0 {0}\n", directory.Name)); this.CheckReturnCode(input); this.InternalUpload(channel, input, directory); this.SendData(channel, "E\n"); this.CheckReturnCode(input); } } private void InternalDownload(ChannelSession channel, Stream input, FileSystemInfo fileSystemInfo) { DateTime modifiedTime = DateTime.Now; DateTime accessedTime = DateTime.Now; var startDirectoryFullName = fileSystemInfo.FullName; var currentDirectoryFullName = startDirectoryFullName; var directoryCounter = 0; while (true) { var message = ReadString(input); if (message == "E") { this.SendConfirmation(channel); // Send reply directoryCounter--; currentDirectoryFullName = new DirectoryInfo(currentDirectoryFullName).Parent.FullName; if (directoryCounter == 0) break; continue; } var match = _directoryInfoRe.Match(message); if (match.Success) { this.SendConfirmation(channel); // Send reply // Read directory var mode = long.Parse(match.Result("${mode}")); var filename = match.Result("${filename}"); DirectoryInfo newDirectoryInfo; if (directoryCounter > 0) { newDirectoryInfo = Directory.CreateDirectory(string.Format("{0}{1}{2}", currentDirectoryFullName, Path.DirectorySeparatorChar, filename)); newDirectoryInfo.LastAccessTime = accessedTime; newDirectoryInfo.LastWriteTime = modifiedTime; } else { // Dont create directory for first level newDirectoryInfo = fileSystemInfo as DirectoryInfo; } directoryCounter++; currentDirectoryFullName = newDirectoryInfo.FullName; continue; } match = _fileInfoRe.Match(message); if (match.Success) { // Read file this.SendConfirmation(channel); // Send reply var mode = match.Result("${mode}"); var length = long.Parse(match.Result("${length}")); var fileName = match.Result("${filename}"); var fileInfo = fileSystemInfo as FileInfo; if (fileInfo == null) fileInfo = new FileInfo(string.Format("{0}{1}{2}", currentDirectoryFullName, Path.DirectorySeparatorChar, fileName)); using (var output = fileInfo.OpenWrite()) { this.InternalDownload(channel, input, output, fileName, length); } fileInfo.LastAccessTime = accessedTime; fileInfo.LastWriteTime = modifiedTime; if (directoryCounter == 0) break; continue; } match = _timestampRe.Match(message); if (match.Success) { // Read timestamp this.SendConfirmation(channel); // Send reply var mtime = long.Parse(match.Result("${mtime}")); var atime = long.Parse(match.Result("${atime}")); var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); modifiedTime = zeroTime.AddSeconds(mtime); accessedTime = zeroTime.AddSeconds(atime); continue; } this.SendConfirmation(channel, 1, string.Format("\"{0}\" is not valid protocol message.", message)); } } partial void SendData(ChannelSession channel, string command) { channel.SendData(System.Text.Encoding.Default.GetBytes(command)); } } }