| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- using System;
- using Renci.SshNet.Channels;
- using System.IO;
- using Renci.SshNet.Common;
- using System.Text.RegularExpressions;
- namespace Renci.SshNet
- {
- /// <summary>
- /// Provides SCP client functionality.
- /// </summary>
- public partial class ScpClient
- {
- private static readonly Regex DirectoryInfoRe = new Regex(@"D(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)");
- private static readonly Regex TimestampRe = new Regex(@"T(?<mtime>\d+) 0 (?<atime>\d+) 0");
- /// <summary>
- /// Uploads the specified file to the remote host.
- /// </summary>
- /// <param name="fileInfo">The file system info.</param>
- /// <param name="path">A relative or absolute path for the remote file.</param>
- /// <exception cref="ArgumentNullException"><paramref name="fileInfo" /> is <c>null</c>.</exception>
- /// <exception cref="ArgumentException"><paramref name="path"/> is <c>null</c> or empty.</exception>
- /// <exception cref="ScpException">A directory with the specified path exists on the remote host.</exception>
- 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 = ServiceFactory.CreatePipeStream())
- using (var channel = Session.CreateChannelSession())
- {
- channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
- channel.Open();
- if (!channel.SendExecRequest(string.Format("scp -t {0}", path.ShellQuote())))
- throw new SshException("Secure copy execution request was rejected by the server. Please consult the server logs.");
- CheckReturnCode(input);
- using (var source = fileInfo.OpenRead())
- {
- UploadTimes(channel, input, fileInfo);
- UploadFileModeAndName(channel, input, source.Length, string.Empty);
- UploadFileContent(channel, input, source, fileInfo.Name);
- }
- }
- }
- /// <summary>
- /// Uploads the specified directory to the remote host.
- /// </summary>
- /// <param name="directoryInfo">The directory info.</param>
- /// <param name="path">A relative or absolute path for the remote directory.</param>
- /// <exception cref="ArgumentNullException">fileSystemInfo</exception>
- /// <exception cref="ArgumentException"><paramref name="path"/> is <c>null</c> or empty.</exception>
- /// <exception cref="ScpException"><paramref name="path"/> exists on the remote host, and is not a directory.</exception>
- 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 = ServiceFactory.CreatePipeStream())
- using (var channel = Session.CreateChannelSession())
- {
- channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
- channel.Open();
- // start recursive upload
- channel.SendExecRequest(string.Format("scp -rt {0}", path.ShellQuote()));
- CheckReturnCode(input);
- UploadTimes(channel, input, directoryInfo);
- UploadDirectoryModeAndName(channel, input, ".");
- UploadDirectoryContent(channel, input, directoryInfo);
- }
- }
- /// <summary>
- /// Downloads the specified file from the remote host to local file.
- /// </summary>
- /// <param name="filename">Remote host file name.</param>
- /// <param name="fileInfo">Local file information.</param>
- /// <exception cref="ArgumentNullException"><paramref name="fileInfo"/> is <c>null</c>.</exception>
- /// <exception cref="ArgumentException"><paramref name="filename"/> is <c>null</c> or empty.</exception>
- /// <exception cref="ScpException"><paramref name="filename"/> exists on the remote host, and is not a regular file.</exception>
- 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 = ServiceFactory.CreatePipeStream())
- using (var channel = Session.CreateChannelSession())
- {
- channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
- channel.Open();
- // Send channel command request
- channel.SendExecRequest(string.Format("scp -pf {0}", filename.ShellQuote()));
- // Send reply
- SendSuccessConfirmation(channel);
- InternalDownload(channel, input, fileInfo);
- }
- }
- /// <summary>
- /// Downloads the specified directory from the remote host to local directory.
- /// </summary>
- /// <param name="directoryName">Remote host directory name.</param>
- /// <param name="directoryInfo">Local directory information.</param>
- /// <exception cref="ArgumentException"><paramref name="directoryName"/> is <c>null</c> or empty.</exception>
- /// <exception cref="ArgumentNullException"><paramref name="directoryInfo"/> is <c>null</c>.</exception>
- /// <exception cref="ScpException">File or directory with the specified path does not exist on the remote host.</exception>
- 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 = ServiceFactory.CreatePipeStream())
- using (var channel = Session.CreateChannelSession())
- {
- channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
- channel.Open();
- // Send channel command request
- channel.SendExecRequest(string.Format("scp -prf {0}", directoryName.ShellQuote()));
- // Send reply
- SendSuccessConfirmation(channel);
- InternalDownload(channel, input, directoryInfo);
- }
- }
- /// <summary>
- /// Uploads the <see cref="FileSystemInfo.LastWriteTimeUtc"/> and <see cref="FileSystemInfo.LastAccessTimeUtc"/>
- /// of the next file or directory to upload.
- /// </summary>
- /// <param name="channel">The channel to perform the upload in.</param>
- /// <param name="input">A <see cref="Stream"/> from which any feedback from the server can be read.</param>
- /// <param name="fileOrDirectory">The file or directory to upload.</param>
- private void UploadTimes(IChannelSession channel, Stream input, FileSystemInfo fileOrDirectory)
- {
- var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
- var modificationSeconds = (long) (fileOrDirectory.LastWriteTimeUtc - zeroTime).TotalSeconds;
- var accessSeconds = (long) (fileOrDirectory.LastAccessTimeUtc - zeroTime).TotalSeconds;
- SendData(channel, string.Format("T{0} 0 {1} 0\n", modificationSeconds, accessSeconds));
- CheckReturnCode(input);
- }
- /// <summary>
- /// Upload the files and subdirectories in the specified directory.
- /// </summary>
- /// <param name="channel">The channel to perform the upload in.</param>
- /// <param name="input">A <see cref="Stream"/> from which any feedback from the server can be read.</param>
- /// <param name="directoryInfo">The directory to upload.</param>
- private void UploadDirectoryContent(IChannelSession channel, Stream input, DirectoryInfo directoryInfo)
- {
- // Upload files
- var files = directoryInfo.GetFiles();
- foreach (var file in files)
- {
- using (var source = file.OpenRead())
- {
- UploadTimes(channel, input, file);
- UploadFileModeAndName(channel, input, source.Length, file.Name);
- UploadFileContent(channel, input, source, file.Name);
- }
- }
- // Upload directories
- var directories = directoryInfo.GetDirectories();
- foreach (var directory in directories)
- {
- UploadTimes(channel, input, directory);
- UploadDirectoryModeAndName(channel, input, directory.Name);
- UploadDirectoryContent(channel, input, directory);
- }
- // Mark upload of current directory complete
- SendData(channel, "E\n");
- CheckReturnCode(input);
- }
- /// <summary>
- /// Sets mode and name of the directory being upload.
- /// </summary>
- private void UploadDirectoryModeAndName(IChannelSession channel, Stream input, string directoryName)
- {
- SendData(channel, string.Format("D0755 0 {0}\n", directoryName));
- CheckReturnCode(input);
- }
- private void InternalDownload(IChannelSession channel, Stream input, FileSystemInfo fileSystemInfo)
- {
- var modifiedTime = DateTime.Now;
- var accessedTime = DateTime.Now;
- var startDirectoryFullName = fileSystemInfo.FullName;
- var currentDirectoryFullName = startDirectoryFullName;
- var directoryCounter = 0;
- while (true)
- {
- var message = ReadString(input);
- if (message == "E")
- {
- SendSuccessConfirmation(channel); // Send reply
- directoryCounter--;
- currentDirectoryFullName = new DirectoryInfo(currentDirectoryFullName).Parent.FullName;
- if (directoryCounter == 0)
- break;
- continue;
- }
- var match = DirectoryInfoRe.Match(message);
- if (match.Success)
- {
- SendSuccessConfirmation(channel); // Send reply
- // Read directory
- var filename = match.Result("${filename}");
- DirectoryInfo newDirectoryInfo;
- if (directoryCounter > 0)
- {
- newDirectoryInfo = Directory.CreateDirectory(Path.Combine(currentDirectoryFullName, filename));
- newDirectoryInfo.LastAccessTime = accessedTime;
- newDirectoryInfo.LastWriteTime = modifiedTime;
- }
- else
- {
- // Don't create directory for first level
- newDirectoryInfo = fileSystemInfo as DirectoryInfo;
- }
- directoryCounter++;
- currentDirectoryFullName = newDirectoryInfo.FullName;
- continue;
- }
- match = FileInfoRe.Match(message);
- if (match.Success)
- {
- // Read file
- SendSuccessConfirmation(channel); // Send reply
- var length = long.Parse(match.Result("${length}"));
- var fileName = match.Result("${filename}");
- var fileInfo = fileSystemInfo as FileInfo;
- if (fileInfo == null)
- fileInfo = new FileInfo(Path.Combine(currentDirectoryFullName, fileName));
- using (var output = fileInfo.OpenWrite())
- {
- 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
- SendSuccessConfirmation(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;
- }
- SendErrorConfirmation(channel, string.Format("\"{0}\" is not valid protocol message.", message));
- }
- }
- }
- }
|