mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-11 01:36:39 +00:00
c66b9f8fb0
SCP performs a transfer by running scp on the server with the remote path embedded in a command. On a shell-based server that command is interpreted by a shell, so a path that is not quoted to suit that shell can be executed as a command on the server (GHSA-mggc-4xg6-vcxf); on a non-shell-based server the path is used literally and must not be quoted at all. The right encoding therefore depends on the server, and no single transformation is safe for every server. Rather than default this choice, obsolete the ScpClient constructors that implicitly used DoubleQuote and add constructors that take an IRemotePathTransformation explicitly, so callers must choose one suited to their server and trust environment. DoubleQuote remains the default for the obsolete constructors, so existing behaviour is unchanged. Document the consideration on ScpClient and IRemotePathTransformation, and recommend using SFTP.
195 lines
7.7 KiB
C#
195 lines
7.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
|
|
using Moq;
|
|
|
|
using Renci.SshNet.Common;
|
|
|
|
namespace Renci.SshNet.Tests.Classes
|
|
{
|
|
[TestClass]
|
|
public class ScpClientTest_Upload_FileInfoAndPath_Success : ScpClientTestBase
|
|
{
|
|
private ConnectionInfo _connectionInfo;
|
|
private ScpClient _scpClient;
|
|
private FileInfo _fileInfo;
|
|
private string _remoteDirectory;
|
|
private string _remoteFile;
|
|
private string _remotePath;
|
|
private string _transformedPath;
|
|
private int _bufferSize;
|
|
private byte[] _fileContent;
|
|
private string _fileName;
|
|
private int _fileSize;
|
|
private IList<ScpUploadEventArgs> _uploadingRegister;
|
|
|
|
protected override void SetupData()
|
|
{
|
|
var random = new Random();
|
|
|
|
_bufferSize = random.Next(5, 15);
|
|
_fileSize = _bufferSize + 2; //force uploading 2 chunks
|
|
_fileContent = CreateContent(_fileSize);
|
|
_fileName = CreateTemporaryFile(_fileContent);
|
|
_connectionInfo = new ConnectionInfo("host", 22, "user", new PasswordAuthenticationMethod("user", "pwd"));
|
|
_fileInfo = new FileInfo(_fileName);
|
|
_remoteDirectory = "/home/sshnet";
|
|
_remoteFile = random.Next().ToString();
|
|
_remotePath = _remoteDirectory + "/" + _remoteFile;
|
|
_transformedPath = random.Next().ToString();
|
|
_uploadingRegister = new List<ScpUploadEventArgs>();
|
|
}
|
|
|
|
protected override void SetupMocks()
|
|
{
|
|
var sequence = new MockSequence();
|
|
|
|
_ = ServiceFactoryMock.InSequence(sequence)
|
|
.Setup(p => p.CreateSocketFactory())
|
|
.Returns(SocketFactoryMock.Object);
|
|
_ = ServiceFactoryMock.InSequence(sequence)
|
|
.Setup(p => p.CreateSession(_connectionInfo, SocketFactoryMock.Object))
|
|
.Returns(SessionMock.Object);
|
|
_ = SessionMock.InSequence(sequence)
|
|
.Setup(p => p.Connect());
|
|
_ = ServiceFactoryMock.InSequence(sequence)
|
|
.Setup(p => p.CreatePipeStream())
|
|
.Returns(_pipeStreamMock.Object);
|
|
_ = SessionMock.InSequence(sequence)
|
|
.Setup(p => p.CreateChannelSession())
|
|
.Returns(_channelSessionMock.Object);
|
|
_ = _channelSessionMock.InSequence(sequence)
|
|
.Setup(p => p.Open());
|
|
_ = _remotePathTransformationMock.InSequence(sequence)
|
|
.Setup(p => p.Transform(_remoteDirectory))
|
|
.Returns(_transformedPath);
|
|
_ = _channelSessionMock.InSequence(sequence)
|
|
.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, _remoteFile), _connectionInfo.Encoding)))));
|
|
_ = _pipeStreamMock.InSequence(sequence)
|
|
.Setup(p => p.ReadByte())
|
|
.Returns(0);
|
|
_ = _channelSessionMock.InSequence(sequence)
|
|
.Setup(p => p.SendData(It.Is<byte[]>(b => b.SequenceEqual(_fileContent.Take(_bufferSize))), 0, _bufferSize));
|
|
_ = _channelSessionMock.InSequence(sequence)
|
|
.Setup(p => p.SendData(It.Is<byte[]>(b => b.Take(0, _fileContent.Length - _bufferSize).SequenceEqual(_fileContent.Take(_bufferSize, _fileContent.Length - _bufferSize))), 0, _fileContent.Length - _bufferSize));
|
|
_ = _channelSessionMock.InSequence(sequence)
|
|
.Setup(p => p.SendData(It.Is<byte[]>(b => b.SequenceEqual(new byte[] { 0 }))));
|
|
_ = _pipeStreamMock.InSequence(sequence)
|
|
.Setup(p => p.ReadByte())
|
|
.Returns(0);
|
|
_ = _channelSessionMock.InSequence(sequence)
|
|
.Setup(p => p.Dispose());
|
|
_ = _pipeStreamMock.InSequence(sequence)
|
|
.Setup(p => p.Close());
|
|
}
|
|
|
|
protected override void Arrange()
|
|
{
|
|
base.Arrange();
|
|
|
|
_scpClient = new ScpClient(_connectionInfo, false, ServiceFactoryMock.Object, _remotePathTransformationMock.Object)
|
|
{
|
|
BufferSize = (uint)_bufferSize
|
|
};
|
|
_scpClient.Uploading += (sender, args) => _uploadingRegister.Add(args);
|
|
_scpClient.Connect();
|
|
}
|
|
|
|
protected override void TearDown()
|
|
{
|
|
base.TearDown();
|
|
|
|
if (_fileName != null)
|
|
{
|
|
File.Delete(_fileName);
|
|
_fileName = null;
|
|
}
|
|
}
|
|
|
|
protected override void Act()
|
|
{
|
|
_scpClient.Upload(_fileInfo, _remotePath);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void SendExecRequestOnChannelSessionShouldBeInvokedOnce()
|
|
{
|
|
_channelSessionMock.Verify(p => p.SendExecRequest(string.Format("scp -t -d {0}", _transformedPath)), Times.Once);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void DisposeOnChannelShouldBeInvokedOnce()
|
|
{
|
|
_channelSessionMock.Verify(p => p.Dispose(), Times.Once);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void DisposeOnPipeStreamShouldBeInvokedOnce()
|
|
{
|
|
_pipeStreamMock.Verify(p => p.Close(), Times.Once);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void UploadingShouldHaveFiredTwice()
|
|
{
|
|
Assert.HasCount(2, _uploadingRegister);
|
|
|
|
var uploading = _uploadingRegister[0];
|
|
Assert.IsNotNull(uploading);
|
|
Assert.AreSame(_fileInfo.Name, uploading.Filename);
|
|
Assert.AreEqual(_fileSize, uploading.Size);
|
|
Assert.AreEqual(_bufferSize, uploading.Uploaded);
|
|
|
|
uploading = _uploadingRegister[1];
|
|
Assert.IsNotNull(uploading);
|
|
Assert.AreSame(_fileInfo.Name, uploading.Filename);
|
|
Assert.AreEqual(_fileSize, uploading.Size);
|
|
Assert.AreEqual(_fileSize, uploading.Uploaded);
|
|
}
|
|
|
|
private static IEnumerable<byte> CreateData(string command, Encoding encoding)
|
|
{
|
|
return encoding.GetBytes(command);
|
|
}
|
|
|
|
private static byte[] CreateContent(int length)
|
|
{
|
|
var random = new Random();
|
|
var content = new byte[length];
|
|
|
|
for (var i = 0; i < length; i++)
|
|
{
|
|
content[i] = (byte)random.Next(byte.MinValue, byte.MaxValue);
|
|
}
|
|
|
|
return content;
|
|
}
|
|
|
|
private static string CreateTemporaryFile(byte[] content)
|
|
{
|
|
var tempFile = Path.GetTempFileName();
|
|
using (var fs = File.OpenWrite(tempFile))
|
|
{
|
|
fs.Write(content, 0, content.Length);
|
|
}
|
|
return tempFile;
|
|
}
|
|
}
|
|
}
|