SftpSession.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950
  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading;
  6. using Renci.SshNet.Channels;
  7. using Renci.SshNet.Common;
  8. using System.Diagnostics;
  9. using System.Collections.Generic;
  10. using System.Globalization;
  11. using Renci.SshNet.Sftp.Responses;
  12. using Renci.SshNet.Sftp.Requests;
  13. namespace Renci.SshNet.Sftp
  14. {
  15. internal class SftpSession : SubsystemSession
  16. {
  17. private Dictionary<uint, SftpRequest> _requests = new Dictionary<uint, SftpRequest>();
  18. private List<byte> _data = new List<byte>(16 * 1024);
  19. private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent(false);
  20. /// <summary>
  21. /// Gets remote working directory.
  22. /// </summary>
  23. public string WorkingDirectory { get; private set; }
  24. /// <summary>
  25. /// Gets SFTP protocol version.
  26. /// </summary>
  27. public int ProtocolVersion { get; private set; }
  28. private long _requestId;
  29. /// <summary>
  30. /// Gets the next request id for sftp session.
  31. /// </summary>
  32. public uint NextRequestId
  33. {
  34. get
  35. {
  36. #if WINDOWS_PHONE
  37. lock (this)
  38. {
  39. this._requestId++;
  40. }
  41. return (uint)this._requestId;
  42. #else
  43. return ((uint)Interlocked.Increment(ref this._requestId));
  44. #endif
  45. }
  46. }
  47. public SftpSession(Session session, TimeSpan operationTimeout)
  48. : base(session, "sftp", operationTimeout)
  49. {
  50. }
  51. public void ChangeDirectory(string path)
  52. {
  53. var fullPath = this.GetCanonicalPath(path);
  54. var handle = this.RequestOpenDir(fullPath);
  55. this.RequestClose(handle);
  56. this.WorkingDirectory = fullPath;
  57. }
  58. internal void SendMessage(SftpMessage sftpMessage)
  59. {
  60. var messageData = sftpMessage.GetBytes();
  61. var data = new byte[4 + messageData.Length];
  62. ((uint)messageData.Length).GetBytes().CopyTo(data, 0);
  63. messageData.CopyTo(data, 4);
  64. this.SendData(data);
  65. }
  66. /// <summary>
  67. /// Resolves path into absolute path on the server.
  68. /// </summary>
  69. /// <param name="path">Path to resolve.</param>
  70. /// <returns>Absolute path</returns>
  71. internal string GetCanonicalPath(string path)
  72. {
  73. var fullPath = path;
  74. if (!string.IsNullOrEmpty(path) && path[0] != '/' && this.WorkingDirectory != null)
  75. {
  76. if (this.WorkingDirectory[this.WorkingDirectory.Length - 1] == '/')
  77. {
  78. fullPath = string.Format(CultureInfo.InvariantCulture, "{0}{1}", this.WorkingDirectory, path);
  79. }
  80. else
  81. {
  82. fullPath = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", this.WorkingDirectory, path);
  83. }
  84. }
  85. var canonizedPath = string.Empty;
  86. var realPathFiles = this.RequestRealPath(fullPath, true);
  87. if (realPathFiles != null)
  88. {
  89. canonizedPath = realPathFiles.First().Key;
  90. }
  91. if (!string.IsNullOrEmpty(canonizedPath))
  92. return canonizedPath;
  93. // Check for special cases
  94. if (fullPath.EndsWith("/.", StringComparison.InvariantCultureIgnoreCase) ||
  95. fullPath.EndsWith("/..", StringComparison.InvariantCultureIgnoreCase) ||
  96. fullPath.Equals("/", StringComparison.InvariantCultureIgnoreCase) ||
  97. fullPath.IndexOf('/') < 0)
  98. return fullPath;
  99. var pathParts = fullPath.Split(new char[] { '/' });
  100. var partialFullPath = string.Join("/", pathParts, 0, pathParts.Length - 1);
  101. if (string.IsNullOrEmpty(partialFullPath))
  102. partialFullPath = "/";
  103. realPathFiles = this.RequestRealPath(partialFullPath, true);
  104. if (realPathFiles != null)
  105. {
  106. canonizedPath = realPathFiles.First().Key;
  107. }
  108. if (string.IsNullOrEmpty(canonizedPath))
  109. {
  110. return fullPath;
  111. }
  112. else
  113. {
  114. var slash = string.Empty;
  115. if (canonizedPath[canonizedPath.Length - 1] != '/')
  116. slash = "/";
  117. return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", canonizedPath, slash, pathParts[pathParts.Length - 1]);
  118. }
  119. }
  120. internal bool FileExistsCommand(string path, Flags flags)
  121. {
  122. var handle = this.RequestOpen(path, flags, true);
  123. if (handle == null)
  124. {
  125. return false;
  126. }
  127. else
  128. {
  129. this.RequestClose(handle);
  130. return true;
  131. }
  132. }
  133. protected override void OnChannelOpen()
  134. {
  135. this.SendMessage(new SftpInitRequest(3));
  136. this.WaitHandle(this._sftpVersionConfirmed, this._operationTimeout);
  137. this.ProtocolVersion = 3;
  138. // Resolve current directory
  139. this.WorkingDirectory = this.RequestRealPath(".").First().Key;
  140. }
  141. protected override void OnDataReceived(uint dataTypeCode, byte[] data)
  142. {
  143. // Add channel data to internal data holder
  144. this._data.AddRange(data);
  145. while (this._data.Count > 4 + 1)
  146. {
  147. // Extract packet length
  148. var packetLength = (this._data[0] << 24 | this._data[1] << 16 | this._data[2] << 8 | this._data[3]);
  149. // Check if complete packet data is available
  150. if (this._data.Count < packetLength + 4)
  151. {
  152. // Wait for complete message to arrive first
  153. break;
  154. }
  155. this._data.RemoveRange(0, 4);
  156. // Create buffer to hold packet data
  157. var packetData = new byte[packetLength];
  158. // Cope packet data to array
  159. this._data.CopyTo(0, packetData, 0, packetLength);
  160. // Remove loaded data from _data holder
  161. this._data.RemoveRange(0, packetLength);
  162. // Load SFTP Message and handle it
  163. var response = SftpMessage.Load(packetData);
  164. try
  165. {
  166. var versionResponse = response as SftpVersionResponse;
  167. if (versionResponse != null)
  168. {
  169. if (versionResponse.Version == 3)
  170. {
  171. this._sftpVersionConfirmed.Set();
  172. }
  173. else
  174. {
  175. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Server SFTP version {0} is not supported.", versionResponse.Version));
  176. }
  177. }
  178. else
  179. {
  180. this.HandleResponse(response as SftpResponse);
  181. }
  182. }
  183. catch (Exception exp)
  184. {
  185. this.RaiseError(exp);
  186. break;
  187. }
  188. }
  189. }
  190. protected override void Dispose(bool disposing)
  191. {
  192. base.Dispose(disposing);
  193. if (disposing)
  194. {
  195. if (this._sftpVersionConfirmed != null)
  196. {
  197. this._sftpVersionConfirmed.Dispose();
  198. this._sftpVersionConfirmed = null;
  199. }
  200. }
  201. }
  202. private void SendRequest(SftpRequest request)
  203. {
  204. lock (this._requests)
  205. {
  206. this._requests.Add(request.RequestId, request);
  207. }
  208. this.SendMessage(request);
  209. }
  210. #region SFTP API functions
  211. /// <summary>
  212. /// Performs SSH_FXP_OPEN request
  213. /// </summary>
  214. /// <param name="path">The path.</param>
  215. /// <param name="flags">The flags.</param>
  216. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  217. /// <returns></returns>
  218. internal byte[] RequestOpen(string path, Flags flags, bool nullOnError = false)
  219. {
  220. byte[] handle = null;
  221. using (var wait = new AutoResetEvent(false))
  222. {
  223. var request = new SftpOpenRequest(this.NextRequestId, path, flags,
  224. (response) =>
  225. {
  226. handle = response.Handle;
  227. wait.Set();
  228. },
  229. (response) =>
  230. {
  231. if (nullOnError)
  232. {
  233. wait.Set();
  234. }
  235. else
  236. {
  237. this.ThrowSftpException(response);
  238. }
  239. });
  240. this.SendRequest(request);
  241. this.WaitHandle(wait, this._operationTimeout);
  242. }
  243. return handle;
  244. }
  245. /// <summary>
  246. /// Performs SSH_FXP_CLOSE request.
  247. /// </summary>
  248. /// <param name="handle">The handle.</param>
  249. internal void RequestClose(byte[] handle)
  250. {
  251. using (var wait = new AutoResetEvent(false))
  252. {
  253. var request = new SftpCloseRequest(this.NextRequestId, handle,
  254. (response) =>
  255. {
  256. if (response.StatusCode == StatusCodes.Ok)
  257. {
  258. wait.Set();
  259. }
  260. else
  261. {
  262. this.ThrowSftpException(response);
  263. }
  264. });
  265. this.SendRequest(request);
  266. this.WaitHandle(wait, this._operationTimeout);
  267. }
  268. }
  269. /// <summary>
  270. /// Performs SSH_FXP_READ request.
  271. /// </summary>
  272. /// <param name="handle">The handle.</param>
  273. /// <param name="offset">The offset.</param>
  274. /// <param name="length">The length.</param>
  275. /// <returns>data array; null if EOF</returns>
  276. internal byte[] RequestRead(byte[] handle, UInt64 offset, UInt32 length)
  277. {
  278. byte[] data = new byte[0];
  279. using (var wait = new AutoResetEvent(false))
  280. {
  281. var request = new SftpReadRequest(this.NextRequestId, handle, offset, length,
  282. (response) =>
  283. {
  284. data = response.Data;
  285. wait.Set();
  286. },
  287. (response) =>
  288. {
  289. if (response.StatusCode == StatusCodes.Eof)
  290. {
  291. wait.Set();
  292. }
  293. else
  294. {
  295. this.ThrowSftpException(response);
  296. }
  297. });
  298. this.SendRequest(request);
  299. this.WaitHandle(wait, this._operationTimeout);
  300. }
  301. return data;
  302. }
  303. /// <summary>
  304. /// Performs SSH_FXP_WRITE request.
  305. /// </summary>
  306. /// <param name="handle">The handle.</param>
  307. /// <param name="offset">The offset.</param>
  308. /// <param name="data">The data to send.</param>
  309. /// <param name="wait">The wait event handle if needed.</param>
  310. internal void RequestWrite(byte[] handle, UInt64 offset, byte[] data, EventWaitHandle wait, Action<SftpStatusResponse> writeCompleted = null)
  311. {
  312. var request = new SftpWriteRequest(this.NextRequestId, handle, offset, data,
  313. (response) =>
  314. {
  315. if (writeCompleted != null)
  316. {
  317. writeCompleted(response);
  318. }
  319. if (response.StatusCode == StatusCodes.Ok)
  320. {
  321. if (wait != null)
  322. wait.Set();
  323. }
  324. else
  325. {
  326. this.ThrowSftpException(response);
  327. }
  328. });
  329. this.SendRequest(request);
  330. if (wait != null)
  331. this.WaitHandle(wait, this._operationTimeout);
  332. }
  333. /// <summary>
  334. /// Performs SSH_FXP_LSTAT request.
  335. /// </summary>
  336. /// <param name="path">The path.</param>
  337. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  338. /// <returns>
  339. /// File attributes
  340. /// </returns>
  341. internal SftpFileAttributes RequestLStat(string path, bool nullOnError = false)
  342. {
  343. SftpFileAttributes attributes = null;
  344. using (var wait = new AutoResetEvent(false))
  345. {
  346. var request = new SftpLStatRequest(this.NextRequestId, path,
  347. (response) =>
  348. {
  349. attributes = response.Attributes;
  350. wait.Set();
  351. },
  352. (response) =>
  353. {
  354. this.ThrowSftpException(response);
  355. });
  356. this.SendRequest(request);
  357. this.WaitHandle(wait, this._operationTimeout);
  358. }
  359. return attributes;
  360. }
  361. /// <summary>
  362. /// Performs SSH_FXP_FSTAT request.
  363. /// </summary>
  364. /// <param name="handle">The handle.</param>
  365. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  366. /// <returns>
  367. /// File attributes
  368. /// </returns>
  369. internal SftpFileAttributes RequestFStat(byte[] handle, bool nullOnError = false)
  370. {
  371. SftpFileAttributes attributes = null;
  372. using (var wait = new AutoResetEvent(false))
  373. {
  374. var request = new SftpFStatRequest(this.NextRequestId, handle,
  375. (response) =>
  376. {
  377. attributes = response.Attributes;
  378. wait.Set();
  379. },
  380. (response) =>
  381. {
  382. this.ThrowSftpException(response);
  383. });
  384. this.SendRequest(request);
  385. this.WaitHandle(wait, this._operationTimeout);
  386. }
  387. return attributes;
  388. }
  389. /// <summary>
  390. /// Performs SSH_FXP_SETSTAT request.
  391. /// </summary>
  392. /// <param name="path">The path.</param>
  393. /// <param name="attributes">The attributes.</param>
  394. internal void RequestSetStat(string path, SftpFileAttributes attributes)
  395. {
  396. using (var wait = new AutoResetEvent(false))
  397. {
  398. var request = new SftpSetStatRequest(this.NextRequestId, path, attributes,
  399. (response) =>
  400. {
  401. if (response.StatusCode == StatusCodes.Ok)
  402. {
  403. wait.Set();
  404. }
  405. else
  406. {
  407. this.ThrowSftpException(response);
  408. }
  409. });
  410. this.SendRequest(request);
  411. this.WaitHandle(wait, this._operationTimeout);
  412. }
  413. }
  414. /// <summary>
  415. /// Performs SSH_FXP_FSETSTAT request.
  416. /// </summary>
  417. /// <param name="handle">The handle.</param>
  418. /// <param name="attributes">The attributes.</param>
  419. internal void RequestFSetStat(byte[] handle, SftpFileAttributes attributes)
  420. {
  421. using (var wait = new AutoResetEvent(false))
  422. {
  423. var request = new SftpFSetStatRequest(this.NextRequestId, handle, attributes,
  424. (response) =>
  425. {
  426. if (response.StatusCode == StatusCodes.Ok)
  427. {
  428. wait.Set();
  429. }
  430. else
  431. {
  432. this.ThrowSftpException(response);
  433. }
  434. });
  435. this.SendRequest(request);
  436. this.WaitHandle(wait, this._operationTimeout);
  437. }
  438. }
  439. /// <summary>
  440. /// Performs SSH_FXP_OPENDIR request
  441. /// </summary>
  442. /// <param name="path">The path.</param>
  443. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  444. /// <returns></returns>
  445. internal byte[] RequestOpenDir(string path, bool nullOnError = false)
  446. {
  447. byte[] handle = null;
  448. using (var wait = new AutoResetEvent(false))
  449. {
  450. var request = new SftpOpenDirRequest(this.NextRequestId, path,
  451. (response) =>
  452. {
  453. handle = response.Handle;
  454. wait.Set();
  455. },
  456. (response) =>
  457. {
  458. if (nullOnError)
  459. {
  460. wait.Set();
  461. }
  462. else
  463. {
  464. this.ThrowSftpException(response);
  465. }
  466. });
  467. this.SendRequest(request);
  468. this.WaitHandle(wait, this._operationTimeout);
  469. }
  470. return handle;
  471. }
  472. /// <summary>
  473. /// Performs SSH_FXP_READDIR request
  474. /// </summary>
  475. /// <param name="handle">The handle.</param>
  476. /// <returns></returns>
  477. internal KeyValuePair<string, SftpFileAttributes>[] RequestReadDir(byte[] handle)
  478. {
  479. KeyValuePair<string, SftpFileAttributes>[] result = null;
  480. using (var wait = new AutoResetEvent(false))
  481. {
  482. var request = new SftpReadDirRequest(this.NextRequestId, handle,
  483. (response) =>
  484. {
  485. result = response.Files;
  486. wait.Set();
  487. },
  488. (response) =>
  489. {
  490. if (response.StatusCode == StatusCodes.Eof)
  491. {
  492. wait.Set();
  493. }
  494. else
  495. {
  496. this.ThrowSftpException(response);
  497. }
  498. });
  499. this.SendRequest(request);
  500. this.WaitHandle(wait, this._operationTimeout);
  501. }
  502. return result;
  503. }
  504. /// <summary>
  505. /// Performs SSH_FXP_REMOVE request.
  506. /// </summary>
  507. /// <param name="path">The path.</param>
  508. internal void RequestRemove(string path)
  509. {
  510. using (var wait = new AutoResetEvent(false))
  511. {
  512. var request = new SftpRemoveRequest(this.NextRequestId, path,
  513. (response) =>
  514. {
  515. if (response.StatusCode == StatusCodes.Ok)
  516. {
  517. wait.Set();
  518. }
  519. else
  520. {
  521. this.ThrowSftpException(response);
  522. }
  523. });
  524. this.SendRequest(request);
  525. this.WaitHandle(wait, this._operationTimeout);
  526. }
  527. }
  528. /// <summary>
  529. /// Performs SSH_FXP_MKDIR request.
  530. /// </summary>
  531. /// <param name="path">The path.</param>
  532. internal void RequestMkDir(string path)
  533. {
  534. using (var wait = new AutoResetEvent(false))
  535. {
  536. var request = new SftpMkDirRequest(this.NextRequestId, path,
  537. (response) =>
  538. {
  539. if (response.StatusCode == StatusCodes.Ok)
  540. {
  541. wait.Set();
  542. }
  543. else
  544. {
  545. this.ThrowSftpException(response);
  546. }
  547. });
  548. this.SendRequest(request);
  549. this.WaitHandle(wait, this._operationTimeout);
  550. }
  551. }
  552. /// <summary>
  553. /// Performs SSH_FXP_RMDIR request.
  554. /// </summary>
  555. /// <param name="path">The path.</param>
  556. internal void RequestRmDir(string path)
  557. {
  558. using (var wait = new AutoResetEvent(false))
  559. {
  560. var request = new SftpRmDirRequest(this.NextRequestId, path,
  561. (response) =>
  562. {
  563. if (response.StatusCode == StatusCodes.Ok)
  564. {
  565. wait.Set();
  566. }
  567. else
  568. {
  569. this.ThrowSftpException(response);
  570. }
  571. });
  572. this.SendRequest(request);
  573. this.WaitHandle(wait, this._operationTimeout);
  574. }
  575. }
  576. /// <summary>
  577. /// Performs SSH_FXP_REALPATH request
  578. /// </summary>
  579. /// <param name="path">The path.</param>
  580. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  581. /// <returns></returns>
  582. internal KeyValuePair<string, SftpFileAttributes>[] RequestRealPath(string path, bool nullOnError = false)
  583. {
  584. KeyValuePair<string, SftpFileAttributes>[] result = null;
  585. using (var wait = new AutoResetEvent(false))
  586. {
  587. var request = new SftpRealPathRequest(this.NextRequestId, path,
  588. (response) =>
  589. {
  590. result = response.Files;
  591. wait.Set();
  592. },
  593. (response) =>
  594. {
  595. if (nullOnError)
  596. {
  597. wait.Set();
  598. }
  599. else
  600. {
  601. this.ThrowSftpException(response);
  602. }
  603. });
  604. this.SendRequest(request);
  605. this.WaitHandle(wait, this._operationTimeout);
  606. }
  607. return result;
  608. }
  609. /// <summary>
  610. /// Performs SSH_FXP_STAT request.
  611. /// </summary>
  612. /// <param name="path">The path.</param>
  613. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  614. /// <returns>
  615. /// File attributes
  616. /// </returns>
  617. internal SftpFileAttributes RequestStat(string path, bool nullOnError = false)
  618. {
  619. SftpFileAttributes attributes = null;
  620. using (var wait = new AutoResetEvent(false))
  621. {
  622. var request = new SftpStatRequest(this.NextRequestId, path,
  623. (response) =>
  624. {
  625. attributes = response.Attributes;
  626. wait.Set();
  627. },
  628. (response) =>
  629. {
  630. if (nullOnError)
  631. {
  632. wait.Set();
  633. }
  634. else
  635. {
  636. this.ThrowSftpException(response);
  637. }
  638. });
  639. this.SendRequest(request);
  640. this.WaitHandle(wait, this._operationTimeout);
  641. }
  642. return attributes;
  643. }
  644. /// <summary>
  645. /// Performs SSH_FXP_RENAME request.
  646. /// </summary>
  647. /// <param name="oldPath">The old path.</param>
  648. /// <param name="newPath">The new path.</param>
  649. internal void RequestRename(string oldPath, string newPath)
  650. {
  651. using (var wait = new AutoResetEvent(false))
  652. {
  653. var request = new SftpRenameRequest(this.NextRequestId, oldPath, newPath,
  654. (response) =>
  655. {
  656. if (response.StatusCode == StatusCodes.Ok)
  657. {
  658. wait.Set();
  659. }
  660. else
  661. {
  662. this.ThrowSftpException(response);
  663. }
  664. });
  665. this.SendRequest(request);
  666. this.WaitHandle(wait, this._operationTimeout);
  667. }
  668. }
  669. /// <summary>
  670. /// Performs SSH_FXP_READLINK request.
  671. /// </summary>
  672. /// <param name="path">The path.</param>
  673. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  674. /// <returns></returns>
  675. internal KeyValuePair<string, SftpFileAttributes>[] RequestReadLink(string path, bool nullOnError = false)
  676. {
  677. KeyValuePair<string, SftpFileAttributes>[] result = null;
  678. using (var wait = new AutoResetEvent(false))
  679. {
  680. var request = new SftpReadLinkRequest(this.NextRequestId, path,
  681. (response) =>
  682. {
  683. result = response.Files;
  684. wait.Set();
  685. },
  686. (response) =>
  687. {
  688. if (nullOnError)
  689. {
  690. wait.Set();
  691. }
  692. else
  693. {
  694. this.ThrowSftpException(response);
  695. }
  696. });
  697. this.SendRequest(request);
  698. this.WaitHandle(wait, this._operationTimeout);
  699. }
  700. return result;
  701. }
  702. /// <summary>
  703. /// Performs SSH_FXP_SYMLINK request.
  704. /// </summary>
  705. /// <param name="linkpath">The linkpath.</param>
  706. /// <param name="targetpath">The targetpath.</param>
  707. internal void RequestSymLink(string linkpath, string targetpath)
  708. {
  709. using (var wait = new AutoResetEvent(false))
  710. {
  711. var request = new SftpSymLinkRequest(this.NextRequestId, linkpath, targetpath,
  712. (response) =>
  713. {
  714. if (response.StatusCode == StatusCodes.Ok)
  715. {
  716. wait.Set();
  717. }
  718. else
  719. {
  720. this.ThrowSftpException(response);
  721. }
  722. });
  723. this.SendRequest(request);
  724. this.WaitHandle(wait, this._operationTimeout);
  725. }
  726. }
  727. /// <summary>
  728. /// Performs posix-rename@openssh.com extended request.
  729. /// </summary>
  730. /// <param name="oldPath">The old path.</param>
  731. /// <param name="newPath">The new path.</param>
  732. internal void RequestPosixRename(string oldPath, string newPath)
  733. {
  734. using (var wait = new AutoResetEvent(false))
  735. {
  736. var request = new PosixRenameRequest(this.NextRequestId, oldPath, newPath,
  737. (response) =>
  738. {
  739. if (response.StatusCode == StatusCodes.Ok)
  740. {
  741. wait.Set();
  742. }
  743. else
  744. {
  745. ThrowSftpException(response);
  746. }
  747. });
  748. this.SendRequest(request);
  749. this.WaitHandle(wait, this._operationTimeout);
  750. }
  751. }
  752. /// <summary>
  753. /// Performs statvfs@openssh.com extended request.
  754. /// </summary>
  755. /// <param name="path">The path.</param>
  756. /// <param name="nullOnError">if set to <c>true</c> [null on error].</param>
  757. /// <returns></returns>
  758. internal SftpFileSytemInformation RequestStatVfs(string path, bool nullOnError = false)
  759. {
  760. SftpFileSytemInformation information = null;
  761. using (var wait = new AutoResetEvent(false))
  762. {
  763. var request = new StatVfsRequest(this.NextRequestId, path,
  764. (response) =>
  765. {
  766. information = response.OfType<StatVfsResponse>().Information;
  767. wait.Set();
  768. },
  769. (response) =>
  770. {
  771. if (nullOnError)
  772. {
  773. wait.Set();
  774. }
  775. else
  776. {
  777. ThrowSftpException(response);
  778. }
  779. });
  780. this.SendRequest(request);
  781. this.WaitHandle(wait, this._operationTimeout);
  782. }
  783. return information;
  784. }
  785. #endregion
  786. private void ThrowSftpException(SftpStatusResponse response)
  787. {
  788. if (response.StatusCode == StatusCodes.PermissionDenied)
  789. {
  790. throw new SftpPermissionDeniedException(response.ErrorMessage);
  791. }
  792. else if (response.StatusCode == StatusCodes.NoSuchFile)
  793. {
  794. throw new SftpPathNotFoundException(response.ErrorMessage);
  795. }
  796. else
  797. {
  798. throw new SshException(response.ErrorMessage);
  799. }
  800. }
  801. private void HandleResponse(SftpResponse response)
  802. {
  803. SftpRequest request = null;
  804. lock (this._requests)
  805. {
  806. this._requests.TryGetValue(response.ResponseId, out request);
  807. if (request != null)
  808. {
  809. this._requests.Remove(response.ResponseId);
  810. }
  811. }
  812. if (request == null)
  813. throw new InvalidOperationException("Invalid response.");
  814. request.Complete(response);
  815. }
  816. }
  817. }