diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index 124ce9d4b..839118252 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -36,12 +36,12 @@ FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_POLL;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII;FEATURE_ECDSA - FEATURE_STRINGBUILDER_CLEAR;FEATURE_HASHALGORITHM_DISPOSE;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII;FEATURE_ECDSA + FEATURE_STRINGBUILDER_CLEAR;FEATURE_HASHALGORITHM_DISPOSE;FEATURE_REGEX_COMPILE;FEATURE_BINARY_SERIALIZATION;FEATURE_RNG_CREATE;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_EAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_STREAM_APM;FEATURE_DNS_SYNC;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HASH_RIPEMD160_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_HMAC_RIPEMD160;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_ENCODING_ASCII;FEATURE_ECDSA;FEATURE_TPL - FEATURE_STRINGBUILDER_CLEAR;FEATURE_HASHALGORITHM_DISPOSE;FEATURE_ENCODING_ASCII;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_DIRECTORYINFO_ENUMERATEFILES;FEATURE_MEMORYSTREAM_TRYGETBUFFER;FEATURE_REFLECTION_TYPEINFO;FEATURE_RNG_CREATE;FEATURE_SOCKET_TAP;FEATURE_SOCKET_EAP;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_DNS_TAP;FEATURE_STREAM_TAP;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_TAP;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512 + FEATURE_STRINGBUILDER_CLEAR;FEATURE_HASHALGORITHM_DISPOSE;FEATURE_ENCODING_ASCII;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_DIRECTORYINFO_ENUMERATEFILES;FEATURE_MEMORYSTREAM_TRYGETBUFFER;FEATURE_REFLECTION_TYPEINFO;FEATURE_RNG_CREATE;FEATURE_SOCKET_TAP;FEATURE_SOCKET_EAP;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_DNS_TAP;FEATURE_STREAM_TAP;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_TAP;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_TPL - FEATURE_STRINGBUILDER_CLEAR;FEATURE_HASHALGORITHM_DISPOSE;FEATURE_ENCODING_ASCII;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_DIRECTORYINFO_ENUMERATEFILES;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_MEMORYSTREAM_TRYGETBUFFER;FEATURE_RNG_CREATE;FEATURE_SOCKET_TAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_EAP;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_DNS_SYNC;FEATURE_DNS_APM;FEATURE_DNS_TAP;FEATURE_STREAM_APM;FEATURE_STREAM_TAP;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_TAP;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_ECDSA + FEATURE_STRINGBUILDER_CLEAR;FEATURE_HASHALGORITHM_DISPOSE;FEATURE_ENCODING_ASCII;FEATURE_DIAGNOSTICS_TRACESOURCE;FEATURE_DIRECTORYINFO_ENUMERATEFILES;FEATURE_MEMORYSTREAM_GETBUFFER;FEATURE_MEMORYSTREAM_TRYGETBUFFER;FEATURE_RNG_CREATE;FEATURE_SOCKET_TAP;FEATURE_SOCKET_APM;FEATURE_SOCKET_EAP;FEATURE_SOCKET_SYNC;FEATURE_SOCKET_SELECT;FEATURE_SOCKET_POLL;FEATURE_SOCKET_DISPOSE;FEATURE_DNS_SYNC;FEATURE_DNS_APM;FEATURE_DNS_TAP;FEATURE_STREAM_APM;FEATURE_STREAM_TAP;FEATURE_THREAD_COUNTDOWNEVENT;FEATURE_THREAD_TAP;FEATURE_THREAD_THREADPOOL;FEATURE_THREAD_SLEEP;FEATURE_WAITHANDLE_DISPOSE;FEATURE_HASH_MD5;FEATURE_HASH_SHA1_CREATE;FEATURE_HASH_SHA256_CREATE;FEATURE_HASH_SHA384_CREATE;FEATURE_HASH_SHA512_CREATE;FEATURE_HMAC_MD5;FEATURE_HMAC_SHA1;FEATURE_HMAC_SHA256;FEATURE_HMAC_SHA384;FEATURE_HMAC_SHA512;FEATURE_ECDSA;FEATURE_TPL diff --git a/src/Renci.SshNet/ScpClient.cs b/src/Renci.SshNet/ScpClient.cs index 8a252cac9..c0a448fd4 100644 --- a/src/Renci.SshNet/ScpClient.cs +++ b/src/Renci.SshNet/ScpClient.cs @@ -7,6 +7,10 @@ using System.Net; using System.Collections.Generic; +#if FEATURE_TPL +using System.Threading; +#endif + namespace Renci.SshNet { /// @@ -90,7 +94,7 @@ public IRemotePathTransformation RemotePathTransformation /// public event EventHandler Uploading; - #region Constructors +#region Constructors /// /// Initializes a new instance of the class. @@ -195,8 +199,10 @@ internal ScpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServ _remotePathTransformation = serviceFactory.CreateRemotePathDoubleQuoteTransformation(); } - #endregion +#endregion + +#if FEATURE_TPL /// /// Uploads the specified stream to the remote host. /// @@ -208,7 +214,71 @@ internal ScpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServ /// The secure copy execution request was rejected by the server. public void Upload(Stream source, string path) { - var posixPath = PosixPath.CreateAbsoluteOrRelativeFilePath(path); + Upload(source, path, CancellationToken.None); + } + +#endif + +#if FEATURE_TPL + /// + /// Uploads the specified stream to the remote host. + /// + /// The to upload. + /// A relative or absolute path for the remote file. + /// cancellation token + /// is null. + /// is a zero-length . + /// A directory with the specified path exists on the remote host. + /// The secure copy execution request was rejected by the server. + /// Download cancelled exception + public void Upload(Stream source, string path,CancellationToken cancellationToken ) + { + try + { + var posixPath = PosixPath.CreateAbsoluteOrRelativeFilePath(path); + + using (var input = ServiceFactory.CreatePipeStream()) + using (var channel = Session.CreateChannelSession()) + using (cancellationToken.Register(() => { channel.Dispose(); input.Dispose(); })) + { + channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length); + channel.Open(); + + // Pass only the directory part of the path to the server, and use the (hidden) -d option to signal + // that we expect the target to be a directory. + if (!channel.SendExecRequest(string.Format("scp -t -d {0}", + _remotePathTransformation.Transform(posixPath.Directory)))) + { + throw SecureExecutionRequestRejectedException(); + } + + CheckReturnCode(input); + + UploadFileModeAndName(channel, input, source.Length, posixPath.File); + UploadFileContent(channel, input, source, posixPath.File); + } + } + catch(Exception) + { + if (cancellationToken.IsCancellationRequested) + throw new OperationCanceledException(); + else + throw; + } + } +#else + /// + /// Uploads the specified stream to the remote host. + /// + /// The to upload. + /// A relative or absolute path for the remote file. + /// is null. + /// is a zero-length . + /// A directory with the specified path exists on the remote host. + /// The secure copy execution request was rejected by the server. + public void Upload(Stream source, string path) + { + var posixPath = PosixPath.CreateAbsoluteOrRelativeFilePath(path); using (var input = ServiceFactory.CreatePipeStream()) using (var channel = Session.CreateChannelSession()) @@ -227,8 +297,98 @@ public void Upload(Stream source, string path) UploadFileModeAndName(channel, input, source.Length, posixPath.File); UploadFileContent(channel, input, source, posixPath.File); } + + } +#endif + +#if FEATURE_TPL + /// + /// Downloads the specified file from the remote host to the stream. + /// + /// A relative or absolute path for the remote file. + /// The to download the remote file to. + /// is null or contains only whitespace characters. + /// is null. + /// exists on the remote host, and is not a regular file. + /// The secure copy execution request was rejected by the server. + public void Download(string filename, Stream destination) + { + Download(filename,destination,CancellationToken.None); + } +#endif + +#if FEATURE_TPL + /// + /// Downloads the specified file from the remote host to the stream. + /// + /// A relative or absolute path for the remote file. + /// The to download the remote file to. + /// cancellation token + /// is null or contains only whitespace characters. + /// is null. + /// exists on the remote host, and is not a regular file. + /// The secure copy execution request was rejected by the server. + /// Download cancelled exception + public void Download(string filename, Stream destination, CancellationToken cancellationToken) + { + if (filename.IsNullOrWhiteSpace()) + throw new ArgumentException("filename"); + + if (destination == null) + throw new ArgumentNullException("destination"); + + try + { + using (var input = ServiceFactory.CreatePipeStream()) + using (var channel = Session.CreateChannelSession()) + using (cancellationToken.Register(() => + { + channel.Dispose(); + input.Dispose(); + })) + { + channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length); + channel.Open(); + + // Send channel command request + if (!channel.SendExecRequest(string.Format("scp -f {0}", + _remotePathTransformation.Transform(filename)))) + { + throw SecureExecutionRequestRejectedException(); + } + + SendSuccessConfirmation(channel); // Send reply + + var message = ReadString(input); + var match = FileInfoRe.Match(message); + + if (match.Success) + { + // Read file + SendSuccessConfirmation(channel); // Send reply + + var length = long.Parse(match.Result("${length}")); + var fileName = match.Result("${filename}"); + + InternalDownload(channel, input, destination, fileName, length); + } + else + { + SendErrorConfirmation(channel, + string.Format("\"{0}\" is not valid protocol message.", message)); + } + } + } + catch (Exception) + { + if (cancellationToken.IsCancellationRequested) + throw new OperationCanceledException(); + else + throw; + } } +#else /// /// Downloads the specified file from the remote host to the stream. /// @@ -278,6 +438,7 @@ public void Download(string filename, Stream destination) } } } +#endif /// /// Sets mode, size and name of file being upload.