diff --git a/src/Renci.SshNet/Sftp/Requests/SftpMkDirRequest.cs b/src/Renci.SshNet/Sftp/Requests/SftpMkDirRequest.cs index fafd481f8..a04a896c1 100644 --- a/src/Renci.SshNet/Sftp/Requests/SftpMkDirRequest.cs +++ b/src/Renci.SshNet/Sftp/Requests/SftpMkDirRequest.cs @@ -8,7 +8,6 @@ namespace Renci.SshNet.Sftp.Requests internal sealed class SftpMkDirRequest : SftpRequest { private byte[] _path; - private byte[] _attributesBytes; public override SftpMessageTypes SftpMessageType { @@ -23,18 +22,6 @@ public string Path public Encoding Encoding { get; private set; } - private SftpFileAttributes Attributes { get; set; } - - private byte[] AttributesBytes - { - get - { - _attributesBytes ??= Attributes.GetBytes(); - - return _attributesBytes; - } - } - /// /// Gets the size of the message in bytes. /// @@ -48,36 +35,30 @@ protected override int BufferCapacity var capacity = base.BufferCapacity; capacity += 4; // Path length capacity += _path.Length; // Path - capacity += AttributesBytes.Length; // Attributes + capacity += 4; // Attributes return capacity; } } public SftpMkDirRequest(uint protocolVersion, uint requestId, string path, Encoding encoding, Action statusAction) - : this(protocolVersion, requestId, path, encoding, SftpFileAttributes.Empty, statusAction) - { - } - - private SftpMkDirRequest(uint protocolVersion, uint requestId, string path, Encoding encoding, SftpFileAttributes attributes, Action statusAction) : base(protocolVersion, requestId, statusAction) { Encoding = encoding; Path = path; - Attributes = attributes; } protected override void LoadData() { base.LoadData(); _path = ReadBinary(); - Attributes = ReadAttributes(); + _ = ReadAttributes(); } protected override void SaveData() { base.SaveData(); WriteBinaryString(_path); - Write(AttributesBytes); + Write(0u); // empty attributes } } } diff --git a/src/Renci.SshNet/Sftp/Requests/SftpOpenRequest.cs b/src/Renci.SshNet/Sftp/Requests/SftpOpenRequest.cs index 6a86b4dad..45065f683 100644 --- a/src/Renci.SshNet/Sftp/Requests/SftpOpenRequest.cs +++ b/src/Renci.SshNet/Sftp/Requests/SftpOpenRequest.cs @@ -9,7 +9,6 @@ internal sealed class SftpOpenRequest : SftpRequest { private readonly Action _handleAction; private byte[] _fileName; - private byte[] _attributes; public override SftpMessageTypes SftpMessageType { @@ -24,12 +23,6 @@ public string Filename public Flags Flags { get; } - public SftpFileAttributes Attributes - { - get { return SftpFileAttributes.FromBytes(_attributes); } - private set { _attributes = value.GetBytes(); } - } - public Encoding Encoding { get; } /// @@ -46,23 +39,17 @@ protected override int BufferCapacity capacity += 4; // FileName length capacity += _fileName.Length; // FileName capacity += 4; // Flags - capacity += _attributes.Length; // Attributes + capacity += 4; // Attributes return capacity; } } public SftpOpenRequest(uint protocolVersion, uint requestId, string fileName, Encoding encoding, Flags flags, Action handleAction, Action statusAction) - : this(protocolVersion, requestId, fileName, encoding, flags, SftpFileAttributes.Empty, handleAction, statusAction) - { - } - - private SftpOpenRequest(uint protocolVersion, uint requestId, string fileName, Encoding encoding, Flags flags, SftpFileAttributes attributes, Action handleAction, Action statusAction) : base(protocolVersion, requestId, statusAction) { Encoding = encoding; Filename = fileName; Flags = flags; - Attributes = attributes; _handleAction = handleAction; } @@ -79,7 +66,7 @@ protected override void SaveData() WriteBinaryString(_fileName); Write((uint)Flags); - Write(_attributes); + Write(0u); // empty attributes } public override void Complete(SftpResponse response) diff --git a/src/Renci.SshNet/Sftp/Responses/SftpVersionResponse.cs b/src/Renci.SshNet/Sftp/Responses/SftpVersionResponse.cs index 1ae8e6d6a..8c2ff2c49 100644 --- a/src/Renci.SshNet/Sftp/Responses/SftpVersionResponse.cs +++ b/src/Renci.SshNet/Sftp/Responses/SftpVersionResponse.cs @@ -11,14 +11,14 @@ public override SftpMessageTypes SftpMessageType public uint Version { get; set; } - public IDictionary Extentions { get; set; } + public IDictionary Extensions { get; set; } protected override void LoadData() { base.LoadData(); Version = ReadUInt32(); - Extentions = ReadExtensionPair(); + Extensions = ReadExtensionPair(); } protected override void SaveData() @@ -27,9 +27,9 @@ protected override void SaveData() Write(Version); - if (Extentions != null) + if (Extensions != null) { - Write(Extentions); + Write(Extensions); } } } diff --git a/src/Renci.SshNet/Sftp/SftpFileAttributes.cs b/src/Renci.SshNet/Sftp/SftpFileAttributes.cs index 503e14706..70ca0ddca 100644 --- a/src/Renci.SshNet/Sftp/SftpFileAttributes.cs +++ b/src/Renci.SshNet/Sftp/SftpFileAttributes.cs @@ -1,7 +1,10 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; -using System.Globalization; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Text; using Renci.SshNet.Common; @@ -23,8 +26,8 @@ public sealed class SftpFileAttributes private const uint S_IFCHR = 0x2000; // character device private const uint S_IFIFO = 0x1000; // FIFO private const uint S_ISUID = 0x0800; // set UID bit - private const uint S_ISGID = 0x0400; // set-group-ID bit (see below) - private const uint S_ISVTX = 0x0200; // sticky bit (see below) + private const uint S_ISGID = 0x0400; // set-group-ID bit + private const uint S_ISVTX = 0x0200; // sticky bit private const uint S_IRUSR = 0x0100; // owner has read permission private const uint S_IWUSR = 0x0080; // owner has write permission private const uint S_IXUSR = 0x0040; // owner has execute permission @@ -43,12 +46,7 @@ public sealed class SftpFileAttributes private readonly int _originalUserId; private readonly int _originalGroupId; private readonly uint _originalPermissions; - private readonly IDictionary _originalExtensions; - - private bool _isBitFiledsBitSet; - private bool _isUIDBitSet; - private bool _isGroupIDBitSet; - private bool _isStickyBitSet; + private readonly Dictionary? _originalExtensions; internal bool IsLastAccessTimeChanged { @@ -82,6 +80,7 @@ internal bool IsPermissionsChanged internal bool IsExtensionsChanged { + [MemberNotNullWhen(true, nameof(Extensions))] get { return _originalExtensions != null && Extensions != null && !_originalExtensions.SequenceEqual(Extensions); } } @@ -169,7 +168,13 @@ public DateTime LastWriteTime /// /// if file represents a socket; otherwise, . /// - public bool IsSocket { get; private set; } + public bool IsSocket + { + get + { + return (Permissions & S_IFMT) == S_IFSOCK; + } + } /// /// Gets a value indicating whether file represents a symbolic link. @@ -177,7 +182,13 @@ public DateTime LastWriteTime /// /// if file represents a symbolic link; otherwise, . /// - public bool IsSymbolicLink { get; private set; } + public bool IsSymbolicLink + { + get + { + return (Permissions & S_IFMT) == S_IFLNK; + } + } /// /// Gets a value indicating whether file represents a regular file. @@ -185,7 +196,13 @@ public DateTime LastWriteTime /// /// if file represents a regular file; otherwise, . /// - public bool IsRegularFile { get; private set; } + public bool IsRegularFile + { + get + { + return (Permissions & S_IFMT) == S_IFREG; + } + } /// /// Gets a value indicating whether file represents a block device. @@ -193,7 +210,13 @@ public DateTime LastWriteTime /// /// if file represents a block device; otherwise, . /// - public bool IsBlockDevice { get; private set; } + public bool IsBlockDevice + { + get + { + return (Permissions & S_IFMT) == S_IFBLK; + } + } /// /// Gets a value indicating whether file represents a directory. @@ -201,7 +224,13 @@ public DateTime LastWriteTime /// /// if file represents a directory; otherwise, . /// - public bool IsDirectory { get; private set; } + public bool IsDirectory + { + get + { + return (Permissions & S_IFMT) == S_IFDIR; + } + } /// /// Gets a value indicating whether file represents a character device. @@ -209,7 +238,13 @@ public DateTime LastWriteTime /// /// if file represents a character device; otherwise, . /// - public bool IsCharacterDevice { get; private set; } + public bool IsCharacterDevice + { + get + { + return (Permissions & S_IFMT) == S_IFCHR; + } + } /// /// Gets a value indicating whether file represents a named pipe. @@ -217,7 +252,88 @@ public DateTime LastWriteTime /// /// if file represents a named pipe; otherwise, . /// - public bool IsNamedPipe { get; private set; } + public bool IsNamedPipe + { + get + { + return (Permissions & S_IFMT) == S_IFIFO; + } + } + + /// + /// Gets or sets a value indicating whether the setuid bit is set. + /// + /// + /// if the setuid bit is set; otherwise, . + /// + public bool IsUIDBitSet + { + get + { + return (Permissions & S_ISUID) == S_ISUID; + } + set + { + if (value) + { + Permissions |= S_ISUID; + } + else + { + Permissions &= ~S_ISUID; + } + } + } + + /// + /// Gets or sets a value indicating whether the setgid bit is set. + /// + /// + /// if the setgid bit is set; otherwise, . + /// + public bool IsGroupIDBitSet + { + get + { + return (Permissions & S_ISGID) == S_ISGID; + } + set + { + if (value) + { + Permissions |= S_ISGID; + } + else + { + Permissions &= ~S_ISGID; + } + } + } + + /// + /// Gets or sets a value indicating whether the sticky bit is set. + /// + /// + /// if the sticky bit is set; otherwise, . + /// + public bool IsStickyBitSet + { + get + { + return (Permissions & S_ISVTX) == S_ISVTX; + } + set + { + if (value) + { + Permissions |= S_ISVTX; + } + else + { + Permissions &= ~S_ISVTX; + } + } + } /// /// Gets or sets a value indicating whether the owner can read from this file. @@ -225,7 +341,24 @@ public DateTime LastWriteTime /// /// if owner can read from this file; otherwise, . /// - public bool OwnerCanRead { get; set; } + public bool OwnerCanRead + { + get + { + return (Permissions & S_IRUSR) == S_IRUSR; + } + set + { + if (value) + { + Permissions |= S_IRUSR; + } + else + { + Permissions &= ~S_IRUSR; + } + } + } /// /// Gets or sets a value indicating whether the owner can write into this file. @@ -233,7 +366,24 @@ public DateTime LastWriteTime /// /// if owner can write into this file; otherwise, . /// - public bool OwnerCanWrite { get; set; } + public bool OwnerCanWrite + { + get + { + return (Permissions & S_IWUSR) == S_IWUSR; + } + set + { + if (value) + { + Permissions |= S_IWUSR; + } + else + { + Permissions &= ~S_IWUSR; + } + } + } /// /// Gets or sets a value indicating whether the owner can execute this file. @@ -241,7 +391,24 @@ public DateTime LastWriteTime /// /// if owner can execute this file; otherwise, . /// - public bool OwnerCanExecute { get; set; } + public bool OwnerCanExecute + { + get + { + return (Permissions & S_IXUSR) == S_IXUSR; + } + set + { + if (value) + { + Permissions |= S_IXUSR; + } + else + { + Permissions &= ~S_IXUSR; + } + } + } /// /// Gets or sets a value indicating whether the group members can read from this file. @@ -249,7 +416,24 @@ public DateTime LastWriteTime /// /// if group members can read from this file; otherwise, . /// - public bool GroupCanRead { get; set; } + public bool GroupCanRead + { + get + { + return (Permissions & S_IRGRP) == S_IRGRP; + } + set + { + if (value) + { + Permissions |= S_IRGRP; + } + else + { + Permissions &= ~S_IRGRP; + } + } + } /// /// Gets or sets a value indicating whether the group members can write into this file. @@ -257,7 +441,24 @@ public DateTime LastWriteTime /// /// if group members can write into this file; otherwise, . /// - public bool GroupCanWrite { get; set; } + public bool GroupCanWrite + { + get + { + return (Permissions & S_IWGRP) == S_IWGRP; + } + set + { + if (value) + { + Permissions |= S_IWGRP; + } + else + { + Permissions &= ~S_IWGRP; + } + } + } /// /// Gets or sets a value indicating whether the group members can execute this file. @@ -265,7 +466,24 @@ public DateTime LastWriteTime /// /// if group members can execute this file; otherwise, . /// - public bool GroupCanExecute { get; set; } + public bool GroupCanExecute + { + get + { + return (Permissions & S_IXGRP) == S_IXGRP; + } + set + { + if (value) + { + Permissions |= S_IXGRP; + } + else + { + Permissions &= ~S_IXGRP; + } + } + } /// /// Gets or sets a value indicating whether the others can read from this file. @@ -273,7 +491,24 @@ public DateTime LastWriteTime /// /// if others can read from this file; otherwise, . /// - public bool OthersCanRead { get; set; } + public bool OthersCanRead + { + get + { + return (Permissions & S_IROTH) == S_IROTH; + } + set + { + if (value) + { + Permissions |= S_IROTH; + } + else + { + Permissions &= ~S_IROTH; + } + } + } /// /// Gets or sets a value indicating whether the others can write into this file. @@ -281,7 +516,24 @@ public DateTime LastWriteTime /// /// if others can write into this file; otherwise, . /// - public bool OthersCanWrite { get; set; } + public bool OthersCanWrite + { + get + { + return (Permissions & S_IWOTH) == S_IWOTH; + } + set + { + if (value) + { + Permissions |= S_IWOTH; + } + else + { + Permissions &= ~S_IWOTH; + } + } + } /// /// Gets or sets a value indicating whether the others can execute this file. @@ -289,209 +541,159 @@ public DateTime LastWriteTime /// /// if others can execute this file; otherwise, . /// - public bool OthersCanExecute { get; set; } - - /// - /// Gets the extensions. - /// - /// - /// The extensions. - /// - public IDictionary Extensions { get; private set; } - - internal uint Permissions + public bool OthersCanExecute { get { - uint permission = 0; - - if (_isBitFiledsBitSet) - { - permission |= S_IFMT; - } - - if (IsSocket) - { - permission |= S_IFSOCK; - } - - if (IsSymbolicLink) - { - permission |= S_IFLNK; - } - - if (IsRegularFile) + return (Permissions & S_IXOTH) == S_IXOTH; + } + set + { + if (value) { - permission |= S_IFREG; + Permissions |= S_IXOTH; } - - if (IsBlockDevice) + else { - permission |= S_IFBLK; + Permissions &= ~S_IXOTH; } + } + } - if (IsDirectory) - { - permission |= S_IFDIR; - } + /// + /// Gets the extensions. + /// + /// + /// The extensions. + /// + public IDictionary? Extensions { get; } - if (IsCharacterDevice) - { - permission |= S_IFCHR; - } + internal uint Permissions { get; private set; } - if (IsNamedPipe) - { - permission |= S_IFIFO; - } + internal SftpFileAttributes(DateTime lastAccessTimeUtc, DateTime lastWriteTimeUtc, long size, int userId, int groupId, uint permissions, Dictionary? extensions) + { + LastAccessTimeUtc = _originalLastAccessTimeUtc = lastAccessTimeUtc; + LastWriteTimeUtc = _originalLastWriteTimeUtc = lastWriteTimeUtc; + Size = _originalSize = size; + UserId = _originalUserId = userId; + GroupId = _originalGroupId = groupId; + Permissions = _originalPermissions = permissions; + Extensions = _originalExtensions = extensions; + } - if (_isUIDBitSet) - { - permission |= S_ISUID; - } + /// + /// Sets the POSIX permissions for this file. + /// + /// + /// The permission mode as an octal number (e.g., 755, 644, 1777). + /// + /// + /// has more than 4 digits or cannot be interpreted as an octal number. + /// + public void SetPermissions(short mode) + { + var special = (uint)Math.DivRem(mode, 1000, out var userGroupOther); - if (_isGroupIDBitSet) - { - permission |= S_ISGID; - } + var user = (uint)Math.DivRem(userGroupOther, 100, out var groupOther); - if (_isStickyBitSet) - { - permission |= S_ISVTX; - } + var group = (uint)Math.DivRem(groupOther, 10, out var iOther); - if (OwnerCanRead) - { - permission |= S_IRUSR; - } + var other = (uint)iOther; - if (OwnerCanWrite) - { - permission |= S_IWUSR; - } + if ((special & ~7u) != 0 || (user & ~7u) != 0 || (group & ~7u) != 0 || (other & ~7u) != 0) + { + throw new ArgumentOutOfRangeException(nameof(mode)); + } - if (OwnerCanExecute) - { - permission |= S_IXUSR; - } + Permissions = (Permissions & ~0xFFFu) | (special << 9) | (user << 6) | (group << 3) | other; + } - if (GroupCanRead) - { - permission |= S_IRGRP; - } + /// + public override string? ToString() + { + var sb = new StringBuilder(); - if (GroupCanWrite) - { - permission |= S_IWGRP; - } + if (Permissions != default) + { + AppendPermissionsString(sb); + sb.Append(' '); + } - if (GroupCanExecute) - { - permission |= S_IXGRP; - } + if (Size != -1) + { + sb.AppendFormat("Size: {0} ", Size); + } - if (OthersCanRead) - { - permission |= S_IROTH; - } + if (LastWriteTime != default) + { + sb.AppendFormat("LastWriteTime: {0:s} ", LastWriteTime); + } - if (OthersCanWrite) + if (sb.Length > 0) + { + if (sb[sb.Length - 1] == ' ') { - permission |= S_IWOTH; + sb.Length--; } - if (OthersCanExecute) - { - permission |= S_IXOTH; - } + Debug.Assert(sb.Length > 0); + Debug.Assert(sb[^1] != ' '); - return permission; + return sb.ToString(); } - private set - { - _isBitFiledsBitSet = (value & S_IFMT) == S_IFMT; - - IsSocket = (value & S_IFSOCK) == S_IFSOCK; - - IsSymbolicLink = (value & S_IFLNK) == S_IFLNK; - - IsRegularFile = (value & S_IFREG) == S_IFREG; - - IsBlockDevice = (value & S_IFBLK) == S_IFBLK; - - IsDirectory = (value & S_IFDIR) == S_IFDIR; - - IsCharacterDevice = (value & S_IFCHR) == S_IFCHR; - - IsNamedPipe = (value & S_IFIFO) == S_IFIFO; - - _isUIDBitSet = (value & S_ISUID) == S_ISUID; - - _isGroupIDBitSet = (value & S_ISGID) == S_ISGID; - - _isStickyBitSet = (value & S_ISVTX) == S_ISVTX; - - OwnerCanRead = (value & S_IRUSR) == S_IRUSR; - - OwnerCanWrite = (value & S_IWUSR) == S_IWUSR; - - OwnerCanExecute = (value & S_IXUSR) == S_IXUSR; - GroupCanRead = (value & S_IRGRP) == S_IRGRP; - - GroupCanWrite = (value & S_IWGRP) == S_IWGRP; - - GroupCanExecute = (value & S_IXGRP) == S_IXGRP; - - OthersCanRead = (value & S_IROTH) == S_IROTH; - - OthersCanWrite = (value & S_IWOTH) == S_IWOTH; - - OthersCanExecute = (value & S_IXOTH) == S_IXOTH; - } + return base.ToString(); } - private SftpFileAttributes() + private void AppendPermissionsString(StringBuilder sb) { - } - - internal SftpFileAttributes(DateTime lastAccessTimeUtc, DateTime lastWriteTimeUtc, long size, int userId, int groupId, uint permissions, IDictionary extensions) - { - LastAccessTimeUtc = _originalLastAccessTimeUtc = lastAccessTimeUtc; - LastWriteTimeUtc = _originalLastWriteTimeUtc = lastWriteTimeUtc; - Size = _originalSize = size; - UserId = _originalUserId = userId; - GroupId = _originalGroupId = groupId; - Permissions = _originalPermissions = permissions; - Extensions = _originalExtensions = extensions; - } - - /// - /// Sets the permissions. - /// - /// The mode. - public void SetPermissions(short mode) - { - if (mode is < 0 or > 999) + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html + + sb.Append( + IsRegularFile ? '-' : + IsDirectory ? 'd' : + IsSymbolicLink ? 'l' : + IsNamedPipe ? 'p' : + IsSocket ? 's' : + IsCharacterDevice ? 'c' : + IsBlockDevice ? 'b' : + '-'); + + sb.Append(OwnerCanRead ? 'r' : '-'); + sb.Append(OwnerCanWrite ? 'w' : '-'); + + if (OwnerCanExecute) { - throw new ArgumentOutOfRangeException(nameof(mode)); + sb.Append(IsUIDBitSet ? 's' : 'x'); + } + else + { + sb.Append(IsUIDBitSet ? 'S' : '-'); } - var modeBytes = mode.ToString(CultureInfo.InvariantCulture).PadLeft(3, '0').ToCharArray(); - - var permission = ((modeBytes[0] & 0x0F) * 8 * 8) + ((modeBytes[1] & 0x0F) * 8) + (modeBytes[2] & 0x0F); + sb.Append(GroupCanRead ? 'r' : '-'); + sb.Append(GroupCanWrite ? 'w' : '-'); - OwnerCanRead = (permission & S_IRUSR) == S_IRUSR; - OwnerCanWrite = (permission & S_IWUSR) == S_IWUSR; - OwnerCanExecute = (permission & S_IXUSR) == S_IXUSR; + if (GroupCanExecute) + { + sb.Append(IsGroupIDBitSet ? 's' : 'x'); + } + else + { + sb.Append(IsGroupIDBitSet ? 'S' : '-'); + } - GroupCanRead = (permission & S_IRGRP) == S_IRGRP; - GroupCanWrite = (permission & S_IWGRP) == S_IWGRP; - GroupCanExecute = (permission & S_IXGRP) == S_IXGRP; + sb.Append(OthersCanRead ? 'r' : '-'); + sb.Append(OthersCanWrite ? 'w' : '-'); - OthersCanRead = (permission & S_IROTH) == S_IROTH; - OthersCanWrite = (permission & S_IWOTH) == S_IWOTH; - OthersCanExecute = (permission & S_IXOTH) == S_IXOTH; + if (OthersCanExecute) + { + sb.Append(IsStickyBitSet ? 't' : 'x'); + } + else + { + sb.Append(IsStickyBitSet ? 'T' : '-'); + } } /// @@ -506,7 +708,7 @@ public byte[] GetBytes() { uint flag = 0; - if (IsSizeChanged && IsRegularFile) + if (IsSizeChanged) { flag |= 0x00000001; } @@ -533,7 +735,7 @@ public byte[] GetBytes() stream.Write(flag); - if (IsSizeChanged && IsRegularFile) + if (IsSizeChanged) { stream.Write((ulong)Size); } @@ -551,9 +753,9 @@ public byte[] GetBytes() if (IsLastAccessTimeChanged || IsLastWriteTimeChanged) { - var time = (uint)((LastAccessTimeUtc.ToFileTimeUtc() / 10000000) - 11644473600); + var time = (uint)((DateTimeOffset)DateTime.SpecifyKind(LastAccessTimeUtc, DateTimeKind.Utc)).ToUnixTimeSeconds(); stream.Write(time); - time = (uint)((LastWriteTimeUtc.ToFileTimeUtc() / 10000000) - 11644473600); + time = (uint)((DateTimeOffset)DateTime.SpecifyKind(LastWriteTimeUtc, DateTimeKind.Utc)).ToUnixTimeSeconds(); stream.Write(time); } @@ -561,12 +763,8 @@ public byte[] GetBytes() { foreach (var item in Extensions) { - /* - * TODO: we write as ASCII but read as UTF8 !!! - */ - - stream.Write(item.Key, SshData.Ascii); - stream.Write(item.Value, SshData.Ascii); + stream.Write(item.Key, Encoding.UTF8); + stream.Write(item.Value, Encoding.UTF8); } } @@ -574,8 +772,6 @@ public byte[] GetBytes() } } - internal static readonly SftpFileAttributes Empty = new SftpFileAttributes(); - internal static SftpFileAttributes FromBytes(SshDataStream stream) { const uint SSH_FILEXFER_ATTR_SIZE = 0x00000001; @@ -592,7 +788,7 @@ internal static SftpFileAttributes FromBytes(SshDataStream stream) uint permissions = 0; DateTime accessTime; DateTime modifyTime; - Dictionary extensions = null; + Dictionary? extensions = null; if ((flag & SSH_FILEXFER_ATTR_SIZE) == SSH_FILEXFER_ATTR_SIZE) { @@ -613,12 +809,8 @@ internal static SftpFileAttributes FromBytes(SshDataStream stream) if ((flag & SSH_FILEXFER_ATTR_ACMODTIME) == SSH_FILEXFER_ATTR_ACMODTIME) { - // The incoming times are "Unix times", so they're already in UTC. We need to preserve that - // to avoid losing information in a local time conversion during the "fall back" hour in DST. - var time = stream.ReadUInt32(); - accessTime = DateTime.FromFileTimeUtc((time + 11644473600) * 10000000); - time = stream.ReadUInt32(); - modifyTime = DateTime.FromFileTimeUtc((time + 11644473600) * 10000000); + accessTime = DateTimeOffset.FromUnixTimeSeconds(stream.ReadUInt32()).UtcDateTime; + modifyTime = DateTimeOffset.FromUnixTimeSeconds(stream.ReadUInt32()).UtcDateTime; } else { @@ -632,8 +824,8 @@ internal static SftpFileAttributes FromBytes(SshDataStream stream) extensions = new Dictionary(extendedCount); for (var i = 0; i < extendedCount; i++) { - var extensionName = stream.ReadString(SshData.Utf8); - var extensionData = stream.ReadString(SshData.Utf8); + var extensionName = stream.ReadString(Encoding.UTF8); + var extensionData = stream.ReadString(Encoding.UTF8); extensions.Add(extensionName, extensionData); } } diff --git a/src/Renci.SshNet/Sftp/SftpSession.cs b/src/Renci.SshNet/Sftp/SftpSession.cs index 1de63eaf2..2b4d8c00e 100644 --- a/src/Renci.SshNet/Sftp/SftpSession.cs +++ b/src/Renci.SshNet/Sftp/SftpSession.cs @@ -379,7 +379,7 @@ private bool TryLoadSftpMessage(ArraySegment packetData) if (response is SftpVersionResponse versionResponse) { ProtocolVersion = versionResponse.Version; - _supportedExtensions = versionResponse.Extentions; + _supportedExtensions = versionResponse.Extensions; _ = _sftpVersionConfirmed.Set(); } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpFSetStatRequestTest.cs b/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpFSetStatRequestTest.cs index 69c0096ec..2fd3bd1e7 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpFSetStatRequestTest.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpFSetStatRequestTest.cs @@ -8,6 +8,7 @@ using Renci.SshNet.Sftp; using Renci.SshNet.Sftp.Requests; using Renci.SshNet.Sftp.Responses; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp.Requests { @@ -29,7 +30,7 @@ public void Init() _requestId = (uint)random.Next(0, int.MaxValue); _handle = new byte[random.Next(1, 10)]; random.NextBytes(_handle); - _attributes = SftpFileAttributes.Empty; + _attributes = SftpFileAttributesBuilder.Empty; _attributesBytes = _attributes.GetBytes(); } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpMkDirRequestTest.cs b/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpMkDirRequestTest.cs index ec0cf1af3..e01d3f7f7 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpMkDirRequestTest.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpMkDirRequestTest.cs @@ -10,6 +10,7 @@ using Renci.SshNet.Sftp; using Renci.SshNet.Sftp.Requests; using Renci.SshNet.Sftp.Responses; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp.Requests { @@ -34,7 +35,7 @@ public void Init() _encoding = Encoding.Unicode; _path = random.Next().ToString(CultureInfo.InvariantCulture); _pathBytes = _encoding.GetBytes(_path); - _attributes = SftpFileAttributes.Empty; + _attributes = SftpFileAttributesBuilder.Empty; _attributesBytes = _attributes.GetBytes(); } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpOpenRequestTest.cs b/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpOpenRequestTest.cs index 20838c86d..dd7e407a3 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpOpenRequestTest.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpOpenRequestTest.cs @@ -10,6 +10,7 @@ using Renci.SshNet.Sftp; using Renci.SshNet.Sftp.Requests; using Renci.SshNet.Sftp.Responses; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp.Requests { @@ -36,7 +37,7 @@ public void Init() _filename = random.Next().ToString(CultureInfo.InvariantCulture); _filenameBytes = _encoding.GetBytes(_filename); _flags = Flags.Read; - _attributes = SftpFileAttributes.Empty; + _attributes = SftpFileAttributesBuilder.Empty; _attributesBytes = _attributes.GetBytes(); } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpSetStatRequestTest.cs b/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpSetStatRequestTest.cs index c7111dd14..ae2e22311 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpSetStatRequestTest.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/Requests/SftpSetStatRequestTest.cs @@ -10,6 +10,7 @@ using Renci.SshNet.Sftp; using Renci.SshNet.Sftp.Requests; using Renci.SshNet.Sftp.Responses; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp.Requests { @@ -34,7 +35,7 @@ public void Init() _encoding = Encoding.Unicode; _path = random.Next().ToString(CultureInfo.InvariantCulture); _pathBytes = _encoding.GetBytes(_path); - _attributes = SftpFileAttributes.Empty; + _attributes = SftpFileAttributesBuilder.Empty; _attributesBytes = _attributes.GetBytes(); } diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/Responses/SftpAttrsResponseTest.cs b/test/Renci.SshNet.Tests/Classes/Sftp/Responses/SftpAttrsResponseTest.cs index ed8996512..4598b5378 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/Responses/SftpAttrsResponseTest.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/Responses/SftpAttrsResponseTest.cs @@ -5,6 +5,7 @@ using Renci.SshNet.Common; using Renci.SshNet.Sftp; using Renci.SshNet.Sftp.Responses; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp.Responses { @@ -61,7 +62,7 @@ public void Load() private SftpFileAttributes CreateSftpFileAttributes() { - var attributes = SftpFileAttributes.Empty; + var attributes = SftpFileAttributesBuilder.Empty; attributes.GroupId = _random.Next(); attributes.LastAccessTime = new DateTime(2014, 8, 23, 17, 43, 50, DateTimeKind.Local); attributes.LastWriteTime = new DateTime(2013, 7, 22, 16, 40, 42, DateTimeKind.Local); diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileAttributesTest.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileAttributesTest.cs new file mode 100644 index 000000000..9bf8cc9e2 --- /dev/null +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileAttributesTest.cs @@ -0,0 +1,223 @@ +using System; +using System.Buffers.Binary; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Renci.SshNet.Common; +using Renci.SshNet.Sftp; + +namespace Renci.SshNet.Tests.Classes.Sftp +{ + [TestClass] + public class SftpFileAttributesTest + { + [TestMethod] + [DataRow(0xC000u, true, false, false, false, false, false, false)] // Socket + [DataRow(0xA000u, false, true, false, false, false, false, false)] // Symbolic link + [DataRow(0x8000u, false, false, true, false, false, false, false)] // Regular file + [DataRow(0x6000u, false, false, false, true, false, false, false)] // Block device + [DataRow(0x4000u, false, false, false, false, true, false, false)] // Directory + [DataRow(0x2000u, false, false, false, false, false, true, false)] // Character device + [DataRow(0x1000u, false, false, false, false, false, false, true)] // Named pipe + public void FileTypePropertiesAreMutuallyExclusive( + uint permissions, + bool isSocket, + bool isSymbolicLink, + bool isRegularFile, + bool isBlockDevice, + bool isDirectory, + bool isCharacterDevice, + bool isNamedPipe) + { + var attributeBytes = new byte[8]; + attributeBytes[3] = 0x4; // SSH_FILEXFER_ATTR_PERMISSIONS + BinaryPrimitives.WriteUInt32BigEndian(attributeBytes.AsSpan(4), permissions); + + var attributes = SftpFileAttributes.FromBytes(attributeBytes); + + Assert.AreEqual(isSocket, attributes.IsSocket); + Assert.AreEqual(isSymbolicLink, attributes.IsSymbolicLink); + Assert.AreEqual(isRegularFile, attributes.IsRegularFile); + Assert.AreEqual(isBlockDevice, attributes.IsBlockDevice); + Assert.AreEqual(isDirectory, attributes.IsDirectory); + Assert.AreEqual(isCharacterDevice, attributes.IsCharacterDevice); + Assert.AreEqual(isNamedPipe, attributes.IsNamedPipe); + } + + [TestMethod] + public void FromBytesGetBytes() + { + // 81a4 in hex = 100644 in octal + var attributes = SftpFileAttributes.FromBytes([0, 0, 0, 0x4, 0, 0, 0x81, 0xa4]); + + Assert.IsTrue(attributes.IsRegularFile); + + Assert.IsFalse(attributes.IsUIDBitSet); + Assert.IsFalse(attributes.IsGroupIDBitSet); + Assert.IsFalse(attributes.IsStickyBitSet); + Assert.IsTrue(attributes.OwnerCanRead); + Assert.IsTrue(attributes.OwnerCanWrite); + Assert.IsFalse(attributes.OwnerCanExecute); + Assert.IsTrue(attributes.GroupCanRead); + Assert.IsFalse(attributes.GroupCanWrite); + Assert.IsFalse(attributes.GroupCanExecute); + Assert.IsTrue(attributes.OthersCanRead); + Assert.IsFalse(attributes.OthersCanWrite); + Assert.IsFalse(attributes.OthersCanExecute); + + Assert.AreEqual(-1, attributes.Size); // Erm, OK? + Assert.AreEqual(-1, attributes.UserId); + Assert.AreEqual(-1, attributes.GroupId); + + Assert.AreEqual(default, attributes.LastAccessTimeUtc); + Assert.AreEqual(DateTimeKind.Utc, attributes.LastAccessTimeUtc.Kind); + + Assert.AreEqual(default, attributes.LastWriteTimeUtc); + Assert.AreEqual(DateTimeKind.Utc, attributes.LastWriteTimeUtc.Kind); + + Assert.AreEqual("-rw-r--r--", attributes.ToString()); + + // No changes + CollectionAssert.AreEqual( + new byte[] { 0, 0, 0, 0 }, + attributes.GetBytes()); + + + // Permissions change + attributes.IsUIDBitSet = true; + attributes.OwnerCanExecute = true; + + CollectionAssert.AreEqual( + new byte[] { 0, 0, 0, 0x4, 0, 0, 0x89, 0xe4 }, + attributes.GetBytes()); + + Assert.AreEqual("-rwsr--r--", attributes.ToString()); + + // Size change + attributes.Size = 123; + + CollectionAssert.AreEqual( + new byte[] { + 0, 0, 0, 0x1 | 0x4, + 0, 0, 0, 0, 0, 0, 0, 123, + 0, 0, 0x89, 0xe4 }, + attributes.GetBytes()); + + Assert.StartsWith("-rwsr--r-- Size: ", attributes.ToString(), StringComparison.Ordinal); + + // Uid/gid change + attributes.UserId = 99; + attributes.GroupId = 66; + + CollectionAssert.AreEqual( + new byte[] { + 0, 0, 0, 0x1 | 0x2 | 0x4, + 0, 0, 0, 0, 0, 0, 0, 123, + 0, 0, 0, 99, 0, 0, 0, 66, + 0, 0, 0x89, 0xe4 }, + attributes.GetBytes()); + + + // Access/mod time change + attributes.LastAccessTimeUtc = new DateTime(2025, 08, 10, 17, 51, 37, DateTimeKind.Unspecified); + attributes.LastWriteTime = new DateTimeOffset(2016, 12, 02, 13, 18, 20, TimeSpan.FromHours(3)).LocalDateTime; + + var expectedTimeBytes = new byte[8]; + BinaryPrimitives.WriteUInt32BigEndian(expectedTimeBytes, 1754848297); + BinaryPrimitives.WriteUInt32BigEndian(expectedTimeBytes.AsSpan(4), 1480673900); + + CollectionAssert.AreEqual( + new byte[] { + 0, 0, 0, 0x1 | 0x2 | 0x4 | 0x8, + 0, 0, 0, 0, 0, 0, 0, 123, + 0, 0, 0, 99, 0, 0, 0, 66, + 0, 0, 0x89, 0xe4 + }.Concat(expectedTimeBytes), + attributes.GetBytes()); + + Assert.AreEqual(new DateTime(2016, 12, 02, 10, 18, 20, DateTimeKind.Utc), attributes.LastWriteTimeUtc); + Assert.AreEqual(DateTimeKind.Utc, attributes.LastWriteTimeUtc.Kind); + + var attributesString = attributes.ToString(); + Assert.StartsWith("-rwsr--r-- Size: ", attributesString, StringComparison.Ordinal); + Assert.Contains(" LastWriteTime: ", attributesString, StringComparison.CurrentCulture); + } + + [TestMethod] + [DataRow((short)8888)] + [DataRow((short)10000)] + [DataRow((short)8000)] + [DataRow((short)0080)] + [DataRow((short)0008)] + [DataRow((short)1797)] + [DataRow((short)-1)] + [DataRow(short.MaxValue)] + public void SetPermissions_InvalidMode_ThrowsArgumentOutOfRangeException(short mode) + { + var attributes = SftpFileAttributes.FromBytes([0, 0, 0, 0]); + + var ex = Assert.Throws(() => attributes.SetPermissions(mode)); + Assert.AreEqual("mode", ex.ParamName); + } + + [TestMethod] + [DataRow((short)0777, false, false, false, true, true, true, true, true, true, true, true, true)] + [DataRow((short)0755, false, false, false, true, true, true, true, false, true, true, false, true)] + [DataRow((short)0644, false, false, false, true, true, false, true, false, false, true, false, false)] + [DataRow((short)0444, false, false, false, true, false, false, true, false, false, true, false, false)] + [DataRow((short)0000, false, false, false, false, false, false, false, false, false, false, false, false)] + [DataRow((short)4700, true, false, false, true, true, true, false, false, false, false, false, false)] + [DataRow((short)3001, false, true, true, false, false, false, false, false, false, false, false, true)] + [DataRow((short)7777, true, true, true, true, true, true, true, true, true, true, true, true)] + public void SetPermissions_ValidMode( + short mode, + bool setUid, bool setGid, bool sticky, + bool ownerRead, bool ownerWrite, bool ownerExec, + bool groupRead, bool groupWrite, bool groupExec, + bool othersRead, bool othersWrite, bool othersExec) + { + var attributes = SftpFileAttributes.FromBytes([0, 0, 0, 0]); + + attributes.SetPermissions(mode); + + Assert.AreEqual(setUid, attributes.IsUIDBitSet); + Assert.AreEqual(setGid, attributes.IsGroupIDBitSet); + Assert.AreEqual(sticky, attributes.IsStickyBitSet); + Assert.AreEqual(ownerRead, attributes.OwnerCanRead); + Assert.AreEqual(ownerWrite, attributes.OwnerCanWrite); + Assert.AreEqual(ownerExec, attributes.OwnerCanExecute); + Assert.AreEqual(groupRead, attributes.GroupCanRead); + Assert.AreEqual(groupWrite, attributes.GroupCanWrite); + Assert.AreEqual(groupExec, attributes.GroupCanExecute); + Assert.AreEqual(othersRead, attributes.OthersCanRead); + Assert.AreEqual(othersWrite, attributes.OthersCanWrite); + Assert.AreEqual(othersExec, attributes.OthersCanExecute); + } + + [TestMethod] + [DataRow(0xC000u, (short)1770, "srwxrwx--T")] // Socket + [DataRow(0xA000u, (short)2707, "lrwx--Srwx")] // Symbolic link + [DataRow(0x8000u, (short)4755, "-rwsr-xr-x")] // Regular file + [DataRow(0x8000u, (short)4644, "-rwSr--r--")] // Regular file + [DataRow(0x6000u, (short)2711, "brwx--s--x")] // Block device + [DataRow(0x4000u, (short)1777, "drwxrwxrwt")] // Directory + [DataRow(0x4000u, (short)1776, "drwxrwxrwT")] // Directory + [DataRow(0x2000u, (short)0660, "crw-rw----")] // Character device + [DataRow(0x1000u, (short)0022, "p----w--w-")] // Named pipe + public void ToStringWithPermissions( + uint fileType, + short permissions, + string expected) + { + var attributeBytes = new byte[8]; + attributeBytes[3] = 0x4; // SSH_FILEXFER_ATTR_PERMISSIONS + BinaryPrimitives.WriteUInt32BigEndian(attributeBytes.AsSpan(4), fileType); + + var attributes = SftpFileAttributes.FromBytes(attributeBytes); + + attributes.SetPermissions(permissions); + + Assert.AreEqual(expected, attributes.ToString()); + } + } +} diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTestBase.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTestBase.cs index c48836ba5..1cb1207b6 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTestBase.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpFileReaderTestBase.cs @@ -41,12 +41,6 @@ public void SetUp() protected abstract void Act(); - protected static SftpFileAttributes CreateSftpFileAttributes(long size) - { - var utcDefault = DateTime.SpecifyKind(default, DateTimeKind.Utc); - return new SftpFileAttributes(utcDefault, utcDefault, size, default, default, default, null); - } - protected static byte[] CreateByteArray(Random random, int length) { var chunk = new byte[length]; diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_Connected_RequestRead.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_Connected_RequestRead.cs index 50125eb7f..3b19a0185 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_Connected_RequestRead.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_Connected_RequestRead.cs @@ -12,6 +12,7 @@ using Renci.SshNet.Common; using Renci.SshNet.Sftp; using Renci.SshNet.Sftp.Responses; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -73,7 +74,7 @@ private void SetupData() _sftpNameResponse = new SftpNameResponseBuilder().WithProtocolVersion(_protocolVersion) .WithResponseId(1) .WithEncoding(_encoding) - .WithFile("XYZ", SftpFileAttributes.Empty) + .WithFile("XYZ", SftpFileAttributesBuilder.Empty) .Build(); #endregion SftpSession.Connect() diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_Connected_RequestStatVfs.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_Connected_RequestStatVfs.cs index a80c744d9..a5086d3d2 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_Connected_RequestStatVfs.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_Connected_RequestStatVfs.cs @@ -10,6 +10,7 @@ using Renci.SshNet.Common; using Renci.SshNet.Sftp; using Renci.SshNet.Sftp.Responses; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -70,7 +71,7 @@ private void SetupData() _sftpNameResponse = new SftpNameResponseBuilder().WithProtocolVersion(_protocolVersion) .WithResponseId(1U) .WithEncoding(_encoding) - .WithFile("ABC", SftpFileAttributes.Empty) + .WithFile("ABC", SftpFileAttributesBuilder.Empty) .Build(); #endregion SftpSession.Connect() diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_DataReceived_MultipleSftpMessagesInSingleSshDataMessage.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_DataReceived_MultipleSftpMessagesInSingleSshDataMessage.cs index 45bf5c020..eac86ccd0 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_DataReceived_MultipleSftpMessagesInSingleSshDataMessage.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_DataReceived_MultipleSftpMessagesInSingleSshDataMessage.cs @@ -11,6 +11,7 @@ using Renci.SshNet.Common; using Renci.SshNet.Sftp; using Renci.SshNet.Sftp.Responses; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -75,7 +76,7 @@ private void SetupData() _sftpNameResponse = new SftpNameResponseBuilder().WithProtocolVersion(_protocolVersion) .WithResponseId(1) .WithEncoding(_encoding) - .WithFile("/ABC", SftpFileAttributes.Empty) + .WithFile("/ABC", SftpFileAttributesBuilder.Empty) .Build(); #endregion SftpSession.Connect() diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_DataReceived_MultipleSftpMessagesSplitOverMultipleSshDataMessages.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_DataReceived_MultipleSftpMessagesSplitOverMultipleSshDataMessages.cs index e0e809af8..beb94c484 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_DataReceived_MultipleSftpMessagesSplitOverMultipleSshDataMessages.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_DataReceived_MultipleSftpMessagesSplitOverMultipleSshDataMessages.cs @@ -11,6 +11,7 @@ using Renci.SshNet.Common; using Renci.SshNet.Sftp; using Renci.SshNet.Sftp.Responses; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -75,7 +76,7 @@ private void SetupData() _sftpNameResponse = new SftpNameResponseBuilder().WithProtocolVersion(_protocolVersion) .WithResponseId(1) .WithEncoding(_encoding) - .WithFile("/ABC", SftpFileAttributes.Empty) + .WithFile("/ABC", SftpFileAttributesBuilder.Empty) .Build(); #endregion SftpSession.Connect() diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_DataReceived_SingleSftpMessageInSshDataMessage.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_DataReceived_SingleSftpMessageInSshDataMessage.cs index 88aad7e6b..ead62c821 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_DataReceived_SingleSftpMessageInSshDataMessage.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpSessionTest_DataReceived_SingleSftpMessageInSshDataMessage.cs @@ -11,6 +11,7 @@ using Renci.SshNet.Common; using Renci.SshNet.Sftp; using Renci.SshNet.Sftp.Responses; +using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes.Sftp { @@ -71,7 +72,7 @@ private void SetupData() _sftpNameResponse = new SftpNameResponseBuilder().WithProtocolVersion(_protocolVersion) .WithResponseId(1) .WithEncoding(_encoding) - .WithFile("/ABC", SftpFileAttributes.Empty) + .WithFile("/ABC", SftpFileAttributesBuilder.Empty) .Build(); #endregion SftpSession.Connect() diff --git a/test/Renci.SshNet.Tests/Classes/Sftp/SftpVersionResponseBuilder.cs b/test/Renci.SshNet.Tests/Classes/Sftp/SftpVersionResponseBuilder.cs index f69af49bd..0590c65eb 100644 --- a/test/Renci.SshNet.Tests/Classes/Sftp/SftpVersionResponseBuilder.cs +++ b/test/Renci.SshNet.Tests/Classes/Sftp/SftpVersionResponseBuilder.cs @@ -31,7 +31,7 @@ public SftpVersionResponse Build() var sftpVersionResponse = new SftpVersionResponse() { Version = _version, - Extentions = _extensions + Extensions = _extensions }; return sftpVersionResponse; } diff --git a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs index 30de20fb5..868175985 100644 --- a/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs +++ b/test/Renci.SshNet.Tests/Classes/ShellStreamTest_Write_WriteBufferNotEmptyAndWriteMoreBytesThanBufferCanContain.cs @@ -10,7 +10,6 @@ using Renci.SshNet.Abstractions; using Renci.SshNet.Channels; using Renci.SshNet.Common; -using Renci.SshNet.Tests.Common; namespace Renci.SshNet.Tests.Classes { @@ -60,9 +59,7 @@ private void SetupData() _offset = 0; _count = _data.Length; - _expectedBytesSent = new ArrayBuilder().Add(_bufferData) - .Add(_data, 0, _bufferSize - _bufferData.Length) - .Build(); + _expectedBytesSent = [.. _bufferData, .. _data.Take(0, _bufferSize - _bufferData.Length)]; } private void CreateMocks() diff --git a/test/Renci.SshNet.Tests/Common/ArrayBuilder`1.cs b/test/Renci.SshNet.Tests/Common/ArrayBuilder`1.cs deleted file mode 100644 index 9f9f7d659..000000000 --- a/test/Renci.SshNet.Tests/Common/ArrayBuilder`1.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; - -namespace Renci.SshNet.Tests.Common -{ - public class ArrayBuilder - { - private readonly List _buffer; - - public ArrayBuilder() - { - _buffer = new List(); - } - - public ArrayBuilder Add(T[] array) - { - return Add(array, 0, array.Length); - } - - public ArrayBuilder Add(T[] array, int index, int length) - { - for (var i = 0; i < length; i++) - { - _buffer.Add(array[index + i]); - } - - return this; - } - - public T[] Build() - { - return _buffer.ToArray(); - } - } -} diff --git a/test/Renci.SshNet.Tests/Common/Extensions.cs b/test/Renci.SshNet.Tests/Common/Extensions.cs index 5f8b1e319..977c182dc 100644 --- a/test/Renci.SshNet.Tests/Common/Extensions.cs +++ b/test/Renci.SshNet.Tests/Common/Extensions.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Renci.SshNet.Common; -using Renci.SshNet.Sftp; namespace Renci.SshNet.Tests.Common { @@ -23,44 +21,5 @@ public static string AsString(this IList exceptionEvents) return reportedExceptions; } - - public static byte[] Copy(this byte[] buffer) - { - var copy = new byte[buffer.Length]; - Buffer.BlockCopy(buffer, 0, copy, 0, buffer.Length); - return copy; - } - - /// - /// Creates a deep clone of the current instance. - /// - /// - /// A deep clone of the current instance. - /// - internal static SftpFileAttributes Clone(this SftpFileAttributes value) - { - Dictionary clonedExtensions; - - if (value.Extensions != null) - { - clonedExtensions = new Dictionary(value.Extensions.Count); - foreach (var entry in value.Extensions) - { - clonedExtensions.Add(entry.Key, entry.Value); - } - } - else - { - clonedExtensions = null; - } - - return new SftpFileAttributes(value.LastAccessTimeUtc, - value.LastWriteTimeUtc, - value.Size, - value.UserId, - value.GroupId, - value.Permissions, - clonedExtensions); - } } } diff --git a/test/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs b/test/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs index ca14b16ca..50e772b92 100644 --- a/test/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs +++ b/test/Renci.SshNet.Tests/Common/SftpFileAttributesBuilder.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Collections.Generic; using Renci.SshNet.Sftp; @@ -7,18 +8,21 @@ namespace Renci.SshNet.Tests.Common { public class SftpFileAttributesBuilder { + public static SftpFileAttributes Empty + { + get + { + return new SftpFileAttributesBuilder().Build(); + } + } + private DateTime? _lastAccessTime; private DateTime? _lastWriteTime; private long? _size; private int? _userId; private int? _groupId; private uint? _permissions; - private readonly IDictionary _extensions; - - public SftpFileAttributesBuilder() - { - _extensions = new Dictionary(); - } + private Dictionary? _extensions; public SftpFileAttributesBuilder WithLastAccessTime(DateTime lastAccessTime) { @@ -58,6 +62,7 @@ public SftpFileAttributesBuilder WithPermissions(uint permissions) public SftpFileAttributesBuilder WithExtension(string name, string value) { + _extensions ??= []; _extensions.Add(name, value); return this; }