| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 | using System;using System.Linq;using System.Text;using Renci.SshNet.Channels;using System.IO;using Renci.SshNet.Common;using System.Text.RegularExpressions;using System.Threading;using System.Diagnostics.CodeAnalysis;namespace Renci.SshNet{    /// <summary>    /// Provides SCP client functionality.    /// </summary>    public partial class ScpClient : BaseClient    {        private static readonly Regex FileInfoRe = new Regex(@"C(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)");        private static char[] _byteToChar;        /// <summary>        /// Gets or sets the operation timeout.        /// </summary>        /// <value>        /// The timeout to wait until an operation completes. The default value is negative        /// one (-1) milliseconds, which indicates an infinite time-out period.        /// </value>        public TimeSpan OperationTimeout { get; set; }        /// <summary>        /// Gets or sets the size of the buffer.        /// </summary>        /// <value>        /// The size of the buffer. The default buffer size is 16384 bytes.        /// </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)            : this(connectionInfo, false)        {        }        /// <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="F: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), 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, ConnectionInfo.DefaultPort, 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="F: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), 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, ConnectionInfo.DefaultPort, username, keyFiles)        {        }        /// <summary>        /// Initializes a new instance of the <see cref="ScpClient"/> class.        /// </summary>        /// <param name="connectionInfo">The connection info.</param>        /// <param name="ownsConnectionInfo">Specified whether this instance owns the connection info.</param>        /// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is null.</exception>        /// <remarks>        /// If <paramref name="ownsConnectionInfo"/> is <c>true</c>, then the        /// connection info will be disposed when this instance is disposed.        /// </remarks>        private ScpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo)            : this(connectionInfo, ownsConnectionInfo, new ServiceFactory())        {        }        /// <summary>        /// Initializes a new instance of the <see cref="ScpClient"/> class.        /// </summary>        /// <param name="connectionInfo">The connection info.</param>        /// <param name="ownsConnectionInfo">Specified whether this instance owns the connection info.</param>        /// <param name="serviceFactory">The factory to use for creating new services.</param>        /// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is null.</exception>        /// <exception cref="ArgumentNullException"><paramref name="serviceFactory"/> is null.</exception>        /// <remarks>        /// If <paramref name="ownsConnectionInfo"/> is <c>true</c>, then the        /// connection info will be disposed when this instance is disposed.        /// </remarks>        internal ScpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServiceFactory serviceFactory)            : base(connectionInfo, ownsConnectionInfo, serviceFactory)        {            OperationTimeout = new TimeSpan(0, 0, 0, 0, -1);            BufferSize = 1024 * 16;            if (_byteToChar == null)            {                _byteToChar = new char[128];                var ch = '\0';                for (var i = 0; i < 128; i++)                {                    _byteToChar[i] = ch++;                }            }        }        #endregion        /// <summary>        /// Uploads the specified stream to the remote host.        /// </summary>        /// <param name="source">Stream to upload.</param>        /// <param name="path">Remote host file name.</param>        public void Upload(Stream source, string path)        {            using (var input = ServiceFactory.CreatePipeStream())            using (var channel = Session.CreateChannelSession())            {                channel.DataReceived += delegate(object sender, ChannelDataEventArgs e)                {                    input.Write(e.Data, 0, e.Data.Length);                    input.Flush();                };                channel.Open();                var pathEnd = path.LastIndexOfAny(new[] { '\\', '/' });                if (pathEnd != -1)                {                    // split the path from the file                    var pathOnly = path.Substring(0, pathEnd);                    var fileOnly = path.Substring(pathEnd + 1);                    //  Send channel command request                    channel.SendExecRequest(string.Format("scp -t \"{0}\"", pathOnly));                    CheckReturnCode(input);                    path = fileOnly;                }                InternalUpload(channel, input, source, path);                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");            using (var input = new PipeStream())            using (var channel = Session.CreateChannelSession())            {                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 -f \"{0}\"", filename));                SendConfirmation(channel); //  Send reply                var message = ReadString(input);                var match = FileInfoRe.Match(message);                if (match.Success)                {                    //  Read file                    SendConfirmation(channel); //  Send reply                    var mode = match.Result("${mode}");                    var length = long.Parse(match.Result("${length}"));                    var fileName = match.Result("${filename}");                    InternalDownload(channel, input, destination, fileName, length);                }                else                {                    SendConfirmation(channel, 1, string.Format("\"{0}\" is not valid protocol message.", message));                }                channel.Close();            }        }        private void InternalSetTimestamp(IChannelSession 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;            SendData(channel, string.Format("T{0} 0 {1} 0\n", modificationSeconds, accessSeconds));            CheckReturnCode(input);        }        private void InternalUpload(IChannelSession channel, Stream input, Stream source, string filename)        {            var length = source.Length;            SendData(channel, string.Format("C0644 {0} {1}\n", length, Path.GetFileName(filename)));            CheckReturnCode(input);            var buffer = new byte[BufferSize];            var read = source.Read(buffer, 0, buffer.Length);            long totalRead = 0;            while (read > 0)            {                SendData(channel, buffer, read);                totalRead += read;                RaiseUploadingEvent(filename, length, totalRead);                read = source.Read(buffer, 0, buffer.Length);            }            SendConfirmation(channel);            CheckReturnCode(input);        }        private void InternalDownload(IChannelSession channel, Stream input, Stream output, string filename, long length)        {            var buffer = new byte[Math.Min(length, BufferSize)];            var needToRead = length;            do            {                var read = input.Read(buffer, 0, (int)Math.Min(needToRead, BufferSize));                output.Write(buffer, 0, read);                RaiseDownloadingEvent(filename, length, length - needToRead);                needToRead -= read;            }            while (needToRead > 0);            output.Flush();            //  Raise one more time when file downloaded            RaiseDownloadingEvent(filename, length, length - needToRead);            //  Send confirmation byte after last data byte was read            SendConfirmation(channel);            CheckReturnCode(input);        }        private void RaiseDownloadingEvent(string filename, long size, long downloaded)        {            if (Downloading != null)            {                Downloading(this, new ScpDownloadEventArgs(filename, size, downloaded));            }        }        private void RaiseUploadingEvent(string filename, long size, long uploaded)        {            if (Uploading != null)            {                Uploading(this, new ScpUploadEventArgs(filename, size, uploaded));            }        }        private void SendConfirmation(IChannelSession channel)        {            SendData(channel, new byte[] { 0 });        }        private void SendConfirmation(IChannelSession channel, byte errorCode, string message)        {            SendData(channel, new[] { errorCode });            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(IChannelSession channel, string command);        private void SendData(IChannelSession channel, byte[] buffer, int length)        {#if TUNING            channel.SendData(buffer, 0, length);#else            if (length == buffer.Length)            {                channel.SendData(buffer);            }            else            {                channel.SendData(buffer.Take(length).ToArray());            }#endif        }        private void SendData(IChannelSession 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;            var 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();        }    }}
 |