| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 | using System;using System.Collections.Generic;using System.Linq;using System.Text;using Renci.SshNet.Channels;using System.IO;using Renci.SshNet.Common;using Renci.SshNet.Messages.Connection;using System.Text.RegularExpressions;using System.Threading;using System.Diagnostics;using System.Diagnostics.CodeAnalysis;namespace Renci.SshNet{    /// <summary>    /// Provides SCP client functionality.    /// </summary>    public partial class ScpClient : BaseClient    {        private static Regex _fileInfoRe = new Regex(@"C(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)");        private static Regex _directoryInfoRe = new Regex(@"D(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)");        private static Regex _timestampRe = new Regex(@"T(?<mtime>\d+) 0 (?<atime>\d+) 0");        private static char[] _byteToChar;        private bool _disposeConnectionInfo;        /// <summary>        /// Gets or sets the operation timeout.        /// </summary>        /// <value>The operation timeout.</value>        public TimeSpan OperationTimeout { get; set; }        /// <summary>        /// Gets or sets the size of the buffer.        /// </summary>        /// <value>The size of the buffer.</value>        public uint BufferSize { get; set; }        /// <summary>        /// Occurs when downloading file.        /// </summary>        public event EventHandler<ScpDownloadEventArgs> Downloading;        /// <summary>        /// Occurs when uploading file.        /// </summary>        public event EventHandler<ScpUploadEventArgs> Uploading;        #region Constructors        /// <summary>        /// Initializes a new instance of the <see cref="SftpClient"/> class.        /// </summary>        /// <param name="connectionInfo">The connection info.</param>        /// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is null.</exception>        public ScpClient(ConnectionInfo connectionInfo)            : base(connectionInfo)        {            this.OperationTimeout = new TimeSpan(0, 0, 0, 0, -1);            this.BufferSize = 1024 * 32 - 38;            if (_byteToChar == null)            {                _byteToChar = new char[128];                var ch = '\0';                for (int i = 0; i < 128; i++)                {                    _byteToChar[i] = ch++;                }            }        }        /// <summary>        /// Initializes a new instance of the <see cref="SftpClient"/> class.        /// </summary>        /// <param name="host">Connection host.</param>        /// <param name="port">Connection port.</param>        /// <param name="username">Authentication username.</param>        /// <param name="password">Authentication password.</param>        /// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>        /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is null or contains whitespace characters.</exception>        /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]        public ScpClient(string host, int port, string username, string password)            : this(new PasswordConnectionInfo(host, port, username, password))        {            this._disposeConnectionInfo = true;        }        /// <summary>        /// Initializes a new instance of the <see cref="SftpClient"/> class.        /// </summary>        /// <param name="host">Connection host.</param>        /// <param name="username">Authentication username.</param>        /// <param name="password">Authentication password.</param>        /// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>        /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is null or contains whitespace characters.</exception>        public ScpClient(string host, string username, string password)            : this(host, 22, username, password)        {        }        /// <summary>        /// Initializes a new instance of the <see cref="SftpClient"/> class.        /// </summary>        /// <param name="host">Connection host.</param>        /// <param name="port">Connection port.</param>        /// <param name="username">Authentication username.</param>        /// <param name="keyFiles">Authentication private key file(s) .</param>        /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is null.</exception>        /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is null or contains whitespace characters.</exception>        /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]        public ScpClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)            : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles))        {            this._disposeConnectionInfo = true;        }        /// <summary>        /// Initializes a new instance of the <see cref="SftpClient"/> class.        /// </summary>        /// <param name="host">Connection host.</param>        /// <param name="username">Authentication username.</param>        /// <param name="keyFiles">Authentication private key file(s) .</param>        /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is null.</exception>        /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is null or contains whitespace characters.</exception>        public ScpClient(string host, string username, params PrivateKeyFile[] keyFiles)            : this(host, 22, username, keyFiles)        {        }        #endregion        /// <summary>        /// Uploads the specified stream to the remote host.        /// </summary>        /// <param name="source">Stream to upload.</param>        /// <param name="filename">Remote host file name.</param>        public void Upload(Stream source, string filename)        {            using (var input = new PipeStream())            using (var channel = this.Session.CreateChannel<ChannelSession>())            {                channel.DataReceived += delegate(object sender, Common.ChannelDataEventArgs e)                {                    input.Write(e.Data, 0, e.Data.Length);                    input.Flush();                };                channel.Open();                //  Send channel command request                channel.SendExecRequest(string.Format("scp -qt \"{0}\"", filename));                this.CheckReturnCode(input);                this.InternalFileUpload(channel, input, source, filename);                channel.Close();            }        }        /// <summary>        /// Downloads the specified file from the remote host to the stream.        /// </summary>        /// <param name="filename">Remote host file name.</param>        /// <param name="destination">The stream where to download remote file.</param>        /// <exception cref="ArgumentException"><paramref name="filename"/> is null or contains whitespace characters.</exception>        /// <exception cref="ArgumentNullException"><paramref name="destination"/> is null.</exception>        /// <remarks>Method calls made by this method to <paramref name="destination"/>, may under certain conditions result in exceptions thrown by the stream.</remarks>        public void Download(string filename, Stream destination)        {            if (filename.IsNullOrWhiteSpace())                throw new ArgumentException("filename");            if (destination == null)                throw new ArgumentNullException("destination");            //  UNDONE:   Should call EnsureConnection here to keep it consistent? If you add the call, please add to comment: <exception cref="SshConnectionException">Client is not connected.</exception>            using (var input = new PipeStream())            using (var channel = this.Session.CreateChannel<ChannelSession>())            {                channel.DataReceived += delegate(object sender, Common.ChannelDataEventArgs e)                {                    input.Write(e.Data, 0, e.Data.Length);                    input.Flush();                };                channel.Open();                //  Send channel command request                channel.SendExecRequest(string.Format("scp -qf \"{0}\"", filename));                this.SendConfirmation(channel); //  Send reply                var message = ReadString(input);                var 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}");                    this.InternalFileDownload(channel, input, destination, fileName, length);                }                else                {                    this.SendConfirmation(channel, 1, string.Format("\"{0}\" is not valid protocol message.", message));                }                channel.Close();            }        }        private void InternalSetTimestamp(ChannelSession channel, Stream input, DateTime lastWriteTime, DateTime lastAccessime)        {            var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);            var modificationSeconds = (long)(lastWriteTime - zeroTime).TotalSeconds;            var accessSeconds = (long)(lastAccessime - zeroTime).TotalSeconds;            this.SendData(channel, string.Format("T{0} 0 {1} 0\n", modificationSeconds, accessSeconds));            this.CheckReturnCode(input);        }        private void InternalFileUpload(ChannelSession channel, Stream input, Stream source, string filename)        {            var length = source.Length;            this.SendData(channel, string.Format("C0644 {0} {1}\n", length, filename));            var buffer = new byte[this.BufferSize];            var read = source.Read(buffer, 0, buffer.Length);            long totalRead = 0;            while (read > 0)            {                this.SendData(channel, buffer, read);                totalRead += read;                this.RaiseUploadingEvent(filename, length, totalRead);                read = source.Read(buffer, 0, buffer.Length);            }            this.SendConfirmation(channel);            this.CheckReturnCode(input);        }        private void InternalFileDownload(ChannelSession channel, Stream input, Stream output, string filename, long length)        {            var buffer = new byte[Math.Min(length, this.BufferSize)];            var needToRead = length;            do            {                var read = input.Read(buffer, 0, (int)Math.Min(needToRead, this.BufferSize));                output.Write(buffer, 0, read);                this.RaiseDownloadingEvent(filename, length, length - needToRead);                needToRead -= read;            }            while (needToRead > 0);            output.Flush();            //  Raise one more time when file downloaded            this.RaiseDownloadingEvent(filename, length, length - needToRead);            //  Send confirmation byte after last data byte was read            this.SendConfirmation(channel);            this.CheckReturnCode(input);        }        private void RaiseDownloadingEvent(string filename, long size, long downloaded)        {            if (this.Downloading != null)            {                this.Downloading(this, new ScpDownloadEventArgs(filename, size, downloaded));            }        }        private void RaiseUploadingEvent(string filename, long size, long uploaded)        {            if (this.Uploading != null)            {                this.Uploading(this, new ScpUploadEventArgs(filename, size, uploaded));            }        }        private void SendConfirmation(ChannelSession channel)        {            this.SendData(channel, new byte[] { 0 });        }        private void SendConfirmation(ChannelSession channel, byte errorCode, string message)        {            this.SendData(channel, new byte[] { errorCode });            this.SendData(channel, string.Format("{0}\n", message));        }        /// <summary>        /// Checks the return code.        /// </summary>        /// <param name="input">The output stream.</param>        private void CheckReturnCode(Stream input)        {            var b = ReadByte(input);            if (b > 0)            {                var errorText = ReadString(input);                throw new ScpException(errorText);            }        }        partial void SendData(ChannelSession channel, string command);        private void SendData(ChannelSession channel, byte[] buffer, int length)        {            if (length == buffer.Length)            {                channel.SendData(buffer);            }            else            {                channel.SendData(buffer.Take(length).ToArray());            }        }        private void SendData(ChannelSession channel, byte[] buffer)        {            channel.SendData(buffer);        }        private static int ReadByte(Stream stream)        {            var b = stream.ReadByte();            while (b < 0)            {                Thread.Sleep(100);                b = stream.ReadByte();            }            return b;        }        private static string ReadString(Stream stream)        {            var hasError = false;            StringBuilder sb = new StringBuilder();            var b = ReadByte(stream);            if (b == 1 || b == 2)            {                hasError = true;                b = ReadByte(stream);            }            var ch = _byteToChar[b];            while (ch != '\n')            {                sb.Append(ch);                b = ReadByte(stream);                ch = _byteToChar[b];            }            if (hasError)                throw new ScpException(sb.ToString());            return sb.ToString();        }        /// <summary>        /// Releases unmanaged and - optionally - managed resources        /// </summary>        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>        protected override void Dispose(bool disposing)        {            base.Dispose(disposing);            if (this._disposeConnectionInfo)                ((IDisposable)this.ConnectionInfo).Dispose();        }    }}
 |