Skip to content

Commit 25a931c

Browse files
mus65Rob-Hague
andauthored
ScpClient: allow disabling the -d flag (#1751)
* ScpClient: allow disabling the -d flag fixes #1746 * Apply suggestion from @Rob-Hague --------- Co-authored-by: Rob Hague <rob.hague00@gmail.com>
1 parent 0ff2c50 commit 25a931c

File tree

2 files changed

+83
-5
lines changed

2 files changed

+83
-5
lines changed

src/Renci.SshNet/ScpClient.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,25 @@ 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. Defaults to <see langword="true"/>.
127+
/// </summary>
128+
/// <remarks>
129+
/// The "-d" flag is an undocumented flag that ensures that the target is actually a directory. However,
130+
/// some scp implementations (like Cisco) do not support this flag and will fail.
131+
/// You can set this to <see langword="false"/> to work around this.
132+
/// </remarks>
133+
public bool UseDirectoryFlag { get; set; } = true;
134+
135+
private string EnsureIsDirectoryArg
136+
{
137+
get
138+
{
139+
return UseDirectoryFlag ? "-d" : string.Empty;
140+
}
141+
}
142+
124143
/// <summary>
125144
/// Occurs when downloading file.
126145
/// </summary>
@@ -258,9 +277,9 @@ public void Upload(Stream source, string path)
258277
channel.Closed += (sender, e) => input.Dispose();
259278
channel.Open();
260279

261-
// Pass only the directory part of the path to the server, and use the (hidden) -d option to signal
280+
// Pass only the directory part of the path to the server, and optionally use the (hidden) -d option to signal
262281
// that we expect the target to be a directory.
263-
if (!channel.SendExecRequest(string.Format("scp -t -d {0}", _remotePathTransformation.Transform(posixPath.Directory))))
282+
if (!channel.SendExecRequest($"scp -t {EnsureIsDirectoryArg} {_remotePathTransformation.Transform(posixPath.Directory)}"))
264283
{
265284
throw SecureExecutionRequestRejectedException();
266285
}
@@ -301,9 +320,9 @@ public void Upload(FileInfo fileInfo, string path)
301320
channel.Closed += (sender, e) => input.Dispose();
302321
channel.Open();
303322

304-
// Pass only the directory part of the path to the server, and use the (hidden) -d option to signal
323+
// Pass only the directory part of the path to the server, and optionally use the (hidden) -d option to signal
305324
// that we expect the target to be a directory.
306-
if (!channel.SendExecRequest($"scp -t -d {_remotePathTransformation.Transform(posixPath.Directory)}"))
325+
if (!channel.SendExecRequest($"scp -t {EnsureIsDirectoryArg} {_remotePathTransformation.Transform(posixPath.Directory)}"))
307326
{
308327
throw SecureExecutionRequestRejectedException();
309328
}
@@ -352,7 +371,7 @@ public void Upload(DirectoryInfo directoryInfo, string path)
352371
// -r copy directories recursively
353372
// -d expect path to be a directory
354373
// -t copy to remote
355-
if (!channel.SendExecRequest($"scp -r -p -d -t {_remotePathTransformation.Transform(path)}"))
374+
if (!channel.SendExecRequest($"scp -r -p {EnsureIsDirectoryArg} -t {_remotePathTransformation.Transform(path)}"))
356375
{
357376
throw SecureExecutionRequestRejectedException();
358377
}

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)