|
|
@@ -6,6 +6,9 @@ using System.Collections.Generic;
|
|
|
using System.Globalization;
|
|
|
using Renci.SshNet.Sftp.Responses;
|
|
|
using Renci.SshNet.Sftp.Requests;
|
|
|
+#if FEATURE_TAP
|
|
|
+using System.Threading.Tasks;
|
|
|
+#endif
|
|
|
|
|
|
namespace Renci.SshNet.Sftp
|
|
|
{
|
|
|
@@ -136,6 +139,54 @@ namespace Renci.SshNet.Sftp
|
|
|
return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", canonizedPath, slash, pathParts[pathParts.Length - 1]);
|
|
|
}
|
|
|
|
|
|
+#if FEATURE_TAP
|
|
|
+ public async Task<string> GetCanonicalPathAsync(string path, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ var fullPath = GetFullRemotePath(path);
|
|
|
+
|
|
|
+ var canonizedPath = string.Empty;
|
|
|
+ var realPathFiles = await RequestRealPathAsync(fullPath, true, cancellationToken).ConfigureAwait(false);
|
|
|
+ if (realPathFiles != null)
|
|
|
+ {
|
|
|
+ canonizedPath = realPathFiles[0].Key;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!string.IsNullOrEmpty(canonizedPath))
|
|
|
+ return canonizedPath;
|
|
|
+
|
|
|
+ // Check for special cases
|
|
|
+ if (fullPath.EndsWith("/.", StringComparison.Ordinal) ||
|
|
|
+ fullPath.EndsWith("/..", StringComparison.Ordinal) ||
|
|
|
+ fullPath.Equals("/", StringComparison.Ordinal) ||
|
|
|
+ fullPath.IndexOf('/') < 0)
|
|
|
+ return fullPath;
|
|
|
+
|
|
|
+ var pathParts = fullPath.Split('/');
|
|
|
+
|
|
|
+ var partialFullPath = string.Join("/", pathParts, 0, pathParts.Length - 1);
|
|
|
+
|
|
|
+ if (string.IsNullOrEmpty(partialFullPath))
|
|
|
+ partialFullPath = "/";
|
|
|
+
|
|
|
+ realPathFiles = await RequestRealPathAsync(partialFullPath, true, cancellationToken).ConfigureAwait(false);
|
|
|
+
|
|
|
+ if (realPathFiles != null)
|
|
|
+ {
|
|
|
+ canonizedPath = realPathFiles[0].Key;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (string.IsNullOrEmpty(canonizedPath))
|
|
|
+ {
|
|
|
+ return fullPath;
|
|
|
+ }
|
|
|
+
|
|
|
+ var slash = string.Empty;
|
|
|
+ if (canonizedPath[canonizedPath.Length - 1] != '/')
|
|
|
+ slash = "/";
|
|
|
+ return canonizedPath + slash + pathParts[pathParts.Length - 1];
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
public ISftpFileReader CreateFileReader(byte[] handle, ISftpSession sftpSession, uint chunkSize, int maxPendingReads, long? fileSize)
|
|
|
{
|
|
|
return new SftpFileReader(handle, sftpSession, chunkSize, maxPendingReads, fileSize);
|
|
|
@@ -149,11 +200,11 @@ namespace Renci.SshNet.Sftp
|
|
|
{
|
|
|
if (WorkingDirectory[WorkingDirectory.Length - 1] == '/')
|
|
|
{
|
|
|
- fullPath = string.Format(CultureInfo.InvariantCulture, "{0}{1}", WorkingDirectory, path);
|
|
|
+ fullPath = WorkingDirectory + path;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- fullPath = string.Format(CultureInfo.InvariantCulture, "{0}/{1}", WorkingDirectory, path);
|
|
|
+ fullPath = WorkingDirectory + '/' + path;
|
|
|
}
|
|
|
}
|
|
|
return fullPath;
|
|
|
@@ -385,6 +436,24 @@ namespace Renci.SshNet.Sftp
|
|
|
return handle;
|
|
|
}
|
|
|
|
|
|
+#if FEATURE_TAP
|
|
|
+ public async Task<byte[]> RequestOpenAsync(string path, Flags flags, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
+
|
|
|
+ TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
+
|
|
|
+ using (cancellationToken.Register((s) => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(), tcs, false))
|
|
|
+ {
|
|
|
+ SendRequest(new SftpOpenRequest(ProtocolVersion, NextRequestId, path, Encoding, flags,
|
|
|
+ response => tcs.TrySetResult(response.Handle),
|
|
|
+ response => tcs.TrySetException(GetSftpException(response))));
|
|
|
+
|
|
|
+ return await tcs.Task.ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Performs SSH_FXP_OPEN request
|
|
|
/// </summary>
|
|
|
@@ -472,6 +541,34 @@ namespace Renci.SshNet.Sftp
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+#if FEATURE_TAP
|
|
|
+ public async Task RequestCloseAsync(byte[] handle, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
+
|
|
|
+ SendRequest(new SftpCloseRequest(ProtocolVersion, NextRequestId, handle,
|
|
|
+ response =>
|
|
|
+ {
|
|
|
+ if (response.StatusCode == StatusCodes.Ok)
|
|
|
+ {
|
|
|
+ tcs.TrySetResult(true);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ tcs.TrySetException(GetSftpException(response));
|
|
|
+ }
|
|
|
+ }));
|
|
|
+
|
|
|
+ // Only check for cancellation after the SftpCloseRequest was sent
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
+ using (cancellationToken.Register((s) => ((TaskCompletionSource<bool>)s).TrySetCanceled(), tcs, false))
|
|
|
+ {
|
|
|
+ await tcs.Task.ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Performs SSH_FXP_CLOSE request.
|
|
|
/// </summary>
|
|
|
@@ -635,6 +732,34 @@ namespace Renci.SshNet.Sftp
|
|
|
return data;
|
|
|
}
|
|
|
|
|
|
+#if FEATURE_TAP
|
|
|
+ public async Task<byte[]> RequestReadAsync(byte[] handle, ulong offset, uint length, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
+
|
|
|
+ TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
+
|
|
|
+ using (cancellationToken.Register((s) => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(), tcs, false))
|
|
|
+ {
|
|
|
+ SendRequest(new SftpReadRequest(ProtocolVersion, NextRequestId, handle, offset, length,
|
|
|
+ response => tcs.TrySetResult(response.Data),
|
|
|
+ response =>
|
|
|
+ {
|
|
|
+ if (response.StatusCode == StatusCodes.Eof)
|
|
|
+ {
|
|
|
+ tcs.TrySetResult(Array<byte>.Empty);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ tcs.TrySetException(GetSftpException(response));
|
|
|
+ }
|
|
|
+ }));
|
|
|
+
|
|
|
+ return await tcs.Task.ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Performs SSH_FXP_WRITE request.
|
|
|
/// </summary>
|
|
|
@@ -679,6 +804,33 @@ namespace Renci.SshNet.Sftp
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+#if FEATURE_TAP
|
|
|
+ public async Task RequestWriteAsync(byte[] handle, ulong serverOffset, byte[] data, int offset, int length, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
+
|
|
|
+ TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
+
|
|
|
+ using (cancellationToken.Register((s) => ((TaskCompletionSource<bool>)s).TrySetCanceled(), tcs, false))
|
|
|
+ {
|
|
|
+ SendRequest(new SftpWriteRequest(ProtocolVersion, NextRequestId, handle, serverOffset, data, offset, length,
|
|
|
+ response =>
|
|
|
+ {
|
|
|
+ if (response.StatusCode == StatusCodes.Ok)
|
|
|
+ {
|
|
|
+ tcs.TrySetResult(true);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ tcs.TrySetException(GetSftpException(response));
|
|
|
+ }
|
|
|
+ }));
|
|
|
+
|
|
|
+ await tcs.Task.ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Performs SSH_FXP_LSTAT request.
|
|
|
/// </summary>
|
|
|
@@ -811,6 +963,24 @@ namespace Renci.SshNet.Sftp
|
|
|
return attributes;
|
|
|
}
|
|
|
|
|
|
+#if FEATURE_TAP
|
|
|
+ public async Task<SftpFileAttributes> RequestFStatAsync(byte[] handle, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
+
|
|
|
+ TaskCompletionSource<SftpFileAttributes> tcs = new TaskCompletionSource<SftpFileAttributes>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
+
|
|
|
+ using (cancellationToken.Register((s) => ((TaskCompletionSource<SftpFileAttributes>)s).TrySetCanceled(), tcs, false))
|
|
|
+ {
|
|
|
+ SendRequest(new SftpFStatRequest(ProtocolVersion, NextRequestId, handle,
|
|
|
+ response => tcs.TrySetResult(response.Attributes),
|
|
|
+ response => tcs.TrySetException(GetSftpException(response))));
|
|
|
+
|
|
|
+ return await tcs.Task.ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Performs SSH_FXP_SETSTAT request.
|
|
|
/// </summary>
|
|
|
@@ -908,6 +1078,24 @@ namespace Renci.SshNet.Sftp
|
|
|
return handle;
|
|
|
}
|
|
|
|
|
|
+#if FEATURE_TAP
|
|
|
+ public async Task<byte[]> RequestOpenDirAsync(string path, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
+
|
|
|
+ TaskCompletionSource<byte[]> tcs = new TaskCompletionSource<byte[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
+
|
|
|
+ using (cancellationToken.Register((s) => ((TaskCompletionSource<byte[]>)s).TrySetCanceled(), tcs, false))
|
|
|
+ {
|
|
|
+ SendRequest(new SftpOpenDirRequest(ProtocolVersion, NextRequestId, path, Encoding,
|
|
|
+ response => tcs.TrySetResult(response.Handle),
|
|
|
+ response => tcs.TrySetException(GetSftpException(response))));
|
|
|
+
|
|
|
+ return await tcs.Task.ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Performs SSH_FXP_READDIR request
|
|
|
/// </summary>
|
|
|
@@ -949,6 +1137,35 @@ namespace Renci.SshNet.Sftp
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+#if FEATURE_TAP
|
|
|
+ public async Task<KeyValuePair<string, SftpFileAttributes>[]> RequestReadDirAsync(byte[] handle, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
+
|
|
|
+ TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]> tcs = new TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
+
|
|
|
+ using (cancellationToken.Register((s) => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>)s).TrySetCanceled(), tcs, false))
|
|
|
+ {
|
|
|
+ SendRequest(new SftpReadDirRequest(ProtocolVersion, NextRequestId, handle,
|
|
|
+ response => tcs.TrySetResult(response.Files),
|
|
|
+ response =>
|
|
|
+ {
|
|
|
+ if (response.StatusCode == StatusCodes.Eof)
|
|
|
+ {
|
|
|
+ tcs.TrySetResult(null);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ tcs.TrySetException(GetSftpException(response));
|
|
|
+ }
|
|
|
+ }));
|
|
|
+
|
|
|
+ return await tcs.Task.ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Performs SSH_FXP_REMOVE request.
|
|
|
/// </summary>
|
|
|
@@ -977,6 +1194,34 @@ namespace Renci.SshNet.Sftp
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+#if FEATURE_TAP
|
|
|
+ public async Task RequestRemoveAsync(string path, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
+
|
|
|
+ TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
+
|
|
|
+ using (cancellationToken.Register((s) => ((TaskCompletionSource<bool>)s).TrySetCanceled(), tcs, false))
|
|
|
+ {
|
|
|
+ SendRequest(new SftpRemoveRequest(ProtocolVersion, NextRequestId, path, Encoding,
|
|
|
+ response =>
|
|
|
+ {
|
|
|
+ if (response.StatusCode == StatusCodes.Ok)
|
|
|
+ {
|
|
|
+ tcs.TrySetResult(true);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ tcs.TrySetException(GetSftpException(response));
|
|
|
+ }
|
|
|
+ }));
|
|
|
+
|
|
|
+ await tcs.Task.ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Performs SSH_FXP_MKDIR request.
|
|
|
/// </summary>
|
|
|
@@ -1070,10 +1315,38 @@ namespace Renci.SshNet.Sftp
|
|
|
{
|
|
|
throw exception;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+#if FEATURE_TAP
|
|
|
+ internal async Task<KeyValuePair<string, SftpFileAttributes>[]> RequestRealPathAsync(string path, bool nullOnError, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
+
|
|
|
+ TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]> tcs = new TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
+
|
|
|
+ using (cancellationToken.Register((s) => ((TaskCompletionSource<KeyValuePair<string, SftpFileAttributes>[]>)s).TrySetCanceled(), tcs, false))
|
|
|
+ {
|
|
|
+ SendRequest(new SftpRealPathRequest(ProtocolVersion, NextRequestId, path, Encoding,
|
|
|
+ response => tcs.TrySetResult(response.Files),
|
|
|
+ response =>
|
|
|
+ {
|
|
|
+ if (nullOnError)
|
|
|
+ {
|
|
|
+ tcs.TrySetResult(null);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ tcs.TrySetException(GetSftpException(response));
|
|
|
+ }
|
|
|
+ }));
|
|
|
+
|
|
|
+ return await tcs.Task.ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Performs SSH_FXP_REALPATH request.
|
|
|
/// </summary>
|
|
|
@@ -1255,6 +1528,34 @@ namespace Renci.SshNet.Sftp
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+#if FEATURE_TAP
|
|
|
+ public async Task RequestRenameAsync(string oldPath, string newPath, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
+
|
|
|
+ TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
+
|
|
|
+ using (cancellationToken.Register((s) => ((TaskCompletionSource<bool>)s).TrySetCanceled(), tcs, false))
|
|
|
+ {
|
|
|
+ SendRequest(new SftpRenameRequest(ProtocolVersion, NextRequestId, oldPath, newPath, Encoding,
|
|
|
+ response =>
|
|
|
+ {
|
|
|
+ if (response.StatusCode == StatusCodes.Ok)
|
|
|
+ {
|
|
|
+ tcs.TrySetResult(true);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ tcs.TrySetException(GetSftpException(response));
|
|
|
+ }
|
|
|
+ }));
|
|
|
+
|
|
|
+ await tcs.Task.ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Performs SSH_FXP_READLINK request.
|
|
|
/// </summary>
|
|
|
@@ -1421,6 +1722,30 @@ namespace Renci.SshNet.Sftp
|
|
|
return information;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+#if FEATURE_TAP
|
|
|
+ public async Task<SftpFileSytemInformation> RequestStatVfsAsync(string path, CancellationToken cancellationToken)
|
|
|
+ {
|
|
|
+ if (ProtocolVersion < 3)
|
|
|
+ {
|
|
|
+ throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "SSH_FXP_EXTENDED operation is not supported in {0} version that server operates in.", ProtocolVersion));
|
|
|
+ }
|
|
|
+
|
|
|
+ cancellationToken.ThrowIfCancellationRequested();
|
|
|
+
|
|
|
+ TaskCompletionSource<SftpFileSytemInformation> tcs = new TaskCompletionSource<SftpFileSytemInformation>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
+
|
|
|
+ using (cancellationToken.Register((s) => ((TaskCompletionSource<SftpFileSytemInformation>)s).TrySetCanceled(), tcs, false))
|
|
|
+ {
|
|
|
+ SendRequest(new StatVfsRequest(ProtocolVersion, NextRequestId, path, Encoding,
|
|
|
+ response => tcs.TrySetResult(response.GetReply<StatVfsReplyInfo>().Information),
|
|
|
+ response => tcs.TrySetException(GetSftpException(response))));
|
|
|
+
|
|
|
+ return await tcs.Task.ConfigureAwait(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
/// <summary>
|
|
|
/// Performs fstatvfs@openssh.com extended request.
|
|
|
/// </summary>
|
|
|
@@ -1460,7 +1785,7 @@ namespace Renci.SshNet.Sftp
|
|
|
|
|
|
WaitOnHandle(wait, OperationTimeout);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (!nullOnError && exception != null)
|
|
|
{
|
|
|
throw exception;
|