ScpClient.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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 * 32 - 38;
  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, 22, 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, 22, 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. using (var input = new PipeStream())
  129. using (var channel = this.Session.CreateChannel<ChannelSession>())
  130. {
  131. channel.DataReceived += delegate(object sender, Common.ChannelDataEventArgs e)
  132. {
  133. input.Write(e.Data, 0, e.Data.Length);
  134. input.Flush();
  135. };
  136. channel.Open();
  137. // Send channel command request
  138. channel.SendExecRequest(string.Format("scp -qt \"{0}\"", filename));
  139. this.CheckReturnCode(input);
  140. this.InternalFileUpload(channel, input, source, filename);
  141. channel.Close();
  142. }
  143. }
  144. /// <summary>
  145. /// Downloads the specified file from the remote host to the stream.
  146. /// </summary>
  147. /// <param name="filename">Remote host file name.</param>
  148. /// <param name="destination">The stream where to download remote file.</param>
  149. public void Download(string filename, Stream destination)
  150. {
  151. using (var input = new PipeStream())
  152. using (var channel = this.Session.CreateChannel<ChannelSession>())
  153. {
  154. channel.DataReceived += delegate(object sender, Common.ChannelDataEventArgs e)
  155. {
  156. input.Write(e.Data, 0, e.Data.Length);
  157. input.Flush();
  158. };
  159. channel.Open();
  160. // Send channel command request
  161. channel.SendExecRequest(string.Format("scp -qf \"{0}\"", filename));
  162. this.SendConfirmation(channel); // Send reply
  163. var message = ReadString(input);
  164. var match = _fileInfoRe.Match(message);
  165. if (match.Success)
  166. {
  167. // Read file
  168. this.SendConfirmation(channel); // Send reply
  169. var mode = match.Result("${mode}");
  170. var length = long.Parse(match.Result("${length}"));
  171. var fileName = match.Result("${filename}");
  172. this.InternalFileDownload(channel, input, destination, fileName, length);
  173. }
  174. else
  175. {
  176. this.SendConfirmation(channel, 1, string.Format("\"{0}\" is not valid protocol message.", message));
  177. }
  178. channel.Close();
  179. }
  180. }
  181. private void InternalSetTimestamp(ChannelSession channel, Stream input, DateTime lastWriteTime, DateTime lastAccessime)
  182. {
  183. var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
  184. var modificationSeconds = (long)(lastWriteTime - zeroTime).TotalSeconds;
  185. var accessSeconds = (long)(lastAccessime - zeroTime).TotalSeconds;
  186. this.SendData(channel, string.Format("T{0} 0 {1} 0\n", modificationSeconds, accessSeconds));
  187. this.CheckReturnCode(input);
  188. }
  189. private void InternalFileUpload(ChannelSession channel, Stream input, Stream source, string filename)
  190. {
  191. var length = source.Length;
  192. this.SendData(channel, string.Format("C0644 {0} {1}\n", length, filename));
  193. var buffer = new byte[this.BufferSize];
  194. var read = source.Read(buffer, 0, buffer.Length);
  195. long totalRead = 0;
  196. while (read > 0)
  197. {
  198. this.SendData(channel, buffer, read);
  199. totalRead += read;
  200. this.RaiseUploadingEvent(filename, length, totalRead);
  201. read = source.Read(buffer, 0, buffer.Length);
  202. }
  203. this.SendConfirmation(channel);
  204. this.CheckReturnCode(input);
  205. }
  206. private void InternalFileDownload(ChannelSession channel, Stream input, Stream output, string filename, long length)
  207. {
  208. var buffer = new byte[Math.Min(length, this.BufferSize)];
  209. var needToRead = length;
  210. do
  211. {
  212. var read = input.Read(buffer, 0, (int)Math.Min(needToRead, this.BufferSize));
  213. output.Write(buffer, 0, read);
  214. this.RaiseDownloadingEvent(filename, length, length - needToRead);
  215. needToRead -= read;
  216. }
  217. while (needToRead > 0);
  218. output.Flush();
  219. // Raise one more time when file downloaded
  220. this.RaiseDownloadingEvent(filename, length, length - needToRead);
  221. // Send confirmation byte after last data byte was read
  222. this.SendConfirmation(channel);
  223. this.CheckReturnCode(input);
  224. }
  225. private void RaiseDownloadingEvent(string filename, long size, long downloaded)
  226. {
  227. if (this.Downloading != null)
  228. {
  229. this.Downloading(this, new ScpDownloadEventArgs(filename, size, downloaded));
  230. }
  231. }
  232. private void RaiseUploadingEvent(string filename, long size, long uploaded)
  233. {
  234. if (this.Uploading != null)
  235. {
  236. this.Uploading(this, new ScpUploadEventArgs(filename, size, uploaded));
  237. }
  238. }
  239. private void SendConfirmation(ChannelSession channel)
  240. {
  241. this.SendData(channel, new byte[] { 0 });
  242. }
  243. private void SendConfirmation(ChannelSession channel, byte errorCode, string message)
  244. {
  245. this.SendData(channel, new byte[] { errorCode });
  246. this.SendData(channel, string.Format("{0}\n", message));
  247. }
  248. /// <summary>
  249. /// Checks the return code.
  250. /// </summary>
  251. /// <param name="input">The output stream.</param>
  252. private void CheckReturnCode(Stream input)
  253. {
  254. var b = ReadByte(input);
  255. if (b > 0)
  256. {
  257. var errorText = ReadString(input);
  258. throw new ScpException(errorText);
  259. }
  260. }
  261. partial void SendData(ChannelSession channel, string command);
  262. private void SendData(ChannelSession channel, byte[] buffer, int length)
  263. {
  264. if (length == buffer.Length)
  265. {
  266. channel.SendData(buffer);
  267. }
  268. else
  269. {
  270. channel.SendData(buffer.Take(length).ToArray());
  271. }
  272. }
  273. private void SendData(ChannelSession channel, byte[] buffer)
  274. {
  275. channel.SendData(buffer);
  276. }
  277. private static int ReadByte(Stream stream)
  278. {
  279. var b = stream.ReadByte();
  280. while (b < 0)
  281. {
  282. Thread.Sleep(100);
  283. b = stream.ReadByte();
  284. }
  285. return b;
  286. }
  287. private static string ReadString(Stream stream)
  288. {
  289. var hasError = false;
  290. StringBuilder sb = new StringBuilder();
  291. var b = ReadByte(stream);
  292. if (b == 1 || b == 2)
  293. {
  294. hasError = true;
  295. b = ReadByte(stream);
  296. }
  297. var ch = _byteToChar[b];
  298. while (ch != '\n')
  299. {
  300. sb.Append(ch);
  301. b = ReadByte(stream);
  302. ch = _byteToChar[b];
  303. }
  304. if (hasError)
  305. throw new ScpException(sb.ToString());
  306. return sb.ToString();
  307. }
  308. /// <summary>
  309. /// Releases unmanaged and - optionally - managed resources
  310. /// </summary>
  311. /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged ResourceMessages.</param>
  312. protected override void Dispose(bool disposing)
  313. {
  314. base.Dispose(disposing);
  315. if (this._disposeConnectionInfo)
  316. ((IDisposable)this.ConnectionInfo).Dispose();
  317. }
  318. }
  319. }