ScpClient.NET.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. using System;
  2. using System.Text;
  3. using Renci.SshNet.Channels;
  4. using System.IO;
  5. using Renci.SshNet.Common;
  6. using System.Text.RegularExpressions;
  7. namespace Renci.SshNet
  8. {
  9. /// <summary>
  10. /// Provides SCP client functionality.
  11. /// </summary>
  12. public partial class ScpClient
  13. {
  14. private static readonly Regex DirectoryInfoRe = new Regex(@"D(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)");
  15. private static readonly Regex TimestampRe = new Regex(@"T(?<mtime>\d+) 0 (?<atime>\d+) 0");
  16. /// <summary>
  17. /// Uploads the specified file to the remote host.
  18. /// </summary>
  19. /// <param name="fileInfo">The file system info.</param>
  20. /// <param name="path">The path.</param>
  21. /// <exception cref="ArgumentNullException"><paramref name="fileInfo" /> is null.</exception>
  22. /// <exception cref="ArgumentException"><paramref name="path"/> is null or empty.</exception>
  23. public void Upload(FileInfo fileInfo, string path)
  24. {
  25. if (fileInfo == null)
  26. throw new ArgumentNullException("fileInfo");
  27. if (string.IsNullOrEmpty(path))
  28. throw new ArgumentException("path");
  29. using (var input = ServiceFactory.CreatePipeStream())
  30. using (var channel = Session.CreateChannelSession())
  31. {
  32. channel.DataReceived += delegate(object sender, ChannelDataEventArgs e)
  33. {
  34. input.Write(e.Data, 0, e.Data.Length);
  35. input.Flush();
  36. };
  37. channel.Open();
  38. if (!channel.SendExecRequest(string.Format("scp -t \"{0}\"", path)))
  39. throw new SshException("Secure copy execution request was rejected by the server. Please consult the server logs.");
  40. CheckReturnCode(input);
  41. InternalUpload(channel, input, fileInfo, fileInfo.Name);
  42. channel.Close();
  43. }
  44. }
  45. /// <summary>
  46. /// Uploads the specified directory to the remote host.
  47. /// </summary>
  48. /// <param name="directoryInfo">The directory info.</param>
  49. /// <param name="path">The path.</param>
  50. /// <exception cref="ArgumentNullException">fileSystemInfo</exception>
  51. /// <exception cref="ArgumentException"><paramref name="path"/> is null or empty.</exception>
  52. public void Upload(DirectoryInfo directoryInfo, string path)
  53. {
  54. if (directoryInfo == null)
  55. throw new ArgumentNullException("directoryInfo");
  56. if (string.IsNullOrEmpty(path))
  57. throw new ArgumentException("path");
  58. using (var input = new PipeStream())
  59. using (var channel = Session.CreateChannelSession())
  60. {
  61. channel.DataReceived += delegate(object sender, ChannelDataEventArgs e)
  62. {
  63. input.Write(e.Data, 0, e.Data.Length);
  64. input.Flush();
  65. };
  66. channel.Open();
  67. // Send channel command request
  68. channel.SendExecRequest(string.Format("scp -rt \"{0}\"", path));
  69. CheckReturnCode(input);
  70. InternalSetTimestamp(channel, input, directoryInfo.LastWriteTimeUtc, directoryInfo.LastAccessTimeUtc);
  71. SendData(channel, string.Format("D0755 0 {0}\n", Path.GetFileName(path)));
  72. CheckReturnCode(input);
  73. InternalUpload(channel, input, directoryInfo);
  74. SendData(channel, "E\n");
  75. CheckReturnCode(input);
  76. channel.Close();
  77. }
  78. }
  79. /// <summary>
  80. /// Downloads the specified file from the remote host to local file.
  81. /// </summary>
  82. /// <param name="filename">Remote host file name.</param>
  83. /// <param name="fileInfo">Local file information.</param>
  84. /// <exception cref="ArgumentNullException"><paramref name="fileInfo"/> is null.</exception>
  85. /// <exception cref="ArgumentException"><paramref name="filename"/> is null or empty.</exception>
  86. public void Download(string filename, FileInfo fileInfo)
  87. {
  88. if (string.IsNullOrEmpty(filename))
  89. throw new ArgumentException("filename");
  90. if (fileInfo == null)
  91. throw new ArgumentNullException("fileInfo");
  92. using (var input = new PipeStream())
  93. using (var channel = Session.CreateChannelSession())
  94. {
  95. channel.DataReceived += delegate(object sender, ChannelDataEventArgs e)
  96. {
  97. input.Write(e.Data, 0, e.Data.Length);
  98. input.Flush();
  99. };
  100. channel.Open();
  101. // Send channel command request
  102. channel.SendExecRequest(string.Format("scp -pf \"{0}\"", filename));
  103. SendConfirmation(channel); // Send reply
  104. InternalDownload(channel, input, fileInfo);
  105. channel.Close();
  106. }
  107. }
  108. /// <summary>
  109. /// Downloads the specified directory from the remote host to local directory.
  110. /// </summary>
  111. /// <param name="directoryName">Remote host directory name.</param>
  112. /// <param name="directoryInfo">Local directory information.</param>
  113. /// <exception cref="ArgumentException"><paramref name="directoryName"/> is null or empty.</exception>
  114. /// <exception cref="ArgumentNullException"><paramref name="directoryInfo"/> is null.</exception>
  115. public void Download(string directoryName, DirectoryInfo directoryInfo)
  116. {
  117. if (string.IsNullOrEmpty(directoryName))
  118. throw new ArgumentException("directoryName");
  119. if (directoryInfo == null)
  120. throw new ArgumentNullException("directoryInfo");
  121. using (var input = new PipeStream())
  122. using (var channel = Session.CreateChannelSession())
  123. {
  124. channel.DataReceived += delegate(object sender, ChannelDataEventArgs e)
  125. {
  126. input.Write(e.Data, 0, e.Data.Length);
  127. input.Flush();
  128. };
  129. channel.Open();
  130. // Send channel command request
  131. channel.SendExecRequest(string.Format("scp -prf \"{0}\"", directoryName));
  132. SendConfirmation(channel); // Send reply
  133. InternalDownload(channel, input, directoryInfo);
  134. channel.Close();
  135. }
  136. }
  137. private void InternalUpload(IChannelSession channel, Stream input, FileInfo fileInfo, string filename)
  138. {
  139. InternalSetTimestamp(channel, input, fileInfo.LastWriteTimeUtc, fileInfo.LastAccessTimeUtc);
  140. using (var source = fileInfo.OpenRead())
  141. {
  142. InternalUpload(channel, input, source, filename);
  143. }
  144. }
  145. private void InternalUpload(IChannelSession channel, Stream input, DirectoryInfo directoryInfo)
  146. {
  147. // Upload files
  148. var files = directoryInfo.GetFiles();
  149. foreach (var file in files)
  150. {
  151. InternalUpload(channel, input, file, file.Name);
  152. }
  153. // Upload directories
  154. var directories = directoryInfo.GetDirectories();
  155. foreach (var directory in directories)
  156. {
  157. InternalSetTimestamp(channel, input, directoryInfo.LastWriteTimeUtc, directoryInfo.LastAccessTimeUtc);
  158. SendData(channel, string.Format("D0755 0 {0}\n", directory.Name));
  159. CheckReturnCode(input);
  160. InternalUpload(channel, input, directory);
  161. SendData(channel, "E\n");
  162. CheckReturnCode(input);
  163. }
  164. }
  165. private void InternalDownload(IChannelSession channel, Stream input, FileSystemInfo fileSystemInfo)
  166. {
  167. var modifiedTime = DateTime.Now;
  168. var accessedTime = DateTime.Now;
  169. var startDirectoryFullName = fileSystemInfo.FullName;
  170. var currentDirectoryFullName = startDirectoryFullName;
  171. var directoryCounter = 0;
  172. while (true)
  173. {
  174. var message = ReadString(input);
  175. if (message == "E")
  176. {
  177. SendConfirmation(channel); // Send reply
  178. directoryCounter--;
  179. currentDirectoryFullName = new DirectoryInfo(currentDirectoryFullName).Parent.FullName;
  180. if (directoryCounter == 0)
  181. break;
  182. continue;
  183. }
  184. var match = DirectoryInfoRe.Match(message);
  185. if (match.Success)
  186. {
  187. SendConfirmation(channel); // Send reply
  188. // Read directory
  189. var mode = long.Parse(match.Result("${mode}"));
  190. var filename = match.Result("${filename}");
  191. DirectoryInfo newDirectoryInfo;
  192. if (directoryCounter > 0)
  193. {
  194. newDirectoryInfo = Directory.CreateDirectory(string.Format("{0}{1}{2}", currentDirectoryFullName, Path.DirectorySeparatorChar, filename));
  195. newDirectoryInfo.LastAccessTime = accessedTime;
  196. newDirectoryInfo.LastWriteTime = modifiedTime;
  197. }
  198. else
  199. {
  200. // Dont create directory for first level
  201. newDirectoryInfo = fileSystemInfo as DirectoryInfo;
  202. }
  203. directoryCounter++;
  204. currentDirectoryFullName = newDirectoryInfo.FullName;
  205. continue;
  206. }
  207. match = FileInfoRe.Match(message);
  208. if (match.Success)
  209. {
  210. // Read file
  211. SendConfirmation(channel); // Send reply
  212. var mode = match.Result("${mode}");
  213. var length = long.Parse(match.Result("${length}"));
  214. var fileName = match.Result("${filename}");
  215. var fileInfo = fileSystemInfo as FileInfo;
  216. if (fileInfo == null)
  217. fileInfo = new FileInfo(string.Format("{0}{1}{2}", currentDirectoryFullName, Path.DirectorySeparatorChar, fileName));
  218. using (var output = fileInfo.OpenWrite())
  219. {
  220. InternalDownload(channel, input, output, fileName, length);
  221. }
  222. fileInfo.LastAccessTime = accessedTime;
  223. fileInfo.LastWriteTime = modifiedTime;
  224. if (directoryCounter == 0)
  225. break;
  226. continue;
  227. }
  228. match = TimestampRe.Match(message);
  229. if (match.Success)
  230. {
  231. // Read timestamp
  232. SendConfirmation(channel); // Send reply
  233. var mtime = long.Parse(match.Result("${mtime}"));
  234. var atime = long.Parse(match.Result("${atime}"));
  235. var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
  236. modifiedTime = zeroTime.AddSeconds(mtime);
  237. accessedTime = zeroTime.AddSeconds(atime);
  238. continue;
  239. }
  240. SendConfirmation(channel, 1, string.Format("\"{0}\" is not valid protocol message.", message));
  241. }
  242. }
  243. partial void SendData(IChannelSession channel, string command)
  244. {
  245. channel.SendData(Encoding.Default.GetBytes(command));
  246. }
  247. }
  248. }