SftpSession.cs 42 KB

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