SftpFileAttributes.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Globalization;
  6. using System.Text;
  7. using Renci.SshNet.Common;
  8. namespace Renci.SshNet.Sftp
  9. {
  10. /// <summary>
  11. /// Contains SFTP file attributes.
  12. /// </summary>
  13. public class SftpFileAttributes
  14. {
  15. #region Bitmask constats
  16. private const UInt32 S_IFMT = 0xF000; // bitmask for the file type bitfields
  17. private const UInt32 S_IFSOCK = 0xC000; // socket
  18. private const UInt32 S_IFLNK = 0xA000; // symbolic link
  19. private const UInt32 S_IFREG = 0x8000; // regular file
  20. private const UInt32 S_IFBLK = 0x6000; // block device
  21. private const UInt32 S_IFDIR = 0x4000; // directory
  22. private const UInt32 S_IFCHR = 0x2000; // character device
  23. private const UInt32 S_IFIFO = 0x1000; // FIFO
  24. private const UInt32 S_ISUID = 0x0800; // set UID bit
  25. private const UInt32 S_ISGID = 0x0400; // set-group-ID bit (see below)
  26. private const UInt32 S_ISVTX = 0x0200; // sticky bit (see below)
  27. private const UInt32 S_IRUSR = 0x0100; // owner has read permission
  28. private const UInt32 S_IWUSR = 0x0080; // owner has write permission
  29. private const UInt32 S_IXUSR = 0x0040; // owner has execute permission
  30. private const UInt32 S_IRGRP = 0x0020; // group has read permission
  31. private const UInt32 S_IWGRP = 0x0010; // group has write permission
  32. private const UInt32 S_IXGRP = 0x0008; // group has execute permission
  33. private const UInt32 S_IROTH = 0x0004; // others have read permission
  34. private const UInt32 S_IWOTH = 0x0002; // others have write permission
  35. private const UInt32 S_IXOTH = 0x0001; // others have execute permission
  36. #endregion
  37. private bool _isBitFiledsBitSet;
  38. private bool _isUIDBitSet;
  39. private bool _isGroupIDBitSet;
  40. private bool _isStickyBitSet;
  41. private readonly DateTime _originalLastAccessTime;
  42. private readonly DateTime _originalLastWriteTime;
  43. private readonly long _originalSize;
  44. private readonly int _originalUserId;
  45. private readonly int _originalGroupId;
  46. private readonly uint _originalPermissions;
  47. private readonly IDictionary<string, string> _originalExtensions;
  48. internal bool IsLastAccessTimeChanged
  49. {
  50. get { return _originalLastAccessTime != LastAccessTime; }
  51. }
  52. internal bool IsLastWriteTimeChanged
  53. {
  54. get { return _originalLastWriteTime != LastWriteTime; }
  55. }
  56. internal bool IsSizeChanged
  57. {
  58. get { return _originalSize != Size; }
  59. }
  60. internal bool IsUserIdChanged
  61. {
  62. get { return _originalUserId != UserId; }
  63. }
  64. internal bool IsGroupIdChanged
  65. {
  66. get { return _originalGroupId != GroupId; }
  67. }
  68. internal bool IsPermissionsChanged
  69. {
  70. get { return _originalPermissions != Permissions; }
  71. }
  72. internal bool IsExtensionsChanged
  73. {
  74. get { return _originalExtensions != null && Extensions != null && !_originalExtensions.SequenceEqual(Extensions); }
  75. }
  76. /// <summary>
  77. /// Gets or sets the time the current file or directory was last accessed.
  78. /// </summary>
  79. /// <value>
  80. /// The time that the current file or directory was last accessed.
  81. /// </value>
  82. public DateTime LastAccessTime { get; set; }
  83. /// <summary>
  84. /// Gets or sets the time when the current file or directory was last written to.
  85. /// </summary>
  86. /// <value>
  87. /// The time the current file was last written.
  88. /// </value>
  89. public DateTime LastWriteTime { get; set; }
  90. /// <summary>
  91. /// Gets or sets the size, in bytes, of the current file.
  92. /// </summary>
  93. /// <value>
  94. /// The size of the current file in bytes.
  95. /// </value>
  96. public long Size { get; set; }
  97. /// <summary>
  98. /// Gets or sets file user id.
  99. /// </summary>
  100. /// <value>
  101. /// File user id.
  102. /// </value>
  103. public int UserId { get; set; }
  104. /// <summary>
  105. /// Gets or sets file group id.
  106. /// </summary>
  107. /// <value>
  108. /// File group id.
  109. /// </value>
  110. public int GroupId { get; set; }
  111. /// <summary>
  112. /// Gets a value indicating whether file represents a socket.
  113. /// </summary>
  114. /// <value>
  115. /// <c>true</c> if file represents a socket; otherwise, <c>false</c>.
  116. /// </value>
  117. public bool IsSocket { get; private set; }
  118. /// <summary>
  119. /// Gets a value indicating whether file represents a symbolic link.
  120. /// </summary>
  121. /// <value>
  122. /// <c>true</c> if file represents a symbolic link; otherwise, <c>false</c>.
  123. /// </value>
  124. public bool IsSymbolicLink { get; private set; }
  125. /// <summary>
  126. /// Gets a value indicating whether file represents a regular file.
  127. /// </summary>
  128. /// <value>
  129. /// <c>true</c> if file represents a regular file; otherwise, <c>false</c>.
  130. /// </value>
  131. public bool IsRegularFile { get; private set; }
  132. /// <summary>
  133. /// Gets a value indicating whether file represents a block device.
  134. /// </summary>
  135. /// <value>
  136. /// <c>true</c> if file represents a block device; otherwise, <c>false</c>.
  137. /// </value>
  138. public bool IsBlockDevice { get; private set; }
  139. /// <summary>
  140. /// Gets a value indicating whether file represents a directory.
  141. /// </summary>
  142. /// <value>
  143. /// <c>true</c> if file represents a directory; otherwise, <c>false</c>.
  144. /// </value>
  145. public bool IsDirectory { get; private set; }
  146. /// <summary>
  147. /// Gets a value indicating whether file represents a character device.
  148. /// </summary>
  149. /// <value>
  150. /// <c>true</c> if file represents a character device; otherwise, <c>false</c>.
  151. /// </value>
  152. public bool IsCharacterDevice { get; private set; }
  153. /// <summary>
  154. /// Gets a value indicating whether file represents a named pipe.
  155. /// </summary>
  156. /// <value>
  157. /// <c>true</c> if file represents a named pipe; otherwise, <c>false</c>.
  158. /// </value>
  159. public bool IsNamedPipe { get; private set; }
  160. /// <summary>
  161. /// Gets a value indicating whether the owner can read from this file.
  162. /// </summary>
  163. /// <value>
  164. /// <c>true</c> if owner can read from this file; otherwise, <c>false</c>.
  165. /// </value>
  166. public bool OwnerCanRead { get; set; }
  167. /// <summary>
  168. /// Gets a value indicating whether the owner can write into this file.
  169. /// </summary>
  170. /// <value>
  171. /// <c>true</c> if owner can write into this file; otherwise, <c>false</c>.
  172. /// </value>
  173. public bool OwnerCanWrite { get; set; }
  174. /// <summary>
  175. /// Gets a value indicating whether the owner can execute this file.
  176. /// </summary>
  177. /// <value>
  178. /// <c>true</c> if owner can execute this file; otherwise, <c>false</c>.
  179. /// </value>
  180. public bool OwnerCanExecute { get; set; }
  181. /// <summary>
  182. /// Gets a value indicating whether the group members can read from this file.
  183. /// </summary>
  184. /// <value>
  185. /// <c>true</c> if group members can read from this file; otherwise, <c>false</c>.
  186. /// </value>
  187. public bool GroupCanRead { get; set; }
  188. /// <summary>
  189. /// Gets a value indicating whether the group members can write into this file.
  190. /// </summary>
  191. /// <value>
  192. /// <c>true</c> if group members can write into this file; otherwise, <c>false</c>.
  193. /// </value>
  194. public bool GroupCanWrite { get; set; }
  195. /// <summary>
  196. /// Gets a value indicating whether the group members can execute this file.
  197. /// </summary>
  198. /// <value>
  199. /// <c>true</c> if group members can execute this file; otherwise, <c>false</c>.
  200. /// </value>
  201. public bool GroupCanExecute { get; set; }
  202. /// <summary>
  203. /// Gets a value indicating whether the others can read from this file.
  204. /// </summary>
  205. /// <value>
  206. /// <c>true</c> if others can read from this file; otherwise, <c>false</c>.
  207. /// </value>
  208. public bool OthersCanRead { get; set; }
  209. /// <summary>
  210. /// Gets a value indicating whether the others can write into this file.
  211. /// </summary>
  212. /// <value>
  213. /// <c>true</c> if others can write into this file; otherwise, <c>false</c>.
  214. /// </value>
  215. public bool OthersCanWrite { get; set; }
  216. /// <summary>
  217. /// Gets a value indicating whether the others can execute this file.
  218. /// </summary>
  219. /// <value>
  220. /// <c>true</c> if others can execute this file; otherwise, <c>false</c>.
  221. /// </value>
  222. public bool OthersCanExecute { get; set; }
  223. /// <summary>
  224. /// Gets or sets the extensions.
  225. /// </summary>
  226. /// <value>
  227. /// The extensions.
  228. /// </value>
  229. public IDictionary<string, string> Extensions { get; private set; }
  230. internal uint Permissions
  231. {
  232. get
  233. {
  234. uint permission = 0;
  235. if (_isBitFiledsBitSet)
  236. permission = permission | S_IFMT;
  237. if (IsSocket)
  238. permission = permission | S_IFSOCK;
  239. if (IsSymbolicLink)
  240. permission = permission | S_IFLNK;
  241. if (IsRegularFile)
  242. permission = permission | S_IFREG;
  243. if (IsBlockDevice)
  244. permission = permission | S_IFBLK;
  245. if (IsDirectory)
  246. permission = permission | S_IFDIR;
  247. if (IsCharacterDevice)
  248. permission = permission | S_IFCHR;
  249. if (IsNamedPipe)
  250. permission = permission | S_IFIFO;
  251. if (_isUIDBitSet)
  252. permission = permission | S_ISUID;
  253. if (_isGroupIDBitSet)
  254. permission = permission | S_ISGID;
  255. if (_isStickyBitSet)
  256. permission = permission | S_ISVTX;
  257. if (OwnerCanRead)
  258. permission = permission | S_IRUSR;
  259. if (OwnerCanWrite)
  260. permission = permission | S_IWUSR;
  261. if (OwnerCanExecute)
  262. permission = permission | S_IXUSR;
  263. if (GroupCanRead)
  264. permission = permission | S_IRGRP;
  265. if (GroupCanWrite)
  266. permission = permission | S_IWGRP;
  267. if (GroupCanExecute)
  268. permission = permission | S_IXGRP;
  269. if (OthersCanRead)
  270. permission = permission | S_IROTH;
  271. if (OthersCanWrite)
  272. permission = permission | S_IWOTH;
  273. if (OthersCanExecute)
  274. permission = permission | S_IXOTH;
  275. return permission;
  276. }
  277. private set
  278. {
  279. _isBitFiledsBitSet = ((value & S_IFMT) == S_IFMT);
  280. IsSocket = ((value & S_IFSOCK) == S_IFSOCK);
  281. IsSymbolicLink = ((value & S_IFLNK) == S_IFLNK);
  282. IsRegularFile = ((value & S_IFREG) == S_IFREG);
  283. IsBlockDevice = ((value & S_IFBLK) == S_IFBLK);
  284. IsDirectory = ((value & S_IFDIR) == S_IFDIR);
  285. IsCharacterDevice = ((value & S_IFCHR) == S_IFCHR);
  286. IsNamedPipe = ((value & S_IFIFO) == S_IFIFO);
  287. _isUIDBitSet = ((value & S_ISUID) == S_ISUID);
  288. _isGroupIDBitSet = ((value & S_ISGID) == S_ISGID);
  289. _isStickyBitSet = ((value & S_ISVTX) == S_ISVTX);
  290. OwnerCanRead = ((value & S_IRUSR) == S_IRUSR);
  291. OwnerCanWrite = ((value & S_IWUSR) == S_IWUSR);
  292. OwnerCanExecute = ((value & S_IXUSR) == S_IXUSR);
  293. GroupCanRead = ((value & S_IRGRP) == S_IRGRP);
  294. GroupCanWrite = ((value & S_IWGRP) == S_IWGRP);
  295. GroupCanExecute = ((value & S_IXGRP) == S_IXGRP);
  296. OthersCanRead = ((value & S_IROTH) == S_IROTH);
  297. OthersCanWrite = ((value & S_IWOTH) == S_IWOTH);
  298. OthersCanExecute = ((value & S_IXOTH) == S_IXOTH);
  299. }
  300. }
  301. private SftpFileAttributes()
  302. {
  303. }
  304. internal SftpFileAttributes(DateTime lastAccessTime, DateTime lastWriteTime, long size, int userId, int groupId, uint permissions, IDictionary<string, string> extensions)
  305. {
  306. LastAccessTime = _originalLastAccessTime = lastAccessTime;
  307. LastWriteTime = _originalLastWriteTime = lastWriteTime;
  308. Size = _originalSize = size;
  309. UserId = _originalUserId = userId;
  310. GroupId = _originalGroupId = groupId;
  311. Permissions = _originalPermissions = permissions;
  312. Extensions = _originalExtensions = extensions;
  313. }
  314. /// <summary>
  315. /// Sets the permissions.
  316. /// </summary>
  317. /// <param name="mode">The mode.</param>
  318. public void SetPermissions(short mode)
  319. {
  320. if (mode < 0 || mode > 999)
  321. {
  322. throw new ArgumentOutOfRangeException("mode");
  323. }
  324. var modeBytes = mode.ToString(CultureInfo.InvariantCulture).PadLeft(3, '0').ToArray();
  325. var permission = (modeBytes[0] & 0x0F) * 8 * 8 + (modeBytes[1] & 0x0F) * 8 + (modeBytes[2] & 0x0F);
  326. OwnerCanRead = (permission & S_IRUSR) == S_IRUSR;
  327. OwnerCanWrite = (permission & S_IWUSR) == S_IWUSR;
  328. OwnerCanExecute = (permission & S_IXUSR) == S_IXUSR;
  329. GroupCanRead = (permission & S_IRGRP) == S_IRGRP;
  330. GroupCanWrite = (permission & S_IWGRP) == S_IWGRP;
  331. GroupCanExecute = (permission & S_IXGRP) == S_IXGRP;
  332. OthersCanRead = (permission & S_IROTH) == S_IROTH;
  333. OthersCanWrite = (permission & S_IWOTH) == S_IWOTH;
  334. OthersCanExecute = (permission & S_IXOTH) == S_IXOTH;
  335. }
  336. public byte[] GetBytes()
  337. {
  338. var stream = new SshDataStream(4);
  339. uint flag = 0;
  340. if (IsSizeChanged && IsRegularFile)
  341. {
  342. flag |= 0x00000001;
  343. }
  344. if (IsUserIdChanged || IsGroupIdChanged)
  345. {
  346. flag |= 0x00000002;
  347. }
  348. if (IsPermissionsChanged)
  349. {
  350. flag |= 0x00000004;
  351. }
  352. if (IsLastAccessTimeChanged || IsLastWriteTimeChanged)
  353. {
  354. flag |= 0x00000008;
  355. }
  356. if (IsExtensionsChanged)
  357. {
  358. flag |= 0x80000000;
  359. }
  360. stream.Write(flag);
  361. if (IsSizeChanged && IsRegularFile)
  362. {
  363. stream.Write((ulong) Size);
  364. }
  365. if (IsUserIdChanged || IsGroupIdChanged)
  366. {
  367. stream.Write((uint) UserId);
  368. stream.Write((uint) GroupId);
  369. }
  370. if (IsPermissionsChanged)
  371. {
  372. stream.Write(Permissions);
  373. }
  374. if (IsLastAccessTimeChanged || IsLastWriteTimeChanged)
  375. {
  376. var time = (uint)(LastAccessTime.ToFileTime() / 10000000 - 11644473600);
  377. stream.Write(time);
  378. time = (uint)(LastWriteTime.ToFileTime() / 10000000 - 11644473600);
  379. stream.Write(time);
  380. }
  381. if (IsExtensionsChanged)
  382. {
  383. foreach (var item in Extensions)
  384. {
  385. // TODO: we write as ASCII but read as UTF8 !!!
  386. stream.Write(item.Key, SshData.Ascii);
  387. stream.Write(item.Value, SshData.Ascii);
  388. }
  389. }
  390. return stream.ToArray();
  391. }
  392. internal static readonly SftpFileAttributes Empty = new SftpFileAttributes();
  393. internal static SftpFileAttributes FromBytes(SshDataStream stream)
  394. {
  395. var flag = stream.ReadUInt32();
  396. long size = -1;
  397. var userId = -1;
  398. var groupId = -1;
  399. uint permissions = 0;
  400. var accessTime = DateTime.MinValue;
  401. var modifyTime = DateTime.MinValue;
  402. IDictionary<string, string> extensions = null;
  403. if ((flag & 0x00000001) == 0x00000001) // SSH_FILEXFER_ATTR_SIZE
  404. {
  405. size = (long) stream.ReadUInt64();
  406. }
  407. if ((flag & 0x00000002) == 0x00000002) // SSH_FILEXFER_ATTR_UIDGID
  408. {
  409. userId = (int) stream.ReadUInt32();
  410. groupId = (int) stream.ReadUInt32();
  411. }
  412. if ((flag & 0x00000004) == 0x00000004) // SSH_FILEXFER_ATTR_PERMISSIONS
  413. {
  414. permissions = stream.ReadUInt32();
  415. }
  416. if ((flag & 0x00000008) == 0x00000008) // SSH_FILEXFER_ATTR_ACMODTIME
  417. {
  418. var time = stream.ReadUInt32();
  419. accessTime = DateTime.FromFileTime((time + 11644473600) * 10000000);
  420. time = stream.ReadUInt32();
  421. modifyTime = DateTime.FromFileTime((time + 11644473600) * 10000000);
  422. }
  423. if ((flag & 0x80000000) == 0x80000000) // SSH_FILEXFER_ATTR_EXTENDED
  424. {
  425. var extendedCount = (int) stream.ReadUInt32();
  426. extensions = new Dictionary<string, string>(extendedCount);
  427. for (var i = 0; i < extendedCount; i++)
  428. {
  429. var extensionName = stream.ReadString(SshData.Utf8);
  430. var extensionData = stream.ReadString(SshData.Utf8);
  431. extensions.Add(extensionName, extensionData);
  432. }
  433. }
  434. return new SftpFileAttributes(accessTime, modifyTime, size, userId, groupId, permissions, extensions);
  435. }
  436. internal static SftpFileAttributes FromBytes(byte[] buffer)
  437. {
  438. using (var stream = new SshDataStream(buffer))
  439. {
  440. return FromBytes(stream);
  441. }
  442. }
  443. }
  444. }