SftpSession.cs 54 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522
  1. using System;
  2. using System.Text;
  3. using System.Threading;
  4. using Renci.SshNet.Common;
  5. using System.Collections.Generic;
  6. using System.Globalization;
  7. using Renci.SshNet.Sftp.Responses;
  8. using Renci.SshNet.Sftp.Requests;
  9. namespace Renci.SshNet.Sftp
  10. {
  11. internal class SftpSession : SubsystemSession, ISftpSession
  12. {
  13. private const int MaximumSupportedVersion = 3;
  14. private const int MinimumSupportedVersion = 0;
  15. private readonly Dictionary<uint, SftpRequest> _requests = new Dictionary<uint, SftpRequest>();
  16. //FIXME: obtain from SftpClient!
  17. private readonly List<byte> _data = new List<byte>(32 * 1024);
  18. private readonly IServiceFactory _serviceFactory;
  19. private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(false);
  20. private IDictionary<string, string> _supportedExtensions;
  21. /// <summary>
  22. /// Gets the remote working directory.
  23. /// </summary>
  24. /// <value>
  25. /// The remote working directory.
  26. /// </value>
  27. public string WorkingDirectory { get; private set; }
  28. /// <summary>
  29. /// Gets the SFTP protocol version.
  30. /// </summary>
  31. /// <value>
  32. /// The SFTP protocol version.
  33. /// </value>
  34. public uint ProtocolVersion { get; private set; }
  35. private long _requestId;
  36. /// <summary>
  37. /// Gets the next request id for sftp session.
  38. /// </summary>
  39. public uint NextRequestId
  40. {
  41. get
  42. {
  43. return (uint) Interlocked.Increment(ref _requestId);
  44. }
  45. }
  46. public SftpSession(ISession session, int operationTimeout, Encoding encoding, IServiceFactory serviceFactory)
  47. : base(session, "sftp", operationTimeout, encoding)
  48. {
  49. _serviceFactory = serviceFactory;
  50. }
  51. /// <summary>
  52. /// Changes the current working directory to the specified path.
  53. /// </summary>
  54. /// <param name="path">The new working directory.</param>
  55. public void ChangeDirectory(string path)
  56. {
  57. var fullPath = GetCanonicalPath(path);
  58. var handle = RequestOpenDir(fullPath);
  59. RequestClose(handle);
  60. WorkingDirectory = fullPath;
  61. }
  62. internal void SendMessage(SftpMessage sftpMessage)
  63. {
  64. var data = sftpMessage.GetBytes();
  65. SendData(data);
  66. }
  67. /// <summary>
  68. /// Resolves a given path into an absolute path on the server.
  69. /// </summary>
  70. /// <param name="path">The path to resolve.</param>
  71. /// <returns>
  72. /// The absolute path.
  73. /// </returns>
  74. public string GetCanonicalPath(string path)
  75. {
  76. var fullPath = GetFullRemotePath(path);
  77. var canonizedPath = string.Empty;
  78. var realPathFiles = RequestRealPath(fullPath, true);
  79. if (realPathFiles != null)
  80. {
  81. canonizedPath = realPathFiles[0].Key;
  82. }
  83. if (!string.IsNullOrEmpty(canonizedPath))
  84. return canonizedPath;
  85. // Check for special cases
  86. if (fullPath.EndsWith("/.", StringComparison.OrdinalIgnoreCase) ||
  87. fullPath.EndsWith("/..", StringComparison.OrdinalIgnoreCase) ||
  88. fullPath.Equals("/", StringComparison.OrdinalIgnoreCase) ||
  89. fullPath.IndexOf('/') < 0)
  90. return fullPath;
  91. var pathParts = fullPath.Split('/');
  92. var partialFullPath = string.Join("/", pathParts, 0, pathParts.Length - 1);
  93. if (string.IsNullOrEmpty(partialFullPath))
  94. partialFullPath = "/";
  95. realPathFiles = RequestRealPath(partialFullPath, true);
  96. if (realPathFiles != null)
  97. {
  98. canonizedPath = realPathFiles[0].Key;
  99. }
  100. if (string.IsNullOrEmpty(canonizedPath))
  101. {
  102. return fullPath;
  103. }
  104. var slash = string.Empty;
  105. if (canonizedPath[canonizedPath.Length - 1] != '/')
  106. slash = "/";
  107. return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", canonizedPath, slash, pathParts[pathParts.Length - 1]);
  108. }
  109. public ISftpFileReader CreateFileReader(byte[] handle, ISftpSession sftpSession, uint chunkSize, int maxPendingReads, long? fileSize)
  110. {
  111. return new SftpFileReader(handle, sftpSession, chunkSize, maxPendingReads, fileSize);
  112. }
  113. internal string GetFullRemotePath(string path)
  114. {
  115. var fullPath = path;
  116. if (!string.IsNullOrEmpty(path) && path[0] != '/' && WorkingDirectory != null)
  117. {
  118. if (WorkingDirectory[WorkingDirectory.Length - 1] == '/')
  119. {
  120. fullPath = string.Format(CultureInfo.InvariantCulture, "{0}{1}", WorkingDirectory, path);
  121. }
  122. else
  123. {
  124. fullPath = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", WorkingDirectory, path);
  125. }
  126. }
  127. return fullPath;
  128. }
  129. protected override void OnChannelOpen()
  130. {
  131. SendMessage(new SftpInitRequest(MaximumSupportedVersion));
  132. WaitOnHandle(_sftpVersionConfirmed, OperationTimeout);
  133. if (ProtocolVersion > MaximumSupportedVersion || ProtocolVersion < MinimumSupportedVersion)
  134. {
  135. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Server SFTP version {0} is not supported.", ProtocolVersion));
  136. }
  137. // Resolve current directory
  138. WorkingDirectory = RequestRealPath(".")[0].Key;
  139. }
  140. protected override void OnDataReceived(byte[] data)
  141. {
  142. const int packetLengthByteCount = 4;
  143. const int sftpMessageTypeByteCount = 1;
  144. const int minimumChannelDataLength = packetLengthByteCount + sftpMessageTypeByteCount;
  145. var offset = 0;
  146. var count = data.Length;
  147. // improve performance and reduce GC pressure by not buffering channel data if the received
  148. // chunk contains the complete packet data.
  149. //
  150. // for this, the buffer should be empty and the chunk should contain at least the packet length
  151. // and the type of the SFTP message
  152. if (_data.Count == 0)
  153. {
  154. while (count >= minimumChannelDataLength)
  155. {
  156. // extract packet length
  157. var packetDataLength = data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 |
  158. data[offset + 3];
  159. var packetTotalLength = packetDataLength + packetLengthByteCount;
  160. // check if complete packet data (or more) is available
  161. if (count >= packetTotalLength)
  162. {
  163. // load and process SFTP message
  164. if (!TryLoadSftpMessage(data, offset + packetLengthByteCount, packetDataLength))
  165. {
  166. return;
  167. }
  168. // remove processed bytes from the number of bytes to process as the channel
  169. // data we received may contain (part of) another message
  170. count -= packetTotalLength;
  171. // move offset beyond bytes we just processed
  172. offset += packetTotalLength;
  173. }
  174. else
  175. {
  176. // we don't have a complete message
  177. break;
  178. }
  179. }
  180. // check if there is channel data left to process or buffer
  181. if (count == 0)
  182. {
  183. return;
  184. }
  185. // check if we processed part of the channel data we received
  186. if (offset > 0)
  187. {
  188. // add (remaining) channel data to internal data holder
  189. var remainingChannelData = new byte[count];
  190. Buffer.BlockCopy(data, offset, remainingChannelData, 0, count);
  191. _data.AddRange(remainingChannelData);
  192. }
  193. else
  194. {
  195. // add (remaining) channel data to internal data holder
  196. _data.AddRange(data);
  197. }
  198. // skip further processing as we'll need a new chunk to complete the message
  199. return;
  200. }
  201. // add (remaining) channel data to internal data holder
  202. _data.AddRange(data);
  203. while (_data.Count >= minimumChannelDataLength)
  204. {
  205. // extract packet length
  206. var packetDataLength = _data[0] << 24 | _data[1] << 16 | _data[2] << 8 | _data[3];
  207. var packetTotalLength = packetDataLength + packetLengthByteCount;
  208. // check if complete packet data is available
  209. if (_data.Count < packetTotalLength)
  210. {
  211. // wait for complete message to arrive first
  212. break;
  213. }
  214. // create buffer to hold packet data
  215. var packetData = new byte[packetDataLength];
  216. // copy packet data and bytes for length to array
  217. _data.CopyTo(packetLengthByteCount, packetData, 0, packetDataLength);
  218. // remove loaded data and bytes for length from _data holder
  219. if (_data.Count == packetTotalLength)
  220. {
  221. // the only buffered data is the data we're processing
  222. _data.Clear();
  223. }
  224. else
  225. {
  226. // remove only the data we're processing
  227. _data.RemoveRange(0, packetTotalLength);
  228. }
  229. // load and process SFTP message
  230. if (!TryLoadSftpMessage(packetData, 0, packetDataLength))
  231. {
  232. break;
  233. }
  234. }
  235. }
  236. private bool TryLoadSftpMessage(byte[] packetData, int offset, int count)
  237. {
  238. // Load SFTP Message and handle it
  239. var response = SftpMessage.Load(ProtocolVersion, packetData, offset, count, Encoding);
  240. try
  241. {
  242. var versionResponse = response as SftpVersionResponse;
  243. if (versionResponse != null)
  244. {
  245. ProtocolVersion = versionResponse.Version;
  246. _supportedExtensions = versionResponse.Extentions;
  247. _sftpVersionConfirmed.Set();
  248. }
  249. else
  250. {
  251. HandleResponse(response as SftpResponse);
  252. }
  253. return true;
  254. }
  255. catch (Exception exp)
  256. {
  257. RaiseError(exp);
  258. return false;
  259. }
  260. }
  261. protected override void Dispose(bool disposing)
  262. {
  263. base.Dispose(disposing);
  264. if (disposing)
  265. {
  266. var sftpVersionConfirmed = _sftpVersionConfirmed;
  267. if (sftpVersionConfirmed != null)
  268. {
  269. _sftpVersionConfirmed = null;
  270. sftpVersionConfirmed.Dispose();
  271. }
  272. }
  273. }
  274. private void SendRequest(SftpRequest request)
  275. {
  276. lock (_requests)
  277. {
  278. _requests.Add(request.RequestId, request);
  279. }
  280. SendMessage(request);
  281. }
  282. #region SFTP API functions
  283. /// <summary>
  284. /// Performs SSH_FXP_OPEN request
  285. /// </summary>
  286. /// <param name="path">The path.</param>
  287. /// <param name="flags">The flags.</param>
  288. /// <param name="nullOnError">if set to <c>true</c> returns <c>null</c> instead of throwing an exception.</param>
  289. /// <returns>File handle.</returns>
  290. public byte[] RequestOpen(string path, Flags flags, bool nullOnError = false)
  291. {
  292. byte[] handle = null;
  293. SshException exception = null;
  294. using (var wait = new AutoResetEvent(false))
  295. {
  296. var request = new SftpOpenRequest(ProtocolVersion, NextRequestId, path, Encoding, flags,
  297. response =>
  298. {
  299. handle = response.Handle;
  300. wait.Set();
  301. },
  302. response =>
  303. {
  304. exception = GetSftpException(response);
  305. wait.Set();
  306. });
  307. SendRequest(request);
  308. WaitOnHandle(wait, OperationTimeout);
  309. }
  310. if (!nullOnError && exception != null)
  311. {
  312. throw exception;
  313. }
  314. return handle;
  315. }
  316. /// <summary>
  317. /// Performs SSH_FXP_OPEN request
  318. /// </summary>
  319. /// <param name="path">The path.</param>
  320. /// <param name="flags">The flags.</param>
  321. /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginOpen(string, Flags, AsyncCallback, object)"/> completes.</param>
  322. /// <param name="state">An object that contains any additional user-defined data.</param>
  323. /// <returns>
  324. /// A <see cref="SftpOpenAsyncResult"/> that represents the asynchronous call.
  325. /// </returns>
  326. public SftpOpenAsyncResult BeginOpen(string path, Flags flags, AsyncCallback callback, object state)
  327. {
  328. var asyncResult = new SftpOpenAsyncResult(callback, state);
  329. var request = new SftpOpenRequest(ProtocolVersion, NextRequestId, path, Encoding, flags,
  330. response =>
  331. {
  332. asyncResult.SetAsCompleted(response.Handle, false);
  333. },
  334. response =>
  335. {
  336. asyncResult.SetAsCompleted(GetSftpException(response), false);
  337. });
  338. SendRequest(request);
  339. return asyncResult;
  340. }
  341. /// <summary>
  342. /// Handles the end of an asynchronous read.
  343. /// </summary>
  344. /// <param name="asyncResult">An <see cref="SftpOpenAsyncResult"/> that represents an asynchronous call.</param>
  345. /// <returns>
  346. /// A <see cref="byte"/> array representing a file handle.
  347. /// </returns>
  348. /// <remarks>
  349. /// If all available data has been read, the <see cref="EndOpen(SftpOpenAsyncResult)"/> method completes
  350. /// immediately and returns zero bytes.
  351. /// </remarks>
  352. /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <c>null</c>.</exception>
  353. public byte[] EndOpen(SftpOpenAsyncResult asyncResult)
  354. {
  355. if (asyncResult == null)
  356. throw new ArgumentNullException("asyncResult");
  357. if (asyncResult.EndInvokeCalled)
  358. throw new InvalidOperationException("EndOpen has already been called.");
  359. using (var waitHandle = asyncResult.AsyncWaitHandle)
  360. {
  361. WaitOnHandle(waitHandle, OperationTimeout);
  362. return asyncResult.EndInvoke();
  363. }
  364. }
  365. /// <summary>
  366. /// Performs SSH_FXP_CLOSE request.
  367. /// </summary>
  368. /// <param name="handle">The handle.</param>
  369. public void RequestClose(byte[] handle)
  370. {
  371. SshException exception = null;
  372. using (var wait = new AutoResetEvent(false))
  373. {
  374. var request = new SftpCloseRequest(ProtocolVersion, NextRequestId, handle,
  375. response =>
  376. {
  377. exception = GetSftpException(response);
  378. wait.Set();
  379. });
  380. SendRequest(request);
  381. WaitOnHandle(wait, OperationTimeout);
  382. }
  383. if (exception != null)
  384. {
  385. throw exception;
  386. }
  387. }
  388. /// <summary>
  389. /// Begins an asynchronous read using a SSH_FXP_READ request.
  390. /// </summary>
  391. /// <param name="handle">The handle to the file to read from.</param>
  392. /// <param name="offset">The offset in the file to start reading from.</param>
  393. /// <param name="length">The number of bytes to read.</param>
  394. /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginRead(byte[], ulong, uint, AsyncCallback, object)"/> completes.</param>
  395. /// <param name="state">An object that contains any additional user-defined data.</param>
  396. /// <returns>
  397. /// A <see cref="SftpReadAsyncResult"/> that represents the asynchronous call.
  398. /// </returns>
  399. public SftpReadAsyncResult BeginRead(byte[] handle, ulong offset, uint length, AsyncCallback callback, object state)
  400. {
  401. var asyncResult = new SftpReadAsyncResult(callback, state);
  402. var request = new SftpReadRequest(ProtocolVersion, NextRequestId, handle, offset, length,
  403. response =>
  404. {
  405. asyncResult.SetAsCompleted(response.Data, false);
  406. },
  407. response =>
  408. {
  409. if (response.StatusCode != StatusCodes.Eof)
  410. {
  411. asyncResult.SetAsCompleted(GetSftpException(response), false);
  412. }
  413. else
  414. {
  415. asyncResult.SetAsCompleted(Array<byte>.Empty, false);
  416. }
  417. });
  418. SendRequest(request);
  419. return asyncResult;
  420. }
  421. /// <summary>
  422. /// Handles the end of an asynchronous read.
  423. /// </summary>
  424. /// <param name="asyncResult">An <see cref="SftpReadAsyncResult"/> that represents an asynchronous call.</param>
  425. /// <returns>
  426. /// A <see cref="byte"/> array representing the data read.
  427. /// </returns>
  428. /// <remarks>
  429. /// If all available data has been read, the <see cref="EndRead(SftpReadAsyncResult)"/> method completes
  430. /// immediately and returns zero bytes.
  431. /// </remarks>
  432. /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <c>null</c>.</exception>
  433. public byte[] EndRead(SftpReadAsyncResult asyncResult)
  434. {
  435. if (asyncResult == null)
  436. throw new ArgumentNullException("asyncResult");
  437. if (asyncResult.EndInvokeCalled)
  438. throw new InvalidOperationException("EndRead has already been called.");
  439. using (var waitHandle = asyncResult.AsyncWaitHandle)
  440. {
  441. WaitOnHandle(waitHandle, OperationTimeout);
  442. return asyncResult.EndInvoke();
  443. }
  444. }
  445. /// <summary>
  446. /// Performs SSH_FXP_READ request.
  447. /// </summary>
  448. /// <param name="handle">The handle.</param>
  449. /// <param name="offset">The offset.</param>
  450. /// <param name="length">The length.</param>
  451. /// <returns>data array; null if EOF</returns>
  452. public byte[] RequestRead(byte[] handle, ulong offset, uint length)
  453. {
  454. SshException exception = null;
  455. byte[] data = null;
  456. using (var wait = new AutoResetEvent(false))
  457. {
  458. var request = new SftpReadRequest(ProtocolVersion, NextRequestId, handle, offset, length,
  459. response =>
  460. {
  461. data = response.Data;
  462. wait.Set();
  463. },
  464. response =>
  465. {
  466. if (response.StatusCode != StatusCodes.Eof)
  467. {
  468. exception = GetSftpException(response);
  469. }
  470. else
  471. {
  472. data = Array<byte>.Empty;
  473. }
  474. wait.Set();
  475. });
  476. SendRequest(request);
  477. WaitOnHandle(wait, OperationTimeout);
  478. }
  479. if (exception != null)
  480. {
  481. throw exception;
  482. }
  483. return data;
  484. }
  485. /// <summary>
  486. /// Performs SSH_FXP_WRITE request.
  487. /// </summary>
  488. /// <param name="handle">The handle.</param>
  489. /// <param name="serverOffset">The the zero-based offset (in bytes) relative to the beginning of the file that the write must start at.</param>
  490. /// <param name="data">The buffer holding the data to write.</param>
  491. /// <param name="offset">the zero-based offset in <paramref name="data" /> at which to begin taking bytes to write.</param>
  492. /// <param name="length">The length (in bytes) of the data to write.</param>
  493. /// <param name="wait">The wait event handle if needed.</param>
  494. /// <param name="writeCompleted">The callback to invoke when the write has completed.</param>
  495. public void RequestWrite(byte[] handle,
  496. ulong serverOffset,
  497. byte[] data,
  498. int offset,
  499. int length,
  500. AutoResetEvent wait,
  501. Action<SftpStatusResponse> writeCompleted = null)
  502. {
  503. SshException exception = null;
  504. var request = new SftpWriteRequest(ProtocolVersion, NextRequestId, handle, serverOffset, data, offset,
  505. length, response =>
  506. {
  507. if (writeCompleted != null)
  508. {
  509. writeCompleted(response);
  510. }
  511. exception = GetSftpException(response);
  512. if (wait != null)
  513. wait.Set();
  514. });
  515. SendRequest(request);
  516. if (wait != null)
  517. WaitOnHandle(wait, OperationTimeout);
  518. if (exception != null)
  519. {
  520. throw exception;
  521. }
  522. }
  523. /// <summary>
  524. /// Performs SSH_FXP_LSTAT request.
  525. /// </summary>
  526. /// <param name="path">The path.</param>
  527. /// <returns>
  528. /// File attributes
  529. /// </returns>
  530. public SftpFileAttributes RequestLStat(string path)
  531. {
  532. SshException exception = null;
  533. SftpFileAttributes attributes = null;
  534. using (var wait = new AutoResetEvent(false))
  535. {
  536. var request = new SftpLStatRequest(ProtocolVersion, NextRequestId, path, Encoding,
  537. response =>
  538. {
  539. attributes = response.Attributes;
  540. wait.Set();
  541. },
  542. response =>
  543. {
  544. exception = GetSftpException(response);
  545. wait.Set();
  546. });
  547. SendRequest(request);
  548. WaitOnHandle(wait, OperationTimeout);
  549. }
  550. if (exception != null)
  551. {
  552. throw exception;
  553. }
  554. return attributes;
  555. }
  556. /// <summary>
  557. /// Performs SSH_FXP_LSTAT request.
  558. /// </summary>
  559. /// <param name="path">The path.</param>
  560. /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginLStat(string, AsyncCallback, object)"/> completes.</param>
  561. /// <param name="state">An object that contains any additional user-defined data.</param>
  562. /// <returns>
  563. /// A <see cref="SFtpStatAsyncResult"/> that represents the asynchronous call.
  564. /// </returns>
  565. public SFtpStatAsyncResult BeginLStat(string path, AsyncCallback callback, object state)
  566. {
  567. var asyncResult = new SFtpStatAsyncResult(callback, state);
  568. var request = new SftpLStatRequest(ProtocolVersion, NextRequestId, path, Encoding,
  569. response =>
  570. {
  571. asyncResult.SetAsCompleted(response.Attributes, false);
  572. },
  573. response =>
  574. {
  575. asyncResult.SetAsCompleted(GetSftpException(response), false);
  576. });
  577. SendRequest(request);
  578. return asyncResult;
  579. }
  580. /// <summary>
  581. /// Handles the end of an asynchronous SSH_FXP_LSTAT request.
  582. /// </summary>
  583. /// <param name="asyncResult">An <see cref="SFtpStatAsyncResult"/> that represents an asynchronous call.</param>
  584. /// <returns>
  585. /// The file attributes.
  586. /// </returns>
  587. /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <c>null</c>.</exception>
  588. public SftpFileAttributes EndLStat(SFtpStatAsyncResult asyncResult)
  589. {
  590. if (asyncResult == null)
  591. throw new ArgumentNullException("asyncResult");
  592. if (asyncResult.EndInvokeCalled)
  593. throw new InvalidOperationException("EndLStat has already been called.");
  594. using (var waitHandle = asyncResult.AsyncWaitHandle)
  595. {
  596. WaitOnHandle(waitHandle, OperationTimeout);
  597. return asyncResult.EndInvoke();
  598. }
  599. }
  600. /// <summary>
  601. /// Performs SSH_FXP_FSTAT request.
  602. /// </summary>
  603. /// <param name="handle">The handle.</param>
  604. /// <param name="nullOnError">if set to <c>true</c> returns <c>null</c> instead of throwing an exception.</param>
  605. /// <returns>
  606. /// File attributes
  607. /// </returns>
  608. public SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError)
  609. {
  610. SshException exception = null;
  611. SftpFileAttributes attributes = null;
  612. using (var wait = new AutoResetEvent(false))
  613. {
  614. var request = new SftpFStatRequest(ProtocolVersion, NextRequestId, handle,
  615. response =>
  616. {
  617. attributes = response.Attributes;
  618. wait.Set();
  619. },
  620. response =>
  621. {
  622. exception = GetSftpException(response);
  623. wait.Set();
  624. });
  625. SendRequest(request);
  626. WaitOnHandle(wait, OperationTimeout);
  627. }
  628. if (exception != null && !nullOnError)
  629. {
  630. throw exception;
  631. }
  632. return attributes;
  633. }
  634. /// <summary>
  635. /// Performs SSH_FXP_SETSTAT request.
  636. /// </summary>
  637. /// <param name="path">The path.</param>
  638. /// <param name="attributes">The attributes.</param>
  639. public void RequestSetStat(string path, SftpFileAttributes attributes)
  640. {
  641. SshException exception = null;
  642. using (var wait = new AutoResetEvent(false))
  643. {
  644. var request = new SftpSetStatRequest(ProtocolVersion, NextRequestId, path, Encoding, attributes,
  645. response =>
  646. {
  647. exception = GetSftpException(response);
  648. wait.Set();
  649. });
  650. SendRequest(request);
  651. WaitOnHandle(wait, OperationTimeout);
  652. }
  653. if (exception != null)
  654. {
  655. throw exception;
  656. }
  657. }
  658. /// <summary>
  659. /// Performs SSH_FXP_FSETSTAT request.
  660. /// </summary>
  661. /// <param name="handle">The handle.</param>
  662. /// <param name="attributes">The attributes.</param>
  663. public void RequestFSetStat(byte[] handle, SftpFileAttributes attributes)
  664. {
  665. SshException exception = null;
  666. using (var wait = new AutoResetEvent(false))
  667. {
  668. var request = new SftpFSetStatRequest(ProtocolVersion, NextRequestId, handle, attributes,
  669. response =>
  670. {
  671. exception = GetSftpException(response);
  672. wait.Set();
  673. });
  674. SendRequest(request);
  675. WaitOnHandle(wait, OperationTimeout);
  676. }
  677. if (exception != null)
  678. {
  679. throw exception;
  680. }
  681. }
  682. /// <summary>
  683. /// Performs SSH_FXP_OPENDIR request
  684. /// </summary>
  685. /// <param name="path">The path.</param>
  686. /// <param name="nullOnError">if set to <c>true</c> returns <c>null</c> instead of throwing an exception.</param>
  687. /// <returns>File handle.</returns>
  688. public byte[] RequestOpenDir(string path, bool nullOnError = false)
  689. {
  690. SshException exception = null;
  691. byte[] handle = null;
  692. using (var wait = new AutoResetEvent(false))
  693. {
  694. var request = new SftpOpenDirRequest(ProtocolVersion, NextRequestId, path, Encoding,
  695. response =>
  696. {
  697. handle = response.Handle;
  698. wait.Set();
  699. },
  700. response =>
  701. {
  702. exception = GetSftpException(response);
  703. wait.Set();
  704. });
  705. SendRequest(request);
  706. WaitOnHandle(wait, OperationTimeout);
  707. }
  708. if (!nullOnError && exception != null)
  709. {
  710. throw exception;
  711. }
  712. return handle;
  713. }
  714. /// <summary>
  715. /// Performs SSH_FXP_READDIR request
  716. /// </summary>
  717. /// <param name="handle">The handle.</param>
  718. /// <returns></returns>
  719. public KeyValuePair<string, SftpFileAttributes>[] RequestReadDir(byte[] handle)
  720. {
  721. SshException exception = null;
  722. KeyValuePair<string, SftpFileAttributes>[] result = null;
  723. using (var wait = new AutoResetEvent(false))
  724. {
  725. var request = new SftpReadDirRequest(ProtocolVersion, NextRequestId, handle,
  726. response =>
  727. {
  728. result = response.Files;
  729. wait.Set();
  730. },
  731. response =>
  732. {
  733. if (response.StatusCode != StatusCodes.Eof)
  734. {
  735. exception = GetSftpException(response);
  736. }
  737. wait.Set();
  738. });
  739. SendRequest(request);
  740. WaitOnHandle(wait, OperationTimeout);
  741. }
  742. if (exception != null)
  743. {
  744. throw exception;
  745. }
  746. return result;
  747. }
  748. /// <summary>
  749. /// Performs SSH_FXP_REMOVE request.
  750. /// </summary>
  751. /// <param name="path">The path.</param>
  752. public void RequestRemove(string path)
  753. {
  754. SshException exception = null;
  755. using (var wait = new AutoResetEvent(false))
  756. {
  757. var request = new SftpRemoveRequest(ProtocolVersion, NextRequestId, path, Encoding,
  758. response =>
  759. {
  760. exception = GetSftpException(response);
  761. wait.Set();
  762. });
  763. SendRequest(request);
  764. WaitOnHandle(wait, OperationTimeout);
  765. }
  766. if (exception != null)
  767. {
  768. throw exception;
  769. }
  770. }
  771. /// <summary>
  772. /// Performs SSH_FXP_MKDIR request.
  773. /// </summary>
  774. /// <param name="path">The path.</param>
  775. public void RequestMkDir(string path)
  776. {
  777. SshException exception = null;
  778. using (var wait = new AutoResetEvent(false))
  779. {
  780. var request = new SftpMkDirRequest(ProtocolVersion, NextRequestId, path, Encoding,
  781. response =>
  782. {
  783. exception = GetSftpException(response);
  784. wait.Set();
  785. });
  786. SendRequest(request);
  787. WaitOnHandle(wait, OperationTimeout);
  788. }
  789. if (exception != null)
  790. {
  791. throw exception;
  792. }
  793. }
  794. /// <summary>
  795. /// Performs SSH_FXP_RMDIR request.
  796. /// </summary>
  797. /// <param name="path">The path.</param>
  798. public void RequestRmDir(string path)
  799. {
  800. SshException exception = null;
  801. using (var wait = new AutoResetEvent(false))
  802. {
  803. var request = new SftpRmDirRequest(ProtocolVersion, NextRequestId, path, Encoding,
  804. response =>
  805. {
  806. exception = GetSftpException(response);
  807. wait.Set();
  808. });
  809. SendRequest(request);
  810. WaitOnHandle(wait, OperationTimeout);
  811. }
  812. if (exception != null)
  813. {
  814. throw exception;
  815. }
  816. }
  817. /// <summary>
  818. /// Performs SSH_FXP_REALPATH request
  819. /// </summary>
  820. /// <param name="path">The path.</param>
  821. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  822. /// <returns>
  823. /// The absolute path.
  824. /// </returns>
  825. internal KeyValuePair<string, SftpFileAttributes>[] RequestRealPath(string path, bool nullOnError = false)
  826. {
  827. SshException exception = null;
  828. KeyValuePair<string, SftpFileAttributes>[] result = null;
  829. using (var wait = new AutoResetEvent(false))
  830. {
  831. var request = new SftpRealPathRequest(ProtocolVersion, NextRequestId, path, Encoding,
  832. response =>
  833. {
  834. result = response.Files;
  835. wait.Set();
  836. },
  837. response =>
  838. {
  839. exception = GetSftpException(response);
  840. wait.Set();
  841. });
  842. SendRequest(request);
  843. WaitOnHandle(wait, OperationTimeout);
  844. }
  845. if (!nullOnError && exception != null)
  846. {
  847. throw exception;
  848. }
  849. return result;
  850. }
  851. /// <summary>
  852. /// Performs SSH_FXP_REALPATH request.
  853. /// </summary>
  854. /// <param name="path">The path.</param>
  855. /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginRealPath(string, AsyncCallback, object)"/> completes.</param>
  856. /// <param name="state">An object that contains any additional user-defined data.</param>
  857. /// <returns>
  858. /// A <see cref="SftpRealPathAsyncResult"/> that represents the asynchronous call.
  859. /// </returns>
  860. public SftpRealPathAsyncResult BeginRealPath(string path, AsyncCallback callback, object state)
  861. {
  862. var asyncResult = new SftpRealPathAsyncResult(callback, state);
  863. var request = new SftpRealPathRequest(ProtocolVersion, NextRequestId, path, Encoding,
  864. response =>
  865. {
  866. asyncResult.SetAsCompleted(response.Files[0].Key, false);
  867. },
  868. response =>
  869. {
  870. asyncResult.SetAsCompleted(GetSftpException(response), false);
  871. });
  872. SendRequest(request);
  873. return asyncResult;
  874. }
  875. /// <summary>
  876. /// Handles the end of an asynchronous SSH_FXP_REALPATH request.
  877. /// </summary>
  878. /// <param name="asyncResult">An <see cref="SftpRealPathAsyncResult"/> that represents an asynchronous call.</param>
  879. /// <returns>
  880. /// The absolute path.
  881. /// </returns>
  882. /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <c>null</c>.</exception>
  883. public string EndRealPath(SftpRealPathAsyncResult asyncResult)
  884. {
  885. if (asyncResult == null)
  886. throw new ArgumentNullException("asyncResult");
  887. if (asyncResult.EndInvokeCalled)
  888. throw new InvalidOperationException("EndRealPath has already been called.");
  889. using (var waitHandle = asyncResult.AsyncWaitHandle)
  890. {
  891. WaitOnHandle(waitHandle, OperationTimeout);
  892. return asyncResult.EndInvoke();
  893. }
  894. }
  895. /// <summary>
  896. /// Performs SSH_FXP_STAT request.
  897. /// </summary>
  898. /// <param name="path">The path.</param>
  899. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  900. /// <returns>
  901. /// File attributes
  902. /// </returns>
  903. public SftpFileAttributes RequestStat(string path, bool nullOnError = false)
  904. {
  905. SshException exception = null;
  906. SftpFileAttributes attributes = null;
  907. using (var wait = new AutoResetEvent(false))
  908. {
  909. var request = new SftpStatRequest(ProtocolVersion, NextRequestId, path, Encoding,
  910. response =>
  911. {
  912. attributes = response.Attributes;
  913. wait.Set();
  914. },
  915. response =>
  916. {
  917. exception = GetSftpException(response);
  918. wait.Set();
  919. });
  920. SendRequest(request);
  921. WaitOnHandle(wait, OperationTimeout);
  922. }
  923. if (!nullOnError && exception != null)
  924. {
  925. throw exception;
  926. }
  927. return attributes;
  928. }
  929. /// <summary>
  930. /// Performs SSH_FXP_STAT request
  931. /// </summary>
  932. /// <param name="path">The path.</param>
  933. /// <param name="callback">The <see cref="AsyncCallback"/> delegate that is executed when <see cref="BeginStat(string, AsyncCallback, object)"/> completes.</param>
  934. /// <param name="state">An object that contains any additional user-defined data.</param>
  935. /// <returns>
  936. /// A <see cref="SFtpStatAsyncResult"/> that represents the asynchronous call.
  937. /// </returns>
  938. public SFtpStatAsyncResult BeginStat(string path, AsyncCallback callback, object state)
  939. {
  940. var asyncResult = new SFtpStatAsyncResult(callback, state);
  941. var request = new SftpStatRequest(ProtocolVersion, NextRequestId, path, Encoding,
  942. response =>
  943. {
  944. asyncResult.SetAsCompleted(response.Attributes, false);
  945. },
  946. response =>
  947. {
  948. asyncResult.SetAsCompleted(GetSftpException(response), false);
  949. });
  950. SendRequest(request);
  951. return asyncResult;
  952. }
  953. /// <summary>
  954. /// Handles the end of an asynchronous read.
  955. /// </summary>
  956. /// <param name="asyncResult">An <see cref="SFtpStatAsyncResult"/> that represents an asynchronous call.</param>
  957. /// <returns>
  958. /// The file attributes.
  959. /// </returns>
  960. /// <exception cref="ArgumentNullException"><paramref name="asyncResult"/> is <c>null</c>.</exception>
  961. public SftpFileAttributes EndStat(SFtpStatAsyncResult asyncResult)
  962. {
  963. if (asyncResult == null)
  964. throw new ArgumentNullException("asyncResult");
  965. if (asyncResult.EndInvokeCalled)
  966. throw new InvalidOperationException("EndStat has already been called.");
  967. using (var waitHandle = asyncResult.AsyncWaitHandle)
  968. {
  969. WaitOnHandle(waitHandle, OperationTimeout);
  970. return asyncResult.EndInvoke();
  971. }
  972. }
  973. /// <summary>
  974. /// Performs SSH_FXP_RENAME request.
  975. /// </summary>
  976. /// <param name="oldPath">The old path.</param>
  977. /// <param name="newPath">The new path.</param>
  978. public void RequestRename(string oldPath, string newPath)
  979. {
  980. if (ProtocolVersion < 2)
  981. {
  982. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_RENAME operation is not supported in {0} version that server operates in.", ProtocolVersion));
  983. }
  984. SshException exception = null;
  985. using (var wait = new AutoResetEvent(false))
  986. {
  987. var request = new SftpRenameRequest(ProtocolVersion, NextRequestId, oldPath, newPath, Encoding,
  988. response =>
  989. {
  990. exception = GetSftpException(response);
  991. wait.Set();
  992. });
  993. SendRequest(request);
  994. WaitOnHandle(wait, OperationTimeout);
  995. }
  996. if (exception != null)
  997. {
  998. throw exception;
  999. }
  1000. }
  1001. /// <summary>
  1002. /// Performs SSH_FXP_READLINK request.
  1003. /// </summary>
  1004. /// <param name="path">The path.</param>
  1005. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  1006. /// <returns></returns>
  1007. internal KeyValuePair<string, SftpFileAttributes>[] RequestReadLink(string path, bool nullOnError = false)
  1008. {
  1009. if (ProtocolVersion < 3)
  1010. {
  1011. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_READLINK operation is not supported in {0} version that server operates in.", ProtocolVersion));
  1012. }
  1013. SshException exception = null;
  1014. KeyValuePair<string, SftpFileAttributes>[] result = null;
  1015. using (var wait = new AutoResetEvent(false))
  1016. {
  1017. var request = new SftpReadLinkRequest(ProtocolVersion, NextRequestId, path, Encoding,
  1018. response =>
  1019. {
  1020. result = response.Files;
  1021. wait.Set();
  1022. },
  1023. response =>
  1024. {
  1025. exception = GetSftpException(response);
  1026. wait.Set();
  1027. });
  1028. SendRequest(request);
  1029. WaitOnHandle(wait, OperationTimeout);
  1030. }
  1031. if (!nullOnError && exception != null)
  1032. {
  1033. throw exception;
  1034. }
  1035. return result;
  1036. }
  1037. /// <summary>
  1038. /// Performs SSH_FXP_SYMLINK request.
  1039. /// </summary>
  1040. /// <param name="linkpath">The linkpath.</param>
  1041. /// <param name="targetpath">The targetpath.</param>
  1042. public void RequestSymLink(string linkpath, string targetpath)
  1043. {
  1044. if (ProtocolVersion < 3)
  1045. {
  1046. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_SYMLINK operation is not supported in {0} version that server operates in.", ProtocolVersion));
  1047. }
  1048. SshException exception = null;
  1049. using (var wait = new AutoResetEvent(false))
  1050. {
  1051. var request = new SftpSymLinkRequest(ProtocolVersion, NextRequestId, linkpath, targetpath, Encoding,
  1052. response =>
  1053. {
  1054. exception = GetSftpException(response);
  1055. wait.Set();
  1056. });
  1057. SendRequest(request);
  1058. WaitOnHandle(wait, OperationTimeout);
  1059. }
  1060. if (exception != null)
  1061. {
  1062. throw exception;
  1063. }
  1064. }
  1065. #endregion
  1066. #region SFTP Extended API functions
  1067. /// <summary>
  1068. /// Performs posix-rename@openssh.com extended request.
  1069. /// </summary>
  1070. /// <param name="oldPath">The old path.</param>
  1071. /// <param name="newPath">The new path.</param>
  1072. public void RequestPosixRename(string oldPath, string newPath)
  1073. {
  1074. if (ProtocolVersion < 3)
  1075. {
  1076. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
  1077. }
  1078. SshException exception = null;
  1079. using (var wait = new AutoResetEvent(false))
  1080. {
  1081. var request = new PosixRenameRequest(ProtocolVersion, NextRequestId, oldPath, newPath, Encoding,
  1082. response =>
  1083. {
  1084. exception = GetSftpException(response);
  1085. wait.Set();
  1086. });
  1087. if (!_supportedExtensions.ContainsKey(request.Name))
  1088. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));
  1089. SendRequest(request);
  1090. WaitOnHandle(wait, OperationTimeout);
  1091. }
  1092. if (exception != null)
  1093. {
  1094. throw exception;
  1095. }
  1096. }
  1097. /// <summary>
  1098. /// Performs statvfs@openssh.com extended request.
  1099. /// </summary>
  1100. /// <param name="path">The path.</param>
  1101. /// <param name="nullOnError">if set to <c>true</c> [null on error].</param>
  1102. /// <returns></returns>
  1103. public SftpFileSytemInformation RequestStatVfs(string path, bool nullOnError = false)
  1104. {
  1105. if (ProtocolVersion < 3)
  1106. {
  1107. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
  1108. }
  1109. SshException exception = null;
  1110. SftpFileSytemInformation information = null;
  1111. using (var wait = new AutoResetEvent(false))
  1112. {
  1113. var request = new StatVfsRequest(ProtocolVersion, NextRequestId, path, Encoding,
  1114. response =>
  1115. {
  1116. information = response.GetReply<StatVfsReplyInfo>().Information;
  1117. wait.Set();
  1118. },
  1119. response =>
  1120. {
  1121. exception = GetSftpException(response);
  1122. wait.Set();
  1123. });
  1124. if (!_supportedExtensions.ContainsKey(request.Name))
  1125. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));
  1126. SendRequest(request);
  1127. WaitOnHandle(wait, OperationTimeout);
  1128. }
  1129. if (!nullOnError && exception != null)
  1130. {
  1131. throw exception;
  1132. }
  1133. return information;
  1134. }
  1135. /// <summary>
  1136. /// Performs fstatvfs@openssh.com extended request.
  1137. /// </summary>
  1138. /// <param name="handle">The file handle.</param>
  1139. /// <param name="nullOnError">if set to <c>true</c> [null on error].</param>
  1140. /// <returns></returns>
  1141. /// <exception cref="NotSupportedException"></exception>
  1142. internal SftpFileSytemInformation RequestFStatVfs(byte[] handle, bool nullOnError = false)
  1143. {
  1144. if (ProtocolVersion < 3)
  1145. {
  1146. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
  1147. }
  1148. SshException exception = null;
  1149. SftpFileSytemInformation information = null;
  1150. using (var wait = new AutoResetEvent(false))
  1151. {
  1152. var request = new FStatVfsRequest(ProtocolVersion, NextRequestId, handle,
  1153. response =>
  1154. {
  1155. information = response.GetReply<StatVfsReplyInfo>().Information;
  1156. wait.Set();
  1157. },
  1158. response =>
  1159. {
  1160. exception = GetSftpException(response);
  1161. wait.Set();
  1162. });
  1163. if (!_supportedExtensions.ContainsKey(request.Name))
  1164. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));
  1165. SendRequest(request);
  1166. WaitOnHandle(wait, OperationTimeout);
  1167. }
  1168. if (!nullOnError && exception != null)
  1169. {
  1170. throw exception;
  1171. }
  1172. return information;
  1173. }
  1174. /// <summary>
  1175. /// Performs hardlink@openssh.com extended request.
  1176. /// </summary>
  1177. /// <param name="oldPath">The old path.</param>
  1178. /// <param name="newPath">The new path.</param>
  1179. internal void HardLink(string oldPath, string newPath)
  1180. {
  1181. if (ProtocolVersion < 3)
  1182. {
  1183. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
  1184. }
  1185. SshException exception = null;
  1186. using (var wait = new AutoResetEvent(false))
  1187. {
  1188. var request = new HardLinkRequest(ProtocolVersion, NextRequestId, oldPath, newPath,
  1189. response =>
  1190. {
  1191. exception = GetSftpException(response);
  1192. wait.Set();
  1193. });
  1194. if (!_supportedExtensions.ContainsKey(request.Name))
  1195. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));
  1196. SendRequest(request);
  1197. WaitOnHandle(wait, OperationTimeout);
  1198. }
  1199. if (exception != null)
  1200. {
  1201. throw exception;
  1202. }
  1203. }
  1204. #endregion
  1205. /// <summary>
  1206. /// Calculates the optimal size of the buffer to read data from the channel.
  1207. /// </summary>
  1208. /// <param name="bufferSize">The buffer size configured on the client.</param>
  1209. /// <returns>
  1210. /// The optimal size of the buffer to read data from the channel.
  1211. /// </returns>
  1212. public uint CalculateOptimalReadLength(uint bufferSize)
  1213. {
  1214. // a SSH_FXP_DATA message has 13 bytes of protocol fields:
  1215. // bytes 1 to 4: packet length
  1216. // byte 5: message type
  1217. // bytes 6 to 9: response id
  1218. // bytes 10 to 13: length of payload‏
  1219. //
  1220. // most ssh servers limit the size of the payload of a SSH_MSG_CHANNEL_DATA
  1221. // response to 16 KB; if we requested 16 KB of data, then the SSH_FXP_DATA
  1222. // payload of the SSH_MSG_CHANNEL_DATA message would be too big (16 KB + 13 bytes), and
  1223. // as a result, the ssh server would split this into two responses:
  1224. // one containing 16384 bytes (13 bytes header, and 16371 bytes file data)
  1225. // and one with the remaining 13 bytes of file data
  1226. const uint lengthOfNonDataProtocolFields = 13u;
  1227. var maximumPacketSize = Channel.LocalPacketSize;
  1228. return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields;
  1229. }
  1230. /// <summary>
  1231. /// Calculates the optimal size of the buffer to write data on the channel.
  1232. /// </summary>
  1233. /// <param name="bufferSize">The buffer size configured on the client.</param>
  1234. /// <param name="handle">The file handle.</param>
  1235. /// <returns>
  1236. /// The optimal size of the buffer to write data on the channel.
  1237. /// </returns>
  1238. /// <remarks>
  1239. /// Currently, we do not take the remote window size into account.
  1240. /// </remarks>
  1241. public uint CalculateOptimalWriteLength(uint bufferSize, byte[] handle)
  1242. {
  1243. // 1-4: package length of SSH_FXP_WRITE message
  1244. // 5: message type
  1245. // 6-9: request id
  1246. // 10-13: handle length
  1247. // <handle>
  1248. // 14-21: offset
  1249. // 22-25: data length
  1250. var lengthOfNonDataProtocolFields = 25u + (uint)handle.Length;
  1251. var maximumPacketSize = Channel.RemotePacketSize;
  1252. return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields;
  1253. }
  1254. private static SshException GetSftpException(SftpStatusResponse response)
  1255. {
  1256. switch (response.StatusCode)
  1257. {
  1258. case StatusCodes.Ok:
  1259. return null;
  1260. case StatusCodes.PermissionDenied:
  1261. return new SftpPermissionDeniedException(response.ErrorMessage);
  1262. case StatusCodes.NoSuchFile:
  1263. return new SftpPathNotFoundException(response.ErrorMessage);
  1264. default:
  1265. return new SshException(response.ErrorMessage);
  1266. }
  1267. }
  1268. private void HandleResponse(SftpResponse response)
  1269. {
  1270. SftpRequest request;
  1271. lock (_requests)
  1272. {
  1273. _requests.TryGetValue(response.ResponseId, out request);
  1274. if (request != null)
  1275. {
  1276. _requests.Remove(response.ResponseId);
  1277. }
  1278. }
  1279. if (request == null)
  1280. throw new InvalidOperationException("Invalid response.");
  1281. request.Complete(response);
  1282. }
  1283. }
  1284. }