| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199 | using System;using System.Linq;using System.Text;using System.Threading;using Renci.SshNet.Common;using System.Collections.Generic;using System.Globalization;using Renci.SshNet.Sftp.Responses;using Renci.SshNet.Sftp.Requests;namespace Renci.SshNet.Sftp{    internal class SftpSession : SubsystemSession, ISftpSession    {        private const int MaximumSupportedVersion = 3;        private const int MinimumSupportedVersion = 0;        private readonly Dictionary<uint, SftpRequest> _requests = new Dictionary<uint, SftpRequest>();#if TUNING        private readonly List<byte> _data = new List<byte>(32 * 1024);        //FIXME: obtain from SftpClient!#else        private readonly List<byte> _data = new List<byte>(16 * 1024);#endif        private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(false);        private IDictionary<string, string> _supportedExtensions;        /// <summary>        /// Gets the remote working directory.        /// </summary>        /// <value>        /// The remote working directory.        /// </value>        public string WorkingDirectory { get; private set; }        /// <summary>        /// Gets the SFTP protocol version.        /// </summary>        /// <value>        /// The SFTP protocol version.        /// </value>        public uint ProtocolVersion { get; private set; }        private long _requestId;        /// <summary>        /// Gets the next request id for sftp session.        /// </summary>        public uint NextRequestId        {            get            {#if WINDOWS_PHONE                lock (this)                {                    this._requestId++;                }                return (uint)this._requestId;#else                return ((uint)Interlocked.Increment(ref _requestId));#endif            }        }        public SftpSession(ISession session, TimeSpan operationTimeout, Encoding encoding)            : base(session, "sftp", operationTimeout, encoding)        {        }        /// <summary>        /// Changes the current working directory to the specified path.        /// </summary>        /// <param name="path">The new working directory.</param>        public void ChangeDirectory(string path)        {            var fullPath = GetCanonicalPath(path);            var handle = RequestOpenDir(fullPath);            RequestClose(handle);            WorkingDirectory = fullPath;        }        internal void SendMessage(SftpMessage sftpMessage)        {#if TUNING            var data = sftpMessage.GetBytes();#else            var messageData = sftpMessage.GetBytes();            var data = new byte[4 + messageData.Length];            ((uint)messageData.Length).GetBytes().CopyTo(data, 0);            messageData.CopyTo(data, 4);#endif            SendData(data);        }        /// <summary>        /// Resolves a given path into an absolute path on the server.        /// </summary>        /// <param name="path">The path to resolve.</param>        /// <returns>        /// The absolute path.        /// </returns>        public string GetCanonicalPath(string path)        {            var fullPath = GetFullRemotePath(path);            var canonizedPath = string.Empty;            var realPathFiles = RequestRealPath(fullPath, true);            if (realPathFiles != null)            {                canonizedPath = realPathFiles.First().Key;            }            if (!string.IsNullOrEmpty(canonizedPath))                return canonizedPath;            //  Check for special cases            if (fullPath.EndsWith("/.", StringComparison.InvariantCultureIgnoreCase) ||                fullPath.EndsWith("/..", StringComparison.InvariantCultureIgnoreCase) ||                fullPath.Equals("/", StringComparison.InvariantCultureIgnoreCase) ||                fullPath.IndexOf('/') < 0)                return fullPath;            var pathParts = fullPath.Split(new[] { '/' });            var partialFullPath = string.Join("/", pathParts, 0, pathParts.Length - 1);            if (string.IsNullOrEmpty(partialFullPath))                partialFullPath = "/";            realPathFiles = RequestRealPath(partialFullPath, true);            if (realPathFiles != null)            {                canonizedPath = realPathFiles.First().Key;            }            if (string.IsNullOrEmpty(canonizedPath))            {                return fullPath;            }            var slash = string.Empty;            if (canonizedPath[canonizedPath.Length - 1] != '/')                slash = "/";            return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", canonizedPath, slash, pathParts[pathParts.Length - 1]);        }        internal string GetFullRemotePath(string path)        {            var fullPath = path;            if (!string.IsNullOrEmpty(path) && path[0] != '/' && WorkingDirectory != null)            {                if (WorkingDirectory[WorkingDirectory.Length - 1] == '/')                {                    fullPath = string.Format(CultureInfo.InvariantCulture, "{0}{1}", WorkingDirectory, path);                }                else                {                    fullPath = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", WorkingDirectory, path);                }            }            return fullPath;        }        protected override void OnChannelOpen()        {            SendMessage(new SftpInitRequest(MaximumSupportedVersion));            WaitOnHandle(_sftpVersionConfirmed, OperationTimeout);            if (ProtocolVersion > MaximumSupportedVersion || ProtocolVersion < MinimumSupportedVersion)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Server SFTP version {0} is not supported.", ProtocolVersion));            }            //  Resolve current directory            WorkingDirectory = RequestRealPath(".").First().Key;        }        protected override void OnDataReceived(byte[] data)        {            //  Add channel data to internal data holder            _data.AddRange(data);            while (_data.Count > 4 + 1)            {                //  Extract packet length                var packetLength = (_data[0] << 24 | _data[1] << 16 | _data[2] << 8 | _data[3]);                //  Check if complete packet data is available                if (_data.Count < packetLength + 4)                {                    //  Wait for complete message to arrive first                    break;                }#if TUNING                var packetLengthIncludingBytesForLength = packetLength + 4;                //  Create buffer to hold packet data and 4 bytes for packet length                var packetData = new byte[packetLengthIncludingBytesForLength];                // copy packet data and bytes for length to array                _data.CopyTo(0, packetData, 0, packetLengthIncludingBytesForLength);                //  Remove loaded data and bytes for length from _data holder                _data.RemoveRange(0, packetLengthIncludingBytesForLength);#else                _data.RemoveRange(0, 4);                //  Create buffer to hold packet data                var packetData = new byte[packetLength];                //  Cope packet data to array                _data.CopyTo(0, packetData, 0, packetLength);                //  Remove loaded data from _data holder                _data.RemoveRange(0, packetLength);#endif                //  Load SFTP Message and handle it                var response = SftpMessage.Load(ProtocolVersion, packetData, Encoding);                try                {                    var versionResponse = response as SftpVersionResponse;                    if (versionResponse != null)                    {                        ProtocolVersion = versionResponse.Version;                        _supportedExtensions = versionResponse.Extentions;                        _sftpVersionConfirmed.Set();                    }                    else                    {                        HandleResponse(response as SftpResponse);                    }                }                catch (Exception exp)                {                    RaiseError(exp);                    break;                }            }        }        protected override void Dispose(bool disposing)        {            base.Dispose(disposing);            if (disposing)            {                if (_sftpVersionConfirmed != null)                {                    _sftpVersionConfirmed.Dispose();                    _sftpVersionConfirmed = null;                }            }        }        private void SendRequest(SftpRequest request)        {            lock (_requests)            {                _requests.Add(request.RequestId, request);            }            SendMessage(request);        }        #region SFTP API functions        /// <summary>        /// Performs SSH_FXP_OPEN request        /// </summary>        /// <param name="path">The path.</param>        /// <param name="flags">The flags.</param>        /// <param name="nullOnError">if set to <c>true</c> returns <c>null</c> instead of throwing an exception.</param>        /// <returns>File handle.</returns>        public byte[] RequestOpen(string path, Flags flags, bool nullOnError = false)        {            byte[] handle = null;            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpOpenRequest(ProtocolVersion, NextRequestId, path, Encoding, flags,                    response =>                        {                            handle = response.Handle;                            wait.Set();                        },                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (!nullOnError && exception != null)            {                throw exception;            }            return handle;        }        /// <summary>        /// Performs SSH_FXP_CLOSE request.        /// </summary>        /// <param name="handle">The handle.</param>        public void RequestClose(byte[] handle)        {            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpCloseRequest(ProtocolVersion, NextRequestId, handle,                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }        }        /// <summary>        /// Performs SSH_FXP_READ request.        /// </summary>        /// <param name="handle">The handle.</param>        /// <param name="offset">The offset.</param>        /// <param name="length">The length.</param>        /// <returns>data array; null if EOF</returns>        public byte[] RequestRead(byte[] handle, ulong offset, uint length)        {            SshException exception = null;            var data = new byte[0];            using (var wait = new AutoResetEvent(false))            {                var request = new SftpReadRequest(ProtocolVersion, NextRequestId, handle, offset, length,                    response =>                        {                            data = response.Data;                            wait.Set();                        },                    response =>                        {                            if (response.StatusCode != StatusCodes.Eof)                            {                                exception = GetSftpException(response);                            }                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }            return data;        }        /// <summary>        /// Performs SSH_FXP_WRITE request.        /// </summary>        /// <param name="handle">The handle.</param>        /// <param name="offset">The offset.</param>        /// <param name="data">The data to send.</param>#if TUNING        /// <param name="length">The number of bytes of <paramref name="data"/> to send.</param>#endif        /// <param name="wait">The wait event handle if needed.</param>        /// <param name="writeCompleted">The callback to invoke when the write has completed.</param>        public void RequestWrite(byte[] handle,                                 ulong offset,                                 byte[] data,#if TUNING                                 int length,#endif                                 AutoResetEvent wait,                                 Action<SftpStatusResponse> writeCompleted = null)        {            SshException exception = null;#if TUNING            var request = new SftpWriteRequest(ProtocolVersion, NextRequestId, handle, offset, data, length,#else            var request = new SftpWriteRequest(ProtocolVersion, NextRequestId, handle, offset, data,#endif                response =>                    {                        if (writeCompleted != null)                        {                            writeCompleted(response);                        }                        exception = GetSftpException(response);                        if (wait != null)                            wait.Set();                    });            SendRequest(request);            if (wait != null)                WaitOnHandle(wait, OperationTimeout);            if (exception != null)            {                throw exception;            }        }        /// <summary>        /// Performs SSH_FXP_LSTAT request.        /// </summary>        /// <param name="path">The path.</param>        /// <returns>        /// File attributes        /// </returns>        public SftpFileAttributes RequestLStat(string path)        {            SshException exception = null;            SftpFileAttributes attributes = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpLStatRequest(ProtocolVersion, NextRequestId, path, Encoding,                    response =>                        {                            attributes = response.Attributes;                            wait.Set();                        },                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }            return attributes;        }        /// <summary>        /// Performs SSH_FXP_FSTAT request.        /// </summary>        /// <param name="handle">The handle.</param>        /// <returns>        /// File attributes        /// </returns>        public SftpFileAttributes RequestFStat(byte[] handle)        {            SshException exception = null;            SftpFileAttributes attributes = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpFStatRequest(ProtocolVersion, NextRequestId, handle,                    response =>                        {                            attributes = response.Attributes;                            wait.Set();                        },                    (response) =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }            return attributes;        }        /// <summary>        /// Performs SSH_FXP_SETSTAT request.        /// </summary>        /// <param name="path">The path.</param>        /// <param name="attributes">The attributes.</param>        public void RequestSetStat(string path, SftpFileAttributes attributes)        {            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpSetStatRequest(ProtocolVersion, NextRequestId, path, Encoding, attributes,                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }        }        /// <summary>        /// Performs SSH_FXP_FSETSTAT request.        /// </summary>        /// <param name="handle">The handle.</param>        /// <param name="attributes">The attributes.</param>        public void RequestFSetStat(byte[] handle, SftpFileAttributes attributes)        {            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpFSetStatRequest(ProtocolVersion, NextRequestId, handle, attributes,                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }        }        /// <summary>        /// Performs SSH_FXP_OPENDIR request        /// </summary>        /// <param name="path">The path.</param>        /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>        /// <returns>File handle.</returns>        public byte[] RequestOpenDir(string path, bool nullOnError = false)        {            SshException exception = null;            byte[] handle = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpOpenDirRequest(ProtocolVersion, NextRequestId, path, Encoding,                    response =>                        {                            handle = response.Handle;                            wait.Set();                        },                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (!nullOnError && exception != null)            {                throw exception;            }            return handle;        }        /// <summary>        /// Performs SSH_FXP_READDIR request        /// </summary>        /// <param name="handle">The handle.</param>        /// <returns></returns>        public KeyValuePair<string, SftpFileAttributes>[] RequestReadDir(byte[] handle)        {            SshException exception = null;            KeyValuePair<string, SftpFileAttributes>[] result = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpReadDirRequest(ProtocolVersion, NextRequestId, handle,                    response =>                        {                            result = response.Files;                            wait.Set();                        },                    response =>                        {                            if (response.StatusCode != StatusCodes.Eof)                            {                                exception = GetSftpException(response);                            }                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }            return result;        }        /// <summary>        /// Performs SSH_FXP_REMOVE request.        /// </summary>        /// <param name="path">The path.</param>        public void RequestRemove(string path)        {            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpRemoveRequest(ProtocolVersion, NextRequestId, path, Encoding,                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }        }        /// <summary>        /// Performs SSH_FXP_MKDIR request.        /// </summary>        /// <param name="path">The path.</param>        public void RequestMkDir(string path)        {            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpMkDirRequest(ProtocolVersion, NextRequestId, path, Encoding,                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }        }        /// <summary>        /// Performs SSH_FXP_RMDIR request.        /// </summary>        /// <param name="path">The path.</param>        public void RequestRmDir(string path)        {            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpRmDirRequest(ProtocolVersion, NextRequestId, path, Encoding,                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }        }        /// <summary>        /// Performs SSH_FXP_REALPATH request        /// </summary>        /// <param name="path">The path.</param>        /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>        /// <returns></returns>        internal KeyValuePair<string, SftpFileAttributes>[] RequestRealPath(string path, bool nullOnError = false)        {            SshException exception = null;            KeyValuePair<string, SftpFileAttributes>[] result = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpRealPathRequest(ProtocolVersion, NextRequestId, path, Encoding,                    response =>                        {                            result = response.Files;                            wait.Set();                        },                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (!nullOnError && exception != null)            {                throw exception;            }                        return result;        }        /// <summary>        /// Performs SSH_FXP_STAT request.        /// </summary>        /// <param name="path">The path.</param>        /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>        /// <returns>        /// File attributes        /// </returns>        internal SftpFileAttributes RequestStat(string path, bool nullOnError = false)        {            SshException exception = null;            SftpFileAttributes attributes = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpStatRequest(ProtocolVersion, NextRequestId, path, Encoding,                    response =>                        {                            attributes = response.Attributes;                            wait.Set();                        },                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (!nullOnError && exception != null)            {                throw exception;            }            return attributes;        }        /// <summary>        /// Performs SSH_FXP_RENAME request.        /// </summary>        /// <param name="oldPath">The old path.</param>        /// <param name="newPath">The new path.</param>        public void RequestRename(string oldPath, string newPath)        {            if (ProtocolVersion < 2)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_RENAME operation is not supported in {0} version that server operates in.", ProtocolVersion));            }            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpRenameRequest(ProtocolVersion, NextRequestId, oldPath, newPath, Encoding,                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }        }        /// <summary>        /// Performs SSH_FXP_READLINK request.        /// </summary>        /// <param name="path">The path.</param>        /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>        /// <returns></returns>        internal KeyValuePair<string, SftpFileAttributes>[] RequestReadLink(string path, bool nullOnError = false)        {            if (ProtocolVersion < 3)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_READLINK operation is not supported in {0} version that server operates in.", ProtocolVersion));            }            SshException exception = null;            KeyValuePair<string, SftpFileAttributes>[] result = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpReadLinkRequest(ProtocolVersion, NextRequestId, path, Encoding,                    response =>                        {                            result = response.Files;                            wait.Set();                        },                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (!nullOnError && exception != null)            {                throw exception;            }            return result;        }        /// <summary>        /// Performs SSH_FXP_SYMLINK request.        /// </summary>        /// <param name="linkpath">The linkpath.</param>        /// <param name="targetpath">The targetpath.</param>        public void RequestSymLink(string linkpath, string targetpath)        {            if (ProtocolVersion < 3)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_SYMLINK operation is not supported in {0} version that server operates in.", ProtocolVersion));            }            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpSymLinkRequest(ProtocolVersion, NextRequestId, linkpath, targetpath, Encoding,                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }        }        #endregion        #region SFTP Extended API functions        /// <summary>        /// Performs posix-rename@openssh.com extended request.        /// </summary>        /// <param name="oldPath">The old path.</param>        /// <param name="newPath">The new path.</param>        public void RequestPosixRename(string oldPath, string newPath)        {            if (ProtocolVersion < 3)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));            }            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new PosixRenameRequest(ProtocolVersion, NextRequestId, oldPath, newPath, Encoding,                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                if (!_supportedExtensions.ContainsKey(request.Name))                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }        }        /// <summary>        /// Performs statvfs@openssh.com extended request.        /// </summary>        /// <param name="path">The path.</param>        /// <param name="nullOnError">if set to <c>true</c> [null on error].</param>        /// <returns></returns>        public SftpFileSytemInformation RequestStatVfs(string path, bool nullOnError = false)        {            if (ProtocolVersion < 3)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));            }            SshException exception = null;            SftpFileSytemInformation information = null;            using (var wait = new AutoResetEvent(false))            {                var request = new StatVfsRequest(ProtocolVersion, NextRequestId, path, Encoding,                    response =>                        {                            information = response.GetReply<StatVfsReplyInfo>().Information;                            wait.Set();                        },                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                if (!_supportedExtensions.ContainsKey(request.Name))                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (!nullOnError && exception != null)            {                throw exception;            }            return information;        }        /// <summary>        /// Performs fstatvfs@openssh.com extended request.        /// </summary>        /// <param name="handle">The file handle.</param>        /// <param name="nullOnError">if set to <c>true</c> [null on error].</param>        /// <returns></returns>        /// <exception cref="System.NotSupportedException"></exception>        internal SftpFileSytemInformation RequestFStatVfs(byte[] handle, bool nullOnError = false)        {            if (ProtocolVersion < 3)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));            }            SshException exception = null;            SftpFileSytemInformation information = null;            using (var wait = new AutoResetEvent(false))            {                var request = new FStatVfsRequest(ProtocolVersion, NextRequestId, handle,                    response =>                        {                            information = response.GetReply<StatVfsReplyInfo>().Information;                            wait.Set();                        },                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                if (!_supportedExtensions.ContainsKey(request.Name))                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }                        if (!nullOnError && exception != null)            {                throw exception;            }            return information;        }        /// <summary>        /// Performs hardlink@openssh.com extended request.        /// </summary>        /// <param name="oldPath">The old path.</param>        /// <param name="newPath">The new path.</param>        internal void HardLink(string oldPath, string newPath)        {            if (ProtocolVersion < 3)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));            }            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new HardLinkRequest(ProtocolVersion, NextRequestId, oldPath, newPath,                    response =>                        {                            exception = GetSftpException(response);                            wait.Set();                        });                if (!_supportedExtensions.ContainsKey(request.Name))                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));                SendRequest(request);                WaitOnHandle(wait, OperationTimeout);            }            if (exception != null)            {                throw exception;            }        }        #endregion        /// <summary>        /// Calculates the optimal size of the buffer to read data from the channel.        /// </summary>        /// <param name="bufferSize">The buffer size configured on the client.</param>        /// <returns>        /// The optimal size of the buffer to read data from the channel.        /// </returns>        public uint CalculateOptimalReadLength(uint bufferSize)        {            // a SSH_FXP_DATA message has 13 bytes of protocol fields:            // bytes 1 to 4: packet length            // byte 5: message type            // bytes 6 to 9: response id            // bytes 10 to 13: length of payload            //            // most ssh servers limit the size of the payload of a SSH_MSG_CHANNEL_DATA            // response to 16 KB; if we requested 16 KB of data, then the SSH_FXP_DATA            // payload of the SSH_MSG_CHANNEL_DATA message would be too big (16 KB + 13 bytes), and            // as a result, the ssh server would split this into two responses:            // one containing 16384 bytes (13 bytes header, and 16371 bytes file data)            // and one with the remaining 13 bytes of file data            const uint lengthOfNonDataProtocolFields = 13u;            var maximumPacketSize = Channel.LocalPacketSize;            return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields;        }        /// <summary>        /// Calculates the optimal size of the buffer to write data on the channel.        /// </summary>        /// <param name="bufferSize">The buffer size configured on the client.</param>        /// <param name="handle">The file handle.</param>        /// <returns>        /// The optimal size of the buffer to write data on the channel.        /// </returns>        /// <remarks>        /// Currently, we do not take the remote window size into account.        /// </remarks>        public uint CalculateOptimalWriteLength(uint bufferSize, byte[] handle)        {            // 1-4: package length of SSH_FXP_WRITE message            // 5: message type            // 6-9: request id            // 10-13: handle length            // <handle>            // 14-21: offset            // 22-25: data length            var lengthOfNonDataProtocolFields = 25u + (uint)handle.Length;            var maximumPacketSize = Channel.RemotePacketSize;            return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields;        }        private SshException GetSftpException(SftpStatusResponse response)        {            if (response.StatusCode == StatusCodes.Ok)            {                return null;            }            if (response.StatusCode == StatusCodes.PermissionDenied)            {                return new SftpPermissionDeniedException(response.ErrorMessage);            }            if (response.StatusCode == StatusCodes.NoSuchFile)            {                return new SftpPathNotFoundException(response.ErrorMessage);            }            return new SshException(response.ErrorMessage);        }        private void HandleResponse(SftpResponse response)        {            SftpRequest request;            lock (_requests)            {                _requests.TryGetValue(response.ResponseId, out request);                if (request != null)                {                    _requests.Remove(response.ResponseId);                }            }            if (request == null)                throw new InvalidOperationException("Invalid response.");            request.Complete(response);        }    }}
 |