|  | @@ -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 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          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 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          private readonly int _originalUserId;
 | 
	
		
			
				|  |  |          private readonly int _originalGroupId;
 | 
	
		
			
				|  |  |          private readonly uint _originalPermissions;
 | 
	
		
			
				|  |  | -        private readonly IDictionary<string, string> _originalExtensions;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        private bool _isBitFiledsBitSet;
 | 
	
		
			
				|  |  | -        private bool _isUIDBitSet;
 | 
	
		
			
				|  |  | -        private bool _isGroupIDBitSet;
 | 
	
		
			
				|  |  | -        private bool _isStickyBitSet;
 | 
	
		
			
				|  |  | +        private readonly Dictionary<string, string>? _originalExtensions;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          internal bool IsLastAccessTimeChanged
 | 
	
		
			
				|  |  |          {
 | 
	
	
		
			
				|  | @@ -82,6 +80,7 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          internal bool IsExtensionsChanged
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  | +            [MemberNotNullWhen(true, nameof(Extensions))]
 | 
	
		
			
				|  |  |              get { return _originalExtensions != null && Extensions != null && !_originalExtensions.SequenceEqual(Extensions); }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -169,7 +168,13 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if file represents a socket; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        public bool IsSocket { get; private set; }
 | 
	
		
			
				|  |  | +        public bool IsSocket
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return (Permissions & S_IFMT) == S_IFSOCK;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets a value indicating whether file represents a symbolic link.
 | 
	
	
		
			
				|  | @@ -177,7 +182,13 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if file represents a symbolic link; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        public bool IsSymbolicLink { get; private set; }
 | 
	
		
			
				|  |  | +        public bool IsSymbolicLink
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return (Permissions & S_IFMT) == S_IFLNK;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets a value indicating whether file represents a regular file.
 | 
	
	
		
			
				|  | @@ -185,7 +196,13 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if file represents a regular file; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        public bool IsRegularFile { get; private set; }
 | 
	
		
			
				|  |  | +        public bool IsRegularFile
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return (Permissions & S_IFMT) == S_IFREG;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets a value indicating whether file represents a block device.
 | 
	
	
		
			
				|  | @@ -193,7 +210,13 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if file represents a block device; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        public bool IsBlockDevice { get; private set; }
 | 
	
		
			
				|  |  | +        public bool IsBlockDevice
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return (Permissions & S_IFMT) == S_IFBLK;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets a value indicating whether file represents a directory.
 | 
	
	
		
			
				|  | @@ -201,7 +224,13 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if file represents a directory; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        public bool IsDirectory { get; private set; }
 | 
	
		
			
				|  |  | +        public bool IsDirectory
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return (Permissions & S_IFMT) == S_IFDIR;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets a value indicating whether file represents a character device.
 | 
	
	
		
			
				|  | @@ -209,7 +238,13 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if file represents a character device; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        public bool IsCharacterDevice { get; private set; }
 | 
	
		
			
				|  |  | +        public bool IsCharacterDevice
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return (Permissions & S_IFMT) == S_IFCHR;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets a value indicating whether file represents a named pipe.
 | 
	
	
		
			
				|  | @@ -217,7 +252,88 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if file represents a named pipe; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        public bool IsNamedPipe { get; private set; }
 | 
	
		
			
				|  |  | +        public bool IsNamedPipe
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return (Permissions & S_IFMT) == S_IFIFO;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// Gets or sets a value indicating whether the setuid bit is set.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        /// <value>
 | 
	
		
			
				|  |  | +        /// <see langword="true"/> if the setuid bit is set; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  | +        /// </value>
 | 
	
		
			
				|  |  | +        public bool IsUIDBitSet
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return (Permissions & S_ISUID) == S_ISUID;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            set
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                if (value)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    Permissions |= S_ISUID;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                else
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    Permissions &= ~S_ISUID;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// Gets or sets a value indicating whether the setgid bit is set.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        /// <value>
 | 
	
		
			
				|  |  | +        /// <see langword="true"/> if the setgid bit is set; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  | +        /// </value>
 | 
	
		
			
				|  |  | +        public bool IsGroupIDBitSet
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return (Permissions & S_ISGID) == S_ISGID;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            set
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                if (value)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    Permissions |= S_ISGID;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                else
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    Permissions &= ~S_ISGID;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// Gets or sets a value indicating whether the sticky bit is set.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        /// <value>
 | 
	
		
			
				|  |  | +        /// <see langword="true"/> if the sticky bit is set; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  | +        /// </value>
 | 
	
		
			
				|  |  | +        public bool IsStickyBitSet
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            get
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return (Permissions & S_ISVTX) == S_ISVTX;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            set
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                if (value)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    Permissions |= S_ISVTX;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                else
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    Permissions &= ~S_ISVTX;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets or sets a value indicating whether the owner can read from this file.
 | 
	
	
		
			
				|  | @@ -225,7 +341,24 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if owner can read from this file; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        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;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets or sets a value indicating whether the owner can write into this file.
 | 
	
	
		
			
				|  | @@ -233,7 +366,24 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if owner can write into this file; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        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;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets or sets a value indicating whether the owner can execute this file.
 | 
	
	
		
			
				|  | @@ -241,7 +391,24 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if owner can execute this file; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        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;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets or sets a value indicating whether the group members can read from this file.
 | 
	
	
		
			
				|  | @@ -249,7 +416,24 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if group members can read from this file; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        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;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets or sets a value indicating whether the group members can write into this file.
 | 
	
	
		
			
				|  | @@ -257,7 +441,24 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if group members can write into this file; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        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;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets or sets a value indicating whether the group members can execute this file.
 | 
	
	
		
			
				|  | @@ -265,7 +466,24 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if group members can execute this file; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        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;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets or sets a value indicating whether the others can read from this file.
 | 
	
	
		
			
				|  | @@ -273,7 +491,24 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if others can read from this file; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        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;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets or sets a value indicating whether the others can write into this file.
 | 
	
	
		
			
				|  | @@ -281,7 +516,24 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if others can write into this file; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        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;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
		
			
				|  |  |          /// Gets or sets a value indicating whether the others can execute this file.
 | 
	
	
		
			
				|  | @@ -289,209 +541,159 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |          /// <value>
 | 
	
		
			
				|  |  |          /// <see langword="true"/> if others can execute this file; otherwise, <see langword="false"/>.
 | 
	
		
			
				|  |  |          /// </value>
 | 
	
		
			
				|  |  | -        public bool OthersCanExecute { get; set; }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        /// <summary>
 | 
	
		
			
				|  |  | -        /// Gets the extensions.
 | 
	
		
			
				|  |  | -        /// </summary>
 | 
	
		
			
				|  |  | -        /// <value>
 | 
	
		
			
				|  |  | -        /// The extensions.
 | 
	
		
			
				|  |  | -        /// </value>
 | 
	
		
			
				|  |  | -        public IDictionary<string, string> 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;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// Gets the extensions.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        /// <value>
 | 
	
		
			
				|  |  | +        /// The extensions.
 | 
	
		
			
				|  |  | +        /// </value>
 | 
	
		
			
				|  |  | +        public IDictionary<string, string>? 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<string, string>? 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;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// Sets the POSIX permissions for this file.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        /// <param name="mode">
 | 
	
		
			
				|  |  | +        /// The permission mode as an octal number (e.g., <c>755</c>, <c>644</c>, <c>1777</c>).
 | 
	
		
			
				|  |  | +        /// </param>
 | 
	
		
			
				|  |  | +        /// <exception cref="ArgumentOutOfRangeException">
 | 
	
		
			
				|  |  | +        /// <paramref name="mode"/> has more than 4 digits or cannot be interpreted as an octal number.
 | 
	
		
			
				|  |  | +        /// </exception>
 | 
	
		
			
				|  |  | +        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;
 | 
	
		
			
				|  |  | -                }
 | 
	
		
			
				|  |  | +        /// <inheritdoc/>
 | 
	
		
			
				|  |  | +        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<string, string> extensions)
 | 
	
		
			
				|  |  | -        {
 | 
	
		
			
				|  |  | -            LastAccessTimeUtc = _originalLastAccessTimeUtc = lastAccessTimeUtc;
 | 
	
		
			
				|  |  | -            LastWriteTimeUtc = _originalLastWriteTimeUtc = lastWriteTimeUtc;
 | 
	
		
			
				|  |  | -            Size = _originalSize = size;
 | 
	
		
			
				|  |  | -            UserId = _originalUserId = userId;
 | 
	
		
			
				|  |  | -            GroupId = _originalGroupId = groupId;
 | 
	
		
			
				|  |  | -            Permissions = _originalPermissions = permissions;
 | 
	
		
			
				|  |  | -            Extensions = _originalExtensions = extensions;
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -        /// <summary>
 | 
	
		
			
				|  |  | -        /// Sets the permissions.
 | 
	
		
			
				|  |  | -        /// </summary>
 | 
	
		
			
				|  |  | -        /// <param name="mode">The mode.</param>
 | 
	
		
			
				|  |  | -        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' : '-');
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          /// <summary>
 | 
	
	
		
			
				|  | @@ -506,7 +708,7 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  uint flag = 0;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                if (IsSizeChanged && IsRegularFile)
 | 
	
		
			
				|  |  | +                if (IsSizeChanged)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      flag |= 0x00000001;
 | 
	
		
			
				|  |  |                  }
 | 
	
	
		
			
				|  | @@ -533,7 +735,7 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  stream.Write(flag);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -                if (IsSizeChanged && IsRegularFile)
 | 
	
		
			
				|  |  | +                if (IsSizeChanged)
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      stream.Write((ulong)Size);
 | 
	
		
			
				|  |  |                  }
 | 
	
	
		
			
				|  | @@ -551,9 +753,9 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  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 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |                  {
 | 
	
		
			
				|  |  |                      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 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        internal static readonly SftpFileAttributes Empty = new SftpFileAttributes();
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          internal static SftpFileAttributes FromBytes(SshDataStream stream)
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              const uint SSH_FILEXFER_ATTR_SIZE = 0x00000001;
 | 
	
	
		
			
				|  | @@ -592,7 +788,7 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |              uint permissions = 0;
 | 
	
		
			
				|  |  |              DateTime accessTime;
 | 
	
		
			
				|  |  |              DateTime modifyTime;
 | 
	
		
			
				|  |  | -            Dictionary<string, string> extensions = null;
 | 
	
		
			
				|  |  | +            Dictionary<string, string>? extensions = null;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              if ((flag & SSH_FILEXFER_ATTR_SIZE) == SSH_FILEXFER_ATTR_SIZE)
 | 
	
		
			
				|  |  |              {
 | 
	
	
		
			
				|  | @@ -613,12 +809,8 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              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 @@ namespace Renci.SshNet.Sftp
 | 
	
		
			
				|  |  |                  extensions = new Dictionary<string, string>(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);
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 |