TarEntry.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. using System;
  2. using System.IO;
  3. namespace ICSharpCode.SharpZipLib.Tar
  4. {
  5. /// <summary>
  6. /// This class represents an entry in a Tar archive. It consists
  7. /// of the entry's header, as well as the entry's File. Entries
  8. /// can be instantiated in one of three ways, depending on how
  9. /// they are to be used.
  10. /// <p>
  11. /// TarEntries that are created from the header bytes read from
  12. /// an archive are instantiated with the TarEntry( byte[] )
  13. /// constructor. These entries will be used when extracting from
  14. /// or listing the contents of an archive. These entries have their
  15. /// header filled in using the header bytes. They also set the File
  16. /// to null, since they reference an archive entry not a file.</p>
  17. /// <p>
  18. /// TarEntries that are created from files that are to be written
  19. /// into an archive are instantiated with the CreateEntryFromFile(string)
  20. /// pseudo constructor. These entries have their header filled in using
  21. /// the File's information. They also keep a reference to the File
  22. /// for convenience when writing entries.</p>
  23. /// <p>
  24. /// Finally, TarEntries can be constructed from nothing but a name.
  25. /// This allows the programmer to construct the entry by hand, for
  26. /// instance when only an InputStream is available for writing to
  27. /// the archive, and the header information is constructed from
  28. /// other information. In this case the header fields are set to
  29. /// defaults and the File is set to null.</p>
  30. /// <see cref="TarHeader"/>
  31. /// </summary>
  32. public class TarEntry
  33. {
  34. #region Constructors
  35. /// <summary>
  36. /// Initialise a default instance of <see cref="TarEntry"/>.
  37. /// </summary>
  38. private TarEntry()
  39. {
  40. header = new TarHeader();
  41. }
  42. /// <summary>
  43. /// Construct an entry from an archive's header bytes. File is set
  44. /// to null.
  45. /// </summary>
  46. /// <param name = "headerBuffer">
  47. /// The header bytes from a tar archive entry.
  48. /// </param>
  49. public TarEntry(byte[] headerBuffer)
  50. {
  51. header = new TarHeader();
  52. header.ParseBuffer(headerBuffer);
  53. }
  54. /// <summary>
  55. /// Construct a TarEntry using the <paramref name="header">header</paramref> provided
  56. /// </summary>
  57. /// <param name="header">Header details for entry</param>
  58. public TarEntry(TarHeader header)
  59. {
  60. if (header == null)
  61. {
  62. throw new ArgumentNullException(nameof(header));
  63. }
  64. this.header = (TarHeader)header.Clone();
  65. }
  66. #endregion Constructors
  67. #region ICloneable Members
  68. /// <summary>
  69. /// Clone this tar entry.
  70. /// </summary>
  71. /// <returns>Returns a clone of this entry.</returns>
  72. public object Clone()
  73. {
  74. var entry = new TarEntry();
  75. entry.file = file;
  76. entry.header = (TarHeader)header.Clone();
  77. entry.Name = Name;
  78. return entry;
  79. }
  80. #endregion ICloneable Members
  81. /// <summary>
  82. /// Construct an entry with only a <paramref name="name">name</paramref>.
  83. /// This allows the programmer to construct the entry's header "by hand".
  84. /// </summary>
  85. /// <param name="name">The name to use for the entry</param>
  86. /// <returns>Returns the newly created <see cref="TarEntry"/></returns>
  87. public static TarEntry CreateTarEntry(string name)
  88. {
  89. var entry = new TarEntry();
  90. TarEntry.NameTarHeader(entry.header, name);
  91. return entry;
  92. }
  93. /// <summary>
  94. /// Construct an entry for a file. File is set to file, and the
  95. /// header is constructed from information from the file.
  96. /// </summary>
  97. /// <param name = "fileName">The file name that the entry represents.</param>
  98. /// <returns>Returns the newly created <see cref="TarEntry"/></returns>
  99. public static TarEntry CreateEntryFromFile(string fileName)
  100. {
  101. var entry = new TarEntry();
  102. entry.GetFileTarHeader(entry.header, fileName);
  103. return entry;
  104. }
  105. /// <summary>
  106. /// Determine if the two entries are equal. Equality is determined
  107. /// by the header names being equal.
  108. /// </summary>
  109. /// <param name="obj">The <see cref="Object"/> to compare with the current Object.</param>
  110. /// <returns>
  111. /// True if the entries are equal; false if not.
  112. /// </returns>
  113. public override bool Equals(object obj)
  114. {
  115. var localEntry = obj as TarEntry;
  116. if (localEntry != null)
  117. {
  118. return Name.Equals(localEntry.Name);
  119. }
  120. return false;
  121. }
  122. /// <summary>
  123. /// Derive a Hash value for the current <see cref="Object"/>
  124. /// </summary>
  125. /// <returns>A Hash code for the current <see cref="Object"/></returns>
  126. public override int GetHashCode()
  127. {
  128. return Name.GetHashCode();
  129. }
  130. /// <summary>
  131. /// Determine if the given entry is a descendant of this entry.
  132. /// Descendancy is determined by the name of the descendant
  133. /// starting with this entry's name.
  134. /// </summary>
  135. /// <param name = "toTest">
  136. /// Entry to be checked as a descendent of this.
  137. /// </param>
  138. /// <returns>
  139. /// True if entry is a descendant of this.
  140. /// </returns>
  141. public bool IsDescendent(TarEntry toTest)
  142. {
  143. if (toTest == null)
  144. {
  145. throw new ArgumentNullException(nameof(toTest));
  146. }
  147. return toTest.Name.StartsWith(Name, StringComparison.Ordinal);
  148. }
  149. /// <summary>
  150. /// Get this entry's header.
  151. /// </summary>
  152. /// <returns>
  153. /// This entry's TarHeader.
  154. /// </returns>
  155. public TarHeader TarHeader
  156. {
  157. get
  158. {
  159. return header;
  160. }
  161. }
  162. /// <summary>
  163. /// Get/Set this entry's name.
  164. /// </summary>
  165. public string Name
  166. {
  167. get
  168. {
  169. return header.Name;
  170. }
  171. set
  172. {
  173. header.Name = value;
  174. }
  175. }
  176. /// <summary>
  177. /// Get/set this entry's user id.
  178. /// </summary>
  179. public int UserId
  180. {
  181. get
  182. {
  183. return header.UserId;
  184. }
  185. set
  186. {
  187. header.UserId = value;
  188. }
  189. }
  190. /// <summary>
  191. /// Get/set this entry's group id.
  192. /// </summary>
  193. public int GroupId
  194. {
  195. get
  196. {
  197. return header.GroupId;
  198. }
  199. set
  200. {
  201. header.GroupId = value;
  202. }
  203. }
  204. /// <summary>
  205. /// Get/set this entry's user name.
  206. /// </summary>
  207. public string UserName
  208. {
  209. get
  210. {
  211. return header.UserName;
  212. }
  213. set
  214. {
  215. header.UserName = value;
  216. }
  217. }
  218. /// <summary>
  219. /// Get/set this entry's group name.
  220. /// </summary>
  221. public string GroupName
  222. {
  223. get
  224. {
  225. return header.GroupName;
  226. }
  227. set
  228. {
  229. header.GroupName = value;
  230. }
  231. }
  232. /// <summary>
  233. /// Convenience method to set this entry's group and user ids.
  234. /// </summary>
  235. /// <param name="userId">
  236. /// This entry's new user id.
  237. /// </param>
  238. /// <param name="groupId">
  239. /// This entry's new group id.
  240. /// </param>
  241. public void SetIds(int userId, int groupId)
  242. {
  243. UserId = userId;
  244. GroupId = groupId;
  245. }
  246. /// <summary>
  247. /// Convenience method to set this entry's group and user names.
  248. /// </summary>
  249. /// <param name="userName">
  250. /// This entry's new user name.
  251. /// </param>
  252. /// <param name="groupName">
  253. /// This entry's new group name.
  254. /// </param>
  255. public void SetNames(string userName, string groupName)
  256. {
  257. UserName = userName;
  258. GroupName = groupName;
  259. }
  260. /// <summary>
  261. /// Get/Set the modification time for this entry
  262. /// </summary>
  263. public DateTime ModTime
  264. {
  265. get
  266. {
  267. return header.ModTime;
  268. }
  269. set
  270. {
  271. header.ModTime = value;
  272. }
  273. }
  274. /// <summary>
  275. /// Get this entry's file.
  276. /// </summary>
  277. /// <returns>
  278. /// This entry's file.
  279. /// </returns>
  280. public string File
  281. {
  282. get
  283. {
  284. return file;
  285. }
  286. }
  287. /// <summary>
  288. /// Get/set this entry's recorded file size.
  289. /// </summary>
  290. public long Size
  291. {
  292. get
  293. {
  294. return header.Size;
  295. }
  296. set
  297. {
  298. header.Size = value;
  299. }
  300. }
  301. /// <summary>
  302. /// Return true if this entry represents a directory, false otherwise
  303. /// </summary>
  304. /// <returns>
  305. /// True if this entry is a directory.
  306. /// </returns>
  307. public bool IsDirectory
  308. {
  309. get
  310. {
  311. if (file != null)
  312. {
  313. return Directory.Exists(file);
  314. }
  315. if (header != null)
  316. {
  317. if ((header.TypeFlag == TarHeader.LF_DIR) || Name.EndsWith("/", StringComparison.Ordinal))
  318. {
  319. return true;
  320. }
  321. }
  322. return false;
  323. }
  324. }
  325. /// <summary>
  326. /// Fill in a TarHeader with information from a File.
  327. /// </summary>
  328. /// <param name="header">
  329. /// The TarHeader to fill in.
  330. /// </param>
  331. /// <param name="file">
  332. /// The file from which to get the header information.
  333. /// </param>
  334. public void GetFileTarHeader(TarHeader header, string file)
  335. {
  336. if (header == null)
  337. {
  338. throw new ArgumentNullException(nameof(header));
  339. }
  340. if (file == null)
  341. {
  342. throw new ArgumentNullException(nameof(file));
  343. }
  344. this.file = file;
  345. // bugfix from torhovl from #D forum:
  346. string name = file;
  347. // 23-Jan-2004 GnuTar allows device names in path where the name is not local to the current directory
  348. if (name.IndexOf(Directory.GetCurrentDirectory(), StringComparison.Ordinal) == 0)
  349. {
  350. name = name.Substring(Directory.GetCurrentDirectory().Length);
  351. }
  352. /*
  353. if (Path.DirectorySeparatorChar == '\\')
  354. {
  355. // check if the OS is Windows
  356. // Strip off drive letters!
  357. if (name.Length > 2)
  358. {
  359. char ch1 = name[0];
  360. char ch2 = name[1];
  361. if (ch2 == ':' && Char.IsLetter(ch1))
  362. {
  363. name = name.Substring(2);
  364. }
  365. }
  366. }
  367. */
  368. name = name.Replace(Path.DirectorySeparatorChar, '/');
  369. // No absolute pathnames
  370. // Windows (and Posix?) paths can start with UNC style "\\NetworkDrive\",
  371. // so we loop on starting /'s.
  372. while (name.StartsWith("/", StringComparison.Ordinal))
  373. {
  374. name = name.Substring(1);
  375. }
  376. header.LinkName = String.Empty;
  377. header.Name = name;
  378. if (Directory.Exists(file))
  379. {
  380. header.Mode = 1003; // Magic number for security access for a UNIX filesystem
  381. header.TypeFlag = TarHeader.LF_DIR;
  382. if ((header.Name.Length == 0) || header.Name[header.Name.Length - 1] != '/')
  383. {
  384. header.Name = header.Name + "/";
  385. }
  386. header.Size = 0;
  387. }
  388. else
  389. {
  390. header.Mode = 33216; // Magic number for security access for a UNIX filesystem
  391. header.TypeFlag = TarHeader.LF_NORMAL;
  392. header.Size = new FileInfo(file.Replace('/', Path.DirectorySeparatorChar)).Length;
  393. }
  394. header.ModTime = System.IO.File.GetLastWriteTime(file.Replace('/', Path.DirectorySeparatorChar)).ToUniversalTime();
  395. header.DevMajor = 0;
  396. header.DevMinor = 0;
  397. }
  398. /// <summary>
  399. /// Get entries for all files present in this entries directory.
  400. /// If this entry doesnt represent a directory zero entries are returned.
  401. /// </summary>
  402. /// <returns>
  403. /// An array of TarEntry's for this entry's children.
  404. /// </returns>
  405. public TarEntry[] GetDirectoryEntries()
  406. {
  407. if ((file == null) || !Directory.Exists(file))
  408. {
  409. return new TarEntry[0];
  410. }
  411. string[] list = Directory.GetFileSystemEntries(file);
  412. TarEntry[] result = new TarEntry[list.Length];
  413. for (int i = 0; i < list.Length; ++i)
  414. {
  415. result[i] = TarEntry.CreateEntryFromFile(list[i]);
  416. }
  417. return result;
  418. }
  419. /// <summary>
  420. /// Write an entry's header information to a header buffer.
  421. /// </summary>
  422. /// <param name = "outBuffer">
  423. /// The tar entry header buffer to fill in.
  424. /// </param>
  425. public void WriteEntryHeader(byte[] outBuffer)
  426. {
  427. header.WriteHeader(outBuffer);
  428. }
  429. /// <summary>
  430. /// Convenience method that will modify an entry's name directly
  431. /// in place in an entry header buffer byte array.
  432. /// </summary>
  433. /// <param name="buffer">
  434. /// The buffer containing the entry header to modify.
  435. /// </param>
  436. /// <param name="newName">
  437. /// The new name to place into the header buffer.
  438. /// </param>
  439. static public void AdjustEntryName(byte[] buffer, string newName)
  440. {
  441. TarHeader.GetNameBytes(newName, buffer, 0, TarHeader.NAMELEN);
  442. }
  443. /// <summary>
  444. /// Fill in a TarHeader given only the entry's name.
  445. /// </summary>
  446. /// <param name="header">
  447. /// The TarHeader to fill in.
  448. /// </param>
  449. /// <param name="name">
  450. /// The tar entry name.
  451. /// </param>
  452. static public void NameTarHeader(TarHeader header, string name)
  453. {
  454. if (header == null)
  455. {
  456. throw new ArgumentNullException(nameof(header));
  457. }
  458. if (name == null)
  459. {
  460. throw new ArgumentNullException(nameof(name));
  461. }
  462. bool isDir = name.EndsWith("/", StringComparison.Ordinal);
  463. header.Name = name;
  464. header.Mode = isDir ? 1003 : 33216;
  465. header.UserId = 0;
  466. header.GroupId = 0;
  467. header.Size = 0;
  468. header.ModTime = DateTime.UtcNow;
  469. header.TypeFlag = isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL;
  470. header.LinkName = String.Empty;
  471. header.UserName = String.Empty;
  472. header.GroupName = String.Empty;
  473. header.DevMajor = 0;
  474. header.DevMinor = 0;
  475. }
  476. #region Instance Fields
  477. /// <summary>
  478. /// The name of the file this entry represents or null if the entry is not based on a file.
  479. /// </summary>
  480. private string file;
  481. /// <summary>
  482. /// The entry's header information.
  483. /// </summary>
  484. private TarHeader header;
  485. #endregion Instance Fields
  486. }
  487. }