Skip to content

Commit 0a1a031

Browse files
committed
Add support for non-ascii characters to ScpClient.
Fixes issue #281.
1 parent 87a45a8 commit 0a1a031

File tree

2 files changed

+31
-30
lines changed

2 files changed

+31
-30
lines changed

src/Renci.SshNet/ScpClient.cs

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
11
using System;
2-
using System.Text;
32
using Renci.SshNet.Channels;
43
using System.IO;
54
using Renci.SshNet.Common;
65
using System.Text.RegularExpressions;
76
using System.Diagnostics.CodeAnalysis;
87
using System.Net;
8+
using System.Collections.Generic;
99

1010
namespace Renci.SshNet
1111
{
1212
/// <summary>
1313
/// Provides SCP client functionality.
1414
/// </summary>
1515
/// <remarks>
16+
/// <para>
1617
/// More information on the SCP protocol is available here:
1718
/// https://github.com/net-ssh/net-scp/blob/master/lib/net/scp.rb
19+
/// </para>
20+
/// <para>
21+
/// Known issues in OpenSSH:
22+
/// <list type="bullet">
23+
/// <item>
24+
/// <description>Recursive download (-prf) does not deal well with specific UTF-8 and newline characters.</description>
25+
/// <description>Recursive update does not support empty path for uploading to home directorydeal well with specific UTF-8 and newline characters.</description>
26+
/// </item>
27+
/// </list>
28+
/// </para>
1829
/// </remarks>
1930
public partial class ScpClient : BaseClient
2031
{
2132
private static readonly Regex FileInfoRe = new Regex(@"C(?<mode>\d{4}) (?<length>\d+) (?<filename>.+)");
22-
private static char[] _byteToChar;
2333

2434
/// <summary>
2535
/// Gets or sets the operation timeout.
@@ -150,16 +160,6 @@ internal ScpClient(ConnectionInfo connectionInfo, bool ownsConnectionInfo, IServ
150160
{
151161
OperationTimeout = SshNet.Session.InfiniteTimeSpan;
152162
BufferSize = 1024 * 16;
153-
154-
if (_byteToChar == null)
155-
{
156-
_byteToChar = new char[128];
157-
var ch = '\0';
158-
for (var i = 0; i < 128; i++)
159-
{
160-
_byteToChar[i] = ch++;
161-
}
162-
}
163163
}
164164

165165
#endregion
@@ -243,7 +243,7 @@ public void Download(string filename, Stream destination)
243243
}
244244
}
245245

246-
private static void InternalSetTimestamp(IChannelSession channel, Stream input, DateTime lastWriteTime, DateTime lastAccessime)
246+
private void InternalSetTimestamp(IChannelSession channel, Stream input, DateTime lastWriteTime, DateTime lastAccessime)
247247
{
248248
var zeroTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
249249
var modificationSeconds = (long) (lastWriteTime - zeroTime).TotalSeconds;
@@ -329,7 +329,7 @@ private static void SendConfirmation(IChannel channel)
329329
SendData(channel, new byte[] { 0 });
330330
}
331331

332-
private static void SendConfirmation(IChannel channel, byte errorCode, string message)
332+
private void SendConfirmation(IChannel channel, byte errorCode, string message)
333333
{
334334
SendData(channel, new[] { errorCode });
335335
SendData(channel, string.Format("{0}\n", message));
@@ -339,7 +339,7 @@ private static void SendConfirmation(IChannel channel, byte errorCode, string me
339339
/// Checks the return code.
340340
/// </summary>
341341
/// <param name="input">The output stream.</param>
342-
private static void CheckReturnCode(Stream input)
342+
private void CheckReturnCode(Stream input)
343343
{
344344
var b = ReadByte(input);
345345

@@ -351,9 +351,9 @@ private static void CheckReturnCode(Stream input)
351351
}
352352
}
353353

354-
private static void SendData(IChannel channel, string command)
354+
private void SendData(IChannel channel, string command)
355355
{
356-
channel.SendData(SshData.Utf8.GetBytes(command));
356+
channel.SendData(ConnectionInfo.Encoding.GetBytes(command));
357357
}
358358

359359
private static void SendData(IChannel channel, byte[] buffer, int length)
@@ -374,35 +374,36 @@ private static int ReadByte(Stream stream)
374374
return b;
375375
}
376376

377-
private static string ReadString(Stream stream)
377+
/// <summary>
378+
/// Read a LF-terminated string from the <see cref="Stream"/>.
379+
/// </summary>
380+
/// <param name="stream">The <see cref="Stream"/> to read from.</param>
381+
/// <returns>
382+
/// The string without trailing LF.
383+
/// </returns>
384+
private string ReadString(Stream stream)
378385
{
379386
var hasError = false;
380387

381-
var sb = new StringBuilder();
388+
var buffer = new List<byte>();
382389

383390
var b = ReadByte(stream);
384-
385391
if (b == 1 || b == 2)
386392
{
387393
hasError = true;
388394
b = ReadByte(stream);
389395
}
390396

391-
var ch = _byteToChar[b];
392-
393-
while (ch != '\n')
397+
while (b != SshNet.Session.LineFeed)
394398
{
395-
sb.Append(ch);
396-
399+
buffer.Add((byte) b);
397400
b = ReadByte(stream);
398-
399-
ch = _byteToChar[b];
400401
}
401402

402403
if (hasError)
403-
throw new ScpException(sb.ToString());
404+
throw new ScpException(ConnectionInfo.Encoding.GetString(buffer.ToArray()));
404405

405-
return sb.ToString();
406+
return ConnectionInfo.Encoding.GetString(buffer.ToArray());
406407
}
407408
}
408409
}

src/Renci.SshNet/Session.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class Session : ISession
2828
{
2929
private const byte Null = 0x00;
3030
private const byte CarriageReturn = 0x0d;
31-
private const byte LineFeed = 0x0a;
31+
internal const byte LineFeed = 0x0a;
3232

3333
/// <summary>
3434
/// Specifies an infinite waiting period.

0 commit comments

Comments
 (0)