| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282 |
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using Renci.SshNet.Common;
- using Renci.SshNet.Sftp.Requests;
- using Renci.SshNet.Sftp.Responses;
- namespace Renci.SshNet.Sftp
- {
- /// <summary>
- /// Represents an SFTP session.
- /// </summary>
- internal sealed class SftpSession : SubsystemSession, ISftpSession
- {
- internal const int MaximumSupportedVersion = 3;
- private const int MinimumSupportedVersion = 0;
- private readonly Dictionary<uint, SftpRequest> _requests = new Dictionary<uint, SftpRequest>();
- private readonly ISftpResponseFactory _sftpResponseFactory;
- private readonly List<byte> _data = new List<byte>(32 * 1024);
- private readonly Encoding _encoding;
- private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(initialState: false);
- private IDictionary<string, string> _supportedExtensions;
- /// <summary>
- /// Gets the remote working directory.
- /// </summary>
- /// <value>
- /// The remote working directory.
- /// </value>
- public string WorkingDirectory { get; private set; }
- /// <summary>
- /// Gets the SFTP protocol version.
- /// </summary>
- /// <value>
- /// The SFTP protocol version.
- /// </value>
- public uint ProtocolVersion { get; private set; }
- private long _requestId;
- /// <summary>
- /// Gets the next request id for sftp session.
- /// </summary>
- public uint NextRequestId
- {
- get
- {
- return (uint)Interlocked.Increment(ref _requestId);
- }
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="SftpSession"/> class.
- /// </summary>
- /// <param name="session">The SSH session.</param>
- /// <param name="operationTimeout">The operation timeout.</param>
- /// <param name="encoding">The character encoding to use.</param>
- /// <param name="sftpResponseFactory">The factory to create SFTP responses.</param>
- public SftpSession(ISession session, int operationTimeout, Encoding encoding, ISftpResponseFactory sftpResponseFactory)
- : base(session, "sftp", operationTimeout)
- {
- _encoding = encoding;
- _sftpResponseFactory = sftpResponseFactory;
- }
- /// <summary>
- /// Changes the current working directory to the specified path.
- /// </summary>
- /// <param name="path">The new working directory.</param>
- public void ChangeDirectory(string path)
- {
- var fullPath = GetCanonicalPath(path);
- var handle = RequestOpenDir(fullPath);
- RequestClose(handle);
- WorkingDirectory = fullPath;
- }
- internal void SendMessage(SftpMessage sftpMessage)
- {
- var data = sftpMessage.GetBytes();
- SendData(data);
- }
- /// <summary>
- /// Resolves a given path into an absolute path on the server.
- /// </summary>
- /// <param name="path">The path to resolve.</param>
- /// <returns>
- /// The absolute path.
- /// </returns>
- public string GetCanonicalPath(string path)
- {
- var fullPath = GetFullRemotePath(path);
- var canonizedPath = string.Empty;
- var realPathFiles = RequestRealPath(fullPath, nullOnError: true);
- if (realPathFiles != null)
- {
- canonizedPath = realPathFiles[0].Key;
- }
- if (!string.IsNullOrEmpty(canonizedPath))
- {
- return canonizedPath;
- }
- // Check for special cases
- if (fullPath.EndsWith("/.", StringComparison.OrdinalIgnoreCase) ||
- fullPath.EndsWith("/..", StringComparison.OrdinalIgnoreCase) ||
- fullPath.Equals("/", StringComparison.OrdinalIgnoreCase) ||
- #if NET || NETSTANDARD2_1_OR_GREATER
- fullPath.IndexOf('/', StringComparison.OrdinalIgnoreCase) < 0)
- #else
- fullPath.IndexOf('/') < 0)
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- return fullPath;
- }
- var pathParts = fullPath.Split('/');
- #if NET || NETSTANDARD2_1_OR_GREATER
- var partialFullPath = string.Join('/', pathParts, 0, pathParts.Length - 1);
- #else
- var partialFullPath = string.Join("/", pathParts, 0, pathParts.Length - 1);
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- if (string.IsNullOrEmpty(partialFullPath))
- {
- partialFullPath = "/";
- }
- realPathFiles = RequestRealPath(partialFullPath, nullOnError: true);
- if (realPathFiles != null)
- {
- canonizedPath = realPathFiles[0].Key;
- }
- if (string.IsNullOrEmpty(canonizedPath))
- {
- return fullPath;
- }
- var slash = string.Empty;
- if (canonizedPath[canonizedPath.Length - 1] != '/')
- {
- slash = "/";
- }
- return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", canonizedPath, slash, pathParts[pathParts.Length - 1]);
- }
- /// <summary>
- /// Asynchronously resolves a given path into an absolute path on the server.
- /// </summary>
- /// <param name="path">The path to resolve.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- /// <returns>
- /// A task representing the absolute path.
- /// </returns>
- public async Task<string> GetCanonicalPathAsync(string path, CancellationToken cancellationToken)
- {
- var fullPath = GetFullRemotePath(path);
- var canonizedPath = string.Empty;
- var realPathFiles = await RequestRealPathAsync(fullPath, nullOnError: true, cancellationToken).ConfigureAwait(false);
- if (realPathFiles != null)
- {
- canonizedPath = realPathFiles[0].Key;
- }
- if (!string.IsNullOrEmpty(canonizedPath))
- {
- return canonizedPath;
- }
- // Check for special cases
- if (fullPath.EndsWith("/.", StringComparison.Ordinal) ||
- fullPath.EndsWith("/..", StringComparison.Ordinal) ||
- fullPath.Equals("/", StringComparison.Ordinal) ||
- #if NET || NETSTANDARD2_1_OR_GREATER
- fullPath.IndexOf('/', StringComparison.Ordinal) < 0)
- #else
- fullPath.IndexOf('/') < 0)
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- return fullPath;
- }
- var pathParts = fullPath.Split('/');
- #if NET || NETSTANDARD2_1_OR_GREATER
- var partialFullPath = string.Join('/', pathParts);
- #else
- var partialFullPath = string.Join("/", pathParts);
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- if (string.IsNullOrEmpty(partialFullPath))
- {
- partialFullPath = "/";
- }
- realPathFiles = await RequestRealPathAsync(partialFullPath, nullOnError: true, cancellationToken).ConfigureAwait(false);
- if (realPathFiles != null)
- {
- canonizedPath = realPathFiles[0].Key;
- }
- if (string.IsNullOrEmpty(canonizedPath))
- {
- return fullPath;
- }
- var slash = string.Empty;
- if (canonizedPath[canonizedPath.Length - 1] != '/')
- {
- slash = "/";
- }
- return canonizedPath + slash + pathParts[pathParts.Length - 1];
- }
- /// <summary>
- /// Creates an <see cref="ISftpFileReader"/> for reading the content of the file represented by a given <paramref name="handle"/>.
- /// </summary>
- /// <param name="handle">The handle of the file to read.</param>
- /// <param name="sftpSession">The SFTP session.</param>
- /// <param name="chunkSize">The maximum number of bytes to read with each chunk.</param>
- /// <param name="maxPendingReads">The maximum number of pending reads.</param>
- /// <param name="fileSize">The size of the file or <see langword="null"/> when the size could not be determined.</param>
- /// <returns>
- /// An <see cref="ISftpFileReader"/> for reading the content of the file represented by the
- /// specified <paramref name="handle"/>.
- /// </returns>
- public ISftpFileReader CreateFileReader(byte[] handle, ISftpSession sftpSession, uint chunkSize, int maxPendingReads, long? fileSize)
- {
- return new SftpFileReader(handle, sftpSession, chunkSize, maxPendingReads, fileSize);
- }
- internal string GetFullRemotePath(string path)
- {
- var fullPath = path;
- if (!string.IsNullOrEmpty(path) && path[0] != '/' && WorkingDirectory != null)
- {
- if (WorkingDirectory[WorkingDirectory.Length - 1] == '/')
- {
- fullPath = WorkingDirectory + path;
- }
- else
- {
- fullPath = WorkingDirectory + '/' + path;
- }
- }
- return fullPath;
- }
- protected override void OnChannelOpen()
- {
- SendMessage(new SftpInitRequest(MaximumSupportedVersion));
- WaitOnHandle(_sftpVersionConfirmed, OperationTimeout);
- if (ProtocolVersion is > MaximumSupportedVersion or < MinimumSupportedVersion)
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Server SFTP version {0} is not supported.", ProtocolVersion));
- }
- // Resolve current directory
- WorkingDirectory = RequestRealPath(".")[0].Key;
- }
- protected override void OnDataReceived(byte[] data)
- {
- const int packetLengthByteCount = 4;
- const int sftpMessageTypeByteCount = 1;
- const int minimumChannelDataLength = packetLengthByteCount + sftpMessageTypeByteCount;
- var offset = 0;
- var count = data.Length;
- // improve performance and reduce GC pressure by not buffering channel data if the received
- // chunk contains the complete packet data.
- //
- // for this, the buffer should be empty and the chunk should contain at least the packet length
- // and the type of the SFTP message
- if (_data.Count == 0)
- {
- while (count >= minimumChannelDataLength)
- {
- // extract packet length
- var packetDataLength = data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 |
- data[offset + 3];
- var packetTotalLength = packetDataLength + packetLengthByteCount;
- // check if complete packet data (or more) is available
- if (count >= packetTotalLength)
- {
- // load and process SFTP message
- if (!TryLoadSftpMessage(data, offset + packetLengthByteCount, packetDataLength))
- {
- return;
- }
- // remove processed bytes from the number of bytes to process as the channel
- // data we received may contain (part of) another message
- count -= packetTotalLength;
- // move offset beyond bytes we just processed
- offset += packetTotalLength;
- }
- else
- {
- // we don't have a complete message
- break;
- }
- }
- // check if there is channel data left to process or buffer
- if (count == 0)
- {
- return;
- }
- // check if we processed part of the channel data we received
- if (offset > 0)
- {
- // add (remaining) channel data to internal data holder
- var remainingChannelData = new byte[count];
- Buffer.BlockCopy(data, offset, remainingChannelData, 0, count);
- _data.AddRange(remainingChannelData);
- }
- else
- {
- // add (remaining) channel data to internal data holder
- _data.AddRange(data);
- }
- // skip further processing as we'll need a new chunk to complete the message
- return;
- }
- // add (remaining) channel data to internal data holder
- _data.AddRange(data);
- while (_data.Count >= minimumChannelDataLength)
- {
- // extract packet length
- var packetDataLength = _data[0] << 24 | _data[1] << 16 | _data[2] << 8 | _data[3];
- var packetTotalLength = packetDataLength + packetLengthByteCount;
- // check if complete packet data is available
- if (_data.Count < packetTotalLength)
- {
- // wait for complete message to arrive first
- break;
- }
- // create buffer to hold packet data
- var packetData = new byte[packetDataLength];
- // copy packet data and bytes for length to array
- _data.CopyTo(packetLengthByteCount, packetData, 0, packetDataLength);
- // remove loaded data and bytes for length from _data holder
- if (_data.Count == packetTotalLength)
- {
- // the only buffered data is the data we're processing
- _data.Clear();
- }
- else
- {
- // remove only the data we're processing
- _data.RemoveRange(0, packetTotalLength);
- }
- // load and process SFTP message
- if (!TryLoadSftpMessage(packetData, 0, packetDataLength))
- {
- break;
- }
- }
- }
- private bool TryLoadSftpMessage(byte[] packetData, int offset, int count)
- {
- // Create SFTP message
- var response = _sftpResponseFactory.Create(ProtocolVersion, packetData[offset], _encoding);
- // Load message data into it
- response.Load(packetData, offset + 1, count - 1);
- try
- {
- if (response is SftpVersionResponse versionResponse)
- {
- ProtocolVersion = versionResponse.Version;
- _supportedExtensions = versionResponse.Extentions;
- _ = _sftpVersionConfirmed.Set();
- }
- else
- {
- HandleResponse(response as SftpResponse);
- }
- return true;
- }
- catch (Exception exp)
- {
- RaiseError(exp);
- return false;
- }
- }
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (disposing)
- {
- var sftpVersionConfirmed = _sftpVersionConfirmed;
- if (sftpVersionConfirmed != null)
- {
- _sftpVersionConfirmed = null;
- sftpVersionConfirmed.Dispose();
- }
- }
- }
- private void SendRequest(SftpRequest request)
- {
- lock (_requests)
- {
- _requests.Add(request.RequestId, request);
- }
- SendMessage(request);
- }
- /// <summary>
- /// Performs SSH_FXP_OPEN request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="flags">The flags.</param>
- /// <param name="nullOnError">If set to <see langword="true"/> returns <see langword="null"/> instead of throwing an exception.</param>
- /// <returns>File handle.</returns>
- public byte[] RequestOpen(string path, Flags flags, bool nullOnError = false)
- {
- byte[] handle = null;
- SshException exception = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpOpenRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- flags,
- response =>
- {
- handle = response.Handle;
- _ = wait.Set();
- },
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (!nullOnError && exception is not null)
- {
- throw exception;
- }
- return handle;
- }
- /// <summary>
- /// Asynchronously performs a <c>SSH_FXP_OPEN</c> request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="flags">The flags.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- /// <returns>
- /// A task that represents the asynchronous <c>SSH_FXP_OPEN</c> request. The value of its
- /// <see cref="Task{Task}.Result"/> contains the file handle of the specified path.
- /// </returns>
- public async Task<byte[]> RequestOpenAsync(string path, Flags flags, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
- var tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
- #if NET || NETSTANDARD2_1_OR_GREATER
- await using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
- #else
- using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- SendRequest(new SftpOpenRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- flags,
- response => tcs.TrySetResult(response.Handle),
- response => tcs.TrySetException(GetSftpException(response))));
- return await tcs.Task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Performs SSH_FXP_OPEN request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="flags">The flags.</param>
- /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginOpen(string, Flags, AsyncCallback, object)"/> completes.</param>
- /// <param name="state">An object that contains any additional user-defined data.</param>
- /// <returns>
- /// A <see cref="SftpOpenAsyncResult"/> that represents the asynchronous call.
- /// </returns>
- public SftpOpenAsyncResult BeginOpen(string path, Flags flags, AsyncCallback callback, object state)
- {
- var asyncResult = new SftpOpenAsyncResult(callback, state);
- var request = new SftpOpenRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- flags,
- response =>
- {
- asyncResult.SetAsCompleted(response.Handle, completedSynchronously: false);
- },
- response =>
- {
- asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false);
- });
- SendRequest(request);
- return asyncResult;
- }
- /// <summary>
- /// Handles the end of an asynchronous open.
- /// </summary>
- /// <param name="asyncResult">An <see cref="SftpOpenAsyncResult"/> that represents an asynchronous call.</param>
- /// <returns>
- /// A <see cref="byte"/> array representing a file handle.
- /// </returns>
- /// <remarks>
- /// If all available data has been read, the <see cref="EndOpen(SftpOpenAsyncResult)"/> method completes
- /// immediately and returns zero bytes.
- /// </remarks>
- /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <see langword="null"/>.</exception>
- public byte[] EndOpen(SftpOpenAsyncResult asyncResult)
- {
- ThrowHelper.ThrowIfNull(asyncResult);
- if (asyncResult.EndInvokeCalled)
- {
- throw new InvalidOperationException("EndOpen has already been called.");
- }
- if (asyncResult.IsCompleted)
- {
- return asyncResult.EndInvoke();
- }
- using (var waitHandle = asyncResult.AsyncWaitHandle)
- {
- WaitOnHandle(waitHandle, OperationTimeout);
- return asyncResult.EndInvoke();
- }
- }
- /// <summary>
- /// Performs SSH_FXP_CLOSE request.
- /// </summary>
- /// <param name="handle">The handle.</param>
- public void RequestClose(byte[] handle)
- {
- SshException exception = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpCloseRequest(ProtocolVersion,
- NextRequestId,
- handle,
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- }
- /// <summary>
- /// Performs a <c>SSH_FXP_CLOSE</c> request.
- /// </summary>
- /// <param name="handle">The handle.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- /// <returns>
- /// A task that represents the asynchronous <c>SSH_FXP_CLOSE</c> request.
- /// </returns>
- public async Task RequestCloseAsync(byte[] handle, CancellationToken cancellationToken)
- {
- var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
- SendRequest(new SftpCloseRequest(ProtocolVersion,
- NextRequestId,
- handle,
- response =>
- {
- if (response.StatusCode == StatusCodes.Ok)
- {
- _ = tcs.TrySetResult(true);
- }
- else
- {
- _ = tcs.TrySetException(GetSftpException(response));
- }
- }));
- // Only check for cancellation after the SftpCloseRequest was sent
- cancellationToken.ThrowIfCancellationRequested();
- #if NET || NETSTANDARD2_1_OR_GREATER
- await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
- #else
- using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- _ = await tcs.Task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Performs SSH_FXP_CLOSE request.
- /// </summary>
- /// <param name="handle">The handle.</param>
- /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginClose(byte[], AsyncCallback, object)"/> completes.</param>
- /// <param name="state">An object that contains any additional user-defined data.</param>
- /// <returns>
- /// A <see cref="SftpCloseAsyncResult"/> that represents the asynchronous call.
- /// </returns>
- public SftpCloseAsyncResult BeginClose(byte[] handle, AsyncCallback callback, object state)
- {
- var asyncResult = new SftpCloseAsyncResult(callback, state);
- var request = new SftpCloseRequest(ProtocolVersion,
- NextRequestId,
- handle,
- response =>
- {
- asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false);
- });
- SendRequest(request);
- return asyncResult;
- }
- /// <summary>
- /// Handles the end of an asynchronous close.
- /// </summary>
- /// <param name="asyncResult">An <see cref="SftpCloseAsyncResult"/> that represents an asynchronous call.</param>
- /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <see langword="null"/>.</exception>
- public void EndClose(SftpCloseAsyncResult asyncResult)
- {
- ThrowHelper.ThrowIfNull(asyncResult);
- if (asyncResult.EndInvokeCalled)
- {
- throw new InvalidOperationException("EndClose has already been called.");
- }
- if (asyncResult.IsCompleted)
- {
- asyncResult.EndInvoke();
- }
- else
- {
- using (var waitHandle = asyncResult.AsyncWaitHandle)
- {
- WaitOnHandle(waitHandle, OperationTimeout);
- asyncResult.EndInvoke();
- }
- }
- }
- /// <summary>
- /// Begins an asynchronous read using a SSH_FXP_READ request.
- /// </summary>
- /// <param name="handle">The handle to the file to read from.</param>
- /// <param name="offset">The offset in the file to start reading from.</param>
- /// <param name="length">The number of bytes to read.</param>
- /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginRead(byte[], ulong, uint, AsyncCallback, object)"/> completes.</param>
- /// <param name="state">An object that contains any additional user-defined data.</param>
- /// <returns>
- /// A <see cref="SftpReadAsyncResult"/> that represents the asynchronous call.
- /// </returns>
- public SftpReadAsyncResult BeginRead(byte[] handle, ulong offset, uint length, AsyncCallback callback, object state)
- {
- var asyncResult = new SftpReadAsyncResult(callback, state);
- var request = new SftpReadRequest(ProtocolVersion,
- NextRequestId,
- handle,
- offset,
- length,
- response =>
- {
- asyncResult.SetAsCompleted(response.Data, completedSynchronously: false);
- },
- response =>
- {
- if (response.StatusCode != StatusCodes.Eof)
- {
- asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false);
- }
- else
- {
- asyncResult.SetAsCompleted(Array.Empty<byte>(), completedSynchronously: false);
- }
- });
- SendRequest(request);
- return asyncResult;
- }
- /// <summary>
- /// Handles the end of an asynchronous read.
- /// </summary>
- /// <param name="asyncResult">An <see cref="SftpReadAsyncResult"/> that represents an asynchronous call.</param>
- /// <returns>
- /// A <see cref="byte"/> array representing the data read.
- /// </returns>
- /// <remarks>
- /// If all available data has been read, the <see cref="EndRead(SftpReadAsyncResult)"/> method completes
- /// immediately and returns zero bytes.
- /// </remarks>
- /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <see langword="null"/>.</exception>
- public byte[] EndRead(SftpReadAsyncResult asyncResult)
- {
- ThrowHelper.ThrowIfNull(asyncResult);
- if (asyncResult.EndInvokeCalled)
- {
- throw new InvalidOperationException("EndRead has already been called.");
- }
- if (asyncResult.IsCompleted)
- {
- return asyncResult.EndInvoke();
- }
- using (var waitHandle = asyncResult.AsyncWaitHandle)
- {
- WaitOnHandle(waitHandle, OperationTimeout);
- return asyncResult.EndInvoke();
- }
- }
- /// <summary>
- /// Performs SSH_FXP_READ request.
- /// </summary>
- /// <param name="handle">The handle.</param>
- /// <param name="offset">The offset.</param>
- /// <param name="length">The length.</param>
- /// <returns>
- /// The data that was read, or an empty array when the end of the file was reached.
- /// </returns>
- public byte[] RequestRead(byte[] handle, ulong offset, uint length)
- {
- SshException exception = null;
- byte[] data = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpReadRequest(ProtocolVersion,
- NextRequestId,
- handle,
- offset,
- length,
- response =>
- {
- data = response.Data;
- _ = wait.Set();
- },
- response =>
- {
- if (response.StatusCode != StatusCodes.Eof)
- {
- exception = GetSftpException(response);
- }
- else
- {
- data = Array.Empty<byte>();
- }
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- return data;
- }
- /// <summary>
- /// Asynchronously performs a <c>SSH_FXP_READ</c> request.
- /// </summary>
- /// <param name="handle">The handle to the file to read from.</param>
- /// <param name="offset">The offset in the file to start reading from.</param>
- /// <param name="length">The number of bytes to read.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- /// <returns>
- /// A task that represents the asynchronous <c>SSH_FXP_READ</c> request. The value of
- /// its <see cref="Task{Task}.Result"/> contains the data read from the file, or an empty
- /// array when the end of the file is reached.
- /// </returns>
- public async Task<byte[]> RequestReadAsync(byte[] handle, ulong offset, uint length, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
- var tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
- #if NET || NETSTANDARD2_1_OR_GREATER
- await using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
- #else
- using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- SendRequest(new SftpReadRequest(ProtocolVersion,
- NextRequestId,
- handle,
- offset,
- length,
- response => tcs.TrySetResult(response.Data),
- response =>
- {
- if (response.StatusCode == StatusCodes.Eof)
- {
- _ = tcs.TrySetResult(Array.Empty<byte>());
- }
- else
- {
- _ = tcs.TrySetException(GetSftpException(response));
- }
- }));
- return await tcs.Task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Performs SSH_FXP_WRITE request.
- /// </summary>
- /// <param name="handle">The handle.</param>
- /// <param name="serverOffset">The the zero-based offset (in bytes) relative to the beginning of the file that the write must start at.</param>
- /// <param name="data">The buffer holding the data to write.</param>
- /// <param name="offset">the zero-based offset in <paramref name="data" /> at which to begin taking bytes to write.</param>
- /// <param name="length">The length (in bytes) of the data to write.</param>
- /// <param name="wait">The wait event handle if needed.</param>
- /// <param name="writeCompleted">The callback to invoke when the write has completed.</param>
- public void RequestWrite(byte[] handle,
- ulong serverOffset,
- byte[] data,
- int offset,
- int length,
- AutoResetEvent wait,
- Action<SftpStatusResponse> writeCompleted = null)
- {
- SshException exception = null;
- var request = new SftpWriteRequest(ProtocolVersion,
- NextRequestId,
- handle,
- serverOffset,
- data,
- offset,
- length,
- response =>
- {
- writeCompleted?.Invoke(response);
- exception = GetSftpException(response);
- if (wait != null)
- {
- _ = wait.Set();
- }
- });
- SendRequest(request);
- if (wait is not null)
- {
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- }
- /// <summary>
- /// Asynchronouly performs a <c>SSH_FXP_WRITE</c> request.
- /// </summary>
- /// <param name="handle">The handle.</param>
- /// <param name="serverOffset">The the zero-based offset (in bytes) relative to the beginning of the file that the write must start at.</param>
- /// <param name="data">The buffer holding the data to write.</param>
- /// <param name="offset">the zero-based offset in <paramref name="data" /> at which to begin taking bytes to write.</param>
- /// <param name="length">The length (in bytes) of the data to write.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- /// <returns>
- /// A task that represents the asynchronous <c>SSH_FXP_WRITE</c> request.
- /// </returns>
- public async Task RequestWriteAsync(byte[] handle, ulong serverOffset, byte[] data, int offset, int length, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
- var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
- #if NET || NETSTANDARD2_1_OR_GREATER
- await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
- #else
- using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- SendRequest(new SftpWriteRequest(ProtocolVersion,
- NextRequestId,
- handle,
- serverOffset,
- data,
- offset,
- length,
- response =>
- {
- if (response.StatusCode == StatusCodes.Ok)
- {
- _ = tcs.TrySetResult(true);
- }
- else
- {
- _ = tcs.TrySetException(GetSftpException(response));
- }
- }));
- _ = await tcs.Task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Performs SSH_FXP_LSTAT request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>
- /// File attributes.
- /// </returns>
- public SftpFileAttributes RequestLStat(string path)
- {
- SshException exception = null;
- SftpFileAttributes attributes = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpLStatRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response =>
- {
- attributes = response.Attributes;
- _ = wait.Set();
- },
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- return attributes;
- }
- /// <summary>
- /// Asynchronously performs SSH_FXP_LSTAT request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- /// <returns>
- /// A task the represents the asynchronous <c>SSH_FXP_LSTAT</c> request. The value of its
- /// <see cref="Task{SftpFileAttributes}.Result"/> contains the file attributes of the specified path.
- /// </returns>
- public async Task<SftpFileAttributes> RequestLStatAsync(string path, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
- var tcs = new TaskCompletionSource<SftpFileAttributes>(TaskCreationOptions.RunContinuationsAsynchronously);
- #if NET || NETSTANDARD2_1_OR_GREATER
- await using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileAttributes>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
- #else
- using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileAttributes>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- SendRequest(new SftpLStatRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response => tcs.TrySetResult(response.Attributes),
- response => tcs.TrySetException(GetSftpException(response))));
- return await tcs.Task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Performs SSH_FXP_LSTAT request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginLStat(string, AsyncCallback, object)"/> completes.</param>
- /// <param name="state">An object that contains any additional user-defined data.</param>
- /// <returns>
- /// A <see cref="SFtpStatAsyncResult"/> that represents the asynchronous call.
- /// </returns>
- public SFtpStatAsyncResult BeginLStat(string path, AsyncCallback callback, object state)
- {
- var asyncResult = new SFtpStatAsyncResult(callback, state);
- var request = new SftpLStatRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response =>
- {
- asyncResult.SetAsCompleted(response.Attributes, completedSynchronously: false);
- },
- response =>
- {
- asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false);
- });
- SendRequest(request);
- return asyncResult;
- }
- /// <summary>
- /// Handles the end of an asynchronous SSH_FXP_LSTAT request.
- /// </summary>
- /// <param name="asyncResult">An <see cref="SFtpStatAsyncResult"/> that represents an asynchronous call.</param>
- /// <returns>
- /// The file attributes.
- /// </returns>
- /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <see langword="null"/>.</exception>
- public SftpFileAttributes EndLStat(SFtpStatAsyncResult asyncResult)
- {
- ThrowHelper.ThrowIfNull(asyncResult);
- if (asyncResult.EndInvokeCalled)
- {
- throw new InvalidOperationException("EndLStat has already been called.");
- }
- if (asyncResult.IsCompleted)
- {
- return asyncResult.EndInvoke();
- }
- using (var waitHandle = asyncResult.AsyncWaitHandle)
- {
- WaitOnHandle(waitHandle, OperationTimeout);
- return asyncResult.EndInvoke();
- }
- }
- /// <summary>
- /// Performs SSH_FXP_FSTAT request.
- /// </summary>
- /// <param name="handle">The handle.</param>
- /// <param name="nullOnError">If set to <see langword="true"/>, returns <see langword="null"/> instead of throwing an exception.</param>
- /// <returns>
- /// File attributes.
- /// </returns>
- public SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError)
- {
- SshException exception = null;
- SftpFileAttributes attributes = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpFStatRequest(ProtocolVersion,
- NextRequestId,
- handle,
- response =>
- {
- attributes = response.Attributes;
- _ = wait.Set();
- },
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (!nullOnError && exception is not null)
- {
- throw exception;
- }
- return attributes;
- }
- /// <summary>
- /// Asynchronously performs a <c>SSH_FXP_FSTAT</c> request.
- /// </summary>
- /// <param name="handle">The handle.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- /// <returns>
- /// A task that represents the asynchronous <c>SSH_FXP_FSTAT</c> request. The value of its
- /// <see cref="Task{Task}.Result"/> contains the file attributes of the specified handle.
- /// </returns>
- public async Task<SftpFileAttributes> RequestFStatAsync(byte[] handle, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
- var tcs = new TaskCompletionSource<SftpFileAttributes>(TaskCreationOptions.RunContinuationsAsynchronously);
- #if NET || NETSTANDARD2_1_OR_GREATER
- await using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileAttributes>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
- #else
- using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileAttributes>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- SendRequest(new SftpFStatRequest(ProtocolVersion,
- NextRequestId,
- handle,
- response => tcs.TrySetResult(response.Attributes),
- response => tcs.TrySetException(GetSftpException(response))));
- return await tcs.Task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Performs SSH_FXP_SETSTAT request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="attributes">The attributes.</param>
- public void RequestSetStat(string path, SftpFileAttributes attributes)
- {
- SshException exception = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpSetStatRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- attributes,
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- }
- /// <summary>
- /// Performs SSH_FXP_FSETSTAT request.
- /// </summary>
- /// <param name="handle">The handle.</param>
- /// <param name="attributes">The attributes.</param>
- public void RequestFSetStat(byte[] handle, SftpFileAttributes attributes)
- {
- SshException exception = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpFSetStatRequest(ProtocolVersion,
- NextRequestId,
- handle,
- attributes,
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- }
- /// <summary>
- /// Performs SSH_FXP_OPENDIR request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="nullOnError">If set to <see langword="true"/>, returns <see langword="null"/> instead of throwing an exception.</param>
- /// <returns>File handle.</returns>
- public byte[] RequestOpenDir(string path, bool nullOnError = false)
- {
- SshException exception = null;
- byte[] handle = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpOpenDirRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response =>
- {
- handle = response.Handle;
- _ = wait.Set();
- },
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (!nullOnError && exception is not null)
- {
- throw exception;
- }
- return handle;
- }
- /// <summary>
- /// Asynchronously performs a <c>SSH_FXP_OPENDIR</c> request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- /// <returns>
- /// A task that represents the asynchronous <c>SSH_FXP_OPENDIR</c> request. The value of its
- /// <see cref="Task{Task}.Result"/> contains the handle of the specified path.
- /// </returns>
- public async Task<byte[]> RequestOpenDirAsync(string path, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
- var tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
- #if NET || NETSTANDARD2_1_OR_GREATER
- await using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
- #else
- using (cancellationToken.Register(s => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- SendRequest(new SftpOpenDirRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response => tcs.TrySetResult(response.Handle),
- response => tcs.TrySetException(GetSftpException(response))));
- return await tcs.Task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Performs SSH_FXP_READDIR request.
- /// </summary>
- /// <param name="handle">The handle of the directory to read.</param>
- /// <returns>
- /// A <see cref="Dictionary{TKey,TValue}"/> where the <c>key</c> is the name of a file in
- /// the directory and the <c>value</c> is the <see cref="SftpFileAttributes"/> of the file.
- /// </returns>
- public KeyValuePair<string, SftpFileAttributes>[] RequestReadDir(byte[] handle)
- {
- SshException exception = null;
- KeyValuePair<string, SftpFileAttributes>[] result = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpReadDirRequest(ProtocolVersion,
- NextRequestId,
- handle,
- response =>
- {
- result = response.Files;
- _ = wait.Set();
- },
- response =>
- {
- if (response.StatusCode != StatusCodes.Eof)
- {
- exception = GetSftpException(response);
- }
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- return result;
- }
- /// <summary>
- /// Performs a <c>SSH_FXP_READDIR</c> request.
- /// </summary>
- /// <param name="handle">The handle of the directory to read.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- /// <returns>
- /// A task that represents the asynchronous <c>SSH_FXP_READDIR</c> request. The value of its
- /// <see cref="Task{Task}.Result"/> contains a <see cref="Dictionary{TKey,TValue}"/> where the
- /// <c>key</c> is the name of a file in the directory and the <c>value</c> is the <see cref="SftpFileAttributes"/>
- /// of the file.
- /// </returns>
- public async Task<KeyValuePair<string, SftpFileAttributes>[]> RequestReadDirAsync(byte[] handle, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
- var tcs = new TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>(TaskCreationOptions.RunContinuationsAsynchronously);
- #if NET || NETSTANDARD2_1_OR_GREATER
- await using (cancellationToken.Register(s => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
- #else
- using (cancellationToken.Register(s => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- SendRequest(new SftpReadDirRequest(ProtocolVersion,
- NextRequestId,
- handle,
- response => tcs.TrySetResult(response.Files),
- response =>
- {
- if (response.StatusCode == StatusCodes.Eof)
- {
- _ = tcs.TrySetResult(null);
- }
- else
- {
- _ = tcs.TrySetException(GetSftpException(response));
- }
- }));
- return await tcs.Task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Performs SSH_FXP_REMOVE request.
- /// </summary>
- /// <param name="path">The path.</param>
- public void RequestRemove(string path)
- {
- SshException exception = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpRemoveRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- }
- /// <summary>
- /// Asynchronously performs a <c>SSH_FXP_REMOVE</c> request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- /// <returns>
- /// A task that represents the asynchronous <c>SSH_FXP_REMOVE</c> request.
- /// </returns>
- public async Task RequestRemoveAsync(string path, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
- var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
- #if NET || NETSTANDARD2_1_OR_GREATER
- await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
- #else
- using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- SendRequest(new SftpRemoveRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response =>
- {
- if (response.StatusCode == StatusCodes.Ok)
- {
- _ = tcs.TrySetResult(true);
- }
- else
- {
- _ = tcs.TrySetException(GetSftpException(response));
- }
- }));
- _ = await tcs.Task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Performs SSH_FXP_MKDIR request.
- /// </summary>
- /// <param name="path">The path.</param>
- public void RequestMkDir(string path)
- {
- SshException exception = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpMkDirRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- }
- /// <summary>
- /// Performs SSH_FXP_RMDIR request.
- /// </summary>
- /// <param name="path">The path.</param>
- public void RequestRmDir(string path)
- {
- SshException exception = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpRmDirRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- }
- /// <summary>
- /// Performs SSH_FXP_REALPATH request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="nullOnError">if set to <see langword="true"/> returns null instead of throwing an exception.</param>
- /// <returns>
- /// The absolute path.
- /// </returns>
- internal KeyValuePair<string, SftpFileAttributes>[] RequestRealPath(string path, bool nullOnError = false)
- {
- SshException exception = null;
- KeyValuePair<string, SftpFileAttributes>[] result = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpRealPathRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response =>
- {
- result = response.Files;
- _ = wait.Set();
- },
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (!nullOnError && exception is not null)
- {
- throw exception;
- }
- return result;
- }
- internal async Task<KeyValuePair<string, SftpFileAttributes>[]> RequestRealPathAsync(string path, bool nullOnError, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
- var tcs = new TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>(TaskCreationOptions.RunContinuationsAsynchronously);
- #if NET || NETSTANDARD2_1_OR_GREATER
- await using (cancellationToken.Register(s => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
- #else
- using (cancellationToken.Register(s => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- SendRequest(new SftpRealPathRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response => tcs.TrySetResult(response.Files),
- response =>
- {
- if (nullOnError)
- {
- _ = tcs.TrySetResult(null);
- }
- else
- {
- _ = tcs.TrySetException(GetSftpException(response));
- }
- }));
- return await tcs.Task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Performs SSH_FXP_REALPATH request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginRealPath(string, AsyncCallback, object)"/> completes.</param>
- /// <param name="state">An object that contains any additional user-defined data.</param>
- /// <returns>
- /// A <see cref="SftpRealPathAsyncResult"/> that represents the asynchronous call.
- /// </returns>
- public SftpRealPathAsyncResult BeginRealPath(string path, AsyncCallback callback, object state)
- {
- var asyncResult = new SftpRealPathAsyncResult(callback, state);
- var request = new SftpRealPathRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response => asyncResult.SetAsCompleted(response.Files[0].Key, completedSynchronously: false),
- response => asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false));
- SendRequest(request);
- return asyncResult;
- }
- /// <summary>
- /// Handles the end of an asynchronous SSH_FXP_REALPATH request.
- /// </summary>
- /// <param name="asyncResult">An <see cref="SftpRealPathAsyncResult"/> that represents an asynchronous call.</param>
- /// <returns>
- /// The absolute path.
- /// </returns>
- /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <see langword="null"/>.</exception>
- public string EndRealPath(SftpRealPathAsyncResult asyncResult)
- {
- ThrowHelper.ThrowIfNull(asyncResult);
- if (asyncResult.EndInvokeCalled)
- {
- throw new InvalidOperationException("EndRealPath has already been called.");
- }
- if (asyncResult.IsCompleted)
- {
- return asyncResult.EndInvoke();
- }
- using (var waitHandle = asyncResult.AsyncWaitHandle)
- {
- WaitOnHandle(waitHandle, OperationTimeout);
- return asyncResult.EndInvoke();
- }
- }
- /// <summary>
- /// Performs SSH_FXP_STAT request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="nullOnError">if set to <see langword="true"/> returns null instead of throwing an exception.</param>
- /// <returns>
- /// File attributes.
- /// </returns>
- public SftpFileAttributes RequestStat(string path, bool nullOnError = false)
- {
- SshException exception = null;
- SftpFileAttributes attributes = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpStatRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response =>
- {
- attributes = response.Attributes;
- _ = wait.Set();
- },
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (!nullOnError && exception is not null)
- {
- throw exception;
- }
- return attributes;
- }
- /// <summary>
- /// Performs SSH_FXP_STAT request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginStat(string, AsyncCallback, object)"/> completes.</param>
- /// <param name="state">An object that contains any additional user-defined data.</param>
- /// <returns>
- /// A <see cref="SFtpStatAsyncResult"/> that represents the asynchronous call.
- /// </returns>
- public SFtpStatAsyncResult BeginStat(string path, AsyncCallback callback, object state)
- {
- var asyncResult = new SFtpStatAsyncResult(callback, state);
- var request = new SftpStatRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response => asyncResult.SetAsCompleted(response.Attributes, completedSynchronously: false),
- response => asyncResult.SetAsCompleted(GetSftpException(response), completedSynchronously: false));
- SendRequest(request);
- return asyncResult;
- }
- /// <summary>
- /// Handles the end of an asynchronous stat.
- /// </summary>
- /// <param name="asyncResult">An <see cref="SFtpStatAsyncResult"/> that represents an asynchronous call.</param>
- /// <returns>
- /// The file attributes.
- /// </returns>
- /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <see langword="null"/>.</exception>
- public SftpFileAttributes EndStat(SFtpStatAsyncResult asyncResult)
- {
- ThrowHelper.ThrowIfNull(asyncResult);
- if (asyncResult.EndInvokeCalled)
- {
- throw new InvalidOperationException("EndStat has already been called.");
- }
- if (asyncResult.IsCompleted)
- {
- return asyncResult.EndInvoke();
- }
- using (var waitHandle = asyncResult.AsyncWaitHandle)
- {
- WaitOnHandle(waitHandle, OperationTimeout);
- return asyncResult.EndInvoke();
- }
- }
- /// <summary>
- /// Performs SSH_FXP_RENAME request.
- /// </summary>
- /// <param name="oldPath">The old path.</param>
- /// <param name="newPath">The new path.</param>
- public void RequestRename(string oldPath, string newPath)
- {
- if (ProtocolVersion < 2)
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_RENAME operation is not supported in {0} version that server operates in.", ProtocolVersion));
- }
- SshException exception = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpRenameRequest(ProtocolVersion,
- NextRequestId,
- oldPath,
- newPath,
- _encoding,
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- }
- /// <summary>
- /// Asynchronously performs a <c>SSH_FXP_RENAME</c> request.
- /// </summary>
- /// <param name="oldPath">The old path.</param>
- /// <param name="newPath">The new path.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- /// <returns>
- /// A task that represents the asynchronous <c>SSH_FXP_RENAME</c> request.
- /// </returns>
- public async Task RequestRenameAsync(string oldPath, string newPath, CancellationToken cancellationToken)
- {
- cancellationToken.ThrowIfCancellationRequested();
- var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
- #if NET || NETSTANDARD2_1_OR_GREATER
- await using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
- #else
- using (cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- SendRequest(new SftpRenameRequest(ProtocolVersion,
- NextRequestId,
- oldPath,
- newPath,
- _encoding,
- response =>
- {
- if (response.StatusCode == StatusCodes.Ok)
- {
- _ = tcs.TrySetResult(true);
- }
- else
- {
- _ = tcs.TrySetException(GetSftpException(response));
- }
- }));
- _ = await tcs.Task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Performs SSH_FXP_READLINK request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="nullOnError">if set to <see langword="true"/> returns <see langword="null"/> instead of throwing an exception.</param>
- /// <returns>
- /// An array of <see cref="KeyValuePair{TKey,TValue}"/> where the <c>key</c> is the name of
- /// a file and the <c>value</c> is the <see cref="SftpFileAttributes"/> of the file.
- /// </returns>
- internal KeyValuePair<string, SftpFileAttributes>[] RequestReadLink(string path, bool nullOnError = false)
- {
- if (ProtocolVersion < 3)
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_READLINK operation is not supported in {0} version that server operates in.", ProtocolVersion));
- }
- SshException exception = null;
- KeyValuePair<string, SftpFileAttributes>[] result = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpReadLinkRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response =>
- {
- result = response.Files;
- _ = wait.Set();
- },
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (!nullOnError && exception is not null)
- {
- throw exception;
- }
- return result;
- }
- /// <summary>
- /// Performs SSH_FXP_SYMLINK request.
- /// </summary>
- /// <param name="linkpath">The linkpath.</param>
- /// <param name="targetpath">The targetpath.</param>
- public void RequestSymLink(string linkpath, string targetpath)
- {
- if (ProtocolVersion < 3)
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_SYMLINK operation is not supported in {0} version that server operates in.", ProtocolVersion));
- }
- SshException exception = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new SftpSymLinkRequest(ProtocolVersion,
- NextRequestId,
- linkpath,
- targetpath,
- _encoding,
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- }
- /// <summary>
- /// Performs posix-rename@openssh.com extended request.
- /// </summary>
- /// <param name="oldPath">The old path.</param>
- /// <param name="newPath">The new path.</param>
- public void RequestPosixRename(string oldPath, string newPath)
- {
- if (ProtocolVersion < 3)
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
- }
- SshException exception = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new PosixRenameRequest(ProtocolVersion,
- NextRequestId,
- oldPath,
- newPath,
- _encoding,
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- if (!_supportedExtensions.ContainsKey(request.Name))
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));
- }
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- }
- /// <summary>
- /// Performs statvfs@openssh.com extended request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="nullOnError">if set to <see langword="true"/> [null on error].</param>
- /// <returns>
- /// A <see cref="SftpFileSystemInformation"/> for the specified path.
- /// </returns>
- public SftpFileSystemInformation RequestStatVfs(string path, bool nullOnError = false)
- {
- if (ProtocolVersion < 3)
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
- }
- SshException exception = null;
- SftpFileSystemInformation information = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new StatVfsRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response =>
- {
- information = response.GetReply<StatVfsReplyInfo>().Information;
- _ = wait.Set();
- },
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- if (!_supportedExtensions.ContainsKey(request.Name))
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));
- }
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (!nullOnError && exception is not null)
- {
- throw exception;
- }
- return information;
- }
- /// <summary>
- /// Asynchronously performs a <c>statvfs@openssh.com</c> extended request.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
- /// <returns>
- /// A task that represents the <c>statvfs@openssh.com</c> extended request. The value of its
- /// <see cref="Task{Task}.Result"/> contains the file system information for the specified
- /// path.
- /// </returns>
- public async Task<SftpFileSystemInformation> RequestStatVfsAsync(string path, CancellationToken cancellationToken)
- {
- if (ProtocolVersion < 3)
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
- }
- cancellationToken.ThrowIfCancellationRequested();
- var tcs = new TaskCompletionSource<SftpFileSystemInformation>(TaskCreationOptions.RunContinuationsAsynchronously);
- #if NET || NETSTANDARD2_1_OR_GREATER
- await using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileSystemInformation>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false).ConfigureAwait(continueOnCapturedContext: false))
- #else
- using (cancellationToken.Register(s => ((TaskCompletionSource<SftpFileSystemInformation>)s).TrySetCanceled(cancellationToken), tcs, useSynchronizationContext: false))
- #endif // NET || NETSTANDARD2_1_OR_GREATER
- {
- SendRequest(new StatVfsRequest(ProtocolVersion,
- NextRequestId,
- path,
- _encoding,
- response => tcs.TrySetResult(response.GetReply<StatVfsReplyInfo>().Information),
- response => tcs.TrySetException(GetSftpException(response))));
- return await tcs.Task.ConfigureAwait(false);
- }
- }
- /// <summary>
- /// Performs fstatvfs@openssh.com extended request.
- /// </summary>
- /// <param name="handle">The file handle.</param>
- /// <param name="nullOnError">if set to <see langword="true"/> [null on error].</param>
- /// <returns>
- /// A <see cref="SftpFileSystemInformation"/> for the specified path.
- /// </returns>
- /// <exception cref="NotSupportedException">This operation is not supported for the current SFTP protocol version.</exception>
- internal SftpFileSystemInformation RequestFStatVfs(byte[] handle, bool nullOnError = false)
- {
- if (ProtocolVersion < 3)
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
- }
- SshException exception = null;
- SftpFileSystemInformation information = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new FStatVfsRequest(ProtocolVersion,
- NextRequestId,
- handle,
- response =>
- {
- information = response.GetReply<StatVfsReplyInfo>().Information;
- _ = wait.Set();
- },
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- if (!_supportedExtensions.ContainsKey(request.Name))
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));
- }
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (!nullOnError && exception is not null)
- {
- throw exception;
- }
- return information;
- }
- /// <summary>
- /// Performs hardlink@openssh.com extended request.
- /// </summary>
- /// <param name="oldPath">The old path.</param>
- /// <param name="newPath">The new path.</param>
- internal void HardLink(string oldPath, string newPath)
- {
- if (ProtocolVersion < 3)
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
- }
- SshException exception = null;
- using (var wait = new AutoResetEvent(initialState: false))
- {
- var request = new HardLinkRequest(ProtocolVersion,
- NextRequestId,
- oldPath,
- newPath,
- response =>
- {
- exception = GetSftpException(response);
- _ = wait.Set();
- });
- if (!_supportedExtensions.ContainsKey(request.Name))
- {
- throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));
- }
- SendRequest(request);
- WaitOnHandle(wait, OperationTimeout);
- }
- if (exception is not null)
- {
- throw exception;
- }
- }
- /// <summary>
- /// Calculates the optimal size of the buffer to read data from the channel.
- /// </summary>
- /// <param name="bufferSize">The buffer size configured on the client.</param>
- /// <returns>
- /// The optimal size of the buffer to read data from the channel.
- /// </returns>
- public uint CalculateOptimalReadLength(uint bufferSize)
- {
- // a SSH_FXP_DATA message has 13 bytes of protocol fields:
- // bytes 1 to 4: packet length
- // byte 5: message type
- // bytes 6 to 9: response id
- // bytes 10 to 13: length of payload
- //
- // WinSCP uses a payload length of 32755 bytes
- //
- // most ssh servers limit the size of the payload of a SSH_MSG_CHANNEL_DATA
- // response to 16 KB; if we requested 16 KB of data, then the SSH_FXP_DATA
- // payload of the SSH_MSG_CHANNEL_DATA message would be too big (16 KB + 13 bytes), and
- // as a result, the ssh server would split this into two responses:
- // one containing 16384 bytes (13 bytes header, and 16371 bytes file data)
- // and one with the remaining 13 bytes of file data
- const uint lengthOfNonDataProtocolFields = 13u;
- var maximumPacketSize = Channel.LocalPacketSize;
- return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields;
- }
- /// <summary>
- /// Calculates the optimal size of the buffer to write data on the channel.
- /// </summary>
- /// <param name="bufferSize">The buffer size configured on the client.</param>
- /// <param name="handle">The file handle.</param>
- /// <returns>
- /// The optimal size of the buffer to write data on the channel.
- /// </returns>
- /// <remarks>
- /// Currently, we do not take the remote window size into account.
- /// </remarks>
- public uint CalculateOptimalWriteLength(uint bufferSize, byte[] handle)
- {
- // 1-4: package length of SSH_FXP_WRITE message
- // 5: message type
- // 6-9: request id
- // 10-13: handle length
- // <handle>
- // 14-21: offset
- // 22-25: data length
- /*
- * Putty uses data length of 4096 bytes
- * WinSCP uses data length of 32739 bytes (total 32768 bytes; 32739 + 25 + 4 bytes for handle)
- */
- var lengthOfNonDataProtocolFields = 25u + (uint)handle.Length;
- var maximumPacketSize = Channel.RemotePacketSize;
- return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields;
- }
- private static SshException GetSftpException(SftpStatusResponse response)
- {
- #pragma warning disable IDE0010 // Add missing cases
- switch (response.StatusCode)
- {
- case StatusCodes.Ok:
- return null;
- case StatusCodes.PermissionDenied:
- return new SftpPermissionDeniedException(response.ErrorMessage);
- case StatusCodes.NoSuchFile:
- return new SftpPathNotFoundException(response.ErrorMessage);
- default:
- return new SshException(response.ErrorMessage);
- }
- #pragma warning restore IDE0010 // Add missing cases
- }
- private void HandleResponse(SftpResponse response)
- {
- SftpRequest request;
- lock (_requests)
- {
- _ = _requests.TryGetValue(response.ResponseId, out request);
- if (request is not null)
- {
- _ = _requests.Remove(response.ResponseId);
- }
- }
- if (request is null)
- {
- throw new InvalidOperationException("Invalid response.");
- }
- request.Complete(response);
- }
- }
- }
|