SftpSession.cs 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150
  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 MaximumSupportedVersion = 3;
  15. private const int MinimumSupportedVersion = 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 _requestId));
  44. #endif
  45. }
  46. }
  47. public SftpSession(ISession session, TimeSpan operationTimeout, Encoding encoding)
  48. : base(session, "sftp", operationTimeout, encoding)
  49. {
  50. }
  51. public void ChangeDirectory(string path)
  52. {
  53. var fullPath = GetCanonicalPath(path);
  54. var handle = RequestOpenDir(fullPath);
  55. RequestClose(handle);
  56. 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. 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 = 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[] { '/' });
  89. var partialFullPath = string.Join("/", pathParts, 0, pathParts.Length - 1);
  90. if (string.IsNullOrEmpty(partialFullPath))
  91. partialFullPath = "/";
  92. realPathFiles = 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] != '/' && WorkingDirectory != null)
  110. {
  111. if (WorkingDirectory[WorkingDirectory.Length - 1] == '/')
  112. {
  113. fullPath = string.Format(CultureInfo.InvariantCulture, "{0}{1}", WorkingDirectory, path);
  114. }
  115. else
  116. {
  117. fullPath = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", WorkingDirectory, path);
  118. }
  119. }
  120. return fullPath;
  121. }
  122. protected override void OnChannelOpen()
  123. {
  124. SendMessage(new SftpInitRequest(MaximumSupportedVersion));
  125. WaitOnHandle(_sftpVersionConfirmed, OperationTimeout);
  126. if (ProtocolVersion > MaximumSupportedVersion || ProtocolVersion < MinimumSupportedVersion)
  127. {
  128. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Server SFTP version {0} is not supported.", ProtocolVersion));
  129. }
  130. // Resolve current directory
  131. WorkingDirectory = RequestRealPath(".").First().Key;
  132. }
  133. protected override void OnDataReceived(uint dataTypeCode, byte[] data)
  134. {
  135. // Add channel data to internal data holder
  136. _data.AddRange(data);
  137. while (_data.Count > 4 + 1)
  138. {
  139. // Extract packet length
  140. var packetLength = (_data[0] << 24 | _data[1] << 16 | _data[2] << 8 | _data[3]);
  141. // Check if complete packet data is available
  142. if (_data.Count < packetLength + 4)
  143. {
  144. // Wait for complete message to arrive first
  145. break;
  146. }
  147. _data.RemoveRange(0, 4);
  148. // Create buffer to hold packet data
  149. var packetData = new byte[packetLength];
  150. // Cope packet data to array
  151. _data.CopyTo(0, packetData, 0, packetLength);
  152. // Remove loaded data from _data holder
  153. _data.RemoveRange(0, packetLength);
  154. // Load SFTP Message and handle it
  155. var response = SftpMessage.Load(ProtocolVersion, packetData, Encoding);
  156. try
  157. {
  158. var versionResponse = response as SftpVersionResponse;
  159. if (versionResponse != null)
  160. {
  161. ProtocolVersion = versionResponse.Version;
  162. _supportedExtensions = versionResponse.Extentions;
  163. _sftpVersionConfirmed.Set();
  164. }
  165. else
  166. {
  167. HandleResponse(response as SftpResponse);
  168. }
  169. }
  170. catch (Exception exp)
  171. {
  172. 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 (_sftpVersionConfirmed != null)
  183. {
  184. _sftpVersionConfirmed.Dispose();
  185. _sftpVersionConfirmed = null;
  186. }
  187. }
  188. }
  189. private void SendRequest(SftpRequest request)
  190. {
  191. lock (_requests)
  192. {
  193. _requests.Add(request.RequestId, request);
  194. }
  195. 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(ProtocolVersion, NextRequestId, path, Encoding, flags,
  212. response =>
  213. {
  214. handle = response.Handle;
  215. wait.Set();
  216. },
  217. response =>
  218. {
  219. exception = GetSftpException(response);
  220. wait.Set();
  221. });
  222. SendRequest(request);
  223. WaitOnHandle(wait, 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(ProtocolVersion, NextRequestId, handle,
  241. response =>
  242. {
  243. exception = GetSftpException(response);
  244. wait.Set();
  245. });
  246. SendRequest(request);
  247. WaitOnHandle(wait, 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(ProtocolVersion, 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 = GetSftpException(response);
  278. }
  279. wait.Set();
  280. });
  281. SendRequest(request);
  282. WaitOnHandle(wait, 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(ProtocolVersion, NextRequestId, handle, offset, data,
  302. response =>
  303. {
  304. if (writeCompleted != null)
  305. {
  306. writeCompleted(response);
  307. }
  308. exception = GetSftpException(response);
  309. if (wait != null)
  310. wait.Set();
  311. });
  312. SendRequest(request);
  313. if (wait != null)
  314. WaitOnHandle(wait, 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. /// <returns>
  325. /// File attributes
  326. /// </returns>
  327. internal SftpFileAttributes RequestLStat(string path)
  328. {
  329. SshException exception = null;
  330. SftpFileAttributes attributes = null;
  331. using (var wait = new AutoResetEvent(false))
  332. {
  333. var request = new SftpLStatRequest(ProtocolVersion, NextRequestId, path, Encoding,
  334. response =>
  335. {
  336. attributes = response.Attributes;
  337. wait.Set();
  338. },
  339. response =>
  340. {
  341. exception = GetSftpException(response);
  342. wait.Set();
  343. });
  344. SendRequest(request);
  345. WaitOnHandle(wait, OperationTimeout);
  346. }
  347. if (exception != null)
  348. {
  349. throw exception;
  350. }
  351. return attributes;
  352. }
  353. /// <summary>
  354. /// Performs SSH_FXP_FSTAT request.
  355. /// </summary>
  356. /// <param name="handle">The handle.</param>
  357. /// <returns>
  358. /// File attributes
  359. /// </returns>
  360. internal SftpFileAttributes RequestFStat(byte[] handle)
  361. {
  362. SshException exception = null;
  363. SftpFileAttributes attributes = null;
  364. using (var wait = new AutoResetEvent(false))
  365. {
  366. var request = new SftpFStatRequest(ProtocolVersion, NextRequestId, handle,
  367. response =>
  368. {
  369. attributes = response.Attributes;
  370. wait.Set();
  371. },
  372. (response) =>
  373. {
  374. exception = GetSftpException(response);
  375. wait.Set();
  376. });
  377. SendRequest(request);
  378. WaitOnHandle(wait, OperationTimeout);
  379. }
  380. if (exception != null)
  381. {
  382. throw exception;
  383. }
  384. return attributes;
  385. }
  386. /// <summary>
  387. /// Performs SSH_FXP_SETSTAT request.
  388. /// </summary>
  389. /// <param name="path">The path.</param>
  390. /// <param name="attributes">The attributes.</param>
  391. internal void RequestSetStat(string path, SftpFileAttributes attributes)
  392. {
  393. SshException exception = null;
  394. using (var wait = new AutoResetEvent(false))
  395. {
  396. var request = new SftpSetStatRequest(ProtocolVersion, NextRequestId, path, Encoding, attributes,
  397. response =>
  398. {
  399. exception = GetSftpException(response);
  400. wait.Set();
  401. });
  402. SendRequest(request);
  403. WaitOnHandle(wait, OperationTimeout);
  404. }
  405. if (exception != null)
  406. {
  407. throw exception;
  408. }
  409. }
  410. /// <summary>
  411. /// Performs SSH_FXP_FSETSTAT request.
  412. /// </summary>
  413. /// <param name="handle">The handle.</param>
  414. /// <param name="attributes">The attributes.</param>
  415. internal void RequestFSetStat(byte[] handle, SftpFileAttributes attributes)
  416. {
  417. SshException exception = null;
  418. using (var wait = new AutoResetEvent(false))
  419. {
  420. var request = new SftpFSetStatRequest(ProtocolVersion, NextRequestId, handle, attributes,
  421. response =>
  422. {
  423. exception = GetSftpException(response);
  424. wait.Set();
  425. });
  426. SendRequest(request);
  427. WaitOnHandle(wait, OperationTimeout);
  428. }
  429. if (exception != null)
  430. {
  431. throw exception;
  432. }
  433. }
  434. /// <summary>
  435. /// Performs SSH_FXP_OPENDIR request
  436. /// </summary>
  437. /// <param name="path">The path.</param>
  438. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  439. /// <returns>File handle.</returns>
  440. internal byte[] RequestOpenDir(string path, bool nullOnError = false)
  441. {
  442. SshException exception = null;
  443. byte[] handle = null;
  444. using (var wait = new AutoResetEvent(false))
  445. {
  446. var request = new SftpOpenDirRequest(ProtocolVersion, NextRequestId, path, Encoding,
  447. response =>
  448. {
  449. handle = response.Handle;
  450. wait.Set();
  451. },
  452. response =>
  453. {
  454. exception = GetSftpException(response);
  455. wait.Set();
  456. });
  457. SendRequest(request);
  458. WaitOnHandle(wait, OperationTimeout);
  459. }
  460. if (!nullOnError && exception != null)
  461. {
  462. throw exception;
  463. }
  464. return handle;
  465. }
  466. /// <summary>
  467. /// Performs SSH_FXP_READDIR request
  468. /// </summary>
  469. /// <param name="handle">The handle.</param>
  470. /// <returns></returns>
  471. internal KeyValuePair<string, SftpFileAttributes>[] RequestReadDir(byte[] handle)
  472. {
  473. SshException exception = null;
  474. KeyValuePair<string, SftpFileAttributes>[] result = null;
  475. using (var wait = new AutoResetEvent(false))
  476. {
  477. var request = new SftpReadDirRequest(ProtocolVersion, NextRequestId, handle,
  478. response =>
  479. {
  480. result = response.Files;
  481. wait.Set();
  482. },
  483. response =>
  484. {
  485. if (response.StatusCode != StatusCodes.Eof)
  486. {
  487. exception = GetSftpException(response);
  488. }
  489. wait.Set();
  490. });
  491. SendRequest(request);
  492. WaitOnHandle(wait, OperationTimeout);
  493. }
  494. if (exception != null)
  495. {
  496. throw exception;
  497. }
  498. return result;
  499. }
  500. /// <summary>
  501. /// Performs SSH_FXP_REMOVE request.
  502. /// </summary>
  503. /// <param name="path">The path.</param>
  504. internal void RequestRemove(string path)
  505. {
  506. SshException exception = null;
  507. using (var wait = new AutoResetEvent(false))
  508. {
  509. var request = new SftpRemoveRequest(ProtocolVersion, NextRequestId, path, Encoding,
  510. response =>
  511. {
  512. exception = GetSftpException(response);
  513. wait.Set();
  514. });
  515. SendRequest(request);
  516. WaitOnHandle(wait, OperationTimeout);
  517. }
  518. if (exception != null)
  519. {
  520. throw exception;
  521. }
  522. }
  523. /// <summary>
  524. /// Performs SSH_FXP_MKDIR request.
  525. /// </summary>
  526. /// <param name="path">The path.</param>
  527. internal void RequestMkDir(string path)
  528. {
  529. SshException exception = null;
  530. using (var wait = new AutoResetEvent(false))
  531. {
  532. var request = new SftpMkDirRequest(ProtocolVersion, NextRequestId, path, Encoding,
  533. response =>
  534. {
  535. exception = GetSftpException(response);
  536. wait.Set();
  537. });
  538. SendRequest(request);
  539. WaitOnHandle(wait, OperationTimeout);
  540. }
  541. if (exception != null)
  542. {
  543. throw exception;
  544. }
  545. }
  546. /// <summary>
  547. /// Performs SSH_FXP_RMDIR request.
  548. /// </summary>
  549. /// <param name="path">The path.</param>
  550. internal void RequestRmDir(string path)
  551. {
  552. SshException exception = null;
  553. using (var wait = new AutoResetEvent(false))
  554. {
  555. var request = new SftpRmDirRequest(ProtocolVersion, NextRequestId, path, Encoding,
  556. response =>
  557. {
  558. exception = GetSftpException(response);
  559. wait.Set();
  560. });
  561. SendRequest(request);
  562. WaitOnHandle(wait, OperationTimeout);
  563. }
  564. if (exception != null)
  565. {
  566. throw exception;
  567. }
  568. }
  569. /// <summary>
  570. /// Performs SSH_FXP_REALPATH request
  571. /// </summary>
  572. /// <param name="path">The path.</param>
  573. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  574. /// <returns></returns>
  575. internal KeyValuePair<string, SftpFileAttributes>[] RequestRealPath(string path, bool nullOnError = false)
  576. {
  577. SshException exception = null;
  578. KeyValuePair<string, SftpFileAttributes>[] result = null;
  579. using (var wait = new AutoResetEvent(false))
  580. {
  581. var request = new SftpRealPathRequest(ProtocolVersion, NextRequestId, path, Encoding,
  582. response =>
  583. {
  584. result = response.Files;
  585. wait.Set();
  586. },
  587. response =>
  588. {
  589. exception = GetSftpException(response);
  590. wait.Set();
  591. });
  592. SendRequest(request);
  593. WaitOnHandle(wait, OperationTimeout);
  594. }
  595. if (!nullOnError && exception != null)
  596. {
  597. throw exception;
  598. }
  599. return result;
  600. }
  601. /// <summary>
  602. /// Performs SSH_FXP_STAT request.
  603. /// </summary>
  604. /// <param name="path">The path.</param>
  605. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  606. /// <returns>
  607. /// File attributes
  608. /// </returns>
  609. internal SftpFileAttributes RequestStat(string path, bool nullOnError = false)
  610. {
  611. SshException exception = null;
  612. SftpFileAttributes attributes = null;
  613. using (var wait = new AutoResetEvent(false))
  614. {
  615. var request = new SftpStatRequest(ProtocolVersion, NextRequestId, path, Encoding,
  616. response =>
  617. {
  618. attributes = response.Attributes;
  619. wait.Set();
  620. },
  621. response =>
  622. {
  623. exception = GetSftpException(response);
  624. wait.Set();
  625. });
  626. SendRequest(request);
  627. WaitOnHandle(wait, OperationTimeout);
  628. }
  629. if (!nullOnError && exception != null)
  630. {
  631. throw exception;
  632. }
  633. return attributes;
  634. }
  635. /// <summary>
  636. /// Performs SSH_FXP_RENAME request.
  637. /// </summary>
  638. /// <param name="oldPath">The old path.</param>
  639. /// <param name="newPath">The new path.</param>
  640. internal void RequestRename(string oldPath, string newPath)
  641. {
  642. if (ProtocolVersion < 2)
  643. {
  644. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_RENAME operation is not supported in {0} version that server operates in.", ProtocolVersion));
  645. }
  646. SshException exception = null;
  647. using (var wait = new AutoResetEvent(false))
  648. {
  649. var request = new SftpRenameRequest(ProtocolVersion, NextRequestId, oldPath, newPath, Encoding,
  650. response =>
  651. {
  652. exception = GetSftpException(response);
  653. wait.Set();
  654. });
  655. SendRequest(request);
  656. WaitOnHandle(wait, OperationTimeout);
  657. }
  658. if (exception != null)
  659. {
  660. throw exception;
  661. }
  662. }
  663. /// <summary>
  664. /// Performs SSH_FXP_READLINK request.
  665. /// </summary>
  666. /// <param name="path">The path.</param>
  667. /// <param name="nullOnError">if set to <c>true</c> returns null instead of throwing an exception.</param>
  668. /// <returns></returns>
  669. internal KeyValuePair<string, SftpFileAttributes>[] RequestReadLink(string path, bool nullOnError = false)
  670. {
  671. if (ProtocolVersion < 3)
  672. {
  673. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_READLINK operation is not supported in {0} version that server operates in.", ProtocolVersion));
  674. }
  675. SshException exception = null;
  676. KeyValuePair<string, SftpFileAttributes>[] result = null;
  677. using (var wait = new AutoResetEvent(false))
  678. {
  679. var request = new SftpReadLinkRequest(ProtocolVersion, NextRequestId, path, Encoding,
  680. response =>
  681. {
  682. result = response.Files;
  683. wait.Set();
  684. },
  685. response =>
  686. {
  687. exception = GetSftpException(response);
  688. wait.Set();
  689. });
  690. SendRequest(request);
  691. WaitOnHandle(wait, OperationTimeout);
  692. }
  693. if (!nullOnError && exception != null)
  694. {
  695. throw exception;
  696. }
  697. return result;
  698. }
  699. /// <summary>
  700. /// Performs SSH_FXP_SYMLINK request.
  701. /// </summary>
  702. /// <param name="linkpath">The linkpath.</param>
  703. /// <param name="targetpath">The targetpath.</param>
  704. internal void RequestSymLink(string linkpath, string targetpath)
  705. {
  706. if (ProtocolVersion < 3)
  707. {
  708. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_SYMLINK operation is not supported in {0} version that server operates in.", ProtocolVersion));
  709. }
  710. SshException exception = null;
  711. using (var wait = new AutoResetEvent(false))
  712. {
  713. var request = new SftpSymLinkRequest(ProtocolVersion, NextRequestId, linkpath, targetpath, Encoding,
  714. response =>
  715. {
  716. exception = GetSftpException(response);
  717. wait.Set();
  718. });
  719. SendRequest(request);
  720. WaitOnHandle(wait, OperationTimeout);
  721. }
  722. if (exception != null)
  723. {
  724. throw exception;
  725. }
  726. }
  727. #endregion
  728. #region SFTP Extended API functions
  729. /// <summary>
  730. /// Performs posix-rename@openssh.com extended request.
  731. /// </summary>
  732. /// <param name="oldPath">The old path.</param>
  733. /// <param name="newPath">The new path.</param>
  734. internal void RequestPosixRename(string oldPath, string newPath)
  735. {
  736. if (ProtocolVersion < 3)
  737. {
  738. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
  739. }
  740. SshException exception = null;
  741. using (var wait = new AutoResetEvent(false))
  742. {
  743. var request = new PosixRenameRequest(ProtocolVersion, NextRequestId, oldPath, newPath, Encoding,
  744. response =>
  745. {
  746. exception = GetSftpException(response);
  747. wait.Set();
  748. });
  749. if (!_supportedExtensions.ContainsKey(request.Name))
  750. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));
  751. SendRequest(request);
  752. WaitOnHandle(wait, OperationTimeout);
  753. }
  754. if (exception != null)
  755. {
  756. throw exception;
  757. }
  758. }
  759. /// <summary>
  760. /// Performs statvfs@openssh.com extended request.
  761. /// </summary>
  762. /// <param name="path">The path.</param>
  763. /// <param name="nullOnError">if set to <c>true</c> [null on error].</param>
  764. /// <returns></returns>
  765. internal SftpFileSytemInformation RequestStatVfs(string path, bool nullOnError = false)
  766. {
  767. if (ProtocolVersion < 3)
  768. {
  769. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
  770. }
  771. SshException exception = null;
  772. SftpFileSytemInformation information = null;
  773. using (var wait = new AutoResetEvent(false))
  774. {
  775. var request = new StatVfsRequest(ProtocolVersion, NextRequestId, path, Encoding,
  776. response =>
  777. {
  778. information = response.GetReply<StatVfsReplyInfo>().Information;
  779. wait.Set();
  780. },
  781. response =>
  782. {
  783. exception = GetSftpException(response);
  784. wait.Set();
  785. });
  786. if (!_supportedExtensions.ContainsKey(request.Name))
  787. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));
  788. SendRequest(request);
  789. WaitOnHandle(wait, OperationTimeout);
  790. }
  791. if (!nullOnError && exception != null)
  792. {
  793. throw exception;
  794. }
  795. return information;
  796. }
  797. /// <summary>
  798. /// Performs fstatvfs@openssh.com extended request.
  799. /// </summary>
  800. /// <param name="handle">The file handle.</param>
  801. /// <param name="nullOnError">if set to <c>true</c> [null on error].</param>
  802. /// <returns></returns>
  803. /// <exception cref="System.NotSupportedException"></exception>
  804. internal SftpFileSytemInformation RequestFStatVfs(byte[] handle, bool nullOnError = false)
  805. {
  806. if (ProtocolVersion < 3)
  807. {
  808. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
  809. }
  810. SshException exception = null;
  811. SftpFileSytemInformation information = null;
  812. using (var wait = new AutoResetEvent(false))
  813. {
  814. var request = new FStatVfsRequest(ProtocolVersion, NextRequestId, handle,
  815. response =>
  816. {
  817. information = response.GetReply<StatVfsReplyInfo>().Information;
  818. wait.Set();
  819. },
  820. response =>
  821. {
  822. exception = GetSftpException(response);
  823. wait.Set();
  824. });
  825. if (!_supportedExtensions.ContainsKey(request.Name))
  826. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));
  827. SendRequest(request);
  828. WaitOnHandle(wait, OperationTimeout);
  829. }
  830. if (!nullOnError && exception != null)
  831. {
  832. throw exception;
  833. }
  834. return information;
  835. }
  836. /// <summary>
  837. /// Performs hardlink@openssh.com extended request.
  838. /// </summary>
  839. /// <param name="oldPath">The old path.</param>
  840. /// <param name="newPath">The new path.</param>
  841. internal void HardLink(string oldPath, string newPath)
  842. {
  843. if (ProtocolVersion < 3)
  844. {
  845. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
  846. }
  847. SshException exception = null;
  848. using (var wait = new AutoResetEvent(false))
  849. {
  850. var request = new HardLinkRequest(ProtocolVersion, NextRequestId, oldPath, newPath,
  851. response =>
  852. {
  853. exception = GetSftpException(response);
  854. wait.Set();
  855. });
  856. if (!_supportedExtensions.ContainsKey(request.Name))
  857. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Extension method {0} currently not supported by the server.", request.Name));
  858. SendRequest(request);
  859. WaitOnHandle(wait, OperationTimeout);
  860. }
  861. if (exception != null)
  862. {
  863. throw exception;
  864. }
  865. }
  866. #endregion
  867. /// <summary>
  868. /// Calculates the optimal size of the buffer to read data from the channel.
  869. /// </summary>
  870. /// <param name="bufferSize">The buffer size configured on the client.</param>
  871. /// <returns>
  872. /// The optimal size of the buffer to read data from the channel.
  873. /// </returns>
  874. internal uint CalculateOptimalReadLength(uint bufferSize)
  875. {
  876. // a SSH_FXP_DATA message has 13 bytes of protocol fields:
  877. // bytes 1 to 4: packet length
  878. // byte 5: message type
  879. // bytes 6 to 9: response id
  880. // bytes 10 to 13: length of payload‏
  881. //
  882. // most ssh servers limit the size of the payload of a SSH_MSG_CHANNEL_DATA
  883. // response to 16 KB; if we requested 16 KB of data, then the SSH_FXP_DATA
  884. // payload of the SSH_MSG_CHANNEL_DATA message would be too big (16 KB + 13 bytes), and
  885. // as a result, the ssh server would split this into two responses:
  886. // one containing 16384 bytes (13 bytes header, and 16371 bytes file data)
  887. // and one with the remaining 13 bytes of file data
  888. const uint lengthOfNonDataProtocolFields = 13u;
  889. var maximumPacketSize = Channel.LocalPacketSize;
  890. return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields;
  891. }
  892. /// <summary>
  893. /// Calculates the optimal size of the buffer to write data on the channel.
  894. /// </summary>
  895. /// <param name="bufferSize">The buffer size configured on the client.</param>
  896. /// <param name="handle">The file handle.</param>
  897. /// <returns>
  898. /// The optimal size of the buffer to write data on the channel.
  899. /// </returns>
  900. /// <remarks>
  901. /// Currently, we do not take the remote window size into account.
  902. /// </remarks>
  903. internal uint CalculateOptimalWriteLength(uint bufferSize, byte[] handle)
  904. {
  905. // 1-4: package length of SSH_FXP_WRITE message
  906. // 5: message type
  907. // 6-9: request id
  908. // 10-13: handle length
  909. // <handle>
  910. // 14-21: offset
  911. // 22-25: data length
  912. var lengthOfNonDataProtocolFields = 25u + (uint)handle.Length;
  913. var maximumPacketSize = Channel.RemotePacketSize;
  914. return Math.Min(bufferSize, maximumPacketSize) - lengthOfNonDataProtocolFields;
  915. }
  916. private SshException GetSftpException(SftpStatusResponse response)
  917. {
  918. if (response.StatusCode == StatusCodes.Ok)
  919. {
  920. return null;
  921. }
  922. if (response.StatusCode == StatusCodes.PermissionDenied)
  923. {
  924. return new SftpPermissionDeniedException(response.ErrorMessage);
  925. }
  926. if (response.StatusCode == StatusCodes.NoSuchFile)
  927. {
  928. return new SftpPathNotFoundException(response.ErrorMessage);
  929. }
  930. return new SshException(response.ErrorMessage);
  931. }
  932. private void HandleResponse(SftpResponse response)
  933. {
  934. SftpRequest request;
  935. lock (_requests)
  936. {
  937. _requests.TryGetValue(response.ResponseId, out request);
  938. if (request != null)
  939. {
  940. _requests.Remove(response.ResponseId);
  941. }
  942. }
  943. if (request == null)
  944. throw new InvalidOperationException("Invalid response.");
  945. request.Complete(response);
  946. }
  947. }
  948. }