2
0

SftpFileAttributes.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using Renci.SshNet.Common;
  6. namespace Renci.SshNet.Sftp
  7. {
  8. /// <summary>
  9. /// Contains SFTP file attributes.
  10. /// </summary>
  11. public class SftpFileAttributes
  12. {
  13. #pragma warning disable IDE1006 // Naming Styles
  14. #pragma warning disable SA1310 // Field names should not contain underscore
  15. private const uint S_IFMT = 0xF000; // bitmask for the file type bitfields
  16. private const uint S_IFSOCK = 0xC000; // socket
  17. private const uint S_IFLNK = 0xA000; // symbolic link
  18. private const uint S_IFREG = 0x8000; // regular file
  19. private const uint S_IFBLK = 0x6000; // block device
  20. private const uint S_IFDIR = 0x4000; // directory
  21. private const uint S_IFCHR = 0x2000; // character device
  22. private const uint S_IFIFO = 0x1000; // FIFO
  23. private const uint S_ISUID = 0x0800; // set UID bit
  24. private const uint S_ISGID = 0x0400; // set-group-ID bit (see below)
  25. private const uint S_ISVTX = 0x0200; // sticky bit (see below)
  26. private const uint S_IRUSR = 0x0100; // owner has read permission
  27. private const uint S_IWUSR = 0x0080; // owner has write permission
  28. private const uint S_IXUSR = 0x0040; // owner has execute permission
  29. private const uint S_IRGRP = 0x0020; // group has read permission
  30. private const uint S_IWGRP = 0x0010; // group has write permission
  31. private const uint S_IXGRP = 0x0008; // group has execute permission
  32. private const uint S_IROTH = 0x0004; // others have read permission
  33. private const uint S_IWOTH = 0x0002; // others have write permission
  34. private const uint S_IXOTH = 0x0001; // others have execute permission
  35. #pragma warning restore SA1310 // Field names should not contain underscore
  36. #pragma warning restore IDE1006 // Naming Styles
  37. private readonly DateTime _originalLastAccessTimeUtc;
  38. private readonly DateTime _originalLastWriteTimeUtc;
  39. private readonly long _originalSize;
  40. private readonly int _originalUserId;
  41. private readonly int _originalGroupId;
  42. private readonly uint _originalPermissions;
  43. private readonly IDictionary<string, string> _originalExtensions;
  44. private bool _isBitFiledsBitSet;
  45. private bool _isUIDBitSet;
  46. private bool _isGroupIDBitSet;
  47. private bool _isStickyBitSet;
  48. internal bool IsLastAccessTimeChanged
  49. {
  50. get { return _originalLastAccessTimeUtc != LastAccessTimeUtc; }
  51. }
  52. internal bool IsLastWriteTimeChanged
  53. {
  54. get { return _originalLastWriteTimeUtc != LastWriteTimeUtc; }
  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 local time the current file or directory was last accessed.
  78. /// </summary>
  79. /// <value>
  80. /// The local time that the current file or directory was last accessed.
  81. /// </value>
  82. public DateTime LastAccessTime
  83. {
  84. get
  85. {
  86. return ToLocalTime(LastAccessTimeUtc);
  87. }
  88. set
  89. {
  90. LastAccessTimeUtc = ToUniversalTime(value);
  91. }
  92. }
  93. /// <summary>
  94. /// Gets or sets the local time when the current file or directory was last written to.
  95. /// </summary>
  96. /// <value>
  97. /// The local time the current file was last written.
  98. /// </value>
  99. public DateTime LastWriteTime
  100. {
  101. get
  102. {
  103. return ToLocalTime(LastWriteTimeUtc);
  104. }
  105. set
  106. {
  107. LastWriteTimeUtc = ToUniversalTime(value);
  108. }
  109. }
  110. /// <summary>
  111. /// Gets or sets the UTC time the current file or directory was last accessed.
  112. /// </summary>
  113. /// <value>
  114. /// The UTC time that the current file or directory was last accessed.
  115. /// </value>
  116. public DateTime LastAccessTimeUtc { get; set; }
  117. /// <summary>
  118. /// Gets or sets the UTC time when the current file or directory was last written to.
  119. /// </summary>
  120. /// <value>
  121. /// The UTC time the current file was last written.
  122. /// </value>
  123. public DateTime LastWriteTimeUtc { get; set; }
  124. /// <summary>
  125. /// Gets or sets the size, in bytes, of the current file.
  126. /// </summary>
  127. /// <value>
  128. /// The size of the current file in bytes.
  129. /// </value>
  130. public long Size { get; set; }
  131. /// <summary>
  132. /// Gets or sets file user id.
  133. /// </summary>
  134. /// <value>
  135. /// File user id.
  136. /// </value>
  137. public int UserId { get; set; }
  138. /// <summary>
  139. /// Gets or sets file group id.
  140. /// </summary>
  141. /// <value>
  142. /// File group id.
  143. /// </value>
  144. public int GroupId { get; set; }
  145. /// <summary>
  146. /// Gets a value indicating whether file represents a socket.
  147. /// </summary>
  148. /// <value>
  149. /// <see langword="true"/> if file represents a socket; otherwise, <see langword="false"/>.
  150. /// </value>
  151. public bool IsSocket { get; private set; }
  152. /// <summary>
  153. /// Gets a value indicating whether file represents a symbolic link.
  154. /// </summary>
  155. /// <value>
  156. /// <see langword="true"/> if file represents a symbolic link; otherwise, <see langword="false"/>.
  157. /// </value>
  158. public bool IsSymbolicLink { get; private set; }
  159. /// <summary>
  160. /// Gets a value indicating whether file represents a regular file.
  161. /// </summary>
  162. /// <value>
  163. /// <see langword="true"/> if file represents a regular file; otherwise, <see langword="false"/>.
  164. /// </value>
  165. public bool IsRegularFile { get; private set; }
  166. /// <summary>
  167. /// Gets a value indicating whether file represents a block device.
  168. /// </summary>
  169. /// <value>
  170. /// <see langword="true"/> if file represents a block device; otherwise, <see langword="false"/>.
  171. /// </value>
  172. public bool IsBlockDevice { get; private set; }
  173. /// <summary>
  174. /// Gets a value indicating whether file represents a directory.
  175. /// </summary>
  176. /// <value>
  177. /// <see langword="true"/> if file represents a directory; otherwise, <see langword="false"/>.
  178. /// </value>
  179. public bool IsDirectory { get; private set; }
  180. /// <summary>
  181. /// Gets a value indicating whether file represents a character device.
  182. /// </summary>
  183. /// <value>
  184. /// <see langword="true"/> if file represents a character device; otherwise, <see langword="false"/>.
  185. /// </value>
  186. public bool IsCharacterDevice { get; private set; }
  187. /// <summary>
  188. /// Gets a value indicating whether file represents a named pipe.
  189. /// </summary>
  190. /// <value>
  191. /// <see langword="true"/> if file represents a named pipe; otherwise, <see langword="false"/>.
  192. /// </value>
  193. public bool IsNamedPipe { get; private set; }
  194. /// <summary>
  195. /// Gets or sets a value indicating whether the owner can read from this file.
  196. /// </summary>
  197. /// <value>
  198. /// <see langword="true"/> if owner can read from this file; otherwise, <see langword="false"/>.
  199. /// </value>
  200. public bool OwnerCanRead { get; set; }
  201. /// <summary>
  202. /// Gets or sets a value indicating whether the owner can write into this file.
  203. /// </summary>
  204. /// <value>
  205. /// <see langword="true"/> if owner can write into this file; otherwise, <see langword="false"/>.
  206. /// </value>
  207. public bool OwnerCanWrite { get; set; }
  208. /// <summary>
  209. /// Gets or sets a value indicating whether the owner can execute this file.
  210. /// </summary>
  211. /// <value>
  212. /// <see langword="true"/> if owner can execute this file; otherwise, <see langword="false"/>.
  213. /// </value>
  214. public bool OwnerCanExecute { get; set; }
  215. /// <summary>
  216. /// Gets or sets a value indicating whether the group members can read from this file.
  217. /// </summary>
  218. /// <value>
  219. /// <see langword="true"/> if group members can read from this file; otherwise, <see langword="false"/>.
  220. /// </value>
  221. public bool GroupCanRead { get; set; }
  222. /// <summary>
  223. /// Gets or sets a value indicating whether the group members can write into this file.
  224. /// </summary>
  225. /// <value>
  226. /// <see langword="true"/> if group members can write into this file; otherwise, <see langword="false"/>.
  227. /// </value>
  228. public bool GroupCanWrite { get; set; }
  229. /// <summary>
  230. /// Gets or sets a value indicating whether the group members can execute this file.
  231. /// </summary>
  232. /// <value>
  233. /// <see langword="true"/> if group members can execute this file; otherwise, <see langword="false"/>.
  234. /// </value>
  235. public bool GroupCanExecute { get; set; }
  236. /// <summary>
  237. /// Gets or sets a value indicating whether the others can read from this file.
  238. /// </summary>
  239. /// <value>
  240. /// <see langword="true"/> if others can read from this file; otherwise, <see langword="false"/>.
  241. /// </value>
  242. public bool OthersCanRead { get; set; }
  243. /// <summary>
  244. /// Gets or sets a value indicating whether the others can write into this file.
  245. /// </summary>
  246. /// <value>
  247. /// <see langword="true"/> if others can write into this file; otherwise, <see langword="false"/>.
  248. /// </value>
  249. public bool OthersCanWrite { get; set; }
  250. /// <summary>
  251. /// Gets or sets a value indicating whether the others can execute this file.
  252. /// </summary>
  253. /// <value>
  254. /// <see langword="true"/> if others can execute this file; otherwise, <see langword="false"/>.
  255. /// </value>
  256. public bool OthersCanExecute { get; set; }
  257. /// <summary>
  258. /// Gets the extensions.
  259. /// </summary>
  260. /// <value>
  261. /// The extensions.
  262. /// </value>
  263. public IDictionary<string, string> Extensions { get; private set; }
  264. internal uint Permissions
  265. {
  266. get
  267. {
  268. uint permission = 0;
  269. if (_isBitFiledsBitSet)
  270. {
  271. permission |= S_IFMT;
  272. }
  273. if (IsSocket)
  274. {
  275. permission |= S_IFSOCK;
  276. }
  277. if (IsSymbolicLink)
  278. {
  279. permission |= S_IFLNK;
  280. }
  281. if (IsRegularFile)
  282. {
  283. permission |= S_IFREG;
  284. }
  285. if (IsBlockDevice)
  286. {
  287. permission |= S_IFBLK;
  288. }
  289. if (IsDirectory)
  290. {
  291. permission |= S_IFDIR;
  292. }
  293. if (IsCharacterDevice)
  294. {
  295. permission |= S_IFCHR;
  296. }
  297. if (IsNamedPipe)
  298. {
  299. permission |= S_IFIFO;
  300. }
  301. if (_isUIDBitSet)
  302. {
  303. permission |= S_ISUID;
  304. }
  305. if (_isGroupIDBitSet)
  306. {
  307. permission |= S_ISGID;
  308. }
  309. if (_isStickyBitSet)
  310. {
  311. permission |= S_ISVTX;
  312. }
  313. if (OwnerCanRead)
  314. {
  315. permission |= S_IRUSR;
  316. }
  317. if (OwnerCanWrite)
  318. {
  319. permission |= S_IWUSR;
  320. }
  321. if (OwnerCanExecute)
  322. {
  323. permission |= S_IXUSR;
  324. }
  325. if (GroupCanRead)
  326. {
  327. permission |= S_IRGRP;
  328. }
  329. if (GroupCanWrite)
  330. {
  331. permission |= S_IWGRP;
  332. }
  333. if (GroupCanExecute)
  334. {
  335. permission |= S_IXGRP;
  336. }
  337. if (OthersCanRead)
  338. {
  339. permission |= S_IROTH;
  340. }
  341. if (OthersCanWrite)
  342. {
  343. permission |= S_IWOTH;
  344. }
  345. if (OthersCanExecute)
  346. {
  347. permission |= S_IXOTH;
  348. }
  349. return permission;
  350. }
  351. private set
  352. {
  353. _isBitFiledsBitSet = (value & S_IFMT) == S_IFMT;
  354. IsSocket = (value & S_IFSOCK) == S_IFSOCK;
  355. IsSymbolicLink = (value & S_IFLNK) == S_IFLNK;
  356. IsRegularFile = (value & S_IFREG) == S_IFREG;
  357. IsBlockDevice = (value & S_IFBLK) == S_IFBLK;
  358. IsDirectory = (value & S_IFDIR) == S_IFDIR;
  359. IsCharacterDevice = (value & S_IFCHR) == S_IFCHR;
  360. IsNamedPipe = (value & S_IFIFO) == S_IFIFO;
  361. _isUIDBitSet = (value & S_ISUID) == S_ISUID;
  362. _isGroupIDBitSet = (value & S_ISGID) == S_ISGID;
  363. _isStickyBitSet = (value & S_ISVTX) == S_ISVTX;
  364. OwnerCanRead = (value & S_IRUSR) == S_IRUSR;
  365. OwnerCanWrite = (value & S_IWUSR) == S_IWUSR;
  366. OwnerCanExecute = (value & S_IXUSR) == S_IXUSR;
  367. GroupCanRead = (value & S_IRGRP) == S_IRGRP;
  368. GroupCanWrite = (value & S_IWGRP) == S_IWGRP;
  369. GroupCanExecute = (value & S_IXGRP) == S_IXGRP;
  370. OthersCanRead = (value & S_IROTH) == S_IROTH;
  371. OthersCanWrite = (value & S_IWOTH) == S_IWOTH;
  372. OthersCanExecute = (value & S_IXOTH) == S_IXOTH;
  373. }
  374. }
  375. private SftpFileAttributes()
  376. {
  377. }
  378. internal SftpFileAttributes(DateTime lastAccessTimeUtc, DateTime lastWriteTimeUtc, long size, int userId, int groupId, uint permissions, IDictionary<string, string> extensions)
  379. {
  380. LastAccessTimeUtc = _originalLastAccessTimeUtc = lastAccessTimeUtc;
  381. LastWriteTimeUtc = _originalLastWriteTimeUtc = lastWriteTimeUtc;
  382. Size = _originalSize = size;
  383. UserId = _originalUserId = userId;
  384. GroupId = _originalGroupId = groupId;
  385. Permissions = _originalPermissions = permissions;
  386. Extensions = _originalExtensions = extensions;
  387. }
  388. /// <summary>
  389. /// Sets the permissions.
  390. /// </summary>
  391. /// <param name="mode">The mode.</param>
  392. public void SetPermissions(short mode)
  393. {
  394. if (mode is < 0 or > 999)
  395. {
  396. throw new ArgumentOutOfRangeException(nameof(mode));
  397. }
  398. var modeBytes = mode.ToString(CultureInfo.InvariantCulture).PadLeft(3, '0').ToCharArray();
  399. var permission = ((modeBytes[0] & 0x0F) * 8 * 8) + ((modeBytes[1] & 0x0F) * 8) + (modeBytes[2] & 0x0F);
  400. OwnerCanRead = (permission & S_IRUSR) == S_IRUSR;
  401. OwnerCanWrite = (permission & S_IWUSR) == S_IWUSR;
  402. OwnerCanExecute = (permission & S_IXUSR) == S_IXUSR;
  403. GroupCanRead = (permission & S_IRGRP) == S_IRGRP;
  404. GroupCanWrite = (permission & S_IWGRP) == S_IWGRP;
  405. GroupCanExecute = (permission & S_IXGRP) == S_IXGRP;
  406. OthersCanRead = (permission & S_IROTH) == S_IROTH;
  407. OthersCanWrite = (permission & S_IWOTH) == S_IWOTH;
  408. OthersCanExecute = (permission & S_IXOTH) == S_IXOTH;
  409. }
  410. /// <summary>
  411. /// Returns a byte array representing the current <see cref="SftpFileAttributes"/>.
  412. /// </summary>
  413. /// <returns>
  414. /// A byte array representing the current <see cref="SftpFileAttributes"/>.
  415. /// </returns>
  416. public byte[] GetBytes()
  417. {
  418. using (var stream = new SshDataStream(4))
  419. {
  420. uint flag = 0;
  421. if (IsSizeChanged && IsRegularFile)
  422. {
  423. flag |= 0x00000001;
  424. }
  425. if (IsUserIdChanged || IsGroupIdChanged)
  426. {
  427. flag |= 0x00000002;
  428. }
  429. if (IsPermissionsChanged)
  430. {
  431. flag |= 0x00000004;
  432. }
  433. if (IsLastAccessTimeChanged || IsLastWriteTimeChanged)
  434. {
  435. flag |= 0x00000008;
  436. }
  437. if (IsExtensionsChanged)
  438. {
  439. flag |= 0x80000000;
  440. }
  441. stream.Write(flag);
  442. if (IsSizeChanged && IsRegularFile)
  443. {
  444. stream.Write((ulong)Size);
  445. }
  446. if (IsUserIdChanged || IsGroupIdChanged)
  447. {
  448. stream.Write((uint)UserId);
  449. stream.Write((uint)GroupId);
  450. }
  451. if (IsPermissionsChanged)
  452. {
  453. stream.Write(Permissions);
  454. }
  455. if (IsLastAccessTimeChanged || IsLastWriteTimeChanged)
  456. {
  457. var time = (uint)((LastAccessTimeUtc.ToFileTimeUtc() / 10000000) - 11644473600);
  458. stream.Write(time);
  459. time = (uint)((LastWriteTimeUtc.ToFileTimeUtc() / 10000000) - 11644473600);
  460. stream.Write(time);
  461. }
  462. if (IsExtensionsChanged)
  463. {
  464. foreach (var item in Extensions)
  465. {
  466. /*
  467. * TODO: we write as ASCII but read as UTF8 !!!
  468. */
  469. stream.Write(item.Key, SshData.Ascii);
  470. stream.Write(item.Value, SshData.Ascii);
  471. }
  472. }
  473. return stream.ToArray();
  474. }
  475. }
  476. internal static readonly SftpFileAttributes Empty = new SftpFileAttributes();
  477. internal static SftpFileAttributes FromBytes(SshDataStream stream)
  478. {
  479. const uint SSH_FILEXFER_ATTR_SIZE = 0x00000001;
  480. const uint SSH_FILEXFER_ATTR_UIDGID = 0x00000002;
  481. const uint SSH_FILEXFER_ATTR_PERMISSIONS = 0x00000004;
  482. const uint SSH_FILEXFER_ATTR_ACMODTIME = 0x00000008;
  483. const uint SSH_FILEXFER_ATTR_EXTENDED = 0x80000000;
  484. var flag = stream.ReadUInt32();
  485. long size = -1;
  486. var userId = -1;
  487. var groupId = -1;
  488. uint permissions = 0;
  489. DateTime accessTime;
  490. DateTime modifyTime;
  491. Dictionary<string, string> extensions = null;
  492. if ((flag & SSH_FILEXFER_ATTR_SIZE) == SSH_FILEXFER_ATTR_SIZE)
  493. {
  494. size = (long)stream.ReadUInt64();
  495. }
  496. if ((flag & SSH_FILEXFER_ATTR_UIDGID) == SSH_FILEXFER_ATTR_UIDGID)
  497. {
  498. userId = (int)stream.ReadUInt32();
  499. groupId = (int)stream.ReadUInt32();
  500. }
  501. if ((flag & SSH_FILEXFER_ATTR_PERMISSIONS) == SSH_FILEXFER_ATTR_PERMISSIONS)
  502. {
  503. permissions = stream.ReadUInt32();
  504. }
  505. if ((flag & SSH_FILEXFER_ATTR_ACMODTIME) == SSH_FILEXFER_ATTR_ACMODTIME)
  506. {
  507. // The incoming times are "Unix times", so they're already in UTC. We need to preserve that
  508. // to avoid losing information in a local time conversion during the "fall back" hour in DST.
  509. var time = stream.ReadUInt32();
  510. accessTime = DateTime.FromFileTimeUtc((time + 11644473600) * 10000000);
  511. time = stream.ReadUInt32();
  512. modifyTime = DateTime.FromFileTimeUtc((time + 11644473600) * 10000000);
  513. }
  514. else
  515. {
  516. accessTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
  517. modifyTime = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
  518. }
  519. if ((flag & SSH_FILEXFER_ATTR_EXTENDED) == SSH_FILEXFER_ATTR_EXTENDED)
  520. {
  521. var extendedCount = (int)stream.ReadUInt32();
  522. extensions = new Dictionary<string, string>(extendedCount);
  523. for (var i = 0; i < extendedCount; i++)
  524. {
  525. var extensionName = stream.ReadString(SshData.Utf8);
  526. var extensionData = stream.ReadString(SshData.Utf8);
  527. extensions.Add(extensionName, extensionData);
  528. }
  529. }
  530. return new SftpFileAttributes(accessTime, modifyTime, size, userId, groupId, permissions, extensions);
  531. }
  532. internal static SftpFileAttributes FromBytes(byte[] buffer)
  533. {
  534. using (var stream = new SshDataStream(buffer))
  535. {
  536. return FromBytes(stream);
  537. }
  538. }
  539. private static DateTime ToLocalTime(DateTime value)
  540. {
  541. DateTime result;
  542. if (value == DateTime.MinValue)
  543. {
  544. result = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Local);
  545. }
  546. else
  547. {
  548. result = value.ToLocalTime();
  549. }
  550. return result;
  551. }
  552. private static DateTime ToUniversalTime(DateTime value)
  553. {
  554. DateTime result;
  555. if (value == DateTime.MinValue)
  556. {
  557. result = DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
  558. }
  559. else
  560. {
  561. result = value.ToUniversalTime();
  562. }
  563. return result;
  564. }
  565. }
  566. }