using System;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Common;
using Renci.SshNet.Tests.Common;
namespace Renci.SshNet.Tests.Classes
{
///
/// Verifies that the upload methods reject names that cannot be sent in an SCP C or D record: a name
/// containing a line feed, or an empty file name (a remote path that ends with /).
///
///
/// A line feed would end the record early, so that the server reads the rest of the name as the
/// start of the file content, and content crafted to match is then read as further records. The
/// integration tests (ScpTests) show the effect on a real server: another file is created.
///
[TestClass]
public class ScpClientTest_Upload_InvalidName : ScpClientTranscriptTestBase
{
[TestMethod]
public void UploadStream_FileNameWithLineFeed_ShouldThrowArgumentExceptionBeforeOpeningChannel()
{
using (var source = new MemoryStream("content"u8.ToArray()))
{
var result = new ScpServerTranscript().Run(
client => client.Upload(source, "/home/sshnet/report.txt\nrest-of-name"),
timeout: TimeSpan.FromSeconds(5));
AssertRejectedBeforeOpeningChannel(result);
}
}
[TestMethod]
public void UploadFileInfo_FileNameWithLineFeed_ShouldThrowArgumentExceptionBeforeOpeningChannel()
{
var filePath = Path.Combine(Root, "file.txt");
File.WriteAllText(filePath, "content");
var result = new ScpServerTranscript().Run(
client => client.Upload(new FileInfo(filePath), "/home/sshnet/report.txt\nrest-of-name"),
timeout: TimeSpan.FromSeconds(5));
AssertRejectedBeforeOpeningChannel(result);
}
[TestMethod]
[DataRow("/home/sshnet/")]
[DataRow("dir/")]
[DataRow("/")]
public void UploadStream_PathWithoutFileName_ShouldThrowArgumentExceptionBeforeOpeningChannel(string remotePath)
{
using (var stream = new MemoryStream("content"u8.ToArray()))
{
var result = new ScpServerTranscript().Run(
client => client.Upload(stream, remotePath),
timeout: TimeSpan.FromSeconds(5));
AssertRejectedBeforeOpeningChannel(result);
}
}
[TestMethod]
[DataRow("/home/sshnet/")]
[DataRow("dir/")]
public void UploadFileInfo_PathWithoutFileName_ShouldThrowArgumentExceptionBeforeOpeningChannel(string remotePath)
{
var localPath = Path.Combine(Root, "file.txt");
File.WriteAllText(localPath, "content");
var result = new ScpServerTranscript().Run(
client => client.Upload(new FileInfo(localPath), remotePath),
timeout: TimeSpan.FromSeconds(5));
AssertRejectedBeforeOpeningChannel(result);
}
[TestMethod]
[OSCondition(ConditionMode.Exclude, OperatingSystems.Windows,
IgnoreMessage = "A line feed is not valid in a file name on Windows.")]
public void UploadDirectoryInfo_LocalFileNameWithLineFeed_ShouldThrowScpException()
{
var localDir = Directory.CreateDirectory(Path.Combine(Root, "source"));
File.WriteAllText(Path.Combine(localDir.FullName, "report.txt\nrest-of-name"), "content");
var transcript = new ScpServerTranscript().Raw([0]); // Status byte for the exec request.
var result = transcript.Run(
client => client.Upload(localDir, "/home/sshnet/target"),
timeout: TimeSpan.FromSeconds(5));
AssertRejectedBeforeSendingAnyRecord(result);
}
[TestMethod]
[OSCondition(ConditionMode.Exclude, OperatingSystems.Windows,
IgnoreMessage = "A line feed is not valid in a directory name on Windows.")]
public void UploadDirectoryInfo_LocalDirectoryNameWithLineFeed_ShouldThrowScpException()
{
var localDir = Directory.CreateDirectory(Path.Combine(Root, "source"));
_ = Directory.CreateDirectory(Path.Combine(localDir.FullName, "dir\nrest-of-name"));
var transcript = new ScpServerTranscript().Raw([0]); // Status byte for the exec request.
var result = transcript.Run(
client => client.Upload(localDir, "/home/sshnet/target"),
timeout: TimeSpan.FromSeconds(5));
AssertRejectedBeforeSendingAnyRecord(result);
}
private static void AssertRejectedBeforeOpeningChannel(ScpTranscriptResult result)
{
Assert.IsTrue(result.Completed);
var ex = Assert.IsInstanceOfType(result.Exception);
Assert.AreEqual("path", ex.ParamName);
Assert.IsEmpty(result.ExecRequests);
Assert.IsEmpty(result.SentData);
}
private static void AssertRejectedBeforeSendingAnyRecord(ScpTranscriptResult result)
{
Assert.IsTrue(result.Completed);
Assert.IsInstanceOfType(result.Exception);
Assert.Contains("line feed", result.Exception.Message, StringComparison.Ordinal);
Assert.IsEmpty(result.SentData);
}
}
}