SftpSession.cs 40 KB

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