using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Renci.SshNet.Common; namespace Renci.SshNet.Sftp { /// /// Contains SFTP file attributes. /// public class SftpFileAttributes { #pragma warning disable IDE1006 // Naming Styles private const uint S_IFMT = 0xF000; // bitmask for the file type bitfields private const uint S_IFSOCK = 0xC000; // socket private const uint S_IFLNK = 0xA000; // symbolic link private const uint S_IFREG = 0x8000; // regular file private const uint S_IFBLK = 0x6000; // block device private const uint S_IFDIR = 0x4000; // directory 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_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 private const uint S_IRGRP = 0x0020; // group has read permission private const uint S_IWGRP = 0x0010; // group has write permission private const uint S_IXGRP = 0x0008; // group has execute permission private const uint S_IROTH = 0x0004; // others have read permission private const uint S_IWOTH = 0x0002; // others have write permission private const uint S_IXOTH = 0x0001; // others have execute permission #pragma warning restore IDE1006 // Naming Styles private readonly DateTime _originalLastAccessTimeUtc; private readonly DateTime _originalLastWriteTimeUtc; private readonly long _originalSize; 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; internal bool IsLastAccessTimeChanged { get { return _originalLastAccessTimeUtc != LastAccessTimeUtc; } } internal bool IsLastWriteTimeChanged { get { return _originalLastWriteTimeUtc != LastWriteTimeUtc; } } internal bool IsSizeChanged { get { return _originalSize != Size; } } internal bool IsUserIdChanged { get { return _originalUserId != UserId; } } internal bool IsGroupIdChanged { get { return _originalGroupId != GroupId; } } internal bool IsPermissionsChanged { get { return _originalPermissions != Permissions; } } internal bool IsExtensionsChanged { get { return _originalExtensions != null && Extensions != null && !_originalExtensions.SequenceEqual(Extensions); } } /// /// Gets or sets the local time the current file or directory was last accessed. /// /// /// The local time that the current file or directory was last accessed. /// public DateTime LastAccessTime { get { return ToLocalTime(LastAccessTimeUtc); } set { LastAccessTimeUtc = ToUniversalTime(value); } } /// /// Gets or sets the local time when the current file or directory was last written to. /// /// /// The local time the current file was last written. /// public DateTime LastWriteTime { get { return ToLocalTime(LastWriteTimeUtc); } set { LastWriteTimeUtc = ToUniversalTime(value); } } /// /// Gets or sets the UTC time the current file or directory was last accessed. /// /// /// The UTC time that the current file or directory was last accessed. /// public DateTime LastAccessTimeUtc { get; set; } /// /// Gets or sets the UTC time when the current file or directory was last written to. /// /// /// The UTC time the current file was last written. /// public DateTime LastWriteTimeUtc { get; set; } /// /// Gets or sets the size, in bytes, of the current file. /// /// /// The size of the current file in bytes. /// public long Size { get; set; } /// /// Gets or sets file user id. /// /// /// File user id. /// public int UserId { get; set; } /// /// Gets or sets file group id. /// /// /// File group id. /// public int GroupId { get; set; } /// /// Gets a value indicating whether file represents a socket. /// /// /// true if file represents a socket; otherwise, false. /// public bool IsSocket { get; private set; } /// /// Gets a value indicating whether file represents a symbolic link. /// /// /// true if file represents a symbolic link; otherwise, false. /// public bool IsSymbolicLink { get; private set; } /// /// Gets a value indicating whether file represents a regular file. /// /// /// true if file represents a regular file; otherwise, false. /// public bool IsRegularFile { get; private set; } /// /// Gets a value indicating whether file represents a block device. /// /// /// true if file represents a block device; otherwise, false. /// public bool IsBlockDevice { get; private set; } /// /// Gets a value indicating whether file represents a directory. /// /// /// true if file represents a directory; otherwise, false. /// public bool IsDirectory { get; private set; } /// /// Gets a value indicating whether file represents a character device. /// /// /// true if file represents a character device; otherwise, false. /// public bool IsCharacterDevice { get; private set; } /// /// Gets a value indicating whether file represents a named pipe. /// /// /// true if file represents a named pipe; otherwise, false. /// public bool IsNamedPipe { get; private set; } /// /// Gets or sets a value indicating whether the owner can read from this file. /// /// /// true if owner can read from this file; otherwise, false. /// public bool OwnerCanRead { get; set; } /// /// Gets or sets a value indicating whether the owner can write into this file. /// /// /// true if owner can write into this file; otherwise, false. /// public bool OwnerCanWrite { get; set; } /// /// Gets or sets a value indicating whether the owner can execute this file. /// /// /// true if owner can execute this file; otherwise, false. /// public bool OwnerCanExecute { get; set; } /// /// Gets or sets a value indicating whether the group members can read from this file. /// /// /// true if group members can read from this file; otherwise, false. /// public bool GroupCanRead { get; set; } /// /// Gets or sets a value indicating whether the group members can write into this file. /// /// /// true if group members can write into this file; otherwise, false. /// public bool GroupCanWrite { get; set; } /// /// Gets or sets a value indicating whether the group members can execute this file. /// /// /// true if group members can execute this file; otherwise, false. /// public bool GroupCanExecute { get; set; } /// /// Gets or sets a value indicating whether the others can read from this file. /// /// /// true if others can read from this file; otherwise, false. /// public bool OthersCanRead { get; set; } /// /// Gets or sets a value indicating whether the others can write into this file. /// /// /// true if others can write into this file; otherwise, false. /// public bool OthersCanWrite { get; set; } /// /// Gets or sets a value indicating whether the others can execute this file. /// /// /// true if others can execute this file; otherwise, false. /// public bool OthersCanExecute { get; set; } /// /// Gets the extensions. /// /// /// The extensions. /// public IDictionary Extensions { get; private set; } internal uint Permissions { get { uint permission = 0; if (_isBitFiledsBitSet) { permission |= S_IFMT; } if (IsSocket) { permission |= S_IFSOCK; } if (IsSymbolicLink) { permission |= S_IFLNK; } if (IsRegularFile) { permission |= S_IFREG; } if (IsBlockDevice) { permission |= S_IFBLK; } if (IsDirectory) { permission |= S_IFDIR; } if (IsCharacterDevice) { permission |= S_IFCHR; } if (IsNamedPipe) { permission |= S_IFIFO; } if (_isUIDBitSet) { permission |= S_ISUID; } if (_isGroupIDBitSet) { permission |= S_ISGID; } if (_isStickyBitSet) { permission |= S_ISVTX; } if (OwnerCanRead) { permission |= S_IRUSR; } if (OwnerCanWrite) { permission |= S_IWUSR; } if (OwnerCanExecute) { permission |= S_IXUSR; } if (GroupCanRead) { permission |= S_IRGRP; } if (GroupCanWrite) { permission |= S_IWGRP; } if (GroupCanExecute) { permission |= S_IXGRP; } if (OthersCanRead) { permission |= S_IROTH; } if (OthersCanWrite) { permission |= S_IWOTH; } if (OthersCanExecute) { permission |= S_IXOTH; } return permission; } 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; } } private SftpFileAttributes() { } 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) { throw new ArgumentOutOfRangeException(nameof(mode)); } var modeBytes = mode.ToString(CultureInfo.InvariantCulture).PadLeft(3, '0').ToCharArray(); var permission = (modeBytes[0] & 0x0F) * 8 * 8 + (modeBytes[1] & 0x0F) * 8 + (modeBytes[2] & 0x0F); OwnerCanRead = (permission & S_IRUSR) == S_IRUSR; OwnerCanWrite = (permission & S_IWUSR) == S_IWUSR; OwnerCanExecute = (permission & S_IXUSR) == S_IXUSR; GroupCanRead = (permission & S_IRGRP) == S_IRGRP; GroupCanWrite = (permission & S_IWGRP) == S_IWGRP; GroupCanExecute = (permission & S_IXGRP) == S_IXGRP; OthersCanRead = (permission & S_IROTH) == S_IROTH; OthersCanWrite = (permission & S_IWOTH) == S_IWOTH; OthersCanExecute = (permission & S_IXOTH) == S_IXOTH; } /// /// Returns a byte array representing the current . /// /// /// A byte array representing the current . /// public byte[] GetBytes() { var stream = new SshDataStream(4); uint flag = 0; if (IsSizeChanged && IsRegularFile) { flag |= 0x00000001; } if (IsUserIdChanged || IsGroupIdChanged) { flag |= 0x00000002; } if (IsPermissionsChanged) { flag |= 0x00000004; } if (IsLastAccessTimeChanged || IsLastWriteTimeChanged) { flag |= 0x00000008; } if (IsExtensionsChanged) { flag |= 0x80000000; } stream.Write(flag); if (IsSizeChanged && IsRegularFile) { stream.Write((ulong) Size); } if (IsUserIdChanged || IsGroupIdChanged) { stream.Write((uint) UserId); stream.Write((uint) GroupId); } if (IsPermissionsChanged) { stream.Write(Permissions); } if (IsLastAccessTimeChanged || IsLastWriteTimeChanged) { var time = (uint)(LastAccessTimeUtc.ToFileTimeUtc() / 10000000 - 11644473600); stream.Write(time); time = (uint)(LastWriteTimeUtc.ToFileTimeUtc() / 10000000 - 11644473600); stream.Write(time); } if (IsExtensionsChanged) { 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); } } return stream.ToArray(); } internal static readonly SftpFileAttributes Empty = new SftpFileAttributes(); internal static SftpFileAttributes FromBytes(SshDataStream stream) { var flag = stream.ReadUInt32(); long size = -1; var userId = -1; var groupId = -1; uint permissions = 0; DateTime accessTime; DateTime modifyTime; Dictionary extensions = null; if ((flag & 0x00000001) == 0x00000001) // SSH_FILEXFER_ATTR_SIZE { size = (long) stream.ReadUInt64(); } if ((flag & 0x00000002) == 0x00000002) // SSH_FILEXFER_ATTR_UIDGID { userId = (int) stream.ReadUInt32(); groupId = (int) stream.ReadUInt32(); } if ((flag & 0x00000004) == 0x00000004) // SSH_FILEXFER_ATTR_PERMISSIONS { permissions = stream.ReadUInt32(); } if ((flag & 0x00000008) == 0x00000008) // 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); } else { accessTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); modifyTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); } if ((flag & 0x80000000) == 0x80000000) // SSH_FILEXFER_ATTR_EXTENDED { var extendedCount = (int) stream.ReadUInt32(); extensions = new Dictionary(extendedCount); for (var i = 0; i < extendedCount; i++) { var extensionName = stream.ReadString(SshData.Utf8); var extensionData = stream.ReadString(SshData.Utf8); extensions.Add(extensionName, extensionData); } } return new SftpFileAttributes(accessTime, modifyTime, size, userId, groupId, permissions, extensions); } internal static SftpFileAttributes FromBytes(byte[] buffer) { using (var stream = new SshDataStream(buffer)) { return FromBytes(stream); } } private static DateTime ToLocalTime(DateTime value) { DateTime result; if (value == DateTime.MinValue) { result = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Local); } else { result = value.ToLocalTime(); } return result; } private static DateTime ToUniversalTime(DateTime value) { DateTime result; if (value == DateTime.MinValue) { result = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); } else { result = value.ToUniversalTime(); } return result; } } }