ScpClient.cs 17 KB

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