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