< Summary

Information
Class: Renci.SshNet.ScpClient
Assembly: Renci.SshNet
File(s): \home\appveyor\projects\ssh-net\src\Renci.SshNet\ScpClient.cs
Line coverage
91%
Covered lines: 329
Uncovered lines: 32
Coverable lines: 361
Total lines: 826
Line coverage: 91.1%
Branch coverage
76%
Covered branches: 58
Total branches: 76
Branch coverage: 76.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

\home\appveyor\projects\ssh-net\src\Renci.SshNet\ScpClient.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Diagnostics.CodeAnalysis;
 4using System.Globalization;
 5using System.IO;
 6using System.Net;
 7using System.Text.RegularExpressions;
 8
 9using Renci.SshNet.Channels;
 10using Renci.SshNet.Common;
 11
 12namespace Renci.SshNet
 13{
 14    /// <summary>
 15    /// Provides SCP client functionality.
 16    /// </summary>
 17    /// <remarks>
 18    /// <para>
 19    /// More information on the SCP protocol is available here: https://github.com/net-ssh/net-scp/blob/master/lib/net/s
 20    /// </para>
 21    /// <para>
 22    /// Known issues in OpenSSH:
 23    /// <list type="bullet">
 24    ///   <item>
 25    ///     <description>Recursive download (-prf) does not deal well with specific UTF-8 and newline characters.</descr
 26    ///     <description>Recursive update does not support empty path for uploading to home directory.</description>
 27    ///   </item>
 28    /// </list>
 29    /// </para>
 30    /// </remarks>
 31    public partial class ScpClient : BaseClient
 32    {
 33        private const string Message = "filename";
 434        private static readonly Regex FileInfoRe = new Regex(@"C(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)", RegexOpt
 435        private static readonly byte[] SuccessConfirmationCode = { 0 };
 436        private static readonly byte[] ErrorConfirmationCode = { 1 };
 437        private static readonly Regex DirectoryInfoRe = new Regex(@"D(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)", Reg
 438        private static readonly Regex TimestampRe = new Regex(@"T(?<mtime>\d+) 0 (?<atime>\d+) 0", RegexOptions.Compiled
 39
 40        private IRemotePathTransformation _remotePathTransformation;
 41
 42        /// <summary>
 43        /// Gets or sets the operation timeout.
 44        /// </summary>
 45        /// <value>
 46        /// The timeout to wait until an operation completes. The default value is negative
 47        /// one (-1) milliseconds, which indicates an infinite time-out period.
 48        /// </value>
 42649        public TimeSpan OperationTimeout { get; set; }
 50
 51        /// <summary>
 52        /// Gets or sets the size of the buffer.
 53        /// </summary>
 54        /// <value>
 55        /// The size of the buffer. The default buffer size is 16384 bytes.
 56        /// </value>
 423657        public uint BufferSize { get; set; }
 58
 59        /// <summary>
 60        /// Gets or sets the transformation to apply to remote paths.
 61        /// </summary>
 62        /// <value>
 63        /// The transformation to apply to remote paths. The default is <see cref="RemotePathTransformation.DoubleQuote"
 64        /// </value>
 65        /// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
 66        /// <remarks>
 67        /// <para>
 68        /// This transformation is applied to the remote file or directory path that is passed to the
 69        /// <c>scp</c> command.
 70        /// </para>
 71        /// <para>
 72        /// See <see cref="SshNet.RemotePathTransformation"/> for the transformations that are supplied
 73        /// out-of-the-box with SSH.NET.
 74        /// </para>
 75        /// </remarks>
 76        public IRemotePathTransformation RemotePathTransformation
 77        {
 78            get
 2479            {
 2480                return _remotePathTransformation;
 2481            }
 82            set
 5183            {
 5184                if (value is null)
 385                {
 386                    throw new ArgumentNullException(nameof(value));
 87                }
 88
 4889                _remotePathTransformation = value;
 4890            }
 91        }
 92
 93        /// <summary>
 94        /// Occurs when downloading file.
 95        /// </summary>
 96        public event EventHandler<ScpDownloadEventArgs> Downloading;
 97
 98        /// <summary>
 99        /// Occurs when uploading file.
 100        /// </summary>
 101        public event EventHandler<ScpUploadEventArgs> Uploading;
 102
 103        /// <summary>
 104        /// Initializes a new instance of the <see cref="ScpClient"/> class.
 105        /// </summary>
 106        /// <param name="connectionInfo">The connection info.</param>
 107        /// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is <see langword="null"/>.</except
 108        public ScpClient(ConnectionInfo connectionInfo)
 286109            : this(connectionInfo, ownsConnectionInfo: false)
 283110        {
 283111        }
 112
 113        /// <summary>
 114        /// Initializes a new instance of the <see cref="ScpClient"/> class.
 115        /// </summary>
 116        /// <param name="host">Connection host.</param>
 117        /// <param name="port">Connection port.</param>
 118        /// <param name="username">Authentication username.</param>
 119        /// <param name="password">Authentication password.</param>
 120        /// <exception cref="ArgumentNullException"><paramref name="password"/> is <see langword="null"/>.</exception>
 121        /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is <s
 122        /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.Mi
 123        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in
 124        public ScpClient(string host, int port, string username, string password)
 20125            : this(new PasswordConnectionInfo(host, port, username, password), ownsConnectionInfo: true)
 20126        {
 20127        }
 128
 129        /// <summary>
 130        /// Initializes a new instance of the <see cref="ScpClient"/> class.
 131        /// </summary>
 132        /// <param name="host">Connection host.</param>
 133        /// <param name="username">Authentication username.</param>
 134        /// <param name="password">Authentication password.</param>
 135        /// <exception cref="ArgumentNullException"><paramref name="password"/> is <see langword="null"/>.</exception>
 136        /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is <s
 137        public ScpClient(string host, string username, string password)
 3138            : this(host, ConnectionInfo.DefaultPort, username, password)
 3139        {
 3140        }
 141
 142        /// <summary>
 143        /// Initializes a new instance of the <see cref="ScpClient"/> class.
 144        /// </summary>
 145        /// <param name="host">Connection host.</param>
 146        /// <param name="port">Connection port.</param>
 147        /// <param name="username">Authentication username.</param>
 148        /// <param name="keyFiles">Authentication private key file(s) .</param>
 149        /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <see langword="null"/>.</exception>
 150        /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is 
 151        /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="IPEndPoint.Mi
 152        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in
 153        public ScpClient(string host, int port, string username, params IPrivateKeySource[] keyFiles)
 6154            : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles), ownsConnectionInfo: true)
 6155        {
 6156        }
 157
 158        /// <summary>
 159        /// Initializes a new instance of the <see cref="ScpClient"/> class.
 160        /// </summary>
 161        /// <param name="host">Connection host.</param>
 162        /// <param name="username">Authentication username.</param>
 163        /// <param name="keyFiles">Authentication private key file(s) .</param>
 164        /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is <see langword="null"/>.</exception>
 165        /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is 
 166        public ScpClient(string host, string username, params IPrivateKeySource[] keyFiles)
 3167            : this(host, ConnectionInfo.DefaultPort, username, keyFiles)
 3168        {
 3169        }
 170
 171        /// <summary>
 172        /// Initializes a new instance of the <see cref="ScpClient"/> class.
 173        /// </summary>
 174        /// <param name="connectionInfo">The connection info.</param>
 175        /// <param name="ownsConnectionInfo">Specified whether this instance owns the connection info.</param>
 176        /// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is <see langword="null"/>.</except
 177        /// <remarks>
 178        /// If <paramref name="ownsConnectionInfo"/> is <see langword="true"/>, then the
 179        /// connection info will be disposed when this instance is disposed.
 180        /// </remarks>
 181        private ScpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo)
 312182            : this(connectionInfo, ownsConnectionInfo, new ServiceFactory())
 309183        {
 309184        }
 185
 186        /// <summary>
 187        /// Initializes a new instance of the <see cref="ScpClient"/> class.
 188        /// </summary>
 189        /// <param name="connectionInfo">The connection info.</param>
 190        /// <param name="ownsConnectionInfo">Specified whether this instance owns the connection info.</param>
 191        /// <param name="serviceFactory">The factory to use for creating new services.</param>
 192        /// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is <see langword="null"/>.</except
 193        /// <exception cref="ArgumentNullException"><paramref name="serviceFactory"/> is <see langword="null"/>.</except
 194        /// <remarks>
 195        /// If <paramref name="ownsConnectionInfo"/> is <see langword="true"/>, then the
 196        /// connection info will be disposed when this instance is disposed.
 197        /// </remarks>
 198        internal ScpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory)
 414199            : base(connectionInfo, ownsConnectionInfo, serviceFactory)
 411200        {
 411201            OperationTimeout = SshNet.Session.InfiniteTimeSpan;
 411202            BufferSize = 1024 * 16;
 411203            _remotePathTransformation = serviceFactory.CreateRemotePathDoubleQuoteTransformation();
 411204        }
 205
 206        /// <summary>
 207        /// Uploads the specified stream to the remote host.
 208        /// </summary>
 209        /// <param name="source">The <see cref="Stream"/> to upload.</param>
 210        /// <param name="path">A relative or absolute path for the remote file.</param>
 211        /// <exception cref="ArgumentNullException"><paramref name="path" /> is <see langword="null"/>.</exception>
 212        /// <exception cref="ArgumentException"><paramref name="path"/> is a zero-length <see cref="string"/>.</exceptio
 213        /// <exception cref="ScpException">A directory with the specified path exists on the remote host.</exception>
 214        /// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
 215        public void Upload(Stream source, string path)
 186216        {
 186217            var posixPath = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
 218
 186219            using (var input = ServiceFactory.CreatePipeStream())
 186220            using (var channel = Session.CreateChannelSession())
 186221            {
 696222                channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
 186223                channel.Open();
 224
 225                // Pass only the directory part of the path to the server, and use the (hidden) -d option to signal
 226                // that we expect the target to be a directory.
 186227                if (!channel.SendExecRequest(string.Format("scp -t -d {0}", _remotePathTransformation.Transform(posixPat
 15228                {
 15229                    throw SecureExecutionRequestRejectedException();
 230                }
 231
 171232                CheckReturnCode(input);
 233
 170234                UploadFileModeAndName(channel, input, source.Length, posixPath.File);
 169235                UploadFileContent(channel, input, source, posixPath.File);
 169236            }
 169237        }
 238
 239        /// <summary>
 240        /// Uploads the specified file to the remote host.
 241        /// </summary>
 242        /// <param name="fileInfo">The file system info.</param>
 243        /// <param name="path">A relative or absolute path for the remote file.</param>
 244        /// <exception cref="ArgumentNullException"><paramref name="fileInfo" /> is <see langword="null"/>.</exception>
 245        /// <exception cref="ArgumentNullException"><paramref name="path" /> is <see langword="null"/>.</exception>
 246        /// <exception cref="ArgumentException"><paramref name="path"/> is a zero-length <see cref="string"/>.</exceptio
 247        /// <exception cref="ScpException">A directory with the specified path exists on the remote host.</exception>
 248        /// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
 249        public void Upload(FileInfo fileInfo, string path)
 68250        {
 68251            if (fileInfo is null)
 0252            {
 0253                throw new ArgumentNullException(nameof(fileInfo));
 254            }
 255
 68256            var posixPath = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
 257
 68258            using (var input = ServiceFactory.CreatePipeStream())
 68259            using (var channel = Session.CreateChannelSession())
 68260            {
 228261                channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
 68262                channel.Open();
 263
 264                // Pass only the directory part of the path to the server, and use the (hidden) -d option to signal
 265                // that we expect the target to be a directory.
 68266                if (!channel.SendExecRequest($"scp -t -d {_remotePathTransformation.Transform(posixPath.Directory)}"))
 15267                {
 15268                    throw SecureExecutionRequestRejectedException();
 269                }
 270
 53271                CheckReturnCode(input);
 272
 52273                using (var source = fileInfo.OpenRead())
 52274                {
 52275                    UploadTimes(channel, input, fileInfo);
 52276                    UploadFileModeAndName(channel, input, source.Length, posixPath.File);
 51277                    UploadFileContent(channel, input, source, fileInfo.Name);
 51278                }
 51279            }
 51280        }
 281
 282        /// <summary>
 283        /// Uploads the specified directory to the remote host.
 284        /// </summary>
 285        /// <param name="directoryInfo">The directory info.</param>
 286        /// <param name="path">A relative or absolute path for the remote directory.</param>
 287        /// <exception cref="ArgumentNullException"><paramref name="directoryInfo"/> is <see langword="null"/>.</excepti
 288        /// <exception cref="ArgumentNullException"><paramref name="path"/> is <see langword="null"/>.</exception>
 289        /// <exception cref="ArgumentException"><paramref name="path"/> is a zero-length string.</exception>
 290        /// <exception cref="ScpException"><paramref name="path"/> does not exist on the remote host, is not a directory
 291        /// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
 292        public void Upload(DirectoryInfo directoryInfo, string path)
 21293        {
 21294            if (directoryInfo is null)
 0295            {
 0296                throw new ArgumentNullException(nameof(directoryInfo));
 297            }
 298
 21299            if (path is null)
 0300            {
 0301                throw new ArgumentNullException(nameof(path));
 302            }
 303
 21304            if (path.Length == 0)
 0305            {
 0306                throw new ArgumentException("The path cannot be a zero-length string.", nameof(path));
 307            }
 308
 21309            using (var input = ServiceFactory.CreatePipeStream())
 21310            using (var channel = Session.CreateChannelSession())
 21311            {
 85312                channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
 21313                channel.Open();
 314
 315                // start copy with the following options:
 316                // -p preserve modification and access times
 317                // -r copy directories recursively
 318                // -d expect path to be a directory
 319                // -t copy to remote
 21320                if (!channel.SendExecRequest($"scp -r -p -d -t {_remotePathTransformation.Transform(path)}"))
 15321                {
 15322                    throw SecureExecutionRequestRejectedException();
 323                }
 324
 6325                CheckReturnCode(input);
 326
 4327                UploadDirectoryContent(channel, input, directoryInfo);
 4328            }
 4329        }
 330
 331        /// <summary>
 332        /// Downloads the specified file from the remote host to local file.
 333        /// </summary>
 334        /// <param name="filename">Remote host file name.</param>
 335        /// <param name="fileInfo">Local file information.</param>
 336        /// <exception cref="ArgumentNullException"><paramref name="fileInfo"/> is <see langword="null"/>.</exception>
 337        /// <exception cref="ArgumentException"><paramref name="filename"/> is <see langword="null"/> or empty.</excepti
 338        /// <exception cref="ScpException"><paramref name="filename"/> exists on the remote host, and is not a regular f
 339        /// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
 340        public void Download(string filename, FileInfo fileInfo)
 56341        {
 56342            if (string.IsNullOrEmpty(filename))
 0343            {
 0344                throw new ArgumentException("filename");
 345            }
 346
 56347            if (fileInfo is null)
 0348            {
 0349                throw new ArgumentNullException(nameof(fileInfo));
 350            }
 351
 56352            using (var input = ServiceFactory.CreatePipeStream())
 56353            using (var channel = Session.CreateChannelSession())
 56354            {
 1449355                channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
 56356                channel.Open();
 357
 358                // Send channel command request
 56359                if (!channel.SendExecRequest($"scp -pf {_remotePathTransformation.Transform(filename)}"))
 15360                {
 15361                    throw SecureExecutionRequestRejectedException();
 362                }
 363
 364                // Send reply
 41365                SendSuccessConfirmation(channel);
 366
 41367                InternalDownload(channel, input, fileInfo);
 38368            }
 38369        }
 370
 371        /// <summary>
 372        /// Downloads the specified directory from the remote host to local directory.
 373        /// </summary>
 374        /// <param name="directoryName">Remote host directory name.</param>
 375        /// <param name="directoryInfo">Local directory information.</param>
 376        /// <exception cref="ArgumentException"><paramref name="directoryName"/> is <see langword="null"/> or empty.</ex
 377        /// <exception cref="ArgumentNullException"><paramref name="directoryInfo"/> is <see langword="null"/>.</excepti
 378        /// <exception cref="ScpException">File or directory with the specified path does not exist on the remote host.<
 379        /// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
 380        public void Download(string directoryName, DirectoryInfo directoryInfo)
 20381        {
 20382            if (string.IsNullOrEmpty(directoryName))
 0383            {
 0384                throw new ArgumentException("directoryName");
 385            }
 386
 20387            if (directoryInfo is null)
 0388            {
 0389                throw new ArgumentNullException(nameof(directoryInfo));
 390            }
 391
 20392            using (var input = ServiceFactory.CreatePipeStream())
 20393            using (var channel = Session.CreateChannelSession())
 20394            {
 57395                channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
 20396                channel.Open();
 397
 398                // Send channel command request
 20399                if (!channel.SendExecRequest($"scp -prf {_remotePathTransformation.Transform(directoryName)}"))
 15400                {
 15401                    throw SecureExecutionRequestRejectedException();
 402                }
 403
 404                // Send reply
 5405                SendSuccessConfirmation(channel);
 406
 5407                InternalDownload(channel, input, directoryInfo);
 4408            }
 4409        }
 410
 411        /// <summary>
 412        /// Downloads the specified file from the remote host to the stream.
 413        /// </summary>
 414        /// <param name="filename">A relative or absolute path for the remote file.</param>
 415        /// <param name="destination">The <see cref="Stream"/> to download the remote file to.</param>
 416        /// <exception cref="ArgumentException"><paramref name="filename"/> is <see langword="null"/> or contains only w
 417        /// <exception cref="ArgumentNullException"><paramref name="destination"/> is <see langword="null"/>.</exception
 418        /// <exception cref="ScpException"><paramref name="filename"/> exists on the remote host, and is not a regular f
 419        /// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
 420        public void Download(string filename, Stream destination)
 110421        {
 110422            if (string.IsNullOrWhiteSpace(filename))
 0423            {
 0424                throw new ArgumentException(Message);
 425            }
 426
 110427            if (destination is null)
 0428            {
 0429                throw new ArgumentNullException(nameof(destination));
 430            }
 431
 110432            using (var input = ServiceFactory.CreatePipeStream())
 110433            using (var channel = Session.CreateChannelSession())
 110434            {
 542435                channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
 110436                channel.Open();
 437
 438                // Send channel command request
 110439                if (!channel.SendExecRequest(string.Concat("scp -f ", _remotePathTransformation.Transform(filename))))
 15440                {
 15441                    throw SecureExecutionRequestRejectedException();
 442                }
 443
 95444                SendSuccessConfirmation(channel); // Send reply
 445
 95446                var message = ReadString(input);
 91447                var match = FileInfoRe.Match(message);
 448
 91449                if (match.Success)
 91450                {
 451                    // Read file
 91452                    SendSuccessConfirmation(channel); //  Send reply
 453
 91454                    var length = long.Parse(match.Result("${length}"), CultureInfo.InvariantCulture);
 91455                    var fileName = match.Result("${filename}");
 456
 91457                    InternalDownload(channel, input, destination, fileName, length);
 91458                }
 459                else
 0460                {
 0461                    SendErrorConfirmation(channel, string.Format("\"{0}\" is not valid protocol message.", message));
 0462                }
 91463            }
 91464        }
 465
 466        private static void SendData(IChannel channel, byte[] buffer, int length)
 3853467        {
 3853468            channel.SendData(buffer, 0, length);
 3853469        }
 470
 471        private static void SendData(IChannel channel, byte[] buffer)
 705472        {
 705473            channel.SendData(buffer);
 705474        }
 475
 476        private static int ReadByte(Stream stream)
 6497477        {
 6497478            var b = stream.ReadByte();
 479
 6497480            if (b == -1)
 0481            {
 0482                throw new SshException("Stream has been closed.");
 483            }
 484
 6497485            return b;
 6497486        }
 487
 488        private static SshException SecureExecutionRequestRejectedException()
 90489        {
 90490            throw new SshException("Secure copy execution request was rejected by the server. Please consult the server 
 491        }
 492
 493        /// <summary>
 494        /// Sets mode, size and name of file being upload.
 495        /// </summary>
 496        /// <param name="channel">The channel to perform the upload in.</param>
 497        /// <param name="input">A <see cref="Stream"/> from which any feedback from the server can be read.</param>
 498        /// <param name="fileSize">The size of the content to upload.</param>
 499        /// <param name="serverFileName">The name of the file, without path, to which the content is to be uploaded.</pa
 500        /// <remarks>
 501        /// <para>
 502        /// When the SCP transfer is already initiated for a file, a zero-length <see cref="string"/> should
 503        /// be specified for <paramref name="serverFileName"/>. This prevents the server from uploading the
 504        /// content to a file with path <c>&lt;file path&gt;/<paramref name="serverFileName"/></c> if there's
 505        /// already a directory with this path, and allows us to receive an error response.
 506        /// </para>
 507        /// </remarks>
 508        private void UploadFileModeAndName(IChannelSession channel, Stream input, long fileSize, string serverFileName)
 234509        {
 234510            SendData(channel, string.Format("C0644 {0} {1}\n", fileSize, serverFileName));
 234511            CheckReturnCode(input);
 232512        }
 513
 514        /// <summary>
 515        /// Uploads the content of a file.
 516        /// </summary>
 517        /// <param name="channel">The channel to perform the upload in.</param>
 518        /// <param name="input">A <see cref="Stream"/> from which any feedback from the server can be read.</param>
 519        /// <param name="source">The content to upload.</param>
 520        /// <param name="remoteFileName">The name of the remote file, without path, to which the content is uploaded.</p
 521        /// <remarks>
 522        /// <paramref name="remoteFileName"/> is only used for raising the <see cref="Uploading"/> event.
 523        /// </remarks>
 524        private void UploadFileContent(IChannelSession channel, Stream input, Stream source, string remoteFileName)
 232525        {
 232526            var totalLength = source.Length;
 232527            var buffer = new byte[BufferSize];
 528
 232529            var read = source.Read(buffer, 0, buffer.Length);
 530
 232531            long totalRead = 0;
 532
 4085533            while (read > 0)
 3853534            {
 3853535                SendData(channel, buffer, read);
 536
 3853537                totalRead += read;
 538
 3853539                RaiseUploadingEvent(remoteFileName, totalLength, totalRead);
 540
 3853541                read = source.Read(buffer, 0, buffer.Length);
 3853542            }
 543
 232544            SendSuccessConfirmation(channel);
 232545            CheckReturnCode(input);
 232546        }
 547
 548        private void RaiseDownloadingEvent(string filename, long size, long downloaded)
 3566549        {
 3566550            Downloading?.Invoke(this, new ScpDownloadEventArgs(filename, size, downloaded));
 3566551        }
 552
 553        private void RaiseUploadingEvent(string filename, long size, long uploaded)
 3853554        {
 3853555            Uploading?.Invoke(this, new ScpUploadEventArgs(filename, size, uploaded));
 3853556        }
 557
 558        private static void SendSuccessConfirmation(IChannel channel)
 705559        {
 705560            SendData(channel, SuccessConfirmationCode);
 705561        }
 562
 563        private void SendErrorConfirmation(IChannel channel, string message)
 0564        {
 0565            SendData(channel, ErrorConfirmationCode);
 0566            SendData(channel, string.Concat(message, "\n"));
 0567        }
 568
 569        /// <summary>
 570        /// Checks the return code.
 571        /// </summary>
 572        /// <param name="input">The output stream.</param>
 573        private void CheckReturnCode(Stream input)
 918574        {
 918575            var b = ReadByte(input);
 576
 918577            if (b > 0)
 6578            {
 6579                var errorText = ReadString(input);
 580
 6581                throw new ScpException(errorText);
 582            }
 912583        }
 584
 585        private void SendData(IChannel channel, string command)
 320586        {
 320587            channel.SendData(ConnectionInfo.Encoding.GetBytes(command));
 320588        }
 589
 590        /// <summary>
 591        /// Read a LF-terminated string from the <see cref="Stream"/>.
 592        /// </summary>
 593        /// <param name="stream">The <see cref="Stream"/> to read from.</param>
 594        /// <returns>
 595        /// The string without trailing LF.
 596        /// </returns>
 597        private string ReadString(Stream stream)
 210598        {
 210599            var hasError = false;
 600
 210601            var buffer = new List<byte>();
 602
 210603            var b = ReadByte(stream);
 210604            if (b is 1 or 2)
 8605            {
 8606                hasError = true;
 8607                b = ReadByte(stream);
 8608            }
 609
 5571610            while (b != SshNet.Session.LineFeed)
 5361611            {
 5361612                buffer.Add((byte) b);
 5361613                b = ReadByte(stream);
 5361614            }
 615
 210616            var readBytes = buffer.ToArray();
 617
 210618            if (hasError)
 8619            {
 8620                throw new ScpException(ConnectionInfo.Encoding.GetString(readBytes, 0, readBytes.Length));
 621            }
 622
 202623            return ConnectionInfo.Encoding.GetString(readBytes, 0, readBytes.Length);
 202624        }
 625
 626        /// <summary>
 627        /// Uploads the <see cref="FileSystemInfo.LastWriteTimeUtc"/> and <see cref="FileSystemInfo.LastAccessTimeUtc"/>
 628        /// of the next file or directory to upload.
 629        /// </summary>
 630        /// <param name="channel">The channel to perform the upload in.</param>
 631        /// <param name="input">A <see cref="Stream"/> from which any feedback from the server can be read.</param>
 632        /// <param name="fileOrDirectory">The file or directory to upload.</param>
 633        private void UploadTimes(IChannelSession channel, Stream input, FileSystemInfo fileOrDirectory)
 70634        {
 635#if NET ||NETSTANDARD2_1_OR_GREATER
 66636            var zeroTime = DateTime.UnixEpoch;
 637#else
 4638            var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
 639#endif
 70640            var modificationSeconds = (long) (fileOrDirectory.LastWriteTimeUtc - zeroTime).TotalSeconds;
 70641            var accessSeconds = (long) (fileOrDirectory.LastAccessTimeUtc - zeroTime).TotalSeconds;
 70642            SendData(channel, string.Format(CultureInfo.InvariantCulture, "T{0} 0 {1} 0\n", modificationSeconds, accessS
 70643            CheckReturnCode(input);
 70644        }
 645
 646        /// <summary>
 647        /// Upload the files and subdirectories in the specified directory.
 648        /// </summary>
 649        /// <param name="channel">The channel to perform the upload in.</param>
 650        /// <param name="input">A <see cref="Stream"/> from which any feedback from the server can be read.</param>
 651        /// <param name="directoryInfo">The directory to upload.</param>
 652        private void UploadDirectoryContent(IChannelSession channel, Stream input, DirectoryInfo directoryInfo)
 10653        {
 654            // Upload files
 10655            var files = directoryInfo.GetFiles();
 54656            foreach (var file in files)
 12657            {
 12658                using (var source = file.OpenRead())
 12659                {
 12660                    UploadTimes(channel, input, file);
 12661                    UploadFileModeAndName(channel, input, source.Length, file.Name);
 12662                    UploadFileContent(channel, input, source, file.Name);
 12663                }
 12664            }
 665
 666            // Upload directories
 10667            var directories = directoryInfo.GetDirectories();
 42668            foreach (var directory in directories)
 6669            {
 6670                UploadTimes(channel, input, directory);
 6671                UploadDirectoryModeAndName(channel, input, directory.Name);
 6672                UploadDirectoryContent(channel, input, directory);
 6673            }
 674
 675            // Mark upload of current directory complete
 10676            SendData(channel, "E\n");
 10677            CheckReturnCode(input);
 10678        }
 679
 680        /// <summary>
 681        /// Sets mode and name of the directory being upload.
 682        /// </summary>
 683        private void UploadDirectoryModeAndName(IChannelSession channel, Stream input, string directoryName)
 6684        {
 6685            SendData(channel, string.Format("D0755 0 {0}\n", directoryName));
 6686            CheckReturnCode(input);
 6687        }
 688
 689        private void InternalDownload(IChannel channel, Stream input, Stream output, string filename, long length)
 136690        {
 136691            var buffer = new byte[Math.Min(length, BufferSize)];
 136692            var needToRead = length;
 693
 694            do
 3430695            {
 3430696                var read = input.Read(buffer, 0, (int) Math.Min(needToRead, BufferSize));
 697
 3430698                output.Write(buffer, 0, read);
 699
 3430700                RaiseDownloadingEvent(filename, length, length - needToRead);
 701
 3430702                needToRead -= read;
 3430703            }
 3430704            while (needToRead > 0);
 705
 136706            output.Flush();
 707
 708            // Raise one more time when file downloaded
 136709            RaiseDownloadingEvent(filename, length, length - needToRead);
 710
 711            // Send confirmation byte after last data byte was read
 136712            SendSuccessConfirmation(channel);
 713
 136714            CheckReturnCode(input);
 136715        }
 716
 717        private void InternalDownload(IChannelSession channel, Stream input, FileSystemInfo fileSystemInfo)
 46718        {
 46719            var modifiedTime = DateTime.Now;
 46720            var accessedTime = DateTime.Now;
 721
 46722            var startDirectoryFullName = fileSystemInfo.FullName;
 46723            var currentDirectoryFullName = startDirectoryFullName;
 46724            var directoryCounter = 0;
 725
 109726            while (true)
 109727            {
 109728                var message = ReadString(input);
 729
 105730                if (message == "E")
 5731                {
 5732                    SendSuccessConfirmation(channel); // Send reply
 733
 5734                    directoryCounter--;
 735
 5736                    currentDirectoryFullName = new DirectoryInfo(currentDirectoryFullName).Parent.FullName;
 737
 5738                    if (directoryCounter == 0)
 3739                    {
 3740                        break;
 741                    }
 742
 2743                    continue;
 744                }
 745
 100746                var match = DirectoryInfoRe.Match(message);
 100747                if (match.Success)
 5748                {
 5749                    SendSuccessConfirmation(channel); // Send reply
 750
 751                    // Read directory
 5752                    var filename = match.Result("${filename}");
 753
 754                    DirectoryInfo newDirectoryInfo;
 5755                    if (directoryCounter > 0)
 2756                    {
 2757                        newDirectoryInfo = Directory.CreateDirectory(Path.Combine(currentDirectoryFullName, filename));
 2758                        newDirectoryInfo.LastAccessTime = accessedTime;
 2759                        newDirectoryInfo.LastWriteTime = modifiedTime;
 2760                    }
 761                    else
 3762                    {
 763                        // Don't create directory for first level
 3764                        newDirectoryInfo = fileSystemInfo as DirectoryInfo;
 3765                    }
 766
 5767                    directoryCounter++;
 768
 5769                    currentDirectoryFullName = newDirectoryInfo.FullName;
 5770                    continue;
 771                }
 772
 95773                match = FileInfoRe.Match(message);
 95774                if (match.Success)
 45775                {
 776                    // Read file
 45777                    SendSuccessConfirmation(channel); //  Send reply
 778
 45779                    var length = long.Parse(match.Result("${length}"), CultureInfo.InvariantCulture);
 45780                    var fileName = match.Result("${filename}");
 781
 45782                    if (fileSystemInfo is not FileInfo fileInfo)
 7783                    {
 7784                        fileInfo = new FileInfo(Path.Combine(currentDirectoryFullName, fileName));
 7785                    }
 786
 45787                    using (var output = fileInfo.OpenWrite())
 45788                    {
 45789                        InternalDownload(channel, input, output, fileName, length);
 45790                    }
 791
 45792                    fileInfo.LastAccessTime = accessedTime;
 45793                    fileInfo.LastWriteTime = modifiedTime;
 794
 45795                    if (directoryCounter == 0)
 39796                    {
 39797                        break;
 798                    }
 799
 6800                    continue;
 801                }
 802
 50803                match = TimestampRe.Match(message);
 50804                if (match.Success)
 50805                {
 806                    // Read timestamp
 50807                    SendSuccessConfirmation(channel); //  Send reply
 808
 50809                    var mtime = long.Parse(match.Result("${mtime}"), CultureInfo.InvariantCulture);
 50810                    var atime = long.Parse(match.Result("${atime}"), CultureInfo.InvariantCulture);
 811
 812#if NET || NETSTANDARD2_1_OR_GREATER
 50813                    var zeroTime = DateTime.UnixEpoch;
 814#else
 0815                    var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
 816#endif
 50817                    modifiedTime = zeroTime.AddSeconds(mtime);
 50818                    accessedTime = zeroTime.AddSeconds(atime);
 50819                    continue;
 820                }
 821
 0822                SendErrorConfirmation(channel, string.Format("\"{0}\" is not valid protocol message.", message));
 0823            }
 42824        }
 825    }
 826}

Methods/Properties

.cctor()
get_OperationTimeout()
get_BufferSize()
get_RemotePathTransformation()
set_RemotePathTransformation(Renci.SshNet.IRemotePathTransformation)
.ctor(Renci.SshNet.ConnectionInfo)
.ctor(System.String,System.Int32,System.String,System.String)
.ctor(System.String,System.String,System.String)
.ctor(System.String,System.Int32,System.String,Renci.SshNet.IPrivateKeySource[])
.ctor(System.String,System.String,Renci.SshNet.IPrivateKeySource[])
.ctor(Renci.SshNet.ConnectionInfo,System.Boolean)
.ctor(Renci.SshNet.ConnectionInfo,System.Boolean,Renci.SshNet.IServiceFactory)
Upload(System.IO.Stream,System.String)
Upload(System.IO.FileInfo,System.String)
Upload(System.IO.DirectoryInfo,System.String)
Download(System.String,System.IO.FileInfo)
Download(System.String,System.IO.DirectoryInfo)
Download(System.String,System.IO.Stream)
SendData(Renci.SshNet.Channels.IChannel,System.Byte[],System.Int32)
SendData(Renci.SshNet.Channels.IChannel,System.Byte[])
ReadByte(System.IO.Stream)
SecureExecutionRequestRejectedException()
UploadFileModeAndName(Renci.SshNet.Channels.IChannelSession,System.IO.Stream,System.Int64,System.String)
UploadFileContent(Renci.SshNet.Channels.IChannelSession,System.IO.Stream,System.IO.Stream,System.String)
RaiseDownloadingEvent(System.String,System.Int64,System.Int64)
RaiseUploadingEvent(System.String,System.Int64,System.Int64)
SendSuccessConfirmation(Renci.SshNet.Channels.IChannel)
SendErrorConfirmation(Renci.SshNet.Channels.IChannel,System.String)
CheckReturnCode(System.IO.Stream)
SendData(Renci.SshNet.Channels.IChannel,System.String)
ReadString(System.IO.Stream)
UploadTimes(Renci.SshNet.Channels.IChannelSession,System.IO.Stream,System.IO.FileSystemInfo)
UploadDirectoryContent(Renci.SshNet.Channels.IChannelSession,System.IO.Stream,System.IO.DirectoryInfo)
UploadDirectoryModeAndName(Renci.SshNet.Channels.IChannelSession,System.IO.Stream,System.String)
InternalDownload(Renci.SshNet.Channels.IChannel,System.IO.Stream,System.IO.Stream,System.String,System.Int64)
InternalDownload(Renci.SshNet.Channels.IChannelSession,System.IO.Stream,System.IO.FileSystemInfo)