Skip to content

Commit f9fd818

Browse files
committed
ScpClient: allow disabling the -d flag
fixes #1746
1 parent 0ff2c50 commit f9fd818

File tree

2 files changed

+81
-5
lines changed

2 files changed

+81
-5
lines changed

src/Renci.SshNet/ScpClient.cs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,23 @@ public IRemotePathTransformation RemotePathTransformation
121121
}
122122
}
123123

124+
/// <summary>
125+
/// Gets or sets a value indicating whether the "-d" flag should be passed to the scp process on the server
126+
/// when uploading files.
127+
/// The "-d" flag is an undocumented flag that ensures that the target is actually a directory. However,
128+
/// some scp implementations (like Cisco) do not support this flag and will fail.
129+
/// You can set this to <see langword="false"/> to work around this.
130+
/// </summary>
131+
public bool UseDirectoryFlag { get; set; } = true;
132+
133+
private string EnsureIsDirectoryArg
134+
{
135+
get
136+
{
137+
return UseDirectoryFlag ? "-d" : string.Empty;
138+
}
139+
}
140+
124141
/// <summary>
125142
/// Occurs when downloading file.
126143
/// </summary>
@@ -258,9 +275,9 @@ public void Upload(Stream source, string path)
258275
channel.Closed += (sender, e) => input.Dispose();
259276
channel.Open();
260277

261-
// Pass only the directory part of the path to the server, and use the (hidden) -d option to signal
278+
// Pass only the directory part of the path to the server, and optionally use the (hidden) -d option to signal
262279
// that we expect the target to be a directory.
263-
if (!channel.SendExecRequest(string.Format("scp -t -d {0}", _remotePathTransformation.Transform(posixPath.Directory))))
280+
if (!channel.SendExecRequest($"scp -t {EnsureIsDirectoryArg} {_remotePathTransformation.Transform(posixPath.Directory)}"))
264281
{
265282
throw SecureExecutionRequestRejectedException();
266283
}
@@ -301,9 +318,9 @@ public void Upload(FileInfo fileInfo, string path)
301318
channel.Closed += (sender, e) => input.Dispose();
302319
channel.Open();
303320

304-
// Pass only the directory part of the path to the server, and use the (hidden) -d option to signal
321+
// Pass only the directory part of the path to the server, and optionally use the (hidden) -d option to signal
305322
// that we expect the target to be a directory.
306-
if (!channel.SendExecRequest($"scp -t -d {_remotePathTransformation.Transform(posixPath.Directory)}"))
323+
if (!channel.SendExecRequest($"scp -t {EnsureIsDirectoryArg} {_remotePathTransformation.Transform(posixPath.Directory)}"))
307324
{
308325
throw SecureExecutionRequestRejectedException();
309326
}
@@ -352,7 +369,7 @@ public void Upload(DirectoryInfo directoryInfo, string path)
352369
// -r copy directories recursively
353370
// -d expect path to be a directory
354371
// -t copy to remote
355-
if (!channel.SendExecRequest($"scp -r -p -d -t {_remotePathTransformation.Transform(path)}"))
372+
if (!channel.SendExecRequest($"scp -r -p {EnsureIsDirectoryArg} -t {_remotePathTransformation.Transform(path)}"))
356373
{
357374
throw SecureExecutionRequestRejectedException();
358375
}

test/Renci.SshNet.IntegrationTests/ScpTests.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2098,5 +2098,64 @@ private static string CombinePaths(string path1, string path2)
20982098

20992099
return path1 + "/" + path2;
21002100
}
2101+
2102+
[TestMethod]
2103+
public async Task UploadWithUseDirectoryFlagFalse()
2104+
{
2105+
string remoteDirectory = "/home/sshnet/usedirectoryflagfalsetest";
2106+
2107+
// remote cleanup
2108+
using (var sftpClient = new SftpClient(_connectionInfoFactory.Create()))
2109+
{
2110+
await sftpClient.ConnectAsync(CancellationToken.None);
2111+
2112+
if (await sftpClient.ExistsAsync(remoteDirectory))
2113+
{
2114+
await sftpClient.DeleteDirectoryAsync(remoteDirectory);
2115+
}
2116+
2117+
await sftpClient.CreateDirectoryAsync(remoteDirectory);
2118+
}
2119+
2120+
using (var client = new ScpClient(_connectionInfoFactory.Create()))
2121+
{
2122+
client.UseDirectoryFlag = false;
2123+
2124+
await client.ConnectAsync(CancellationToken.None);
2125+
int tempFileSize = 1024;
2126+
string tempFilePath = CreateTempFile(tempFileSize);
2127+
MemoryStream downloadedStream = new();
2128+
2129+
// FileInfo overload
2130+
client.Upload(new FileInfo(tempFilePath), $"{remoteDirectory}/file1");
2131+
client.Download($"{remoteDirectory}/file1", downloadedStream);
2132+
Assert.AreEqual(tempFileSize, downloadedStream.Length);
2133+
2134+
// Stream overload
2135+
downloadedStream = new();
2136+
using (Stream stream = File.OpenRead(tempFilePath))
2137+
{
2138+
client.Upload(stream, $"{remoteDirectory}/file2");
2139+
client.Download($"{remoteDirectory}/file2", downloadedStream);
2140+
Assert.AreEqual(tempFileSize, downloadedStream.Length);
2141+
}
2142+
2143+
// DirectoryInfo overload
2144+
downloadedStream = new();
2145+
string tempDir = Path.Combine(Path.GetTempPath(), "SSH.NET_UploadWithUseDirectoryFlagFalseTest");
2146+
2147+
if (Directory.Exists(tempDir))
2148+
{
2149+
Directory.Delete(tempDir, true);
2150+
}
2151+
2152+
Directory.CreateDirectory(tempDir);
2153+
File.Move(tempFilePath, $"{tempDir}/file3");
2154+
2155+
client.Upload(new DirectoryInfo(tempDir), remoteDirectory);
2156+
client.Download($"{remoteDirectory}/file3", downloadedStream);
2157+
Assert.AreEqual(tempFileSize, downloadedStream.Length);
2158+
}
2159+
}
21012160
}
21022161
}

0 commit comments

Comments
 (0)