Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions src/Renci.SshNet/ScpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,23 @@ public IRemotePathTransformation RemotePathTransformation
}
}

/// <summary>
/// Gets or sets a value indicating whether the "-d" flag should be passed to the scp process on the server
/// when uploading files.
/// The "-d" flag is an undocumented flag that ensures that the target is actually a directory. However,
/// some scp implementations (like Cisco) do not support this flag and will fail.
/// You can set this to <see langword="false"/> to work around this.
/// </summary>
public bool UseDirectoryFlag { get; set; } = true;

private string EnsureIsDirectoryArg
{
get
{
return UseDirectoryFlag ? "-d" : string.Empty;
}
}

/// <summary>
/// Occurs when downloading file.
/// </summary>
Expand Down Expand Up @@ -258,9 +275,9 @@ public void Upload(Stream source, string path)
channel.Closed += (sender, e) => input.Dispose();
channel.Open();

// Pass only the directory part of the path to the server, and use the (hidden) -d option to signal
// Pass only the directory part of the path to the server, and optionally 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))))
if (!channel.SendExecRequest($"scp -t {EnsureIsDirectoryArg} {_remotePathTransformation.Transform(posixPath.Directory)}"))
{
throw SecureExecutionRequestRejectedException();
}
Expand Down Expand Up @@ -301,9 +318,9 @@ public void Upload(FileInfo fileInfo, string path)
channel.Closed += (sender, e) => input.Dispose();
channel.Open();

// Pass only the directory part of the path to the server, and use the (hidden) -d option to signal
// Pass only the directory part of the path to the server, and optionally use the (hidden) -d option to signal
// that we expect the target to be a directory.
if (!channel.SendExecRequest($"scp -t -d {_remotePathTransformation.Transform(posixPath.Directory)}"))
if (!channel.SendExecRequest($"scp -t {EnsureIsDirectoryArg} {_remotePathTransformation.Transform(posixPath.Directory)}"))
{
throw SecureExecutionRequestRejectedException();
}
Expand Down Expand Up @@ -352,7 +369,7 @@ public void Upload(DirectoryInfo directoryInfo, string path)
// -r copy directories recursively
// -d expect path to be a directory
// -t copy to remote
if (!channel.SendExecRequest($"scp -r -p -d -t {_remotePathTransformation.Transform(path)}"))
if (!channel.SendExecRequest($"scp -r -p {EnsureIsDirectoryArg} -t {_remotePathTransformation.Transform(path)}"))
{
throw SecureExecutionRequestRejectedException();
}
Expand Down
59 changes: 59 additions & 0 deletions test/Renci.SshNet.IntegrationTests/ScpTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2098,5 +2098,64 @@ private static string CombinePaths(string path1, string path2)

return path1 + "/" + path2;
}

[TestMethod]
public async Task UploadWithUseDirectoryFlagFalse()
{
string remoteDirectory = "/home/sshnet/usedirectoryflagfalsetest";

// remote cleanup
using (var sftpClient = new SftpClient(_connectionInfoFactory.Create()))
{
await sftpClient.ConnectAsync(CancellationToken.None);

if (await sftpClient.ExistsAsync(remoteDirectory))
{
await sftpClient.DeleteDirectoryAsync(remoteDirectory);
}

await sftpClient.CreateDirectoryAsync(remoteDirectory);
}

using (var client = new ScpClient(_connectionInfoFactory.Create()))
{
client.UseDirectoryFlag = false;

await client.ConnectAsync(CancellationToken.None);
int tempFileSize = 1024;
string tempFilePath = CreateTempFile(tempFileSize);
MemoryStream downloadedStream = new();

// FileInfo overload
client.Upload(new FileInfo(tempFilePath), $"{remoteDirectory}/file1");
client.Download($"{remoteDirectory}/file1", downloadedStream);
Assert.AreEqual(tempFileSize, downloadedStream.Length);

// Stream overload
downloadedStream = new();
using (Stream stream = File.OpenRead(tempFilePath))
{
client.Upload(stream, $"{remoteDirectory}/file2");
client.Download($"{remoteDirectory}/file2", downloadedStream);
Assert.AreEqual(tempFileSize, downloadedStream.Length);
}

// DirectoryInfo overload
downloadedStream = new();
string tempDir = Path.Combine(Path.GetTempPath(), "SSH.NET_UploadWithUseDirectoryFlagFalseTest");

if (Directory.Exists(tempDir))
{
Directory.Delete(tempDir, true);
}

Directory.CreateDirectory(tempDir);
File.Move(tempFilePath, $"{tempDir}/file3");

client.Upload(new DirectoryInfo(tempDir), remoteDirectory);
client.Download($"{remoteDirectory}/file3", downloadedStream);
Assert.AreEqual(tempFileSize, downloadedStream.Length);
}
}
}
}
Loading