using System;
using System.IO;
using System.Text.RegularExpressions;
using Renci.SshNet.Channels;
using Renci.SshNet.Common;
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.
/// A relative or absolute path for the remote file.
/// is null.
/// is null.
/// is a zero-length .
/// A directory with the specified path exists on the remote host.
/// The secure copy execution request was rejected by the server.
public void Upload(FileInfo fileInfo, string path)
{
if (fileInfo is null)
{
throw new ArgumentNullException(nameof(fileInfo));
}
var posixPath = PosixPath.CreateAbsoluteOrRelativeFilePath(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();
// Pass only the directory part of the path to the server, and use the (hidden) -d option to signal
// that we expect the target to be a directory.
if (!channel.SendExecRequest(string.Format("scp -t -d {0}", _remotePathTransformation.Transform(posixPath.Directory))))
{
throw SecureExecutionRequestRejectedException();
}
CheckReturnCode(input);
using (var source = fileInfo.OpenRead())
{
UploadTimes(channel, input, fileInfo);
UploadFileModeAndName(channel, input, source.Length, posixPath.File);
UploadFileContent(channel, input, source, fileInfo.Name);
}
}
}
///
/// Uploads the specified directory to the remote host.
///
/// The directory info.
/// A relative or absolute path for the remote directory.
/// is null.
/// is null.
/// is a zero-length string.
/// does not exist on the remote host, is not a directory or the user does not have the required permission.
/// The secure copy execution request was rejected by the server.
public void Upload(DirectoryInfo directoryInfo, string path)
{
if (directoryInfo is null)
{
throw new ArgumentNullException(nameof(directoryInfo));
}
if (path is null)
{
throw new ArgumentNullException(nameof(path));
}
if (path.Length == 0)
{
throw new ArgumentException("The path cannot be a zero-length string.", nameof(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 copy with the following options:
// -p preserve modification and access times
// -r copy directories recursively
// -d expect path to be a directory
// -t copy to remote
if (!channel.SendExecRequest(string.Format("scp -r -p -d -t {0}", _remotePathTransformation.Transform(path))))
{
throw SecureExecutionRequestRejectedException();
}
CheckReturnCode(input);
UploadDirectoryContent(channel, input, directoryInfo);
}
}
///
/// Downloads the specified file from the remote host to local file.
///
/// Remote host file name.
/// Local file information.
/// is null.
/// is null or empty.
/// exists on the remote host, and is not a regular file.
/// The secure copy execution request was rejected by the server.
public void Download(string filename, FileInfo fileInfo)
{
if (string.IsNullOrEmpty(filename))
{
throw new ArgumentException("filename");
}
if (fileInfo is null)
{
throw new ArgumentNullException(nameof(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
if (!channel.SendExecRequest(string.Format("scp -pf {0}", _remotePathTransformation.Transform(filename))))
{
throw SecureExecutionRequestRejectedException();
}
// Send reply
SendSuccessConfirmation(channel);
InternalDownload(channel, input, fileInfo);
}
}
///
/// Downloads the specified directory from the remote host to local directory.
///
/// Remote host directory name.
/// Local directory information.
/// is null or empty.
/// is null.
/// File or directory with the specified path does not exist on the remote host.
/// The secure copy execution request was rejected by the server.
public void Download(string directoryName, DirectoryInfo directoryInfo)
{
if (string.IsNullOrEmpty(directoryName))
{
throw new ArgumentException("directoryName");
}
if (directoryInfo is null)
{
throw new ArgumentNullException(nameof(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
if (!channel.SendExecRequest(string.Format("scp -prf {0}", _remotePathTransformation.Transform(directoryName))))
{
throw SecureExecutionRequestRejectedException();
}
// Send reply
SendSuccessConfirmation(channel);
InternalDownload(channel, input, directoryInfo);
}
}
///
/// Uploads the and
/// of the next file or directory to upload.
///
/// The channel to perform the upload in.
/// A from which any feedback from the server can be read.
/// The file or directory to upload.
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);
}
///
/// Upload the files and subdirectories in the specified directory.
///
/// The channel to perform the upload in.
/// A from which any feedback from the server can be read.
/// The directory to upload.
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);
}
///
/// Sets mode and name of the directory being upload.
///
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}");
if (fileSystemInfo is not FileInfo fileInfo)
{
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));
}
}
}
}