SftpSession.cs 38 KB

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