mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-10 01:05:42 +00:00
Update ScpClient to work with revent versions of OpenSSH (#625)
Update ScpClient to work with versions of OpenSSH that include fix for CVE-2018-20685.
This commit is contained in:
Binary file not shown.
+177
@@ -0,0 +1,177 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Renci.SshNet.Common;
|
||||
using System;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes.Common
|
||||
{
|
||||
[TestClass]
|
||||
public class PosixPathTest_CreateAbsoluteOrRelativeFilePath
|
||||
{
|
||||
[TestMethod]
|
||||
public void Path_Null()
|
||||
{
|
||||
const string path = null;
|
||||
|
||||
try
|
||||
{
|
||||
PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual("path", ex.ParamName);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_Empty()
|
||||
{
|
||||
var path = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual(string.Format("The path is a zero-length string.{0}Parameter name: {1}", Environment.NewLine, ex.ParamName), ex.Message);
|
||||
Assert.AreEqual("path", ex.ParamName);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_TrailingForwardSlash()
|
||||
{
|
||||
var path = "/abc/";
|
||||
|
||||
var actual = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/abc", actual.Directory);
|
||||
Assert.IsNull(actual.File);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_FileWithoutNoDirectory()
|
||||
{
|
||||
var path = "abc.log";
|
||||
|
||||
var actual = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual(".", actual.Directory);
|
||||
Assert.AreSame(path, actual.File);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_FileInRootDirectory()
|
||||
{
|
||||
var path = "/abc.log";
|
||||
|
||||
var actual = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/", actual.Directory);
|
||||
Assert.AreEqual("abc.log", actual.File);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_RootDirectoryOnly()
|
||||
{
|
||||
var path = "/";
|
||||
|
||||
var actual = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/", actual.Directory);
|
||||
Assert.IsNull(actual.File);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_FileInNonRootDirectory()
|
||||
{
|
||||
var path = "/home/sshnet/xyz";
|
||||
|
||||
var actual = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/home/sshnet", actual.Directory);
|
||||
Assert.AreEqual("xyz", actual.File);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_BackslashIsNotConsideredDirectorySeparator()
|
||||
{
|
||||
var path = "/home\\abc.log";
|
||||
|
||||
var actual = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/", actual.Directory);
|
||||
Assert.AreEqual("home\\abc.log", actual.File);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_ColonIsNotConsideredPathSeparator()
|
||||
{
|
||||
var path = "/home:abc.log";
|
||||
|
||||
var actual = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/", actual.Directory);
|
||||
Assert.AreEqual("home:abc.log", actual.File);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_LeadingWhitespace()
|
||||
{
|
||||
var path = " / \tabc";
|
||||
|
||||
var actual = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual(" ", actual.Directory);
|
||||
Assert.AreEqual(" \tabc", actual.File);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_TrailingWhitespace()
|
||||
{
|
||||
var path = "/abc \t ";
|
||||
|
||||
var actual = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/", actual.Directory);
|
||||
Assert.AreEqual("abc \t ", actual.File);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_OnlyWhitespace()
|
||||
{
|
||||
var path = " ";
|
||||
|
||||
var actual = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual(".", actual.Directory);
|
||||
Assert.AreSame(path, actual.File);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_FileNameOnlyWhitespace()
|
||||
{
|
||||
var path = "/home/\t ";
|
||||
|
||||
var actual = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/home", actual.Directory);
|
||||
Assert.AreEqual("\t ", actual.File);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Renci.SshNet.Common;
|
||||
|
||||
namespace Renci.SshNet.Tests.Classes.Common
|
||||
{
|
||||
[TestClass]
|
||||
public class PosixPathTest_GetDirectoryName
|
||||
{
|
||||
[TestMethod]
|
||||
public void Path_Null()
|
||||
{
|
||||
const string path = null;
|
||||
|
||||
try
|
||||
{
|
||||
PosixPath.GetDirectoryName(path);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual("path", ex.ParamName);
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_Empty()
|
||||
{
|
||||
var path = string.Empty;
|
||||
|
||||
var actual = PosixPath.GetDirectoryName(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual(".", actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_TrailingForwardSlash()
|
||||
{
|
||||
var path = "/abc/";
|
||||
|
||||
var actual = PosixPath.GetDirectoryName(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/abc", actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_FileWithoutNoDirectory()
|
||||
{
|
||||
var path = "abc.log";
|
||||
|
||||
var actual = PosixPath.GetDirectoryName(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual(".", actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_FileInRootDirectory()
|
||||
{
|
||||
var path = "/abc.log";
|
||||
|
||||
var actual = PosixPath.GetDirectoryName(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/", actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_RootDirectoryOnly()
|
||||
{
|
||||
var path = "/";
|
||||
|
||||
var actual = PosixPath.GetDirectoryName(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/", actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_FileInNonRootDirectory()
|
||||
{
|
||||
var path = "/home/sshnet/xyz";
|
||||
|
||||
var actual = PosixPath.GetDirectoryName(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/home/sshnet", actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_BackslashIsNotConsideredDirectorySeparator()
|
||||
{
|
||||
var path = "/home\\abc.log";
|
||||
|
||||
var actual = PosixPath.GetDirectoryName(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/", actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_ColonIsNotConsideredPathSeparator()
|
||||
{
|
||||
var path = "/home:abc.log";
|
||||
|
||||
var actual = PosixPath.GetDirectoryName(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/", actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_LeadingWhitespace()
|
||||
{
|
||||
var path = " / \tabc";
|
||||
|
||||
var actual = PosixPath.GetDirectoryName(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual(" ", actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_TrailingWhitespace()
|
||||
{
|
||||
var path = "/abc \t ";
|
||||
|
||||
var actual = PosixPath.GetDirectoryName(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/", actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_OnlyWhitespace()
|
||||
{
|
||||
var path = " ";
|
||||
|
||||
var actual = PosixPath.GetDirectoryName(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual(".", actual);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Path_FileNameOnlyWhitespace()
|
||||
{
|
||||
var path = "/home/\t ";
|
||||
|
||||
var actual = PosixPath.GetDirectoryName(path);
|
||||
|
||||
Assert.IsNotNull(actual);
|
||||
Assert.AreEqual("/home", actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,10 @@ namespace Renci.SshNet.Tests.Classes.Common
|
||||
PosixPath.GetFileName(path);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
Assert.IsNull(ex.InnerException);
|
||||
Assert.AreEqual("path", ex.ParamName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-4
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
@@ -25,7 +24,7 @@ namespace Renci.SshNet.Tests.Classes
|
||||
|
||||
_connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
|
||||
_directoryInfo = new DirectoryInfo("source");
|
||||
_path = "/home/sshnet/" + random.Next().ToString(CultureInfo.InvariantCulture);
|
||||
_path = "/home/sshnet/" + random.Next().ToString();
|
||||
_transformedPath = random.Next().ToString();
|
||||
_uploadingRegister = new List<ScpUploadEventArgs>();
|
||||
}
|
||||
@@ -48,7 +47,7 @@ namespace Renci.SshNet.Tests.Classes
|
||||
.Setup(p => p.Transform(_path))
|
||||
.Returns(_transformedPath);
|
||||
_channelSessionMock.InSequence(sequence)
|
||||
.Setup(p => p.SendExecRequest(string.Format("scp -rt {0}", _transformedPath)))
|
||||
.Setup(p => p.SendExecRequest(string.Format("scp -r -p -d -t {0}", _transformedPath)))
|
||||
.Returns(false);
|
||||
_channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
|
||||
_pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
|
||||
@@ -87,7 +86,7 @@ namespace Renci.SshNet.Tests.Classes
|
||||
[TestMethod]
|
||||
public void SendExecREquestOnChannelSessionShouldBeInvokedOnce()
|
||||
{
|
||||
_channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -rt {0}", _transformedPath)), Times.Once);
|
||||
_channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -r -p -d -t {0}", _transformedPath)), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
+10
-6
@@ -13,7 +13,9 @@ namespace Renci.SshNet.Tests.Classes
|
||||
private ConnectionInfo _connectionInfo;
|
||||
private ScpClient _scpClient;
|
||||
private FileInfo _fileInfo;
|
||||
private string _path;
|
||||
private string _remoteDirectory;
|
||||
private string _remoteFile;
|
||||
private string _remotePath;
|
||||
private string _transformedPath;
|
||||
private string _fileName;
|
||||
private IList<ScpUploadEventArgs> _uploadingRegister;
|
||||
@@ -36,7 +38,9 @@ namespace Renci.SshNet.Tests.Classes
|
||||
_fileName = CreateTemporaryFile(new byte[] { 1 });
|
||||
_connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
|
||||
_fileInfo = new FileInfo(_fileName);
|
||||
_path = "/home/sshnet/" + random.Next();
|
||||
_remoteDirectory = "/home/sshnet";
|
||||
_remoteFile = random.Next().ToString();
|
||||
_remotePath = _remoteDirectory + "/" + _remoteFile;
|
||||
_transformedPath = random.Next().ToString();
|
||||
_uploadingRegister = new List<ScpUploadEventArgs>();
|
||||
}
|
||||
@@ -56,10 +60,10 @@ namespace Renci.SshNet.Tests.Classes
|
||||
_sessionMock.InSequence(sequence).Setup(p => p.CreateChannelSession()).Returns(_channelSessionMock.Object);
|
||||
_channelSessionMock.InSequence(sequence).Setup(p => p.Open());
|
||||
_remotePathTransformationMock.InSequence(sequence)
|
||||
.Setup(p => p.Transform(_path))
|
||||
.Setup(p => p.Transform(_remoteDirectory))
|
||||
.Returns(_transformedPath);
|
||||
_channelSessionMock.InSequence(sequence)
|
||||
.Setup(p => p.SendExecRequest(string.Format("scp -t {0}", _transformedPath)))
|
||||
.Setup(p => p.SendExecRequest(string.Format("scp -t -d {0}", _transformedPath)))
|
||||
.Returns(false);
|
||||
_channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
|
||||
_pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
|
||||
@@ -78,7 +82,7 @@ namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
try
|
||||
{
|
||||
_scpClient.Upload(_fileInfo, _path);
|
||||
_scpClient.Upload(_fileInfo, _remotePath);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
@@ -98,7 +102,7 @@ namespace Renci.SshNet.Tests.Classes
|
||||
[TestMethod]
|
||||
public void SendExecRequestOnChannelSessionShouldBeInvokedOnce()
|
||||
{
|
||||
_channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t {0}", _transformedPath)), Times.Once);
|
||||
_channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t -d {0}", _transformedPath)), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -16,7 +15,9 @@ namespace Renci.SshNet.Tests.Classes
|
||||
private ConnectionInfo _connectionInfo;
|
||||
private ScpClient _scpClient;
|
||||
private FileInfo _fileInfo;
|
||||
private string _path;
|
||||
private string _remoteDirectory;
|
||||
private string _remoteFile;
|
||||
private string _remotePath;
|
||||
private string _transformedPath;
|
||||
private int _bufferSize;
|
||||
private byte[] _fileContent;
|
||||
@@ -44,7 +45,9 @@ namespace Renci.SshNet.Tests.Classes
|
||||
_fileName = CreateTemporaryFile(_fileContent);
|
||||
_connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
|
||||
_fileInfo = new FileInfo(_fileName);
|
||||
_path = "/home/sshnet/" + random.Next().ToString(CultureInfo.InvariantCulture);
|
||||
_remoteDirectory = "/home/sshnet";
|
||||
_remoteFile = random.Next().ToString();
|
||||
_remotePath = _remoteDirectory + "/" + _remoteFile;
|
||||
_transformedPath = random.Next().ToString();
|
||||
_uploadingRegister = new List<ScpUploadEventArgs>();
|
||||
}
|
||||
@@ -64,17 +67,18 @@ namespace Renci.SshNet.Tests.Classes
|
||||
_sessionMock.InSequence(sequence).Setup(p => p.CreateChannelSession()).Returns(_channelSessionMock.Object);
|
||||
_channelSessionMock.InSequence(sequence).Setup(p => p.Open());
|
||||
_remotePathTransformationMock.InSequence(sequence)
|
||||
.Setup(p => p.Transform(_path))
|
||||
.Setup(p => p.Transform(_remoteDirectory))
|
||||
.Returns(_transformedPath);
|
||||
_channelSessionMock.InSequence(sequence)
|
||||
.Setup(p => p.SendExecRequest(string.Format("scp -t {0}", _transformedPath)))
|
||||
.Setup(p => p.SendExecRequest(string.Format("scp -t -d {0}", _transformedPath)))
|
||||
.Returns(true);
|
||||
_pipeStreamMock.InSequence(sequence).Setup(p => p.ReadByte()).Returns(0);
|
||||
_channelSessionMock.InSequence(sequence).Setup(p => p.SendData(It.IsAny<byte[]>()));
|
||||
_pipeStreamMock.InSequence(sequence).Setup(p => p.ReadByte()).Returns(0);
|
||||
_channelSessionMock.InSequence(sequence)
|
||||
.Setup(p => p.SendData(It.Is<byte[]>(b => b.SequenceEqual(CreateData(
|
||||
string.Format("C0644 {0} {1}\n", _fileInfo.Length, string.Empty))))));
|
||||
.Setup(p => p.SendData(It.Is<byte[]>(b => b.SequenceEqual(
|
||||
CreateData(string.Format("C0644 {0} {1}\n", _fileInfo.Length, _remoteFile),
|
||||
_connectionInfo.Encoding)))));
|
||||
_pipeStreamMock.InSequence(sequence).Setup(p => p.ReadByte()).Returns(0);
|
||||
_channelSessionMock.InSequence(sequence)
|
||||
.Setup(
|
||||
@@ -104,13 +108,13 @@ namespace Renci.SshNet.Tests.Classes
|
||||
|
||||
protected override void Act()
|
||||
{
|
||||
_scpClient.Upload(_fileInfo, _path);
|
||||
_scpClient.Upload(_fileInfo, _remotePath);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void SendExecRequestOnChannelSessionShouldBeInvokedOnce()
|
||||
{
|
||||
_channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t {0}", _transformedPath)), Times.Once);
|
||||
_channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t -d {0}", _transformedPath)), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -143,9 +147,9 @@ namespace Renci.SshNet.Tests.Classes
|
||||
Assert.AreEqual(_fileSize, uploading.Uploaded);
|
||||
}
|
||||
|
||||
private static IEnumerable<byte> CreateData(string command)
|
||||
private static IEnumerable<byte> CreateData(string command, Encoding encoding)
|
||||
{
|
||||
return Encoding.Default.GetBytes(command);
|
||||
return encoding.GetBytes(command);
|
||||
}
|
||||
|
||||
private static byte[] CreateContent(int length)
|
||||
|
||||
+10
-7
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
@@ -14,7 +13,9 @@ namespace Renci.SshNet.Tests.Classes
|
||||
private ConnectionInfo _connectionInfo;
|
||||
private ScpClient _scpClient;
|
||||
private Stream _source;
|
||||
private string _path;
|
||||
private string _remoteDirectory;
|
||||
private string _remoteFile;
|
||||
private string _remotePath;
|
||||
private string _transformedPath;
|
||||
private IList<ScpUploadEventArgs> _uploadingRegister;
|
||||
private SshException _actualException;
|
||||
@@ -34,7 +35,9 @@ namespace Renci.SshNet.Tests.Classes
|
||||
|
||||
_connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
|
||||
_source = new MemoryStream();
|
||||
_path = "/home/sshnet/" + random.Next().ToString(CultureInfo.InvariantCulture);
|
||||
_remoteDirectory = "/home/sshnet";
|
||||
_remoteFile = random.Next().ToString();
|
||||
_remotePath = _remoteDirectory + "/" + _remoteFile;
|
||||
_transformedPath = random.Next().ToString();
|
||||
_uploadingRegister = new List<ScpUploadEventArgs>();
|
||||
}
|
||||
@@ -54,10 +57,10 @@ namespace Renci.SshNet.Tests.Classes
|
||||
_sessionMock.InSequence(sequence).Setup(p => p.CreateChannelSession()).Returns(_channelSessionMock.Object);
|
||||
_channelSessionMock.InSequence(sequence).Setup(p => p.Open());
|
||||
_remotePathTransformationMock.InSequence(sequence)
|
||||
.Setup(p => p.Transform(_path))
|
||||
.Setup(p => p.Transform(_remoteDirectory))
|
||||
.Returns(_transformedPath);
|
||||
_channelSessionMock.InSequence(sequence)
|
||||
.Setup(p => p.SendExecRequest(string.Format("scp -t {0}", _transformedPath)))
|
||||
.Setup(p => p.SendExecRequest(string.Format("scp -t -d {0}", _transformedPath)))
|
||||
.Returns(false);
|
||||
_channelSessionMock.InSequence(sequence).Setup(p => p.Dispose());
|
||||
_pipeStreamMock.As<IDisposable>().InSequence(sequence).Setup(p => p.Dispose());
|
||||
@@ -76,7 +79,7 @@ namespace Renci.SshNet.Tests.Classes
|
||||
{
|
||||
try
|
||||
{
|
||||
_scpClient.Upload(_source, _path);
|
||||
_scpClient.Upload(_source, _remotePath);
|
||||
Assert.Fail();
|
||||
}
|
||||
catch (SshException ex)
|
||||
@@ -96,7 +99,7 @@ namespace Renci.SshNet.Tests.Classes
|
||||
[TestMethod]
|
||||
public void SendExecRequestOnChannelSessionShouldBeInvokedOnce()
|
||||
{
|
||||
_channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t {0}", _transformedPath)), Times.Once);
|
||||
_channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t -d {0}", _transformedPath)), Times.Once);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -173,6 +173,8 @@
|
||||
<Compile Include="Classes\Common\ExtensionsTest_Pad.cs" />
|
||||
<Compile Include="Classes\Common\ExtensionsTest_TrimLeadingZeros.cs" />
|
||||
<Compile Include="Classes\Common\PackTest.cs" />
|
||||
<Compile Include="Classes\Common\PosixPathTest_CreateAbsoluteOrRelativeFilePath.cs" />
|
||||
<Compile Include="Classes\Common\PosixPathTest_GetDirectoryName.cs" />
|
||||
<Compile Include="Classes\Common\PosixPathTest_GetFileName.cs" />
|
||||
<Compile Include="Classes\ConnectionInfoTest_Authenticate_Failure.cs" />
|
||||
<Compile Include="Classes\ConnectionInfoTest_Authenticate_Success.cs" />
|
||||
@@ -731,4 +733,4 @@
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -4,6 +4,49 @@ namespace Renci.SshNet.Common
|
||||
{
|
||||
internal class PosixPath
|
||||
{
|
||||
public string Directory { get; private set; }
|
||||
public string File { get; private set; }
|
||||
|
||||
public static PosixPath CreateAbsoluteOrRelativeFilePath(string path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException("path");
|
||||
}
|
||||
|
||||
var posixPath = new PosixPath();
|
||||
|
||||
var pathEnd = path.LastIndexOf('/');
|
||||
if (pathEnd == -1)
|
||||
{
|
||||
if (path.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("The path is a zero-length string.", "path");
|
||||
}
|
||||
|
||||
posixPath.Directory = ".";
|
||||
posixPath.File = path;
|
||||
}
|
||||
else if (pathEnd == 0)
|
||||
{
|
||||
posixPath.Directory = "/";
|
||||
if (path.Length > 1)
|
||||
{
|
||||
posixPath.File = path.Substring(pathEnd + 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
posixPath.Directory = path.Substring(0, pathEnd);
|
||||
if (pathEnd < path.Length - 1)
|
||||
{
|
||||
posixPath.File = path.Substring(pathEnd + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return posixPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file name part of a given POSIX path.
|
||||
/// </summary>
|
||||
@@ -18,11 +61,14 @@ namespace Renci.SshNet.Common
|
||||
/// is returned.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If path has a trailing slash, but <see cref="GetFileName(string)"/> return a zero-length string.
|
||||
/// If path has a trailing slash, <see cref="GetFileName(string)"/> return a zero-length string.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static string GetFileName(string path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException("path");
|
||||
|
||||
var pathEnd = path.LastIndexOf('/');
|
||||
if (pathEnd == -1)
|
||||
return path;
|
||||
@@ -30,5 +76,29 @@ namespace Renci.SshNet.Common
|
||||
return string.Empty;
|
||||
return path.Substring(pathEnd + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the directory name part of a given POSIX path.
|
||||
/// </summary>
|
||||
/// <param name="path">The POSIX path to get the directory name for.</param>
|
||||
/// <returns>
|
||||
/// The directory part of the specified <paramref name="path"/>, or <c>.</c> if <paramref name="path"/>
|
||||
/// does not contain any directory information.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception>
|
||||
public static string GetDirectoryName(string path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException("path");
|
||||
|
||||
var pathEnd = path.LastIndexOf('/');
|
||||
if (pathEnd == -1)
|
||||
return ".";
|
||||
if (pathEnd == 0)
|
||||
return "/";
|
||||
if (pathEnd == path.Length - 1)
|
||||
return path.Substring(0, pathEnd);
|
||||
return path.Substring(0, pathEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,15 +20,16 @@ namespace Renci.SshNet
|
||||
/// <param name="fileInfo">The file system info.</param>
|
||||
/// <param name="path">A relative or absolute path for the remote file.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="fileInfo" /> is <c>null</c>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="path"/> is <c>null</c> or empty.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="path" /> is <c>null</c>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="path"/> is a zero-length <see cref="string"/>.</exception>
|
||||
/// <exception cref="ScpException">A directory with the specified path exists on the remote host.</exception>
|
||||
/// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
|
||||
public void Upload(FileInfo fileInfo, string path)
|
||||
{
|
||||
if (fileInfo == null)
|
||||
throw new ArgumentNullException("fileInfo");
|
||||
if (string.IsNullOrEmpty(path))
|
||||
throw new ArgumentException("path");
|
||||
|
||||
var posixPath = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
using (var input = ServiceFactory.CreatePipeStream())
|
||||
using (var channel = Session.CreateChannelSession())
|
||||
@@ -36,7 +37,9 @@ namespace Renci.SshNet
|
||||
channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
|
||||
channel.Open();
|
||||
|
||||
if (!channel.SendExecRequest(string.Format("scp -t {0}", _remotePathTransformation.Transform(path))))
|
||||
// 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();
|
||||
}
|
||||
@@ -45,7 +48,7 @@ namespace Renci.SshNet
|
||||
using (var source = fileInfo.OpenRead())
|
||||
{
|
||||
UploadTimes(channel, input, fileInfo);
|
||||
UploadFileModeAndName(channel, input, source.Length, string.Empty);
|
||||
UploadFileModeAndName(channel, input, source.Length, posixPath.File);
|
||||
UploadFileContent(channel, input, source, fileInfo.Name);
|
||||
}
|
||||
}
|
||||
@@ -56,16 +59,19 @@ namespace Renci.SshNet
|
||||
/// </summary>
|
||||
/// <param name="directoryInfo">The directory info.</param>
|
||||
/// <param name="path">A relative or absolute path for the remote directory.</param>
|
||||
/// <exception cref="ArgumentNullException">fileSystemInfo</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="path"/> is <c>null</c> or empty.</exception>
|
||||
/// <exception cref="ScpException"><paramref name="path"/> exists on the remote host, and is not a directory.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="directoryInfo"/> is <c>null</c>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="path"/> is a zero-length string.</exception>
|
||||
/// <exception cref="ScpException"><paramref name="path"/> does not exist on the remote host, is not a directory or the user does not have the required permission.</exception>
|
||||
/// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
|
||||
public void Upload(DirectoryInfo directoryInfo, string path)
|
||||
{
|
||||
if (directoryInfo == null)
|
||||
throw new ArgumentNullException("directoryInfo");
|
||||
if (string.IsNullOrEmpty(path))
|
||||
throw new ArgumentException("path");
|
||||
if (path == null)
|
||||
throw new ArgumentNullException("path");
|
||||
if (path.Length == 0)
|
||||
throw new ArgumentException("The path cannot be a zero-length string.", "path");
|
||||
|
||||
using (var input = ServiceFactory.CreatePipeStream())
|
||||
using (var channel = Session.CreateChannelSession())
|
||||
@@ -73,15 +79,18 @@ namespace Renci.SshNet
|
||||
channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
|
||||
channel.Open();
|
||||
|
||||
// start recursive upload
|
||||
if (!channel.SendExecRequest(string.Format("scp -rt {0}", _remotePathTransformation.Transform(path))))
|
||||
// start copy with the following options:
|
||||
// -p preserve modification and access times
|
||||
// -r copy directories recursively
|
||||
// -d expect path to be a directory
|
||||
// -t copy to remote
|
||||
if (!channel.SendExecRequest(string.Format("scp -r -p -d -t {0}", _remotePathTransformation.Transform(path))))
|
||||
{
|
||||
throw SecureExecutionRequestRejectedException();
|
||||
}
|
||||
|
||||
CheckReturnCode(input);
|
||||
|
||||
UploadTimes(channel, input, directoryInfo);
|
||||
UploadDirectoryModeAndName(channel, input, ".");
|
||||
UploadDirectoryContent(channel, input, directoryInfo);
|
||||
}
|
||||
}
|
||||
@@ -311,5 +320,39 @@ namespace Renci.SshNet
|
||||
SendErrorConfirmation(channel, string.Format("\"{0}\" is not valid protocol message.", message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a value indicating whether the specified path is a valid SCP file path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to verify.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true"/> if <paramref name="path"/> is a valid SCP file path; otherwise, <see langword="false"/>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// To match OpenSSH behavior (introduced as a result of CVE-2018-20685), a file path is considered
|
||||
/// invalid in any of the following conditions:
|
||||
/// <list type="bullet">
|
||||
/// <item>
|
||||
/// <description><paramref name="path"/> is a zero-length string.</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description><paramref name="path"/> is "<c>.</c>".</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description><paramref name="path"/> is "<c>..</c>".</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <description><paramref name="path"/> contains a forward slash (/).</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
private static bool IsValidScpFilePath(string path)
|
||||
{
|
||||
return path != null &&
|
||||
path.Length != 0 &&
|
||||
path != "." &&
|
||||
path != ".." &&
|
||||
path.IndexOf('/') == -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,28 +202,30 @@ namespace Renci.SshNet
|
||||
/// </summary>
|
||||
/// <param name="source">The <see cref="Stream"/> to upload.</param>
|
||||
/// <param name="path">A relative or absolute path for the remote file.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="path" /> is <c>null</c>.</exception>
|
||||
/// <exception cref="ArgumentException"><paramref name="path"/> is a zero-length <see cref="string"/>.</exception>
|
||||
/// <exception cref="ScpException">A directory with the specified path exists on the remote host.</exception>
|
||||
/// <exception cref="SshException">The secure copy execution request was rejected by the server.</exception>
|
||||
public void Upload(Stream source, string path)
|
||||
{
|
||||
var posixPath = PosixPath.CreateAbsoluteOrRelativeFilePath(path);
|
||||
|
||||
using (var input = ServiceFactory.CreatePipeStream())
|
||||
using (var channel = Session.CreateChannelSession())
|
||||
{
|
||||
channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
|
||||
channel.Open();
|
||||
|
||||
// pass the full path to ensure the server does not create the directory part
|
||||
// as a file in case the directory does not exist
|
||||
if (!channel.SendExecRequest(string.Format("scp -t {0}", _remotePathTransformation.Transform(path))))
|
||||
// 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);
|
||||
|
||||
// specify a zero-length file name to avoid creating a file with absolute
|
||||
// path '<path>/<filename part of path>' if directory '<path>' already exists
|
||||
UploadFileModeAndName(channel, input, source.Length, string.Empty);
|
||||
UploadFileContent(channel, input, source, PosixPath.GetFileName(path));
|
||||
UploadFileModeAndName(channel, input, source.Length, posixPath.File);
|
||||
UploadFileContent(channel, input, source, posixPath.File);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user