| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157 | using System;using System.Linq;using System.Text;using System.Threading;using Renci.SshNet.Channels;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    {        private const int MAXIMUM_SUPPORTED_VERSION = 3;        private const int MINIMUM_SUPPORTED_VERSION = 0;        private readonly Dictionary<uint, SftpRequest> _requests = new Dictionary<uint, SftpRequest>();        private readonly List<byte> _data = new List<byte>(16 * 1024);        private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(false);        private IDictionary<string, string> _supportedExtensions;        /// <summary>        /// Gets remote working directory.        /// </summary>        public string WorkingDirectory { get; private set; }        /// <summary>        /// Gets SFTP protocol version.        /// </summary>        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 this._requestId));#endif            }        }        public SftpSession(Session session, TimeSpan operationTimeout, Encoding encoding)            : base(session, "sftp", operationTimeout, encoding)        {        }        public void ChangeDirectory(string path)        {            var fullPath = this.GetCanonicalPath(path);            var handle = this.RequestOpenDir(fullPath);            this.RequestClose(handle);            this.WorkingDirectory = fullPath;        }        internal void SendMessage(SftpMessage sftpMessage)        {            var messageData = sftpMessage.GetBytes();            var data = new byte[4 + messageData.Length];            ((uint)messageData.Length).GetBytes().CopyTo(data, 0);            messageData.CopyTo(data, 4);            this.SendData(data);        }        /// <summary>        /// Resolves path into absolute path on the server.        /// </summary>        /// <param name="path">Path to resolve.</param>        /// <returns>Absolute path</returns>        internal string GetCanonicalPath(string path)        {            var fullPath = GetFullRemotePath(path);            var canonizedPath = string.Empty;            var realPathFiles = this.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 char[] { '/' });            var partialFullPath = string.Join("/", pathParts, 0, pathParts.Length - 1);            if (string.IsNullOrEmpty(partialFullPath))                partialFullPath = "/";            realPathFiles = this.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] != '/' && this.WorkingDirectory != null)            {                if (this.WorkingDirectory[this.WorkingDirectory.Length - 1] == '/')                {                    fullPath = string.Format(CultureInfo.InvariantCulture, "{0}{1}", this.WorkingDirectory, path);                }                else                {                    fullPath = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", this.WorkingDirectory, path);                }            }            return fullPath;        }        protected override void OnChannelOpen()        {            this.SendMessage(new SftpInitRequest(MAXIMUM_SUPPORTED_VERSION));            this.WaitOnHandle(this._sftpVersionConfirmed, this._operationTimeout);            if (this.ProtocolVersion > MAXIMUM_SUPPORTED_VERSION || this.ProtocolVersion < MINIMUM_SUPPORTED_VERSION)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Server SFTP version {0} is not supported.", this.ProtocolVersion));            }            //  Resolve current directory            this.WorkingDirectory = this.RequestRealPath(".").First().Key;        }        protected override void OnDataReceived(uint dataTypeCode, byte[] data)        {            //  Add channel data to internal data holder            this._data.AddRange(data);            while (this._data.Count > 4 + 1)            {                //  Extract packet length                var packetLength = (this._data[0] << 24 | this._data[1] << 16 | this._data[2] << 8 | this._data[3]);                //  Check if complete packet data is available                if (this._data.Count < packetLength + 4)                {                    //  Wait for complete message to arrive first                    break;                }                this._data.RemoveRange(0, 4);                //  Create buffer to hold packet data                var packetData = new byte[packetLength];                //  Cope packet data to array                this._data.CopyTo(0, packetData, 0, packetLength);                //  Remove loaded data from _data holder                this._data.RemoveRange(0, packetLength);                //  Load SFTP Message and handle it                var response = SftpMessage.Load(this.ProtocolVersion, packetData, this.Encoding);                try                {                    var versionResponse = response as SftpVersionResponse;                    if (versionResponse != null)                    {                        this.ProtocolVersion = versionResponse.Version;                        this._supportedExtensions = versionResponse.Extentions;                        this._sftpVersionConfirmed.Set();                    }                    else                    {                        this.HandleResponse(response as SftpResponse);                    }                }                catch (Exception exp)                {                    this.RaiseError(exp);                    break;                }            }        }        protected override void Dispose(bool disposing)        {            base.Dispose(disposing);            if (disposing)            {                if (this._sftpVersionConfirmed != null)                {                    this._sftpVersionConfirmed.Dispose();                    this._sftpVersionConfirmed = null;                }            }        }        private void SendRequest(SftpRequest request)        {            lock (this._requests)            {                this._requests.Add(request.RequestId, request);            }            this.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 null instead of throwing an exception.</param>        /// <returns>File handle.</returns>        internal 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(this.ProtocolVersion, this.NextRequestId, path, this.Encoding, flags,                    response =>                    {                        handle = response.Handle;                        wait.Set();                    },                    response =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._operationTimeout);            }            if (!nullOnError && exception != null)            {                throw exception;            }            return handle;        }        /// <summary>        /// Performs SSH_FXP_CLOSE request.        /// </summary>        /// <param name="handle">The handle.</param>        internal void RequestClose(byte[] handle)        {            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpCloseRequest(this.ProtocolVersion, this.NextRequestId, handle,                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._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>        internal byte[] RequestRead(byte[] handle, UInt64 offset, UInt32 length)        {            SshException exception = null;            var data = new byte[0];            using (var wait = new AutoResetEvent(false))            {                var request = new SftpReadRequest(this.ProtocolVersion, this.NextRequestId, handle, offset, length,                    (response) =>                    {                        data = response.Data;                        wait.Set();                    },                    (response) =>                    {                        if (response.StatusCode != StatusCodes.Eof)                        {                            exception = this.GetSftpException(response);                        }                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._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>        /// <param name="wait">The wait event handle if needed.</param>        /// <param name="writeCompleted">The callback to invoke when the write has completed.</param>        internal void RequestWrite(byte[] handle, UInt64 offset, byte[] data, EventWaitHandle wait, Action<SftpStatusResponse> writeCompleted = null)        {            SshException exception = null;            var request = new SftpWriteRequest(this.ProtocolVersion, this.NextRequestId, handle, offset, data,                (response) =>                {                    if (writeCompleted != null)                    {                        writeCompleted(response);                    }                    exception = this.GetSftpException(response);                    if (wait != null)                        wait.Set();                });            this.SendRequest(request);            if (wait != null)                this.WaitOnHandle(wait, this._operationTimeout);            if (exception != null)            {                throw exception;            }        }        /// <summary>        /// Performs SSH_FXP_LSTAT request.        /// </summary>        /// <param name="path">The path.</param>        /// <returns>        /// File attributes        /// </returns>        internal SftpFileAttributes RequestLStat(string path)        {            SshException exception = null;            SftpFileAttributes attributes = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpLStatRequest(this.ProtocolVersion, this.NextRequestId, path, this.Encoding,                    (response) =>                    {                        attributes = response.Attributes;                        wait.Set();                    },                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._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>        internal SftpFileAttributes RequestFStat(byte[] handle)        {            SshException exception = null;            SftpFileAttributes attributes = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpFStatRequest(this.ProtocolVersion, this.NextRequestId, handle,                    (response) =>                    {                        attributes = response.Attributes;                        wait.Set();                    },                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._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>        internal void RequestSetStat(string path, SftpFileAttributes attributes)        {            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpSetStatRequest(this.ProtocolVersion, this.NextRequestId, path, this.Encoding, attributes,                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._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>        internal void RequestFSetStat(byte[] handle, SftpFileAttributes attributes)        {            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpFSetStatRequest(this.ProtocolVersion, this.NextRequestId, handle, attributes,                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._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>        internal byte[] RequestOpenDir(string path, bool nullOnError = false)        {            SshException exception = null;            byte[] handle = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpOpenDirRequest(this.ProtocolVersion, this.NextRequestId, path, this.Encoding,                    (response) =>                    {                        handle = response.Handle;                        wait.Set();                    },                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._operationTimeout);            }            if (!nullOnError && exception != null)            {                throw exception;            }            return handle;        }        /// <summary>        /// Performs SSH_FXP_READDIR request        /// </summary>        /// <param name="handle">The handle.</param>        /// <returns></returns>        internal KeyValuePair<string, SftpFileAttributes>[] RequestReadDir(byte[] handle)        {            SshException exception = null;            KeyValuePair<string, SftpFileAttributes>[] result = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpReadDirRequest(this.ProtocolVersion, this.NextRequestId, handle,                    (response) =>                    {                        result = response.Files;                        wait.Set();                    },                    (response) =>                    {                        if (response.StatusCode != StatusCodes.Eof)                        {                            exception = this.GetSftpException(response);                        }                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._operationTimeout);            }            if (exception != null)            {                throw exception;            }            return result;        }        /// <summary>        /// Performs SSH_FXP_REMOVE request.        /// </summary>        /// <param name="path">The path.</param>        internal void RequestRemove(string path)        {            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpRemoveRequest(this.ProtocolVersion, this.NextRequestId, path, this.Encoding,                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._operationTimeout);            }            if (exception != null)            {                throw exception;            }        }        /// <summary>        /// Performs SSH_FXP_MKDIR request.        /// </summary>        /// <param name="path">The path.</param>        internal void RequestMkDir(string path)        {            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpMkDirRequest(this.ProtocolVersion, this.NextRequestId, path, this.Encoding,                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._operationTimeout);            }            if (exception != null)            {                throw exception;            }        }        /// <summary>        /// Performs SSH_FXP_RMDIR request.        /// </summary>        /// <param name="path">The path.</param>        internal void RequestRmDir(string path)        {            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpRmDirRequest(this.ProtocolVersion, this.NextRequestId, path, this.Encoding,                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._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(this.ProtocolVersion, this.NextRequestId, path, this.Encoding,                    (response) =>                    {                        result = response.Files;                        wait.Set();                    },                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._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(this.ProtocolVersion, this.NextRequestId, path, this.Encoding,                    (response) =>                    {                        attributes = response.Attributes;                        wait.Set();                    },                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._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>        internal void RequestRename(string oldPath, string newPath)        {            if (this.ProtocolVersion < 2)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_RENAME operation is not supported in {0} version that server operates in.", this.ProtocolVersion));            }            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpRenameRequest(this.ProtocolVersion, this.NextRequestId, oldPath, newPath, this.Encoding,                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._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 (this.ProtocolVersion < 3)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_READLINK operation is not supported in {0} version that server operates in.", this.ProtocolVersion));            }            SshException exception = null;            KeyValuePair<string, SftpFileAttributes>[] result = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpReadLinkRequest(this.ProtocolVersion, this.NextRequestId, path, this.Encoding,                    (response) =>                    {                        result = response.Files;                        wait.Set();                    },                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._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>        internal void RequestSymLink(string linkpath, string targetpath)        {            if (this.ProtocolVersion < 3)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_SYMLINK operation is not supported in {0} version that server operates in.", this.ProtocolVersion));            }            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new SftpSymLinkRequest(this.ProtocolVersion, this.NextRequestId, linkpath, targetpath, this.Encoding,                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                this.SendRequest(request);                this.WaitOnHandle(wait, this._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>        internal void RequestPosixRename(string oldPath, string newPath)        {            if (this.ProtocolVersion < 3)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", this.ProtocolVersion));            }            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new PosixRenameRequest(this.ProtocolVersion, this.NextRequestId, oldPath, newPath, this.Encoding,                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                if (!this._supportedExtensions.ContainsKey(request.Name))                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));                this.SendRequest(request);                this.WaitOnHandle(wait, this._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>        internal SftpFileSytemInformation RequestStatVfs(string path, bool nullOnError = false)        {            if (this.ProtocolVersion < 3)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", this.ProtocolVersion));            }            SshException exception = null;            SftpFileSytemInformation information = null;            using (var wait = new AutoResetEvent(false))            {                var request = new StatVfsRequest(this.ProtocolVersion, this.NextRequestId, path, this.Encoding,                    (response) =>                    {                        information = response.GetReply<StatVfsReplyInfo>().Information;                        wait.Set();                    },                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                if (!this._supportedExtensions.ContainsKey(request.Name))                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));                this.SendRequest(request);                this.WaitOnHandle(wait, this._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 (this.ProtocolVersion < 3)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", this.ProtocolVersion));            }            SshException exception = null;            SftpFileSytemInformation information = null;            using (var wait = new AutoResetEvent(false))            {                var request = new FStatVfsRequest(this.ProtocolVersion, this.NextRequestId, handle,                    (response) =>                    {                        information = response.GetReply<StatVfsReplyInfo>().Information;                        wait.Set();                    },                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                if (!this._supportedExtensions.ContainsKey(request.Name))                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));                this.SendRequest(request);                this.WaitOnHandle(wait, this._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 (this.ProtocolVersion < 3)            {                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", this.ProtocolVersion));            }            SshException exception = null;            using (var wait = new AutoResetEvent(false))            {                var request = new HardLinkRequest(this.ProtocolVersion, this.NextRequestId, oldPath, newPath,                    (response) =>                    {                        exception = this.GetSftpException(response);                        wait.Set();                    });                if (!this._supportedExtensions.ContainsKey(request.Name))                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));                this.SendRequest(request);                this.WaitOnHandle(wait, this._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>        internal 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>        internal 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);            }            else if (response.StatusCode == StatusCodes.NoSuchFile)            {                return new SftpPathNotFoundException(response.ErrorMessage);            }            else            {                return new SshException(response.ErrorMessage);            }        }        private void HandleResponse(SftpResponse response)        {            SftpRequest request;            lock (this._requests)            {                this._requests.TryGetValue(response.ResponseId, out request);                if (request != null)                {                    this._requests.Remove(response.ResponseId);                }            }            if (request == null)                throw new InvalidOperationException("Invalid response.");            request.Complete(response);        }    }}
 |