ScpClient.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Renci.SshNet.Channels;
  6. using System.IO;
  7. using Renci.SshNet.Common;
  8. using Renci.SshNet.Messages.Connection;
  9. using System.Text.RegularExpressions;
  10. using System.Threading;
  11. using System.Diagnostics;
  12. using System.Diagnostics.CodeAnalysis;
  13. namespace Renci.SshNet
  14. {
  15. /// <summary>
  16. /// Provides SCP client functionality.
  17. /// </summary>
  18. public partial class ScpClient : BaseClient
  19. {
  20. private static Regex _fileInfoRe = new Regex(@"C(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)");
  21. private static Regex _directoryInfoRe = new Regex(@"D(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)");
  22. private static Regex _timestampRe = new Regex(@"T(?<mtime>\d+) 0 (?<atime>\d+) 0");
  23. private static char[] _byteToChar;
  24. private bool _disposeConnectionInfo;
  25. /// <summary>
  26. /// Gets or sets the operation timeout.
  27. /// </summary>
  28. /// <value>The operation timeout.</value>
  29. public TimeSpan OperationTimeout { get; set; }
  30. /// <summary>
  31. /// Gets or sets the size of the buffer.
  32. /// </summary>
  33. /// <value>The size of the buffer.</value>
  34. public uint BufferSize { get; set; }
  35. /// <summary>
  36. /// Occurs when downloading file.
  37. /// </summary>
  38. public event EventHandler<ScpDownloadEventArgs> Downloading;
  39. /// <summary>
  40. /// Occurs when uploading file.
  41. /// </summary>
  42. public event EventHandler<ScpUploadEventArgs> Uploading;
  43. #region Constructors
  44. /// <summary>
  45. /// Initializes a new instance of the <see cref="SftpClient"/> class.
  46. /// </summary>
  47. /// <param name="connectionInfo">The connection info.</param>
  48. /// <exception cref="ArgumentNullException"><paramref name="connectionInfo"/> is null.</exception>
  49. public ScpClient(ConnectionInfo connectionInfo)
  50. : base(connectionInfo)
  51. {
  52. this.OperationTimeout = new TimeSpan(0, 0, 0, 0, -1);
  53. this.BufferSize = 1024 * 16;
  54. if (_byteToChar == null)
  55. {
  56. _byteToChar = new char[128];
  57. var ch = '\0';
  58. for (int i = 0; i < 128; i++)
  59. {
  60. _byteToChar[i] = ch++;
  61. }
  62. }
  63. }
  64. /// <summary>
  65. /// Initializes a new instance of the <see cref="SftpClient"/> class.
  66. /// </summary>
  67. /// <param name="host">Connection host.</param>
  68. /// <param name="port">Connection port.</param>
  69. /// <param name="username">Authentication username.</param>
  70. /// <param name="password">Authentication password.</param>
  71. /// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
  72. /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is null or contains whitespace characters.</exception>
  73. /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
  74. [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
  75. public ScpClient(string host, int port, string username, string password)
  76. : this(new PasswordConnectionInfo(host, port, username, password))
  77. {
  78. this._disposeConnectionInfo = true;
  79. }
  80. /// <summary>
  81. /// Initializes a new instance of the <see cref="SftpClient"/> class.
  82. /// </summary>
  83. /// <param name="host">Connection host.</param>
  84. /// <param name="username">Authentication username.</param>
  85. /// <param name="password">Authentication password.</param>
  86. /// <exception cref="ArgumentNullException"><paramref name="password"/> is null.</exception>
  87. /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, or <paramref name="username"/> is null or contains whitespace characters.</exception>
  88. public ScpClient(string host, string username, string password)
  89. : this(host, ConnectionInfo.DEFAULT_PORT, username, password)
  90. {
  91. }
  92. /// <summary>
  93. /// Initializes a new instance of the <see cref="SftpClient"/> class.
  94. /// </summary>
  95. /// <param name="host">Connection host.</param>
  96. /// <param name="port">Connection port.</param>
  97. /// <param name="username">Authentication username.</param>
  98. /// <param name="keyFiles">Authentication private key file(s) .</param>
  99. /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is null.</exception>
  100. /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is null or contains whitespace characters.</exception>
  101. /// <exception cref="ArgumentOutOfRangeException"><paramref name="port"/> is not within <see cref="System.Net.IPEndPoint.MinPort"/> and <see cref="System.Net.IPEndPoint.MaxPort"/>.</exception>
  102. [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope", Justification = "Disposed in Dispose(bool) method.")]
  103. public ScpClient(string host, int port, string username, params PrivateKeyFile[] keyFiles)
  104. : this(new PrivateKeyConnectionInfo(host, port, username, keyFiles))
  105. {
  106. this._disposeConnectionInfo = true;
  107. }
  108. /// <summary>
  109. /// Initializes a new instance of the <see cref="SftpClient"/> class.
  110. /// </summary>
  111. /// <param name="host">Connection host.</param>
  112. /// <param name="username">Authentication username.</param>
  113. /// <param name="keyFiles">Authentication private key file(s) .</param>
  114. /// <exception cref="ArgumentNullException"><paramref name="keyFiles"/> is null.</exception>
  115. /// <exception cref="ArgumentException"><paramref name="host"/> is invalid, -or- <paramref name="username"/> is null or contains whitespace characters.</exception>
  116. public ScpClient(string host, string username, params PrivateKeyFile[] keyFiles)
  117. : this(host, ConnectionInfo.DEFAULT_PORT, username, keyFiles)
  118. {
  119. }
  120. #endregion
  121. /// <summary>
  122. /// Uploads the specified stream to the remote host.
  123. /// </summary>
  124. /// <param name="source">Stream to upload.</param>
  125. /// <param name="filename">Remote host file name.</param>
  126. public void Upload(Stream source, string filename)
  127. {
  128. // Ensure that connection is established.
  129. this.EnsureConnection();
  130. using (var input = new PipeStream())
  131. using (var channel = this.Session.CreateChannel<ChannelSession>())
  132. {
  133. channel.DataReceived += delegate(object sender, Common.ChannelDataEventArgs e)
  134. {
  135. input.Write(e.Data, 0, e.Data.Length);
  136. input.Flush();
  137. };
  138. channel.Open();
  139. // Send channel command request
  140. channel.SendExecRequest(string.Format("scp -t \"{0}\"", filename));
  141. this.CheckReturnCode(input);
  142. this.InternalFileUpload(channel, input, source, filename);
  143. channel.Close();
  144. }
  145. }
  146. /// <summary>
  147. /// Downloads the specified file from the remote host to the stream.
  148. /// </summary>
  149. /// <param name="filename">Remote host file name.</param>
  150. /// <param name="destination">The stream where to download remote file.</param>
  151. /// <exception cref="ArgumentException"><paramref name="filename"/> is null or contains whitespace characters.</exception>
  152. /// <exception cref="ArgumentNullException"><paramref name="destination"/> is null.</exception>
  153. /// <remarks>Method calls made by this method to <paramref name="destination"/>, may under certain conditions result in exceptions thrown by the stream.</remarks>
  154. public void Download(string filename, Stream destination)
  155. {
  156. if (filename.IsNullOrWhiteSpace())
  157. throw new ArgumentException("filename");
  158. if (destination == null)
  159. throw new ArgumentNullException("destination");
  160. // Ensure that connection is established.
  161. this.EnsureConnection();
  162. using (var input = new PipeStream())
  163. using (var channel = this.Session.CreateChannel<ChannelSession>())
  164. {
  165. channel.DataReceived += delegate(object sender, Common.ChannelDataEventArgs e)
  166. {
  167. input.Write(e.Data, 0, e.Data.Length);
  168. input.Flush();
  169. };
  170. channel.Open();
  171. // Send channel command request
  172. channel.SendExecRequest(string.Format("scp -f \"{0}\"", filename));
  173. this.SendConfirmation(channel); // Send reply
  174. var message = ReadString(input);
  175. var match = _fileInfoRe.Match(message);
  176. if (match.Success)
  177. {
  178. // Read file
  179. this.SendConfirmation(channel); // Send reply
  180. var mode = match.Result("${mode}");
  181. var length = long.Parse(match.Result("${length}"));
  182. var fileName = match.Result("${filename}");
  183. this.InternalFileDownload(channel, input, destination, fileName, length);
  184. }
  185. else
  186. {
  187. this.SendConfirmation(channel, 1, string.Format("\"{0}\" is not valid protocol message.", message));
  188. }
  189. channel.Close();
  190. }
  191. }
  192. private void InternalSetTimestamp(ChannelSession channel, Stream input, DateTime lastWriteTime, DateTime lastAccessime)
  193. {
  194. var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
  195. var modificationSeconds = (long)(lastWriteTime - zeroTime).TotalSeconds;
  196. var accessSeconds = (long)(lastAccessime - zeroTime).TotalSeconds;
  197. this.SendData(channel, string.Format("T{0} 0 {1} 0\n", modificationSeconds, accessSeconds));
  198. this.CheckReturnCode(input);
  199. }
  200. private void InternalFileUpload(ChannelSession channel, Stream input, Stream source, string filename)
  201. {
  202. var length = source.Length;
  203. this.SendData(channel, string.Format("C0644 {0} {1}\n", length, Path.GetFileName(filename)));
  204. var buffer = new byte[this.BufferSize];
  205. var read = source.Read(buffer, 0, buffer.Length);
  206. long totalRead = 0;
  207. while (read > 0)
  208. {
  209. this.SendData(channel, buffer, read);
  210. totalRead += read;
  211. this.RaiseUploadingEvent(filename, length, totalRead);
  212. read = source.Read(buffer, 0, buffer.Length);
  213. }
  214. this.SendConfirmation(channel);
  215. this.CheckReturnCode(input);
  216. }
  217. private void InternalFileDownload(ChannelSession channel, Stream input, Stream output, string filename, long length)
  218. {
  219. var buffer = new byte[Math.Min(length, this.BufferSize)];
  220. var needToRead = length;
  221. do
  222. {
  223. var read = input.Read(buffer, 0, (int)Math.Min(needToRead, this.BufferSize));
  224. output.Write(buffer, 0, read);
  225. this.RaiseDownloadingEvent(filename, length, length - needToRead);
  226. needToRead -= read;
  227. }
  228. while (needToRead > 0);
  229. output.Flush();
  230. // Raise one more time when file downloaded
  231. this.RaiseDownloadingEvent(filename, length, length - needToRead);
  232. // Send confirmation byte after last data byte was read
  233. this.SendConfirmation(channel);
  234. this.CheckReturnCode(input);
  235. }
  236. private void RaiseDownloadingEvent(string filename, long size, long downloaded)
  237. {
  238. if (this.Downloading != null)
  239. {
  240. this.Downloading(this, new ScpDownloadEventArgs(filename, size, downloaded));
  241. }
  242. }
  243. private void RaiseUploadingEvent(string filename, long size, long uploaded)
  244. {
  245. if (this.Uploading != null)
  246. {
  247. this.Uploading(this, new ScpUploadEventArgs(filename, size, uploaded));
  248. }
  249. }
  250. private void SendConfirmation(ChannelSession channel)
  251. {
  252. this.SendData(channel, new byte[] { 0 });
  253. }
  254. private void SendConfirmation(ChannelSession channel, byte errorCode, string message)
  255. {
  256. this.SendData(channel, new byte[] { errorCode });
  257. this.SendData(channel, string.Format("{0}\n", message));
  258. }
  259. /// <summary>
  260. /// Checks the return code.
  261. /// </summary>
  262. /// <param name="input">The output stream.</param>
  263. private void CheckReturnCode(Stream input)
  264. {
  265. var b = ReadByte(input);
  266. if (b > 0)
  267. {
  268. var errorText = ReadString(input);
  269. throw new ScpException(errorText);
  270. }
  271. }
  272. partial void SendData(ChannelSession channel, string command);
  273. private void SendData(ChannelSession channel, byte[] buffer, int length)
  274. {
  275. if (length == buffer.Length)
  276. {
  277. channel.SendData(buffer);
  278. }
  279. else
  280. {
  281. channel.SendData(buffer.Take(length).ToArray());
  282. }
  283. }
  284. private void SendData(ChannelSession channel, byte[] buffer)
  285. {
  286. channel.SendData(buffer);
  287. }
  288. private static int ReadByte(Stream stream)
  289. {
  290. var b = stream.ReadByte();
  291. while (b < 0)
  292. {
  293. Thread.Sleep(100);
  294. b = stream.ReadByte();
  295. }
  296. return b;
  297. }
  298. private static string ReadString(Stream stream)
  299. {
  300. var hasError = false;
  301. StringBuilder sb = new StringBuilder();
  302. var b = ReadByte(stream);
  303. if (b == 1 || b == 2)
  304. {
  305. hasError = true;
  306. b = ReadByte(stream);
  307. }
  308. var ch = _byteToChar[b];
  309. while (ch != '\n')
  310. {
  311. sb.Append(ch);
  312. b = ReadByte(stream);
  313. ch = _byteToChar[b];
  314. }
  315. if (hasError)
  316. throw new ScpException(sb.ToString());
  317. return sb.ToString();
  318. }
  319. /// <summary>
  320. /// Releases unmanaged and - optionally - managed resources
  321. /// </summary>
  322. /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>
  323. protected override void Dispose(bool disposing)
  324. {
  325. base.Dispose(disposing);
  326. if (this._disposeConnectionInfo)
  327. ((IDisposable)this.ConnectionInfo).Dispose();
  328. }
  329. }
  330. }