< Summary

Information
Class: Renci.SshNet.Sftp.SftpSession
Assembly: Renci.SshNet
File(s): \home\appveyor\projects\ssh-net\src\Renci.SshNet\Sftp\SftpSession.cs
Line coverage
64%
Covered lines: 805
Uncovered lines: 441
Coverable lines: 1246
Total lines: 2313
Line coverage: 64.6%
Branch coverage
50%
Covered branches: 116
Total branches: 231
Branch coverage: 50.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
.ctor(...)100%1100%
get_WorkingDirectory()100%1100%
get_ProtocolVersion()100%1100%
get_NextRequestId()100%1100%
ChangeDirectory(...)100%1100%
SendMessage(...)100%1100%
GetCanonicalPath(...)75%2087.17%
GetCanonicalPathAsync()15%2030.76%
CreateFileReader(...)100%1100%
GetFullRemotePath(...)87.5%878.57%
OnChannelOpen()50%275%
OnDataReceived(...)90%2092.85%
TryLoadSftpMessage(...)100%278.94%
Dispose(...)100%4100%
SendRequest(...)100%1100%
RequestOpen(...)100%4100%
RequestOpenAsync()100%1100%
BeginOpen(...)100%1100%
EndOpen(...)50%660%
RequestClose(...)50%289.47%
RequestCloseAsync()100%1100%
BeginClose(...)100%1100%
EndClose(...)50%663.15%
BeginRead(...)50%288%
EndRead(...)50%660%
RequestRead(...)50%486.11%
RequestReadAsync()100%10%
RequestWrite(...)87.5%892.85%
RequestWriteAsync()100%1100%
RequestLStat(...)100%2100%
BeginLStat(...)100%188.23%
EndLStat(...)66.66%673.33%
RequestFStat(...)75%480.76%
RequestFStatAsync()100%10%
RequestSetStat(...)50%290.47%
RequestFSetStat(...)50%290%
RequestOpenDir(...)75%4100%
RequestOpenDirAsync()100%1100%
RequestReadDir(...)50%483.33%
RequestReadDirAsync()100%1100%
RequestRemove(...)50%290%
RequestRemoveAsync()100%10%
RequestMkDir(...)100%2100%
RequestRmDir(...)100%2100%
RequestRealPath(...)75%492.59%
RequestRealPathAsync()100%1100%
BeginRealPath(...)100%10%
EndRealPath(...)0%60%
RequestStat(...)0%40%
BeginStat(...)100%10%
EndStat(...)0%60%
RequestRename(...)50%483.33%
RequestRenameAsync()100%1100%
RequestReadLink(...)0%60%
RequestSymLink(...)0%40%
RequestPosixRename(...)0%60%
RequestStatVfs(...)50%872.72%
RequestStatVfsAsync()0%20%
RequestFStatVfs(...)0%80%
HardLink(...)0%60%
CalculateOptimalReadLength(...)100%1100%
CalculateOptimalWriteLength(...)100%1100%
GetSftpException(...)100%5100%
HandleResponse(...)75%485.71%

File(s)

\home\appveyor\projects\ssh-net\src\Renci.SshNet\Sftp\SftpSession.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.Text;
 5using System.Threading;
 6using System.Threading.Tasks;
 7
 8using Renci.SshNet.Common;
 9using Renci.SshNet.Sftp.Requests;
 10using Renci.SshNet.Sftp.Responses;
 11
 12namespace Renci.SshNet.Sftp
 13{
 14    /// <summary>
 15    /// Represents an SFTP session.
 16    /// </summary>
 17    internal sealed class SftpSession : SubsystemSession, ISftpSession
 18    {
 19        internal const int MaximumSupportedVersion = 3;
 20        private const int MinimumSupportedVersion = 0;
 21
 64222        private readonly Dictionary<uint, SftpRequest> _requests = new Dictionary<uint, SftpRequest>();
 23        private readonly ISftpResponseFactory _sftpResponseFactory;
 64224        private readonly List<byte> _data = new List<byte>(32 * 1024);
 25        private readonly Encoding _encoding;
 64226        private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(initialState: false);
 27        private IDictionary<string, string> _supportedExtensions;
 28
 29        /// <summary>
 30        /// Gets the remote working directory.
 31        /// </summary>
 32        /// <value>
 33        /// The remote working directory.
 34        /// </value>
 4155635        public string WorkingDirectory { get; private set; }
 36
 37        /// <summary>
 38        /// Gets the SFTP protocol version.
 39        /// </summary>
 40        /// <value>
 41        /// The SFTP protocol version.
 42        /// </value>
 6497043        public uint ProtocolVersion { get; private set; }
 44
 45        private long _requestId;
 46
 47        /// <summary>
 48        /// Gets the next request id for sftp session.
 49        /// </summary>
 50        public uint NextRequestId
 51        {
 52            get
 3152153            {
 3152154                return (uint) Interlocked.Increment(ref _requestId);
 3152155            }
 56        }
 57
 58        /// <summary>
 59        /// Initializes a new instance of the <see cref="SftpSession"/> class.
 60        /// </summary>
 61        /// <param name="session">The SSH session.</param>
 62        /// <param name="operationTimeout">The operation timeout.</param>
 63        /// <param name="encoding">The character encoding to use.</param>
 64        /// <param name="sftpResponseFactory">The factory to create SFTP responses.</param>
 65        public SftpSession(ISession session, int operationTimeout, Encoding encoding, ISftpResponseFactory sftpResponseF
 64266            : base(session, "sftp", operationTimeout)
 64267        {
 64268            _encoding = encoding;
 64269            _sftpResponseFactory = sftpResponseFactory;
 64270        }
 71
 72        /// <summary>
 73        /// Changes the current working directory to the specified path.
 74        /// </summary>
 75        /// <param name="path">The new working directory.</param>
 76        public void ChangeDirectory(string path)
 2077        {
 2078            var fullPath = GetCanonicalPath(path);
 2079            var handle = RequestOpenDir(fullPath);
 80
 1581            RequestClose(handle);
 1582            WorkingDirectory = fullPath;
 1583        }
 84
 85        internal void SendMessage(SftpMessage sftpMessage)
 3216286        {
 3216287            var data = sftpMessage.GetBytes();
 3216288            SendData(data);
 3216089        }
 90
 91        /// <summary>
 92        /// Resolves a given path into an absolute path on the server.
 93        /// </summary>
 94        /// <param name="path">The path to resolve.</param>
 95        /// <returns>
 96        /// The absolute path.
 97        /// </returns>
 98        public string GetCanonicalPath(string path)
 1088599        {
 10885100            var fullPath = GetFullRemotePath(path);
 101
 10885102            var canonizedPath = string.Empty;
 103
 10885104            var realPathFiles = RequestRealPath(fullPath, nullOnError: true);
 105
 10882106            if (realPathFiles != null)
 10793107            {
 10793108                canonizedPath = realPathFiles[0].Key;
 10793109            }
 110
 10882111            if (!string.IsNullOrEmpty(canonizedPath))
 10793112            {
 10793113                return canonizedPath;
 114            }
 115
 116            // Check for special cases
 89117            if (fullPath.EndsWith("/.", StringComparison.OrdinalIgnoreCase) ||
 89118                fullPath.EndsWith("/..", StringComparison.OrdinalIgnoreCase) ||
 89119                fullPath.Equals("/", StringComparison.OrdinalIgnoreCase) ||
 89120#if NET || NETSTANDARD2_1_OR_GREATER
 89121                fullPath.IndexOf('/', StringComparison.OrdinalIgnoreCase) < 0)
 0122#else
 0123                fullPath.IndexOf('/') < 0)
 124#endif // NET || NETSTANDARD2_1_OR_GREATER
 0125            {
 0126                return fullPath;
 127            }
 128
 89129            var pathParts = fullPath.Split('/');
 130
 131#if NET || NETSTANDARD2_1_OR_GREATER
 89132            var partialFullPath = string.Join('/', pathParts, 0, pathParts.Length - 1);
 133#else
 0134            var partialFullPath = string.Join("/", pathParts, 0, pathParts.Length - 1);
 135#endif // NET || NETSTANDARD2_1_OR_GREATER
 136
 89137            if (string.IsNullOrEmpty(partialFullPath))
 0138            {
 0139                partialFullPath = "/";
 0140            }
 141
 89142            realPathFiles = RequestRealPath(partialFullPath, nullOnError: true);
 143
 89144            if (realPathFiles != null)
 81145            {
 81146                canonizedPath = realPathFiles[0].Key;
 81147            }
 148
 89149            if (string.IsNullOrEmpty(canonizedPath))
 8150            {
 8151                return fullPath;
 152            }
 153
 81154            var slash = string.Empty;
 81155            if (canonizedPath[canonizedPath.Length - 1] != '/')
 81156            {
 81157                slash = "/";
 81158            }
 159
 81160            return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", canonizedPath, slash, pathParts[pathParts.Le
 10882161        }
 162
 163        /// <summary>
 164        /// Asynchronously resolves a given path into an absolute path on the server.
 165        /// </summary>
 166        /// <param name="path">The path to resolve.</param>
 167        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 168        /// <returns>
 169        /// A task representing the absolute path.
 170        /// </returns>
 171        public async Task<string> GetCanonicalPathAsync(string path, CancellationToken cancellationToken)
 4172        {
 4173            var fullPath = GetFullRemotePath(path);
 174
 4175            var canonizedPath = string.Empty;
 4176            var realPathFiles = await RequestRealPathAsync(fullPath, nullOnError: true, cancellationToken).ConfigureAwai
 4177            if (realPathFiles != null)
 4178            {
 4179                canonizedPath = realPathFiles[0].Key;
 4180            }
 181
 4182            if (!string.IsNullOrEmpty(canonizedPath))
 4183            {
 4184                return canonizedPath;
 185            }
 186
 187            // Check for special cases
 0188            if (fullPath.EndsWith("/.", StringComparison.Ordinal) ||
 0189                fullPath.EndsWith("/..", StringComparison.Ordinal) ||
 0190                fullPath.Equals("/", StringComparison.Ordinal) ||
 0191#if NET || NETSTANDARD2_1_OR_GREATER
 0192                fullPath.IndexOf('/', StringComparison.Ordinal) < 0)
 0193#else
 0194                fullPath.IndexOf('/') < 0)
 195#endif // NET || NETSTANDARD2_1_OR_GREATER
 0196            {
 0197                return fullPath;
 198            }
 199
 0200            var pathParts = fullPath.Split('/');
 201
 202#if NET || NETSTANDARD2_1_OR_GREATER
 0203            var partialFullPath = string.Join('/', pathParts);
 204#else
 0205            var partialFullPath = string.Join("/", pathParts);
 206#endif // NET || NETSTANDARD2_1_OR_GREATER
 207
 0208            if (string.IsNullOrEmpty(partialFullPath))
 0209            {
 0210                partialFullPath = "/";
 0211            }
 212
 0213            realPathFiles = await RequestRealPathAsync(partialFullPath, nullOnError: true, cancellationToken).ConfigureA
 214
 0215            if (realPathFiles != null)
 0216            {
 0217                canonizedPath = realPathFiles[0].Key;
 0218            }
 219
 0220            if (string.IsNullOrEmpty(canonizedPath))
 0221            {
 0222                return fullPath;
 223            }
 224
 0225            var slash = string.Empty;
 0226            if (canonizedPath[canonizedPath.Length - 1] != '/')
 0227            {
 0228                slash = "/";
 0229            }
 230
 0231            return canonizedPath + slash + pathParts[pathParts.Length - 1];
 4232        }
 233
 234        /// <summary>
 235        /// Creates an <see cref="ISftpFileReader"/> for reading the content of the file represented by a given <paramre
 236        /// </summary>
 237        /// <param name="handle">The handle of the file to read.</param>
 238        /// <param name="sftpSession">The SFTP session.</param>
 239        /// <param name="chunkSize">The maximum number of bytes to read with each chunk.</param>
 240        /// <param name="maxPendingReads">The maximum number of pending reads.</param>
 241        /// <param name="fileSize">The size of the file or <see langword="null"/> when the size could not be determined.
 242        /// <returns>
 243        /// An <see cref="ISftpFileReader"/> for reading the content of the file represented by the
 244        /// specified <paramref name="handle"/>.
 245        /// </returns>
 246        public ISftpFileReader CreateFileReader(byte[] handle, ISftpSession sftpSession, uint chunkSize, int maxPendingR
 46247        {
 46248            return new SftpFileReader(handle, sftpSession, chunkSize, maxPendingReads, fileSize);
 46249        }
 250
 251        internal string GetFullRemotePath(string path)
 10889252        {
 10889253            var fullPath = path;
 254
 10889255            if (!string.IsNullOrEmpty(path) && path[0] != '/' && WorkingDirectory != null)
 10205256            {
 10205257                if (WorkingDirectory[WorkingDirectory.Length - 1] == '/')
 0258                {
 0259                    fullPath = WorkingDirectory + path;
 0260                }
 261                else
 10205262                {
 10205263                    fullPath = WorkingDirectory + '/' + path;
 10205264                }
 10205265            }
 266
 10889267            return fullPath;
 10889268        }
 269
 270        protected override void OnChannelOpen()
 641271        {
 641272            SendMessage(new SftpInitRequest(MaximumSupportedVersion));
 273
 641274            WaitOnHandle(_sftpVersionConfirmed, OperationTimeout);
 275
 641276            if (ProtocolVersion is > MaximumSupportedVersion or < MinimumSupportedVersion)
 0277            {
 0278                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Server SFTP version {0} is no
 279            }
 280
 281            // Resolve current directory
 641282            WorkingDirectory = RequestRealPath(".")[0].Key;
 641283        }
 284
 285        protected override void OnDataReceived(byte[] data)
 31887286        {
 287            const int packetLengthByteCount = 4;
 288            const int sftpMessageTypeByteCount = 1;
 289            const int minimumChannelDataLength = packetLengthByteCount + sftpMessageTypeByteCount;
 290
 31887291            var offset = 0;
 31887292            var count = data.Length;
 293
 294            // improve performance and reduce GC pressure by not buffering channel data if the received
 295            // chunk contains the complete packet data.
 296            //
 297            // for this, the buffer should be empty and the chunk should contain at least the packet length
 298            // and the type of the SFTP message
 31887299            if (_data.Count == 0)
 31492300            {
 63203301                while (count >= minimumChannelDataLength)
 31729302                {
 303                    // extract packet length
 31729304                    var packetDataLength = data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 |
 31729305                                           data[offset + 3];
 306
 31729307                    var packetTotalLength = packetDataLength + packetLengthByteCount;
 308
 309                    // check if complete packet data (or more) is available
 31729310                    if (count >= packetTotalLength)
 31711311                    {
 312                        // load and process SFTP message
 31711313                        if (!TryLoadSftpMessage(data, offset + packetLengthByteCount, packetDataLength))
 0314                        {
 0315                            return;
 316                        }
 317
 318                        // remove processed bytes from the number of bytes to process as the channel
 319                        // data we received may contain (part of) another message
 31711320                        count -= packetTotalLength;
 321
 322                        // move offset beyond bytes we just processed
 31711323                        offset += packetTotalLength;
 31711324                    }
 325                    else
 18326                    {
 327                        // we don't have a complete message
 18328                        break;
 329                    }
 31711330                }
 331
 332                // check if there is channel data left to process or buffer
 31492333                if (count == 0)
 31474334                {
 31474335                    return;
 336                }
 337
 338                // check if we processed part of the channel data we received
 18339                if (offset > 0)
 10340                {
 341                    // add (remaining) channel data to internal data holder
 10342                    var remainingChannelData = new byte[count];
 10343                    Buffer.BlockCopy(data, offset, remainingChannelData, 0, count);
 10344                    _data.AddRange(remainingChannelData);
 10345                }
 346                else
 8347                {
 348                    // add (remaining) channel data to internal data holder
 8349                    _data.AddRange(data);
 8350                }
 351
 352                // skip further processing as we'll need a new chunk to complete the message
 18353                return;
 354            }
 355
 356            // add (remaining) channel data to internal data holder
 395357            _data.AddRange(data);
 358
 843359            while (_data.Count >= minimumChannelDataLength)
 825360            {
 361                // extract packet length
 825362                var packetDataLength = _data[0] << 24 | _data[1] << 16 | _data[2] << 8 | _data[3];
 363
 825364                var packetTotalLength = packetDataLength + packetLengthByteCount;
 365
 366                // check if complete packet data is available
 825367                if (_data.Count < packetTotalLength)
 377368                {
 369                    // wait for complete message to arrive first
 377370                    break;
 371                }
 372
 373                // create buffer to hold packet data
 448374                var packetData = new byte[packetDataLength];
 375
 376                // copy packet data and bytes for length to array
 448377                _data.CopyTo(packetLengthByteCount, packetData, 0, packetDataLength);
 378
 379                // remove loaded data and bytes for length from _data holder
 448380                if (_data.Count == packetTotalLength)
 18381                {
 382                    // the only buffered data is the data we're processing
 18383                    _data.Clear();
 18384                }
 385                else
 430386                {
 387                    // remove only the data we're processing
 430388                    _data.RemoveRange(0, packetTotalLength);
 430389                }
 390
 391                // load and process SFTP message
 448392                if (!TryLoadSftpMessage(packetData, 0, packetDataLength))
 0393                {
 0394                    break;
 395                }
 448396            }
 31887397        }
 398
 399        private bool TryLoadSftpMessage(byte[] packetData, int offset, int count)
 32159400        {
 401            // Create SFTP message
 32159402            var response = _sftpResponseFactory.Create(ProtocolVersion, packetData[offset], _encoding);
 403
 404            // Load message data into it
 32159405            response.Load(packetData, offset + 1, count - 1);
 406
 407            try
 32159408            {
 32159409                if (response is SftpVersionResponse versionResponse)
 641410                {
 641411                    ProtocolVersion = versionResponse.Version;
 641412                    _supportedExtensions = versionResponse.Extentions;
 413
 641414                    _ = _sftpVersionConfirmed.Set();
 641415                }
 416                else
 31518417                {
 31518418                    HandleResponse(response as SftpResponse);
 31518419                }
 420
 32159421                return true;
 422            }
 0423            catch (Exception exp)
 0424            {
 0425                RaiseError(exp);
 0426                return false;
 427            }
 32159428        }
 429
 430        protected override void Dispose(bool disposing)
 642431        {
 642432            base.Dispose(disposing);
 433
 642434            if (disposing)
 618435            {
 618436                var sftpVersionConfirmed = _sftpVersionConfirmed;
 618437                if (sftpVersionConfirmed != null)
 618438                {
 618439                    _sftpVersionConfirmed = null;
 618440                    sftpVersionConfirmed.Dispose();
 618441                }
 618442            }
 642443        }
 444
 445        private void SendRequest(SftpRequest request)
 31521446        {
 31521447            lock (_requests)
 31521448            {
 31521449                _requests.Add(request.RequestId, request);
 31521450            }
 451
 31521452            SendMessage(request);
 31519453        }
 454
 455        /// <summary>
 456        /// Performs SSH_FXP_OPEN request.
 457        /// </summary>
 458        /// <param name="path">The path.</param>
 459        /// <param name="flags">The flags.</param>
 460        /// <param name="nullOnError">If set to <see langword="true"/> returns <see langword="null"/> instead of throwin
 461        /// <returns>File handle.</returns>
 462        public byte[] RequestOpen(string path, Flags flags, bool nullOnError = false)
 354463        {
 354464            byte[] handle = null;
 354465            SshException exception = null;
 466
 354467            using (var wait = new AutoResetEvent(initialState: false))
 354468            {
 354469                var request = new SftpOpenRequest(ProtocolVersion,
 354470                                                  NextRequestId,
 354471                                                  path,
 354472                                                  _encoding,
 354473                                                  flags,
 354474                                                  response =>
 314475                                                  {
 314476                                                      handle = response.Handle;
 314477                                                      _ = wait.Set();
 314478                                                  },
 354479                                                  response =>
 40480                                                  {
 40481                                                      exception = GetSftpException(response);
 40482                                                      _ = wait.Set();
 394483                                                  });
 484
 354485                SendRequest(request);
 486
 354487                WaitOnHandle(wait, OperationTimeout);
 354488            }
 489
 490#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 354491            if (!nullOnError && exception is not null)
 492#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 31493            {
 31494                throw exception;
 495            }
 496#pragma warning restore CA1508 // Avoid dead conditional code
 497
 323498            return handle;
 323499        }
 500
 501        /// <summary>
 502        /// Asynchronously performs a <c>SSH_FXP_OPEN</c> request.
 503        /// </summary>
 504        /// <param name="path">The path.</param>
 505        /// <param name="flags">The flags.</param>
 506        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 507        /// <returns>
 508        /// A task that represents the asynchronous <c>SSH_FXP_OPEN</c> request. The value of its
 509        /// <see cref="Task{Task}.Result"/> contains the file handle of the specified path.
 510        /// </returns>
 511        public async Task<byte[]> RequestOpenAsync(string path, Flags flags, CancellationToken cancellationToken)
 1512        {
 1513            cancellationToken.ThrowIfCancellationRequested();
 514
 1515            var tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
 516
 517#if NET || NETSTANDARD2_1_OR_GREATER
 1518            await using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>) s).TrySetCanceled(cancellationT
 519#else
 0520            using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>) s).TrySetCanceled(cancellationToken),
 521#endif // NET || NETSTANDARD2_1_OR_GREATER
 1522            {
 1523                SendRequest(new SftpOpenRequest(ProtocolVersion,
 1524                                                    NextRequestId,
 1525                                                    path,
 1526                                                    _encoding,
 1527                                                    flags,
 1528                                                    response => tcs.TrySetResult(response.Handle),
 1529                                                    response => tcs.TrySetException(GetSftpException(response))));
 530
 1531                return await tcs.Task.ConfigureAwait(false);
 532            }
 1533        }
 534
 535        /// <summary>
 536        /// Performs SSH_FXP_OPEN request.
 537        /// </summary>
 538        /// <param name="path">The path.</param>
 539        /// <param name="flags">The flags.</param>
 540        /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginOpen(s
 541        /// <param name="state">An object that contains any additional user-defined data.</param>
 542        /// <returns>
 543        /// A <see cref="SftpOpenAsyncResult"/> that represents the asynchronous call.
 544        /// </returns>
 545        public SftpOpenAsyncResult BeginOpen(string path, Flags flags, AsyncCallback callback, object state)
 62546        {
 62547            var asyncResult = new SftpOpenAsyncResult(callback, state);
 548
 62549            var request = new SftpOpenRequest(ProtocolVersion,
 62550                                              NextRequestId,
 62551                                              path,
 62552                                              _encoding,
 62553                                              flags,
 62554                                              response =>
 58555                                              {
 58556                                                  asyncResult.SetAsCompleted(response.Handle, completedSynchronously: fa
 58557                                              },
 62558                                              response =>
 4559                                              {
 4560                                                  asyncResult.SetAsCompleted(GetSftpException(response), completedSynchr
 66561                                              });
 562
 62563            SendRequest(request);
 564
 62565            return asyncResult;
 62566        }
 567
 568        /// <summary>
 569        /// Handles the end of an asynchronous open.
 570        /// </summary>
 571        /// <param name="asyncResult">An <see cref="SftpOpenAsyncResult"/> that represents an asynchronous call.</param>
 572        /// <returns>
 573        /// A <see cref="byte"/> array representing a file handle.
 574        /// </returns>
 575        /// <remarks>
 576        /// If all available data has been read, the <see cref="EndOpen(SftpOpenAsyncResult)"/> method completes
 577        /// immediately and returns zero bytes.
 578        /// </remarks>
 579        /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <see langword="null"/>.</exception
 580        public byte[] EndOpen(SftpOpenAsyncResult asyncResult)
 62581        {
 62582            if (asyncResult is null)
 0583            {
 0584                throw new ArgumentNullException(nameof(asyncResult));
 585            }
 586
 62587            if (asyncResult.EndInvokeCalled)
 0588            {
 0589                throw new InvalidOperationException("EndOpen has already been called.");
 590            }
 591
 62592            if (asyncResult.IsCompleted)
 12593            {
 12594                return asyncResult.EndInvoke();
 595            }
 596
 50597            using (var waitHandle = asyncResult.AsyncWaitHandle)
 50598            {
 50599                WaitOnHandle(waitHandle, OperationTimeout);
 50600                return asyncResult.EndInvoke();
 601            }
 58602        }
 603
 604        /// <summary>
 605        /// Performs SSH_FXP_CLOSE request.
 606        /// </summary>
 607        /// <param name="handle">The handle.</param>
 608        public void RequestClose(byte[] handle)
 342609        {
 342610            SshException exception = null;
 611
 342612            using (var wait = new AutoResetEvent(initialState: false))
 342613            {
 342614                var request = new SftpCloseRequest(ProtocolVersion,
 342615                                                   NextRequestId,
 342616                                                   handle,
 342617                                                   response =>
 342618                                                   {
 342619                                                       exception = GetSftpException(response);
 342620                                                       _ = wait.Set();
 684621                                                   });
 622
 342623                SendRequest(request);
 624
 342625                WaitOnHandle(wait, OperationTimeout);
 342626            }
 627
 628#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 342629            if (exception is not null)
 630#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 0631            {
 0632                throw exception;
 633            }
 342634        }
 635
 636        /// <summary>
 637        /// Performs a <c>SSH_FXP_CLOSE</c> request.
 638        /// </summary>
 639        /// <param name="handle">The handle.</param>
 640        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 641        /// <returns>
 642        /// A task that represents the asynchronous <c>SSH_FXP_CLOSE</c> request.
 643        /// </returns>
 644        public async Task RequestCloseAsync(byte[] handle, CancellationToken cancellationToken)
 2645        {
 2646            var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
 647
 2648            SendRequest(new SftpCloseRequest(ProtocolVersion,
 2649                                             NextRequestId,
 2650                                             handle,
 2651                                             response =>
 2652                                             {
 2653                                                 if (response.StatusCode == StatusCodes.Ok)
 2654                                                 {
 2655                                                     _ = tcs.TrySetResult(true);
 2656                                                 }
 2657                                                 else
 0658                                                 {
 0659                                                     _ = tcs.TrySetException(GetSftpException(response));
 0660                                                 }
 4661                                             }));
 662
 663            // Only check for cancellation after the SftpCloseRequest was sent
 2664            cancellationToken.ThrowIfCancellationRequested();
 665
 666#if NET || NETSTANDARD2_1_OR_GREATER
 2667            await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).TrySetCanceled(cancellationTok
 668#else
 0669            using (cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).TrySetCanceled(cancellationToken), t
 670#endif // NET || NETSTANDARD2_1_OR_GREATER
 2671            {
 2672                _ = await tcs.Task.ConfigureAwait(false);
 2673            }
 2674        }
 675
 676        /// <summary>
 677        /// Performs SSH_FXP_CLOSE request.
 678        /// </summary>
 679        /// <param name="handle">The handle.</param>
 680        /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginClose(
 681        /// <param name="state">An object that contains any additional user-defined data.</param>
 682        /// <returns>
 683        /// A <see cref="SftpCloseAsyncResult"/> that represents the asynchronous call.
 684        /// </returns>
 685        public SftpCloseAsyncResult BeginClose(byte[] handle, AsyncCallback callback, object state)
 46686        {
 46687            var asyncResult = new SftpCloseAsyncResult(callback, state);
 688
 46689            var request = new SftpCloseRequest(ProtocolVersion,
 46690                                               NextRequestId,
 46691                                               handle,
 46692                                               response =>
 46693                                               {
 46694                                                   asyncResult.SetAsCompleted(GetSftpException(response), completedSynch
 92695                                               });
 46696            SendRequest(request);
 697
 46698            return asyncResult;
 46699        }
 700
 701        /// <summary>
 702        /// Handles the end of an asynchronous close.
 703        /// </summary>
 704        /// <param name="asyncResult">An <see cref="SftpCloseAsyncResult"/> that represents an asynchronous call.</param
 705        /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <see langword="null"/>.</exception
 706        public void EndClose(SftpCloseAsyncResult asyncResult)
 46707        {
 46708            if (asyncResult is null)
 0709            {
 0710                throw new ArgumentNullException(nameof(asyncResult));
 711            }
 712
 46713            if (asyncResult.EndInvokeCalled)
 0714            {
 0715                throw new InvalidOperationException("EndClose has already been called.");
 716            }
 717
 46718            if (asyncResult.IsCompleted)
 0719            {
 0720                asyncResult.EndInvoke();
 0721            }
 722            else
 46723            {
 46724                using (var waitHandle = asyncResult.AsyncWaitHandle)
 46725                {
 46726                    WaitOnHandle(waitHandle, OperationTimeout);
 46727                    asyncResult.EndInvoke();
 46728                }
 46729            }
 46730        }
 731
 732        /// <summary>
 733        /// Begins an asynchronous read using a SSH_FXP_READ request.
 734        /// </summary>
 735        /// <param name="handle">The handle to the file to read from.</param>
 736        /// <param name="offset">The offset in the file to start reading from.</param>
 737        /// <param name="length">The number of bytes to read.</param>
 738        /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginRead(b
 739        /// <param name="state">An object that contains any additional user-defined data.</param>
 740        /// <returns>
 741        /// A <see cref="SftpReadAsyncResult"/> that represents the asynchronous call.
 742        /// </returns>
 743        public SftpReadAsyncResult BeginRead(byte[] handle, ulong offset, uint length, AsyncCallback callback, object st
 3705744        {
 3705745            var asyncResult = new SftpReadAsyncResult(callback, state);
 746
 3705747            var request = new SftpReadRequest(ProtocolVersion,
 3705748                                              NextRequestId,
 3705749                                              handle,
 3705750                                              offset,
 3705751                                              length,
 3705752                                              response =>
 3659753                                              {
 3659754                                                  asyncResult.SetAsCompleted(response.Data, completedSynchronously: fals
 3659755                                              },
 3705756                                              response =>
 46757                                              {
 46758                                                  if (response.StatusCode != StatusCodes.Eof)
 0759                                                  {
 0760                                                      asyncResult.SetAsCompleted(GetSftpException(response), completedSy
 0761                                                  }
 3705762                                                  else
 46763                                                  {
 46764                                                      asyncResult.SetAsCompleted(Array.Empty<byte>(), completedSynchrono
 46765                                                  }
 3751766                                              });
 3705767            SendRequest(request);
 768
 3705769            return asyncResult;
 3705770        }
 771
 772        /// <summary>
 773        /// Handles the end of an asynchronous read.
 774        /// </summary>
 775        /// <param name="asyncResult">An <see cref="SftpReadAsyncResult"/> that represents an asynchronous call.</param>
 776        /// <returns>
 777        /// A <see cref="byte"/> array representing the data read.
 778        /// </returns>
 779        /// <remarks>
 780        /// If all available data has been read, the <see cref="EndRead(SftpReadAsyncResult)"/> method completes
 781        /// immediately and returns zero bytes.
 782        /// </remarks>
 783        /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <see langword="null"/>.</exception
 784        public byte[] EndRead(SftpReadAsyncResult asyncResult)
 56785        {
 56786            if (asyncResult is null)
 0787            {
 0788                throw new ArgumentNullException(nameof(asyncResult));
 789            }
 790
 56791            if (asyncResult.EndInvokeCalled)
 0792            {
 0793                throw new InvalidOperationException("EndRead has already been called.");
 794            }
 795
 56796            if (asyncResult.IsCompleted)
 12797            {
 12798                return asyncResult.EndInvoke();
 799            }
 800
 44801            using (var waitHandle = asyncResult.AsyncWaitHandle)
 44802            {
 44803                WaitOnHandle(waitHandle, OperationTimeout);
 44804                return asyncResult.EndInvoke();
 805            }
 56806        }
 807
 808        /// <summary>
 809        /// Performs SSH_FXP_READ request.
 810        /// </summary>
 811        /// <param name="handle">The handle.</param>
 812        /// <param name="offset">The offset.</param>
 813        /// <param name="length">The length.</param>
 814        /// <returns>
 815        /// The data that was read, or an empty array when the end of the file was reached.
 816        /// </returns>
 817        public byte[] RequestRead(byte[] handle, ulong offset, uint length)
 375818        {
 375819            SshException exception = null;
 820
 375821            byte[] data = null;
 822
 375823            using (var wait = new AutoResetEvent(initialState: false))
 375824            {
 375825                var request = new SftpReadRequest(ProtocolVersion,
 375826                                                  NextRequestId,
 375827                                                  handle,
 375828                                                  offset,
 375829                                                  length,
 375830                                                  response =>
 308831                                                  {
 308832                                                      data = response.Data;
 308833                                                      _ = wait.Set();
 308834                                                  },
 375835                                                  response =>
 67836                                                  {
 67837                                                      if (response.StatusCode != StatusCodes.Eof)
 0838                                                      {
 0839                                                          exception = GetSftpException(response);
 0840                                                      }
 375841                                                      else
 67842                                                      {
 67843                                                          data = Array.Empty<byte>();
 67844                                                      }
 375845
 67846                                                      _ = wait.Set();
 442847                                                  });
 848
 375849                SendRequest(request);
 850
 375851                WaitOnHandle(wait, OperationTimeout);
 375852            }
 853
 854#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 375855            if (exception is not null)
 856#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 0857            {
 0858                throw exception;
 859            }
 860
 375861            return data;
 375862        }
 863
 864        /// <summary>
 865        /// Asynchronously performs a <c>SSH_FXP_READ</c> request.
 866        /// </summary>
 867        /// <param name="handle">The handle to the file to read from.</param>
 868        /// <param name="offset">The offset in the file to start reading from.</param>
 869        /// <param name="length">The number of bytes to read.</param>
 870        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 871        /// <returns>
 872        /// A task that represents the asynchronous <c>SSH_FXP_READ</c> request. The value of
 873        /// its <see cref="Task{Task}.Result"/> contains the data read from the file, or an empty
 874        /// array when the end of the file is reached.
 875        /// </returns>
 876        public async Task<byte[]> RequestReadAsync(byte[] handle, ulong offset, uint length, CancellationToken cancellat
 0877        {
 0878            cancellationToken.ThrowIfCancellationRequested();
 879
 0880            var tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
 881
 882#if NET || NETSTANDARD2_1_OR_GREATER
 0883            await using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>) s).TrySetCanceled(cancellationT
 884#else
 0885            using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>) s).TrySetCanceled(cancellationToken),
 886#endif // NET || NETSTANDARD2_1_OR_GREATER
 0887            {
 0888                SendRequest(new SftpReadRequest(ProtocolVersion,
 0889                                                NextRequestId,
 0890                                                handle,
 0891                                                offset,
 0892                                                length,
 0893                                                response => tcs.TrySetResult(response.Data),
 0894                                                response =>
 0895                                                {
 0896                                                    if (response.StatusCode == StatusCodes.Eof)
 0897                                                    {
 0898                                                        _ = tcs.TrySetResult(Array.Empty<byte>());
 0899                                                    }
 0900                                                    else
 0901                                                    {
 0902                                                        _ = tcs.TrySetException(GetSftpException(response));
 0903                                                    }
 0904                                                }));
 905
 0906                return await tcs.Task.ConfigureAwait(false);
 907            }
 0908        }
 909
 910        /// <summary>
 911        /// Performs SSH_FXP_WRITE request.
 912        /// </summary>
 913        /// <param name="handle">The handle.</param>
 914        /// <param name="serverOffset">The the zero-based offset (in bytes) relative to the beginning of the file that t
 915        /// <param name="data">The buffer holding the data to write.</param>
 916        /// <param name="offset">the zero-based offset in <paramref name="data" /> at which to begin taking bytes to wri
 917        /// <param name="length">The length (in bytes) of the data to write.</param>
 918        /// <param name="wait">The wait event handle if needed.</param>
 919        /// <param name="writeCompleted">The callback to invoke when the write has completed.</param>
 920        public void RequestWrite(byte[] handle,
 921                                 ulong serverOffset,
 922                                 byte[] data,
 923                                 int offset,
 924                                 int length,
 925                                 AutoResetEvent wait,
 926                                 Action<SftpStatusResponse> writeCompleted = null)
 3898927        {
 3898928            SshException exception = null;
 929
 3898930            var request = new SftpWriteRequest(ProtocolVersion,
 3898931                                               NextRequestId,
 3898932                                               handle,
 3898933                                               serverOffset,
 3898934                                               data,
 3898935                                               offset,
 3898936                                               length,
 3898937                                               response =>
 3898938                                               {
 3898939                                                   writeCompleted?.Invoke(response);
 3898940
 3898941                                                   exception = GetSftpException(response);
 3898942                                                   if (wait != null)
 157943                                                   {
 157944                                                       _ = wait.Set();
 157945                                                   }
 7796946                                               });
 947
 3898948            SendRequest(request);
 949
 3898950            if (wait is not null)
 157951            {
 157952                WaitOnHandle(wait, OperationTimeout);
 157953            }
 954
 955#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 3898956            if (exception is not null)
 957#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 0958            {
 0959                throw exception;
 960            }
 3898961        }
 962
 963        /// <summary>
 964        /// Asynchronouly performs a <c>SSH_FXP_WRITE</c> request.
 965        /// </summary>
 966        /// <param name="handle">The handle.</param>
 967        /// <param name="serverOffset">The the zero-based offset (in bytes) relative to the beginning of the file that t
 968        /// <param name="data">The buffer holding the data to write.</param>
 969        /// <param name="offset">the zero-based offset in <paramref name="data" /> at which to begin taking bytes to wri
 970        /// <param name="length">The length (in bytes) of the data to write.</param>
 971        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 972        /// <returns>
 973        /// A task that represents the asynchronous <c>SSH_FXP_WRITE</c> request.
 974        /// </returns>
 975        public async Task RequestWriteAsync(byte[] handle, ulong serverOffset, byte[] data, int offset, int length, Canc
 32976        {
 32977            cancellationToken.ThrowIfCancellationRequested();
 978
 32979            var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
 980
 981#if NET || NETSTANDARD2_1_OR_GREATER
 32982            await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).TrySetCanceled(cancellationTok
 983#else
 0984            using (cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).TrySetCanceled(cancellationToken), t
 985#endif // NET || NETSTANDARD2_1_OR_GREATER
 32986            {
 32987                SendRequest(new SftpWriteRequest(ProtocolVersion,
 32988                                                 NextRequestId,
 32989                                                 handle,
 32990                                                 serverOffset,
 32991                                                 data,
 32992                                                 offset,
 32993                                                 length,
 32994                                                 response =>
 32995                                                 {
 32996                                                     if (response.StatusCode == StatusCodes.Ok)
 32997                                                     {
 32998                                                         _ = tcs.TrySetResult(true);
 32999                                                     }
 321000                                                     else
 01001                                                     {
 01002                                                         _ = tcs.TrySetException(GetSftpException(response));
 01003                                                     }
 641004                                                 }));
 1005
 321006                _ = await tcs.Task.ConfigureAwait(false);
 321007            }
 321008        }
 1009
 1010        /// <summary>
 1011        /// Performs SSH_FXP_LSTAT request.
 1012        /// </summary>
 1013        /// <param name="path">The path.</param>
 1014        /// <returns>
 1015        /// File attributes.
 1016        /// </returns>
 1017        public SftpFileAttributes RequestLStat(string path)
 5051018        {
 5051019            SshException exception = null;
 1020
 5051021            SftpFileAttributes attributes = null;
 5051022            using (var wait = new AutoResetEvent(initialState: false))
 5051023            {
 5051024                var request = new SftpLStatRequest(ProtocolVersion,
 5051025                                                   NextRequestId,
 5051026                                                   path,
 5051027                                                   _encoding,
 5051028                                                   response =>
 2501029                                                   {
 2501030                                                       attributes = response.Attributes;
 2501031                                                       _ = wait.Set();
 2501032                                                   },
 5051033                                                   response =>
 2551034                                                   {
 2551035                                                       exception = GetSftpException(response);
 2551036                                                       _ = wait.Set();
 7601037                                                   });
 1038
 5051039                SendRequest(request);
 1040
 5051041                WaitOnHandle(wait, OperationTimeout);
 5051042            }
 1043
 1044#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 5051045            if (exception is not null)
 1046#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 2551047            {
 2551048                throw exception;
 1049            }
 1050
 2501051            return attributes;
 2501052        }
 1053
 1054        /// <summary>
 1055        /// Performs SSH_FXP_LSTAT request.
 1056        /// </summary>
 1057        /// <param name="path">The path.</param>
 1058        /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginLStat(
 1059        /// <param name="state">An object that contains any additional user-defined data.</param>
 1060        /// <returns>
 1061        /// A <see cref="SFtpStatAsyncResult"/> that represents the asynchronous call.
 1062        /// </returns>
 1063        public SFtpStatAsyncResult BeginLStat(string path, AsyncCallback callback, object state)
 461064        {
 461065            var asyncResult = new SFtpStatAsyncResult(callback, state);
 1066
 461067            var request = new SftpLStatRequest(ProtocolVersion,
 461068                                               NextRequestId,
 461069                                               path,
 461070                                               _encoding,
 461071                                               response =>
 461072                                               {
 461073                                                   asyncResult.SetAsCompleted(response.Attributes, completedSynchronousl
 461074                                               },
 461075                                               response =>
 01076                                               {
 01077                                                   asyncResult.SetAsCompleted(GetSftpException(response), completedSynch
 461078                                               });
 461079            SendRequest(request);
 1080
 461081            return asyncResult;
 461082        }
 1083
 1084        /// <summary>
 1085        /// Handles the end of an asynchronous SSH_FXP_LSTAT request.
 1086        /// </summary>
 1087        /// <param name="asyncResult">An <see cref="SFtpStatAsyncResult"/> that represents an asynchronous call.</param>
 1088        /// <returns>
 1089        /// The file attributes.
 1090        /// </returns>
 1091        /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <see langword="null"/>.</exception
 1092        public SftpFileAttributes EndLStat(SFtpStatAsyncResult asyncResult)
 461093        {
 461094            if (asyncResult is null)
 01095            {
 01096                throw new ArgumentNullException(nameof(asyncResult));
 1097            }
 1098
 461099            if (asyncResult.EndInvokeCalled)
 01100            {
 01101                throw new InvalidOperationException("EndLStat has already been called.");
 1102            }
 1103
 461104            if (asyncResult.IsCompleted)
 11105            {
 11106                return asyncResult.EndInvoke();
 1107            }
 1108
 451109            using (var waitHandle = asyncResult.AsyncWaitHandle)
 451110            {
 451111                WaitOnHandle(waitHandle, OperationTimeout);
 451112                return asyncResult.EndInvoke();
 1113            }
 461114        }
 1115
 1116        /// <summary>
 1117        /// Performs SSH_FXP_FSTAT request.
 1118        /// </summary>
 1119        /// <param name="handle">The handle.</param>
 1120        /// <param name="nullOnError">If set to <see langword="true"/>, returns <see langword="null"/> instead of throwi
 1121        /// <returns>
 1122        /// File attributes.
 1123        /// </returns>
 1124        public SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError)
 1131125        {
 1131126            SshException exception = null;
 1131127            SftpFileAttributes attributes = null;
 1128
 1131129            using (var wait = new AutoResetEvent(initialState: false))
 1131130            {
 1131131                var request = new SftpFStatRequest(ProtocolVersion,
 1131132                                                   NextRequestId,
 1131133                                                   handle,
 1131134                                                   response =>
 1131135                                                   {
 1131136                                                       attributes = response.Attributes;
 1131137                                                       _ = wait.Set();
 1131138                                                   },
 1131139                                                   response =>
 01140                                                   {
 01141                                                       exception = GetSftpException(response);
 01142                                                       _ = wait.Set();
 1131143                                                   });
 1144
 1131145                SendRequest(request);
 1146
 1131147                WaitOnHandle(wait, OperationTimeout);
 1131148            }
 1149
 1150#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 1131151            if (!nullOnError && exception is not null)
 1152#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01153            {
 01154                throw exception;
 1155            }
 1156
 1131157            return attributes;
 1131158        }
 1159
 1160        /// <summary>
 1161        /// Asynchronously performs a <c>SSH_FXP_FSTAT</c> request.
 1162        /// </summary>
 1163        /// <param name="handle">The handle.</param>
 1164        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 1165        /// <returns>
 1166        /// A task that represents the asynchronous <c>SSH_FXP_FSTAT</c> request. The value of its
 1167        /// <see cref="Task{Task}.Result"/> contains the file attributes of the specified handle.
 1168        /// </returns>
 1169        public async Task<SftpFileAttributes> RequestFStatAsync(byte[] handle, CancellationToken cancellationToken)
 01170        {
 01171            cancellationToken.ThrowIfCancellationRequested();
 1172
 01173            var tcs = new TaskCompletionSource<SftpFileAttributes>(TaskCreationOptions.RunContinuationsAsynchronously);
 1174
 1175#if NET || NETSTANDARD2_1_OR_GREATER
 01176            await using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileAttributes>) s).TrySetCanceled(c
 1177#else
 01178            using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileAttributes>) s).TrySetCanceled(cancell
 1179#endif // NET || NETSTANDARD2_1_OR_GREATER
 01180            {
 01181                SendRequest(new SftpFStatRequest(ProtocolVersion,
 01182                                                 NextRequestId,
 01183                                                 handle,
 01184                                                 response => tcs.TrySetResult(response.Attributes),
 01185                                                 response => tcs.TrySetException(GetSftpException(response))));
 1186
 01187                return await tcs.Task.ConfigureAwait(false);
 1188            }
 01189        }
 1190
 1191        /// <summary>
 1192        /// Performs SSH_FXP_SETSTAT request.
 1193        /// </summary>
 1194        /// <param name="path">The path.</param>
 1195        /// <param name="attributes">The attributes.</param>
 1196        public void RequestSetStat(string path, SftpFileAttributes attributes)
 51197        {
 51198            SshException exception = null;
 1199
 51200            using (var wait = new AutoResetEvent(initialState: false))
 51201            {
 51202                var request = new SftpSetStatRequest(ProtocolVersion,
 51203                                                     NextRequestId,
 51204                                                     path,
 51205                                                     _encoding,
 51206                                                     attributes,
 51207                                                     response =>
 51208                                                     {
 51209                                                         exception = GetSftpException(response);
 51210                                                         _ = wait.Set();
 101211                                                     });
 1212
 51213                SendRequest(request);
 1214
 51215                WaitOnHandle(wait, OperationTimeout);
 51216            }
 1217
 1218#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 51219            if (exception is not null)
 1220#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01221            {
 01222                throw exception;
 1223            }
 51224        }
 1225
 1226        /// <summary>
 1227        /// Performs SSH_FXP_FSETSTAT request.
 1228        /// </summary>
 1229        /// <param name="handle">The handle.</param>
 1230        /// <param name="attributes">The attributes.</param>
 1231        public void RequestFSetStat(byte[] handle, SftpFileAttributes attributes)
 41232        {
 41233            SshException exception = null;
 1234
 41235            using (var wait = new AutoResetEvent(initialState: false))
 41236            {
 41237                var request = new SftpFSetStatRequest(ProtocolVersion,
 41238                                                      NextRequestId,
 41239                                                      handle,
 41240                                                      attributes,
 41241                                                      response =>
 41242                                                      {
 41243                                                          exception = GetSftpException(response);
 41244                                                          _ = wait.Set();
 81245                                                      });
 1246
 41247                SendRequest(request);
 1248
 41249                WaitOnHandle(wait, OperationTimeout);
 41250            }
 1251
 1252#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 41253            if (exception is not null)
 1254#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01255            {
 01256                throw exception;
 1257            }
 41258        }
 1259
 1260        /// <summary>
 1261        /// Performs SSH_FXP_OPENDIR request.
 1262        /// </summary>
 1263        /// <param name="path">The path.</param>
 1264        /// <param name="nullOnError">If set to <see langword="true"/>, returns <see langword="null"/> instead of throwi
 1265        /// <returns>File handle.</returns>
 1266        public byte[] RequestOpenDir(string path, bool nullOnError = false)
 361267        {
 361268            SshException exception = null;
 1269
 361270            byte[] handle = null;
 1271
 361272            using (var wait = new AutoResetEvent(initialState: false))
 361273            {
 361274                var request = new SftpOpenDirRequest(ProtocolVersion,
 361275                                                     NextRequestId,
 361276                                                     path,
 361277                                                     _encoding,
 361278                                                     response =>
 271279                                                     {
 271280                                                         handle = response.Handle;
 271281                                                         _ = wait.Set();
 271282                                                     },
 361283                                                     response =>
 91284                                                     {
 91285                                                         exception = GetSftpException(response);
 91286                                                         _ = wait.Set();
 451287                                                     });
 1288
 361289                SendRequest(request);
 1290
 361291                WaitOnHandle(wait, OperationTimeout);
 361292            }
 1293
 1294#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 361295            if (!nullOnError && exception is not null)
 1296#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 91297            {
 91298                throw exception;
 1299            }
 1300
 271301            return handle;
 271302        }
 1303
 1304        /// <summary>
 1305        /// Asynchronously performs a <c>SSH_FXP_OPENDIR</c> request.
 1306        /// </summary>
 1307        /// <param name="path">The path.</param>
 1308        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 1309        /// <returns>
 1310        /// A task that represents the asynchronous <c>SSH_FXP_OPENDIR</c> request. The value of its
 1311        /// <see cref="Task{Task}.Result"/> contains the handle of the specified path.
 1312        /// </returns>
 1313        public async Task<byte[]> RequestOpenDirAsync(string path, CancellationToken cancellationToken)
 21314        {
 21315            cancellationToken.ThrowIfCancellationRequested();
 1316
 21317            var tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
 1318
 1319#if NET || NETSTANDARD2_1_OR_GREATER
 21320            await using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>) s).TrySetCanceled(cancellationT
 1321#else
 01322            using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>) s).TrySetCanceled(cancellationToken),
 1323#endif // NET || NETSTANDARD2_1_OR_GREATER
 21324            {
 21325                SendRequest(new SftpOpenDirRequest(ProtocolVersion,
 21326                                                   NextRequestId,
 21327                                                   path,
 21328                                                   _encoding,
 21329                                                   response => tcs.TrySetResult(response.Handle),
 21330                                                   response => tcs.TrySetException(GetSftpException(response))));
 1331
 21332                return await tcs.Task.ConfigureAwait(false);
 1333            }
 21334        }
 1335
 1336        /// <summary>
 1337        /// Performs SSH_FXP_READDIR request.
 1338        /// </summary>
 1339        /// <param name="handle">The handle of the directory to read.</param>
 1340        /// <returns>
 1341        /// A <see cref="Dictionary{TKey,TValue}"/> where the <c>key</c> is the name of a file in
 1342        /// the directory and the <c>value</c> is the <see cref="SftpFileAttributes"/> of the file.
 1343        /// </returns>
 1344        public KeyValuePair<string, SftpFileAttributes>[] RequestReadDir(byte[] handle)
 1241345        {
 1241346            SshException exception = null;
 1347
 1241348            KeyValuePair<string, SftpFileAttributes>[] result = null;
 1349
 1241350            using (var wait = new AutoResetEvent(initialState: false))
 1241351            {
 1241352                var request = new SftpReadDirRequest(ProtocolVersion,
 1241353                                                     NextRequestId,
 1241354                                                     handle,
 1241355                                                     response =>
 1121356                                                     {
 1121357                                                         result = response.Files;
 1121358                                                         _ = wait.Set();
 1121359                                                     },
 1241360                                                     response =>
 121361                                                     {
 121362                                                         if (response.StatusCode != StatusCodes.Eof)
 01363                                                         {
 01364                                                             exception = GetSftpException(response);
 01365                                                         }
 1241366
 121367                                                         _ = wait.Set();
 1361368                                                     });
 1369
 1241370                SendRequest(request);
 1371
 1241372                WaitOnHandle(wait, OperationTimeout);
 1241373            }
 1374
 1375#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 1241376            if (exception is not null)
 1377#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01378            {
 01379                throw exception;
 1380            }
 1381
 1241382            return result;
 1241383        }
 1384
 1385        /// <summary>
 1386        /// Performs a <c>SSH_FXP_READDIR</c> request.
 1387        /// </summary>
 1388        /// <param name="handle">The handle of the directory to read.</param>
 1389        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 1390        /// <returns>
 1391        /// A task that represents the asynchronous <c>SSH_FXP_READDIR</c> request. The value of its
 1392        /// <see cref="Task{Task}.Result"/> contains a <see cref="Dictionary{TKey,TValue}"/> where the
 1393        /// <c>key</c> is the name of a file in the directory and the <c>value</c> is the <see cref="SftpFileAttributes"
 1394        /// of the file.
 1395        /// </returns>
 1396        public async Task<KeyValuePair<string, SftpFileAttributes>[]> RequestReadDirAsync(byte[] handle, CancellationTok
 41397        {
 41398            cancellationToken.ThrowIfCancellationRequested();
 1399
 41400            var tcs = new TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>(TaskCreationOptions.RunContin
 1401
 1402#if NET || NETSTANDARD2_1_OR_GREATER
 41403            await using (cancellationToken.Register(s => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>
 1404#else
 01405            using (cancellationToken.Register(s => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>) s
 1406#endif // NET || NETSTANDARD2_1_OR_GREATER
 41407            {
 41408                SendRequest(new SftpReadDirRequest(ProtocolVersion,
 41409                                                   NextRequestId,
 41410                                                   handle,
 21411                                                   response => tcs.TrySetResult(response.Files),
 41412                                                   response =>
 21413                                                   {
 21414                                                       if (response.StatusCode == StatusCodes.Eof)
 21415                                                       {
 21416                                                           _ = tcs.TrySetResult(null);
 21417                                                       }
 41418                                                       else
 01419                                                       {
 01420                                                           _ = tcs.TrySetException(GetSftpException(response));
 01421                                                       }
 61422                                                   }));
 1423
 41424                return await tcs.Task.ConfigureAwait(false);
 1425            }
 41426        }
 1427
 1428        /// <summary>
 1429        /// Performs SSH_FXP_REMOVE request.
 1430        /// </summary>
 1431        /// <param name="path">The path.</param>
 1432        public void RequestRemove(string path)
 1621433        {
 1621434            SshException exception = null;
 1435
 1621436            using (var wait = new AutoResetEvent(initialState: false))
 1621437            {
 1621438                var request = new SftpRemoveRequest(ProtocolVersion,
 1621439                                                    NextRequestId,
 1621440                                                    path,
 1621441                                                    _encoding,
 1621442                                                    response =>
 1621443                                                    {
 1621444                                                        exception = GetSftpException(response);
 1621445                                                        _ = wait.Set();
 3241446                                                    });
 1447
 1621448                SendRequest(request);
 1449
 1621450                WaitOnHandle(wait, OperationTimeout);
 1621451            }
 1452
 1453#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 1621454            if (exception is not null)
 1455#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01456            {
 01457                throw exception;
 1458            }
 1621459        }
 1460
 1461        /// <summary>
 1462        /// Asynchronously performs a <c>SSH_FXP_REMOVE</c> request.
 1463        /// </summary>
 1464        /// <param name="path">The path.</param>
 1465        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 1466        /// <returns>
 1467        /// A task that represents the asynchronous <c>SSH_FXP_REMOVE</c> request.
 1468        /// </returns>
 1469        public async Task RequestRemoveAsync(string path, CancellationToken cancellationToken)
 01470        {
 01471            cancellationToken.ThrowIfCancellationRequested();
 1472
 01473            var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
 1474
 1475#if NET || NETSTANDARD2_1_OR_GREATER
 01476            await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).TrySetCanceled(cancellationTok
 1477#else
 01478            using (cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).TrySetCanceled(cancellationToken), t
 1479#endif // NET || NETSTANDARD2_1_OR_GREATER
 01480            {
 01481                SendRequest(new SftpRemoveRequest(ProtocolVersion,
 01482                                                  NextRequestId,
 01483                                                  path,
 01484                                                  _encoding,
 01485                                                  response =>
 01486                                                  {
 01487                                                      if (response.StatusCode == StatusCodes.Ok)
 01488                                                      {
 01489                                                          _ = tcs.TrySetResult(true);
 01490                                                      }
 01491                                                      else
 01492                                                      {
 01493                                                          _ = tcs.TrySetException(GetSftpException(response));
 01494                                                      }
 01495                                                  }));
 1496
 01497                _ = await tcs.Task.ConfigureAwait(false);
 01498            }
 01499        }
 1500
 1501        /// <summary>
 1502        /// Performs SSH_FXP_MKDIR request.
 1503        /// </summary>
 1504        /// <param name="path">The path.</param>
 1505        public void RequestMkDir(string path)
 100371506        {
 100371507            SshException exception = null;
 1508
 100371509            using (var wait = new AutoResetEvent(initialState: false))
 100371510            {
 100371511                var request = new SftpMkDirRequest(ProtocolVersion,
 100371512                                                   NextRequestId,
 100371513                                                   path,
 100371514                                                   _encoding,
 100371515                                                   response =>
 100371516                                                   {
 100371517                                                       exception = GetSftpException(response);
 100371518                                                       _ = wait.Set();
 200741519                                                   });
 1520
 100371521                SendRequest(request);
 1522
 100371523                WaitOnHandle(wait, OperationTimeout);
 100371524            }
 1525
 1526#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 100371527            if (exception is not null)
 1528#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 31529            {
 31530                throw exception;
 1531            }
 100341532        }
 1533
 1534        /// <summary>
 1535        /// Performs SSH_FXP_RMDIR request.
 1536        /// </summary>
 1537        /// <param name="path">The path.</param>
 1538        public void RequestRmDir(string path)
 381539        {
 381540            SshException exception = null;
 1541
 381542            using (var wait = new AutoResetEvent(initialState: false))
 381543            {
 381544                var request = new SftpRmDirRequest(ProtocolVersion,
 381545                                                   NextRequestId,
 381546                                                   path,
 381547                                                   _encoding,
 381548                                                   response =>
 381549                                                   {
 381550                                                       exception = GetSftpException(response);
 381551                                                       _ = wait.Set();
 761552                                                   });
 1553
 381554                SendRequest(request);
 1555
 381556                WaitOnHandle(wait, OperationTimeout);
 381557            }
 1558
 1559#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 381560            if (exception is not null)
 1561#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 21562            {
 21563                throw exception;
 1564            }
 361565        }
 1566
 1567        /// <summary>
 1568        /// Performs SSH_FXP_REALPATH request.
 1569        /// </summary>
 1570        /// <param name="path">The path.</param>
 1571        /// <param name="nullOnError">if set to <see langword="true"/> returns null instead of throwing an exception.</p
 1572        /// <returns>
 1573        /// The absolute path.
 1574        /// </returns>
 1575        internal KeyValuePair<string, SftpFileAttributes>[] RequestRealPath(string path, bool nullOnError = false)
 116151576        {
 116151577            SshException exception = null;
 1578
 116151579            KeyValuePair<string, SftpFileAttributes>[] result = null;
 1580
 116151581            using (var wait = new AutoResetEvent(initialState: false))
 116151582            {
 116151583                var request = new SftpRealPathRequest(ProtocolVersion,
 116151584                                                      NextRequestId,
 116151585                                                      path,
 116151586                                                      _encoding,
 116151587                                                      response =>
 115151588                                                      {
 115151589                                                          result = response.Files;
 115151590                                                          _ = wait.Set();
 115151591                                                      },
 116151592                                                      response =>
 971593                                                      {
 971594                                                          exception = GetSftpException(response);
 971595                                                          _ = wait.Set();
 117121596                                                      });
 1597
 116151598                SendRequest(request);
 1599
 116131600                WaitOnHandle(wait, OperationTimeout);
 116121601            }
 1602
 1603#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 116121604            if (!nullOnError && exception is not null)
 1605#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01606            {
 01607                throw exception;
 1608            }
 1609
 116121610            return result;
 116121611        }
 1612
 1613        internal async Task<KeyValuePair<string, SftpFileAttributes>[]> RequestRealPathAsync(string path, bool nullOnErr
 41614        {
 41615            cancellationToken.ThrowIfCancellationRequested();
 1616
 41617            var tcs = new TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>(TaskCreationOptions.RunContin
 1618
 1619#if NET || NETSTANDARD2_1_OR_GREATER
 41620            await using (cancellationToken.Register(s => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>
 1621#else
 01622            using (cancellationToken.Register(s => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>) s
 1623#endif // NET || NETSTANDARD2_1_OR_GREATER
 41624            {
 41625                SendRequest(new SftpRealPathRequest(ProtocolVersion,
 41626                                                    NextRequestId,
 41627                                                    path,
 41628                                                    _encoding,
 41629                                                    response => tcs.TrySetResult(response.Files),
 41630                                                    response =>
 01631                                                    {
 01632                                                        if (nullOnError)
 01633                                                        {
 01634                                                            _ = tcs.TrySetResult(null);
 01635                                                        }
 41636                                                        else
 01637                                                        {
 01638                                                            _ = tcs.TrySetException(GetSftpException(response));
 01639                                                        }
 41640                                                    }));
 1641
 41642                return await tcs.Task.ConfigureAwait(false);
 1643            }
 41644        }
 1645
 1646        /// <summary>
 1647        /// Performs SSH_FXP_REALPATH request.
 1648        /// </summary>
 1649        /// <param name="path">The path.</param>
 1650        /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginRealPa
 1651        /// <param name="state">An object that contains any additional user-defined data.</param>
 1652        /// <returns>
 1653        /// A <see cref="SftpRealPathAsyncResult"/> that represents the asynchronous call.
 1654        /// </returns>
 1655        public SftpRealPathAsyncResult BeginRealPath(string path, AsyncCallback callback, object state)
 01656        {
 01657            var asyncResult = new SftpRealPathAsyncResult(callback, state);
 1658
 01659            var request = new SftpRealPathRequest(ProtocolVersion,
 01660                                                  NextRequestId,
 01661                                                  path,
 01662                                                  _encoding,
 01663                                                  response => asyncResult.SetAsCompleted(response.Files[0].Key, complete
 01664                                                  response => asyncResult.SetAsCompleted(GetSftpException(response), com
 01665            SendRequest(request);
 1666
 01667            return asyncResult;
 01668        }
 1669
 1670        /// <summary>
 1671        /// Handles the end of an asynchronous SSH_FXP_REALPATH request.
 1672        /// </summary>
 1673        /// <param name="asyncResult">An <see cref="SftpRealPathAsyncResult"/> that represents an asynchronous call.</pa
 1674        /// <returns>
 1675        /// The absolute path.
 1676        /// </returns>
 1677        /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <see langword="null"/>.</exception
 1678        public string EndRealPath(SftpRealPathAsyncResult asyncResult)
 01679        {
 01680            if (asyncResult is null)
 01681            {
 01682                throw new ArgumentNullException(nameof(asyncResult));
 1683            }
 1684
 01685            if (asyncResult.EndInvokeCalled)
 01686            {
 01687                throw new InvalidOperationException("EndRealPath has already been called.");
 1688            }
 1689
 01690            if (asyncResult.IsCompleted)
 01691            {
 01692                return asyncResult.EndInvoke();
 1693            }
 1694
 01695            using (var waitHandle = asyncResult.AsyncWaitHandle)
 01696            {
 01697                WaitOnHandle(waitHandle, OperationTimeout);
 01698                return asyncResult.EndInvoke();
 1699            }
 01700        }
 1701
 1702        /// <summary>
 1703        /// Performs SSH_FXP_STAT request.
 1704        /// </summary>
 1705        /// <param name="path">The path.</param>
 1706        /// <param name="nullOnError">if set to <see langword="true"/> returns null instead of throwing an exception.</p
 1707        /// <returns>
 1708        /// File attributes.
 1709        /// </returns>
 1710        public SftpFileAttributes RequestStat(string path, bool nullOnError = false)
 01711        {
 01712            SshException exception = null;
 1713
 01714            SftpFileAttributes attributes = null;
 1715
 01716            using (var wait = new AutoResetEvent(initialState: false))
 01717            {
 01718                var request = new SftpStatRequest(ProtocolVersion,
 01719                                                  NextRequestId,
 01720                                                  path,
 01721                                                  _encoding,
 01722                                                  response =>
 01723                                                  {
 01724                                                      attributes = response.Attributes;
 01725                                                      _ = wait.Set();
 01726                                                  },
 01727                                                  response =>
 01728                                                  {
 01729                                                      exception = GetSftpException(response);
 01730                                                      _ = wait.Set();
 01731                                                  });
 1732
 01733                SendRequest(request);
 1734
 01735                WaitOnHandle(wait, OperationTimeout);
 01736            }
 1737
 1738#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01739            if (!nullOnError && exception is not null)
 1740#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01741            {
 01742                throw exception;
 1743            }
 1744
 01745            return attributes;
 01746        }
 1747
 1748        /// <summary>
 1749        /// Performs SSH_FXP_STAT request.
 1750        /// </summary>
 1751        /// <param name="path">The path.</param>
 1752        /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginStat(s
 1753        /// <param name="state">An object that contains any additional user-defined data.</param>
 1754        /// <returns>
 1755        /// A <see cref="SFtpStatAsyncResult"/> that represents the asynchronous call.
 1756        /// </returns>
 1757        public SFtpStatAsyncResult BeginStat(string path, AsyncCallback callback, object state)
 01758        {
 01759            var asyncResult = new SFtpStatAsyncResult(callback, state);
 1760
 01761            var request = new SftpStatRequest(ProtocolVersion,
 01762                                              NextRequestId,
 01763                                              path,
 01764                                              _encoding,
 01765                                              response => asyncResult.SetAsCompleted(response.Attributes, completedSynch
 01766                                              response => asyncResult.SetAsCompleted(GetSftpException(response), complet
 01767            SendRequest(request);
 1768
 01769            return asyncResult;
 01770        }
 1771
 1772        /// <summary>
 1773        /// Handles the end of an asynchronous stat.
 1774        /// </summary>
 1775        /// <param name="asyncResult">An <see cref="SFtpStatAsyncResult"/> that represents an asynchronous call.</param>
 1776        /// <returns>
 1777        /// The file attributes.
 1778        /// </returns>
 1779        /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <see langword="null"/>.</exception
 1780        public SftpFileAttributes EndStat(SFtpStatAsyncResult asyncResult)
 01781        {
 01782            if (asyncResult is null)
 01783            {
 01784                throw new ArgumentNullException(nameof(asyncResult));
 1785            }
 1786
 01787            if (asyncResult.EndInvokeCalled)
 01788            {
 01789                throw new InvalidOperationException("EndStat has already been called.");
 1790            }
 1791
 01792            if (asyncResult.IsCompleted)
 01793            {
 01794                return asyncResult.EndInvoke();
 1795            }
 1796
 01797            using (var waitHandle = asyncResult.AsyncWaitHandle)
 01798            {
 01799                WaitOnHandle(waitHandle, OperationTimeout);
 01800                return asyncResult.EndInvoke();
 1801            }
 01802        }
 1803
 1804        /// <summary>
 1805        /// Performs SSH_FXP_RENAME request.
 1806        /// </summary>
 1807        /// <param name="oldPath">The old path.</param>
 1808        /// <param name="newPath">The new path.</param>
 1809        public void RequestRename(string oldPath, string newPath)
 21810        {
 21811            if (ProtocolVersion < 2)
 01812            {
 01813                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_RENAME operation is n
 1814            }
 1815
 21816            SshException exception = null;
 1817
 21818            using (var wait = new AutoResetEvent(initialState: false))
 21819            {
 21820                var request = new SftpRenameRequest(ProtocolVersion,
 21821                                                    NextRequestId,
 21822                                                    oldPath,
 21823                                                    newPath,
 21824                                                    _encoding,
 21825                                                    response =>
 21826                                                    {
 21827                                                        exception = GetSftpException(response);
 21828                                                        _ = wait.Set();
 41829                                                    });
 1830
 21831                SendRequest(request);
 1832
 21833                WaitOnHandle(wait, OperationTimeout);
 21834            }
 1835
 1836#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 21837            if (exception is not null)
 1838#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01839            {
 01840                throw exception;
 1841            }
 21842        }
 1843
 1844        /// <summary>
 1845        /// Asynchronously performs a <c>SSH_FXP_RENAME</c> request.
 1846        /// </summary>
 1847        /// <param name="oldPath">The old path.</param>
 1848        /// <param name="newPath">The new path.</param>
 1849        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 1850        /// <returns>
 1851        /// A task that represents the asynchronous <c>SSH_FXP_RENAME</c> request.
 1852        /// </returns>
 1853        public async Task RequestRenameAsync(string oldPath, string newPath, CancellationToken cancellationToken)
 11854        {
 11855            cancellationToken.ThrowIfCancellationRequested();
 1856
 11857            var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
 1858
 1859#if NET || NETSTANDARD2_1_OR_GREATER
 11860            await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).TrySetCanceled(cancellationTok
 1861#else
 01862            using (cancellationToken.Register(s => ((TaskCompletionSource<bool>) s).TrySetCanceled(cancellationToken), t
 1863#endif // NET || NETSTANDARD2_1_OR_GREATER
 11864            {
 11865                SendRequest(new SftpRenameRequest(ProtocolVersion,
 11866                                                  NextRequestId,
 11867                                                  oldPath,
 11868                                                  newPath,
 11869                                                  _encoding,
 11870                                                  response =>
 11871                                                  {
 11872                                                      if (response.StatusCode == StatusCodes.Ok)
 11873                                                      {
 11874                                                          _ = tcs.TrySetResult(true);
 11875                                                      }
 11876                                                      else
 01877                                                      {
 01878                                                          _ = tcs.TrySetException(GetSftpException(response));
 01879                                                      }
 21880                                                  }));
 1881
 11882                _ = await tcs.Task.ConfigureAwait(false);
 11883            }
 11884        }
 1885
 1886        /// <summary>
 1887        /// Performs SSH_FXP_READLINK request.
 1888        /// </summary>
 1889        /// <param name="path">The path.</param>
 1890        /// <param name="nullOnError">if set to <see langword="true"/> returns <see langword="null"/> instead of throwin
 1891        /// <returns>
 1892        /// An array of <see cref="KeyValuePair{TKey,TValue}"/> where the <c>key</c> is the name of
 1893        /// a file and the <c>value</c> is the <see cref="SftpFileAttributes"/> of the file.
 1894        /// </returns>
 1895        internal KeyValuePair<string, SftpFileAttributes>[] RequestReadLink(string path, bool nullOnError = false)
 01896        {
 01897            if (ProtocolVersion < 3)
 01898            {
 01899                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_READLINK operation is
 1900            }
 1901
 01902            SshException exception = null;
 1903
 01904            KeyValuePair<string, SftpFileAttributes>[] result = null;
 1905
 01906            using (var wait = new AutoResetEvent(initialState: false))
 01907            {
 01908                var request = new SftpReadLinkRequest(ProtocolVersion,
 01909                                                      NextRequestId,
 01910                                                      path,
 01911                                                      _encoding,
 01912                                                      response =>
 01913                                                      {
 01914                                                          result = response.Files;
 01915                                                          _ = wait.Set();
 01916                                                      },
 01917                                                      response =>
 01918                                                      {
 01919                                                          exception = GetSftpException(response);
 01920                                                          _ = wait.Set();
 01921                                                      });
 1922
 01923                SendRequest(request);
 1924
 01925                WaitOnHandle(wait, OperationTimeout);
 01926            }
 1927
 1928#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01929            if (!nullOnError && exception is not null)
 1930#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01931            {
 01932                throw exception;
 1933            }
 1934
 01935            return result;
 01936        }
 1937
 1938        /// <summary>
 1939        /// Performs SSH_FXP_SYMLINK request.
 1940        /// </summary>
 1941        /// <param name="linkpath">The linkpath.</param>
 1942        /// <param name="targetpath">The targetpath.</param>
 1943        public void RequestSymLink(string linkpath, string targetpath)
 01944        {
 01945            if (ProtocolVersion < 3)
 01946            {
 01947                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_SYMLINK operation is 
 1948            }
 1949
 01950            SshException exception = null;
 1951
 01952            using (var wait = new AutoResetEvent(initialState: false))
 01953            {
 01954                var request = new SftpSymLinkRequest(ProtocolVersion,
 01955                                                     NextRequestId,
 01956                                                     linkpath,
 01957                                                     targetpath,
 01958                                                     _encoding,
 01959                                                     response =>
 01960                                                     {
 01961                                                         exception = GetSftpException(response);
 01962                                                         _ = wait.Set();
 01963                                                     });
 1964
 01965                SendRequest(request);
 1966
 01967                WaitOnHandle(wait, OperationTimeout);
 01968            }
 1969
 1970#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01971            if (exception is not null)
 1972#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 01973            {
 01974                throw exception;
 1975            }
 01976        }
 1977
 1978        /// <summary>
 1979        /// Performs posix-rename@openssh.com extended request.
 1980        /// </summary>
 1981        /// <param name="oldPath">The old path.</param>
 1982        /// <param name="newPath">The new path.</param>
 1983        public void RequestPosixRename(string oldPath, string newPath)
 01984        {
 01985            if (ProtocolVersion < 3)
 01986            {
 01987                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is
 1988            }
 1989
 01990            SshException exception = null;
 1991
 01992            using (var wait = new AutoResetEvent(initialState: false))
 01993            {
 01994                var request = new PosixRenameRequest(ProtocolVersion,
 01995                                                     NextRequestId,
 01996                                                     oldPath,
 01997                                                     newPath,
 01998                                                     _encoding,
 01999                                                     response =>
 02000                                                     {
 02001                                                         exception = GetSftpException(response);
 02002                                                         _ = wait.Set();
 02003                                                     });
 2004
 02005                if (!_supportedExtensions.ContainsKey(request.Name))
 02006                {
 02007                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} curr
 2008                }
 2009
 02010                SendRequest(request);
 2011
 02012                WaitOnHandle(wait, OperationTimeout);
 02013            }
 2014
 2015#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 02016            if (exception is not null)
 2017#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 02018            {
 02019                throw exception;
 2020            }
 02021        }
 2022
 2023        /// <summary>
 2024        /// Performs statvfs@openssh.com extended request.
 2025        /// </summary>
 2026        /// <param name="path">The path.</param>
 2027        /// <param name="nullOnError">if set to <see langword="true"/> [null on error].</param>
 2028        /// <returns>
 2029        /// A <see cref="SftpFileSytemInformation"/> for the specified path.
 2030        /// </returns>
 2031        public SftpFileSytemInformation RequestStatVfs(string path, bool nullOnError = false)
 62032        {
 62033            if (ProtocolVersion < 3)
 02034            {
 02035                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is
 2036            }
 2037
 62038            SshException exception = null;
 2039
 62040            SftpFileSytemInformation information = null;
 2041
 62042            using (var wait = new AutoResetEvent(initialState: false))
 62043            {
 62044                var request = new StatVfsRequest(ProtocolVersion,
 62045                                                 NextRequestId,
 62046                                                 path,
 62047                                                 _encoding,
 62048                                                 response =>
 62049                                                 {
 62050                                                     information = response.GetReply<StatVfsReplyInfo>().Information;
 62051                                                     _ = wait.Set();
 62052                                                 },
 62053                                                 response =>
 02054                                                 {
 02055                                                     exception = GetSftpException(response);
 02056                                                     _ = wait.Set();
 62057                                                 });
 2058
 62059                if (!_supportedExtensions.ContainsKey(request.Name))
 02060                {
 02061                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} curr
 2062                }
 2063
 62064                SendRequest(request);
 2065
 62066                WaitOnHandle(wait, OperationTimeout);
 62067            }
 2068
 2069#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 62070            if (!nullOnError && exception is not null)
 2071#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 02072            {
 02073                throw exception;
 2074            }
 2075
 62076            return information;
 62077        }
 2078
 2079        /// <summary>
 2080        /// Asynchronously performs a <c>statvfs@openssh.com</c> extended request.
 2081        /// </summary>
 2082        /// <param name="path">The path.</param>
 2083        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
 2084        /// <returns>
 2085        /// A task that represents the <c>statvfs@openssh.com</c> extended request. The value of its
 2086        /// <see cref="Task{Task}.Result"/> contains the file system information for the specified
 2087        /// path.
 2088        /// </returns>
 2089        public async Task<SftpFileSytemInformation> RequestStatVfsAsync(string path, CancellationToken cancellationToken
 02090        {
 02091            if (ProtocolVersion < 3)
 02092            {
 02093                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is
 2094            }
 2095
 02096            cancellationToken.ThrowIfCancellationRequested();
 2097
 02098            var tcs = new TaskCompletionSource<SftpFileSytemInformation>(TaskCreationOptions.RunContinuationsAsynchronou
 2099
 2100#if NET || NETSTANDARD2_1_OR_GREATER
 02101            await using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileSytemInformation>) s).TrySetCanc
 2102#else
 02103            using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileSytemInformation>) s).TrySetCanceled(c
 2104#endif // NET || NETSTANDARD2_1_OR_GREATER
 02105            {
 02106                SendRequest(new StatVfsRequest(ProtocolVersion,
 02107                                               NextRequestId,
 02108                                               path,
 02109                                               _encoding,
 02110                                               response => tcs.TrySetResult(response.GetReply<StatVfsReplyInfo>().Inform
 02111                                               response => tcs.TrySetException(GetSftpException(response))));
 2112
 02113                return await tcs.Task.ConfigureAwait(false);
 2114            }
 02115        }
 2116
 2117        /// <summary>
 2118        /// Performs fstatvfs@openssh.com extended request.
 2119        /// </summary>
 2120        /// <param name="handle">The file handle.</param>
 2121        /// <param name="nullOnError">if set to <see langword="true"/> [null on error].</param>
 2122        /// <returns>
 2123        /// A <see cref="SftpFileSytemInformation"/> for the specified path.
 2124        /// </returns>
 2125        /// <exception cref="NotSupportedException">This operation is not supported for the current SFTP protocol versio
 2126        internal SftpFileSytemInformation RequestFStatVfs(byte[] handle, bool nullOnError = false)
 02127        {
 02128            if (ProtocolVersion < 3)
 02129            {
 02130                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is
 2131            }
 2132
 02133            SshException exception = null;
 2134
 02135            SftpFileSytemInformation information = null;
 2136
 02137            using (var wait = new AutoResetEvent(initialState: false))
 02138            {
 02139                var request = new FStatVfsRequest(ProtocolVersion,
 02140                                                  NextRequestId,
 02141                                                  handle,
 02142                                                  response =>
 02143                                                  {
 02144                                                      information = response.GetReply<StatVfsReplyInfo>().Information;
 02145                                                      _ = wait.Set();
 02146                                                  },
 02147                                                  response =>
 02148                                                  {
 02149                                                      exception = GetSftpException(response);
 02150                                                      _ = wait.Set();
 02151                                                  });
 2152
 02153                if (!_supportedExtensions.ContainsKey(request.Name))
 02154                {
 02155                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} curr
 2156                }
 2157
 02158                SendRequest(request);
 2159
 02160                WaitOnHandle(wait, OperationTimeout);
 02161            }
 2162
 2163#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 02164            if (!nullOnError && exception is not null)
 2165#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 02166            {
 02167                throw exception;
 2168            }
 2169
 02170            return information;
 02171        }
 2172
 2173        /// <summary>
 2174        /// Performs hardlink@openssh.com extended request.
 2175        /// </summary>
 2176        /// <param name="oldPath">The old path.</param>
 2177        /// <param name="newPath">The new path.</param>
 2178        internal void HardLink(string oldPath, string newPath)
 02179        {
 02180            if (ProtocolVersion < 3)
 02181            {
 02182                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is
 2183            }
 2184
 02185            SshException exception = null;
 2186
 02187            using (var wait = new AutoResetEvent(initialState: false))
 02188            {
 02189                var request = new HardLinkRequest(ProtocolVersion,
 02190                                                  NextRequestId,
 02191                                                  oldPath,
 02192                                                  newPath,
 02193                                                  response =>
 02194                                                  {
 02195                                                      exception = GetSftpException(response);
 02196                                                      _ = wait.Set();
 02197                                                  });
 2198
 02199                if (!_supportedExtensions.ContainsKey(request.Name))
 02200                {
 02201                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} curr
 2202                }
 2203
 02204                SendRequest(request);
 2205
 02206                WaitOnHandle(wait, OperationTimeout);
 02207            }
 2208
 2209#pragma warning disable CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 02210            if (exception is not null)
 2211#pragma warning restore CA1508 // Avoid dead conditional code; Remove when we upgrade to newer SDK in which bug is fixed
 02212            {
 02213                throw exception;
 2214            }
 02215        }
 2216
 2217        /// <summary>
 2218        /// Calculates the optimal size of the buffer to read data from the channel.
 2219        /// </summary>
 2220        /// <param name="bufferSize">The buffer size configured on the client.</param>
 2221        /// <returns>
 2222        /// The optimal size of the buffer to read data from the channel.
 2223        /// </returns>
 2224        public uint CalculateOptimalReadLength(uint bufferSize)
 3092225        {
 2226            // a SSH_FXP_DATA message has 13 bytes of protocol fields:
 2227            // bytes 1 to 4: packet length
 2228            // byte 5: message type
 2229            // bytes 6 to 9: response id
 2230            // bytes 10 to 13: length of payload
 2231            //
 2232            // WinSCP uses a payload length of 32755 bytes
 2233            //
 2234            // most ssh servers limit the size of the payload of a SSH_MSG_CHANNEL_DATA
 2235            // response to 16 KB; if we requested 16 KB of data, then the SSH_FXP_DATA
 2236            // payload of the SSH_MSG_CHANNEL_DATA message would be too big (16 KB + 13 bytes), and
 2237            // as a result, the ssh server would split this into two responses:
 2238            // one containing 16384 bytes (13 bytes header, and 16371 bytes file data)
 2239            // and one with the remaining 13 bytes of file data
 2240            const uint lengthOfNonDataProtocolFields = 13u;
 3092241            var maximumPacketSize = Channel.LocalPacketSize;
 3092242            return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields;
 3092243        }
 2244
 2245        /// <summary>
 2246        /// Calculates the optimal size of the buffer to write data on the channel.
 2247        /// </summary>
 2248        /// <param name="bufferSize">The buffer size configured on the client.</param>
 2249        /// <param name="handle">The file handle.</param>
 2250        /// <returns>
 2251        /// The optimal size of the buffer to write data on the channel.
 2252        /// </returns>
 2253        /// <remarks>
 2254        /// Currently, we do not take the remote window size into account.
 2255        /// </remarks>
 2256        public uint CalculateOptimalWriteLength(uint bufferSize, byte[] handle)
 3152257        {
 2258            // 1-4: package length of SSH_FXP_WRITE message
 2259            // 5: message type
 2260            // 6-9: request id
 2261            // 10-13: handle length
 2262            // <handle>
 2263            // 14-21: offset
 2264            // 22-25: data length
 2265
 2266            /*
 2267             * Putty uses data length of 4096 bytes
 2268             * WinSCP uses data length of 32739 bytes (total 32768 bytes; 32739 + 25 + 4 bytes for handle)
 2269             */
 2270
 3152271            var lengthOfNonDataProtocolFields = 25u + (uint) handle.Length;
 3152272            var maximumPacketSize = Channel.RemotePacketSize;
 3152273            return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields;
 3152274        }
 2275
 2276        private static SshException GetSftpException(SftpStatusResponse response)
 149392277        {
 2278#pragma warning disable IDE0010 // Add missing cases
 149392279            switch (response.StatusCode)
 2280            {
 2281                case StatusCodes.Ok:
 145292282                    return null;
 2283                case StatusCodes.PermissionDenied:
 82284                    return new SftpPermissionDeniedException(response.ErrorMessage);
 2285                case StatusCodes.NoSuchFile:
 3992286                    return new SftpPathNotFoundException(response.ErrorMessage);
 2287                default:
 32288                    return new SshException(response.ErrorMessage);
 2289            }
 2290#pragma warning restore IDE0010 // Add missing cases
 149392291        }
 2292
 2293        private void HandleResponse(SftpResponse response)
 315182294        {
 2295            SftpRequest request;
 315182296            lock (_requests)
 315182297            {
 315182298                _ = _requests.TryGetValue(response.ResponseId, out request);
 315182299                if (request is not null)
 315182300                {
 315182301                    _ = _requests.Remove(response.ResponseId);
 315182302                }
 315182303            }
 2304
 315182305            if (request is null)
 02306            {
 02307                throw new InvalidOperationException("Invalid response.");
 2308            }
 2309
 315182310            request.Complete(response);
 315182311        }
 2312    }
 2313}

Methods/Properties

.ctor(Renci.SshNet.ISession,System.Int32,System.Text.Encoding,Renci.SshNet.Sftp.ISftpResponseFactory)
get_WorkingDirectory()
get_ProtocolVersion()
get_NextRequestId()
ChangeDirectory(System.String)
SendMessage(Renci.SshNet.Sftp.SftpMessage)
GetCanonicalPath(System.String)
GetCanonicalPathAsync()
CreateFileReader(System.Byte[],Renci.SshNet.Sftp.ISftpSession,System.UInt32,System.Int32,System.Nullable`1<System.Int64>)
GetFullRemotePath(System.String)
OnChannelOpen()
OnDataReceived(System.Byte[])
TryLoadSftpMessage(System.Byte[],System.Int32,System.Int32)
Dispose(System.Boolean)
SendRequest(Renci.SshNet.Sftp.Requests.SftpRequest)
RequestOpen(System.String,Renci.SshNet.Sftp.Flags,System.Boolean)
RequestOpenAsync()
BeginOpen(System.String,Renci.SshNet.Sftp.Flags,System.AsyncCallback,System.Object)
EndOpen(Renci.SshNet.Sftp.SftpOpenAsyncResult)
RequestClose(System.Byte[])
RequestCloseAsync()
BeginClose(System.Byte[],System.AsyncCallback,System.Object)
EndClose(Renci.SshNet.Sftp.SftpCloseAsyncResult)
BeginRead(System.Byte[],System.UInt64,System.UInt32,System.AsyncCallback,System.Object)
EndRead(Renci.SshNet.Sftp.SftpReadAsyncResult)
RequestRead(System.Byte[],System.UInt64,System.UInt32)
RequestReadAsync()
RequestWrite(System.Byte[],System.UInt64,System.Byte[],System.Int32,System.Int32,System.Threading.AutoResetEvent,System.Action`1<Renci.SshNet.Sftp.Responses.SftpStatusResponse>)
RequestWriteAsync()
RequestLStat(System.String)
BeginLStat(System.String,System.AsyncCallback,System.Object)
EndLStat(Renci.SshNet.Sftp.SFtpStatAsyncResult)
RequestFStat(System.Byte[],System.Boolean)
RequestFStatAsync()
RequestSetStat(System.String,Renci.SshNet.Sftp.SftpFileAttributes)
RequestFSetStat(System.Byte[],Renci.SshNet.Sftp.SftpFileAttributes)
RequestOpenDir(System.String,System.Boolean)
RequestOpenDirAsync()
RequestReadDir(System.Byte[])
RequestReadDirAsync()
RequestRemove(System.String)
RequestRemoveAsync()
RequestMkDir(System.String)
RequestRmDir(System.String)
RequestRealPath(System.String,System.Boolean)
RequestRealPathAsync()
BeginRealPath(System.String,System.AsyncCallback,System.Object)
EndRealPath(Renci.SshNet.Sftp.SftpRealPathAsyncResult)
RequestStat(System.String,System.Boolean)
BeginStat(System.String,System.AsyncCallback,System.Object)
EndStat(Renci.SshNet.Sftp.SFtpStatAsyncResult)
RequestRename(System.String,System.String)
RequestRenameAsync()
RequestReadLink(System.String,System.Boolean)
RequestSymLink(System.String,System.String)
RequestPosixRename(System.String,System.String)
RequestStatVfs(System.String,System.Boolean)
RequestStatVfsAsync()
RequestFStatVfs(System.Byte[],System.Boolean)
HardLink(System.String,System.String)
CalculateOptimalReadLength(System.UInt32)
CalculateOptimalWriteLength(System.UInt32,System.Byte[])
GetSftpException(Renci.SshNet.Sftp.Responses.SftpStatusResponse)
HandleResponse(Renci.SshNet.Sftp.Responses.SftpResponse)