| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150 | 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    {        private const int MaximumSupportedVersion = 3;        private const int MinimumSupportedVersion = 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 _requestId));#endif            }        }        public SftpSession(ISession session, TimeSpan operationTimeout, Encoding encoding)            : base(session, "sftp", operationTimeout, encoding)        {        }        public void ChangeDirectory(string path)        {            var fullPath = GetCanonicalPath(path);            var handle = RequestOpenDir(fullPath);            RequestClose(handle);            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);            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 = 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(uint dataTypeCode, 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;                }                _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);                //  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 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(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>        internal 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>        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(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>        /// <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(ProtocolVersion, NextRequestId, handle, offset, data,                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>        internal 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>        internal 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>        internal 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>        internal 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>        internal 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>        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(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>        internal 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>        internal 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>        internal 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>        internal 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>        internal 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>        internal 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>        internal 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>        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);            }            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);        }    }}
 |