2
0

ScpClient.NET.cs 13 KB

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