TarArchive.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895
  1. // TarArchive.cs
  2. //
  3. // Copyright (C) 2001 Mike Krueger
  4. //
  5. // This program is free software; you can redistribute it and/or
  6. // modify it under the terms of the GNU General Public License
  7. // as published by the Free Software Foundation; either version 2
  8. // of the License, or (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU General Public License
  16. // along with this program; if not, write to the Free Software
  17. // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  18. //
  19. // Linking this library statically or dynamically with other modules is
  20. // making a combined work based on this library. Thus, the terms and
  21. // conditions of the GNU General Public License cover the whole
  22. // combination.
  23. //
  24. // As a special exception, the copyright holders of this library give you
  25. // permission to link this library with independent modules to produce an
  26. // executable, regardless of the license terms of these independent
  27. // modules, and to copy and distribute the resulting executable under
  28. // terms of your choice, provided that you also meet, for each linked
  29. // independent module, the terms and conditions of the license of that
  30. // module. An independent module is a module which is not derived from
  31. // or based on this library. If you modify this library, you may extend
  32. // this exception to your version of the library, but you are not
  33. // obligated to do so. If you do not wish to do so, delete this
  34. // exception statement from your version.
  35. // HISTORY
  36. // 2010-01-28 Added IsStreamOwner
  37. // 2012-06-07 Z-1675 RootPath was case and slash direction sensitive; trailing slash caused failure
  38. using System;
  39. using System.IO;
  40. using System.Text;
  41. namespace CommonMPQ.SharpZipLib.Tar
  42. {
  43. /// <summary>
  44. /// Used to advise clients of 'events' while processing archives
  45. /// </summary>
  46. public delegate void ProgressMessageHandler(TarArchive archive, TarEntry entry, string message);
  47. /// <summary>
  48. /// The TarArchive class implements the concept of a
  49. /// 'Tape Archive'. A tar archive is a series of entries, each of
  50. /// which represents a file system object. Each entry in
  51. /// the archive consists of a header block followed by 0 or more data blocks.
  52. /// Directory entries consist only of the header block, and are followed by entries
  53. /// for the directory's contents. File entries consist of a
  54. /// header followed by the number of blocks needed to
  55. /// contain the file's contents. All entries are written on
  56. /// block boundaries. Blocks are 512 bytes long.
  57. ///
  58. /// TarArchives are instantiated in either read or write mode,
  59. /// based upon whether they are instantiated with an InputStream
  60. /// or an OutputStream. Once instantiated TarArchives read/write
  61. /// mode can not be changed.
  62. ///
  63. /// There is currently no support for random access to tar archives.
  64. /// However, it seems that subclassing TarArchive, and using the
  65. /// TarBuffer.CurrentRecord and TarBuffer.CurrentBlock
  66. /// properties, this would be rather trivial.
  67. /// </summary>
  68. public class TarArchive : IDisposable
  69. {
  70. /// <summary>
  71. /// Client hook allowing detailed information to be reported during processing
  72. /// </summary>
  73. public event ProgressMessageHandler ProgressMessageEvent;
  74. /// <summary>
  75. /// Raises the ProgressMessage event
  76. /// </summary>
  77. /// <param name="entry">The <see cref="TarEntry">TarEntry</see> for this event</param>
  78. /// <param name="message">message for this event. Null is no message</param>
  79. protected virtual void OnProgressMessageEvent(TarEntry entry, string message)
  80. {
  81. ProgressMessageHandler handler = ProgressMessageEvent;
  82. if (handler != null) {
  83. handler(this, entry, message);
  84. }
  85. }
  86. #region Constructors
  87. /// <summary>
  88. /// Constructor for a default <see cref="TarArchive"/>.
  89. /// </summary>
  90. protected TarArchive()
  91. {
  92. }
  93. /// <summary>
  94. /// Initalise a TarArchive for input.
  95. /// </summary>
  96. /// <param name="stream">The <see cref="TarInputStream"/> to use for input.</param>
  97. protected TarArchive(TarInputStream stream)
  98. {
  99. if ( stream == null ) {
  100. throw new ArgumentNullException("stream");
  101. }
  102. tarIn = stream;
  103. }
  104. /// <summary>
  105. /// Initialise a TarArchive for output.
  106. /// </summary>
  107. /// <param name="stream">The <see cref="TarOutputStream"/> to use for output.</param>
  108. protected TarArchive(TarOutputStream stream)
  109. {
  110. if ( stream == null ) {
  111. throw new ArgumentNullException("stream");
  112. }
  113. tarOut = stream;
  114. }
  115. #endregion
  116. #region Static factory methods
  117. /// <summary>
  118. /// The InputStream based constructors create a TarArchive for the
  119. /// purposes of extracting or listing a tar archive. Thus, use
  120. /// these constructors when you wish to extract files from or list
  121. /// the contents of an existing tar archive.
  122. /// </summary>
  123. /// <param name="inputStream">The stream to retrieve archive data from.</param>
  124. /// <returns>Returns a new <see cref="TarArchive"/> suitable for reading from.</returns>
  125. public static TarArchive CreateInputTarArchive(Stream inputStream)
  126. {
  127. if ( inputStream == null ) {
  128. throw new ArgumentNullException("inputStream");
  129. }
  130. TarInputStream tarStream = inputStream as TarInputStream;
  131. TarArchive result;
  132. if ( tarStream != null ) {
  133. result = new TarArchive(tarStream);
  134. }
  135. else {
  136. result = CreateInputTarArchive(inputStream, TarBuffer.DefaultBlockFactor);
  137. }
  138. return result;
  139. }
  140. /// <summary>
  141. /// Create TarArchive for reading setting block factor
  142. /// </summary>
  143. /// <param name="inputStream">A stream containing the tar archive contents</param>
  144. /// <param name="blockFactor">The blocking factor to apply</param>
  145. /// <returns>Returns a <see cref="TarArchive"/> suitable for reading.</returns>
  146. public static TarArchive CreateInputTarArchive(Stream inputStream, int blockFactor)
  147. {
  148. if ( inputStream == null ) {
  149. throw new ArgumentNullException("inputStream");
  150. }
  151. if ( inputStream is TarInputStream ) {
  152. throw new ArgumentException("TarInputStream not valid");
  153. }
  154. return new TarArchive(new TarInputStream(inputStream, blockFactor));
  155. }
  156. /// <summary>
  157. /// Create a TarArchive for writing to, using the default blocking factor
  158. /// </summary>
  159. /// <param name="outputStream">The <see cref="Stream"/> to write to</param>
  160. /// <returns>Returns a <see cref="TarArchive"/> suitable for writing.</returns>
  161. public static TarArchive CreateOutputTarArchive(Stream outputStream)
  162. {
  163. if ( outputStream == null ) {
  164. throw new ArgumentNullException("outputStream");
  165. }
  166. TarOutputStream tarStream = outputStream as TarOutputStream;
  167. TarArchive result;
  168. if ( tarStream != null ) {
  169. result = new TarArchive(tarStream);
  170. }
  171. else {
  172. result = CreateOutputTarArchive(outputStream, TarBuffer.DefaultBlockFactor);
  173. }
  174. return result;
  175. }
  176. /// <summary>
  177. /// Create a <see cref="TarArchive">tar archive</see> for writing.
  178. /// </summary>
  179. /// <param name="outputStream">The stream to write to</param>
  180. /// <param name="blockFactor">The blocking factor to use for buffering.</param>
  181. /// <returns>Returns a <see cref="TarArchive"/> suitable for writing.</returns>
  182. public static TarArchive CreateOutputTarArchive(Stream outputStream, int blockFactor)
  183. {
  184. if ( outputStream == null ) {
  185. throw new ArgumentNullException("outputStream");
  186. }
  187. if ( outputStream is TarOutputStream ) {
  188. throw new ArgumentException("TarOutputStream is not valid");
  189. }
  190. return new TarArchive(new TarOutputStream(outputStream, blockFactor));
  191. }
  192. #endregion
  193. /// <summary>
  194. /// Set the flag that determines whether existing files are
  195. /// kept, or overwritten during extraction.
  196. /// </summary>
  197. /// <param name="keepExistingFiles">
  198. /// If true, do not overwrite existing files.
  199. /// </param>
  200. public void SetKeepOldFiles(bool keepExistingFiles)
  201. {
  202. if ( isDisposed ) {
  203. throw new ObjectDisposedException("TarArchive");
  204. }
  205. keepOldFiles = keepExistingFiles;
  206. }
  207. /// <summary>
  208. /// Get/set the ascii file translation flag. If ascii file translation
  209. /// is true, then the file is checked to see if it a binary file or not.
  210. /// If the flag is true and the test indicates it is ascii text
  211. /// file, it will be translated. The translation converts the local
  212. /// operating system's concept of line ends into the UNIX line end,
  213. /// '\n', which is the defacto standard for a TAR archive. This makes
  214. /// text files compatible with UNIX.
  215. /// </summary>
  216. public bool AsciiTranslate
  217. {
  218. get {
  219. if ( isDisposed ) {
  220. throw new ObjectDisposedException("TarArchive");
  221. }
  222. return asciiTranslate;
  223. }
  224. set {
  225. if ( isDisposed ) {
  226. throw new ObjectDisposedException("TarArchive");
  227. }
  228. asciiTranslate = value;
  229. }
  230. }
  231. /// <summary>
  232. /// Set the ascii file translation flag.
  233. /// </summary>
  234. /// <param name= "translateAsciiFiles">
  235. /// If true, translate ascii text files.
  236. /// </param>
  237. [Obsolete("Use the AsciiTranslate property")]
  238. public void SetAsciiTranslation(bool translateAsciiFiles)
  239. {
  240. if ( isDisposed ) {
  241. throw new ObjectDisposedException("TarArchive");
  242. }
  243. asciiTranslate = translateAsciiFiles;
  244. }
  245. /// <summary>
  246. /// PathPrefix is added to entry names as they are written if the value is not null.
  247. /// A slash character is appended after PathPrefix
  248. /// </summary>
  249. public string PathPrefix
  250. {
  251. get {
  252. if ( isDisposed ) {
  253. throw new ObjectDisposedException("TarArchive");
  254. }
  255. return pathPrefix;
  256. }
  257. set {
  258. if ( isDisposed ) {
  259. throw new ObjectDisposedException("TarArchive");
  260. }
  261. pathPrefix = value;
  262. }
  263. }
  264. /// <summary>
  265. /// RootPath is removed from entry names if it is found at the
  266. /// beginning of the name.
  267. /// </summary>
  268. public string RootPath
  269. {
  270. get {
  271. if ( isDisposed ) {
  272. throw new ObjectDisposedException("TarArchive");
  273. }
  274. return rootPath;
  275. }
  276. set {
  277. if ( isDisposed ) {
  278. throw new ObjectDisposedException("TarArchive");
  279. }
  280. // Convert to forward slashes for matching. Trim trailing / for correct final path
  281. rootPath = value.Replace('\\', '/').TrimEnd('/');
  282. }
  283. }
  284. /// <summary>
  285. /// Set user and group information that will be used to fill in the
  286. /// tar archive's entry headers. This information is based on that available
  287. /// for the linux operating system, which is not always available on other
  288. /// operating systems. TarArchive allows the programmer to specify values
  289. /// to be used in their place.
  290. /// <see cref="ApplyUserInfoOverrides"/> is set to true by this call.
  291. /// </summary>
  292. /// <param name="userId">
  293. /// The user id to use in the headers.
  294. /// </param>
  295. /// <param name="userName">
  296. /// The user name to use in the headers.
  297. /// </param>
  298. /// <param name="groupId">
  299. /// The group id to use in the headers.
  300. /// </param>
  301. /// <param name="groupName">
  302. /// The group name to use in the headers.
  303. /// </param>
  304. public void SetUserInfo(int userId, string userName, int groupId, string groupName)
  305. {
  306. if ( isDisposed ) {
  307. throw new ObjectDisposedException("TarArchive");
  308. }
  309. this.userId = userId;
  310. this.userName = userName;
  311. this.groupId = groupId;
  312. this.groupName = groupName;
  313. applyUserInfoOverrides = true;
  314. }
  315. /// <summary>
  316. /// Get or set a value indicating if overrides defined by <see cref="SetUserInfo">SetUserInfo</see> should be applied.
  317. /// </summary>
  318. /// <remarks>If overrides are not applied then the values as set in each header will be used.</remarks>
  319. public bool ApplyUserInfoOverrides
  320. {
  321. get {
  322. if ( isDisposed ) {
  323. throw new ObjectDisposedException("TarArchive");
  324. }
  325. return applyUserInfoOverrides;
  326. }
  327. set {
  328. if ( isDisposed ) {
  329. throw new ObjectDisposedException("TarArchive");
  330. }
  331. applyUserInfoOverrides = value;
  332. }
  333. }
  334. /// <summary>
  335. /// Get the archive user id.
  336. /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
  337. /// on how to allow setting values on a per entry basis.
  338. /// </summary>
  339. /// <returns>
  340. /// The current user id.
  341. /// </returns>
  342. public int UserId {
  343. get {
  344. if ( isDisposed ) {
  345. throw new ObjectDisposedException("TarArchive");
  346. }
  347. return userId;
  348. }
  349. }
  350. /// <summary>
  351. /// Get the archive user name.
  352. /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
  353. /// on how to allow setting values on a per entry basis.
  354. /// </summary>
  355. /// <returns>
  356. /// The current user name.
  357. /// </returns>
  358. public string UserName {
  359. get {
  360. if ( isDisposed ) {
  361. throw new ObjectDisposedException("TarArchive");
  362. }
  363. return userName;
  364. }
  365. }
  366. /// <summary>
  367. /// Get the archive group id.
  368. /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
  369. /// on how to allow setting values on a per entry basis.
  370. /// </summary>
  371. /// <returns>
  372. /// The current group id.
  373. /// </returns>
  374. public int GroupId {
  375. get {
  376. if ( isDisposed ) {
  377. throw new ObjectDisposedException("TarArchive");
  378. }
  379. return groupId;
  380. }
  381. }
  382. /// <summary>
  383. /// Get the archive group name.
  384. /// See <see cref="ApplyUserInfoOverrides">ApplyUserInfoOverrides</see> for detail
  385. /// on how to allow setting values on a per entry basis.
  386. /// </summary>
  387. /// <returns>
  388. /// The current group name.
  389. /// </returns>
  390. public string GroupName {
  391. get {
  392. if ( isDisposed ) {
  393. throw new ObjectDisposedException("TarArchive");
  394. }
  395. return groupName;
  396. }
  397. }
  398. /// <summary>
  399. /// Get the archive's record size. Tar archives are composed of
  400. /// a series of RECORDS each containing a number of BLOCKS.
  401. /// This allowed tar archives to match the IO characteristics of
  402. /// the physical device being used. Archives are expected
  403. /// to be properly "blocked".
  404. /// </summary>
  405. /// <returns>
  406. /// The record size this archive is using.
  407. /// </returns>
  408. public int RecordSize {
  409. get {
  410. if ( isDisposed ) {
  411. throw new ObjectDisposedException("TarArchive");
  412. }
  413. if (tarIn != null) {
  414. return tarIn.RecordSize;
  415. } else if (tarOut != null) {
  416. return tarOut.RecordSize;
  417. }
  418. return TarBuffer.DefaultRecordSize;
  419. }
  420. }
  421. /// <summary>
  422. /// Sets the IsStreamOwner property on the underlying stream.
  423. /// Set this to false to prevent the Close of the TarArchive from closing the stream.
  424. /// </summary>
  425. public bool IsStreamOwner {
  426. set {
  427. if (tarIn != null) {
  428. tarIn.IsStreamOwner = value;
  429. } else {
  430. tarOut.IsStreamOwner = value;
  431. }
  432. }
  433. }
  434. /// <summary>
  435. /// Close the archive.
  436. /// </summary>
  437. [Obsolete("Use Close instead")]
  438. public void CloseArchive()
  439. {
  440. Close();
  441. }
  442. /// <summary>
  443. /// Perform the "list" command for the archive contents.
  444. ///
  445. /// NOTE That this method uses the <see cref="ProgressMessageEvent"> progress event</see> to actually list
  446. /// the contents. If the progress display event is not set, nothing will be listed!
  447. /// </summary>
  448. public void ListContents()
  449. {
  450. if ( isDisposed ) {
  451. throw new ObjectDisposedException("TarArchive");
  452. }
  453. while (true) {
  454. TarEntry entry = tarIn.GetNextEntry();
  455. if (entry == null) {
  456. break;
  457. }
  458. OnProgressMessageEvent(entry, null);
  459. }
  460. }
  461. /// <summary>
  462. /// Perform the "extract" command and extract the contents of the archive.
  463. /// </summary>
  464. /// <param name="destinationDirectory">
  465. /// The destination directory into which to extract.
  466. /// </param>
  467. public void ExtractContents(string destinationDirectory)
  468. {
  469. if ( isDisposed ) {
  470. throw new ObjectDisposedException("TarArchive");
  471. }
  472. while (true) {
  473. TarEntry entry = tarIn.GetNextEntry();
  474. if (entry == null) {
  475. break;
  476. }
  477. ExtractEntry(destinationDirectory, entry);
  478. }
  479. }
  480. /// <summary>
  481. /// Extract an entry from the archive. This method assumes that the
  482. /// tarIn stream has been properly set with a call to GetNextEntry().
  483. /// </summary>
  484. /// <param name="destDir">
  485. /// The destination directory into which to extract.
  486. /// </param>
  487. /// <param name="entry">
  488. /// The TarEntry returned by tarIn.GetNextEntry().
  489. /// </param>
  490. void ExtractEntry(string destDir, TarEntry entry)
  491. {
  492. OnProgressMessageEvent(entry, null);
  493. string name = entry.Name;
  494. if (Path.IsPathRooted(name)) {
  495. // NOTE:
  496. // for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt
  497. name = name.Substring(Path.GetPathRoot(name).Length);
  498. }
  499. name = name.Replace('/', Path.DirectorySeparatorChar);
  500. string destFile = Path.Combine(destDir, name);
  501. if (entry.IsDirectory) {
  502. EnsureDirectoryExists(destFile);
  503. } else {
  504. string parentDirectory = Path.GetDirectoryName(destFile);
  505. EnsureDirectoryExists(parentDirectory);
  506. bool process = true;
  507. FileInfo fileInfo = new FileInfo(destFile);
  508. if (fileInfo.Exists) {
  509. if (keepOldFiles) {
  510. OnProgressMessageEvent(entry, "Destination file already exists");
  511. process = false;
  512. } else if ((fileInfo.Attributes & FileAttributes.ReadOnly) != 0) {
  513. OnProgressMessageEvent(entry, "Destination file already exists, and is read-only");
  514. process = false;
  515. }
  516. }
  517. if (process) {
  518. bool asciiTrans = false;
  519. Stream outputStream = File.Create(destFile);
  520. if (this.asciiTranslate) {
  521. asciiTrans = !IsBinary(destFile);
  522. }
  523. StreamWriter outw = null;
  524. if (asciiTrans) {
  525. outw = new StreamWriter(outputStream);
  526. }
  527. byte[] rdbuf = new byte[32 * 1024];
  528. while (true) {
  529. int numRead = tarIn.Read(rdbuf, 0, rdbuf.Length);
  530. if (numRead <= 0) {
  531. break;
  532. }
  533. if (asciiTrans) {
  534. for (int off = 0, b = 0; b < numRead; ++b) {
  535. if (rdbuf[b] == 10) {
  536. string s = Encoding.ASCII.GetString(rdbuf, off, (b - off));
  537. outw.WriteLine(s);
  538. off = b + 1;
  539. }
  540. }
  541. } else {
  542. outputStream.Write(rdbuf, 0, numRead);
  543. }
  544. }
  545. if (asciiTrans) {
  546. outw.Close();
  547. } else {
  548. outputStream.Close();
  549. }
  550. }
  551. }
  552. }
  553. /// <summary>
  554. /// Write an entry to the archive. This method will call the putNextEntry
  555. /// and then write the contents of the entry, and finally call closeEntry()
  556. /// for entries that are files. For directories, it will call putNextEntry(),
  557. /// and then, if the recurse flag is true, process each entry that is a
  558. /// child of the directory.
  559. /// </summary>
  560. /// <param name="sourceEntry">
  561. /// The TarEntry representing the entry to write to the archive.
  562. /// </param>
  563. /// <param name="recurse">
  564. /// If true, process the children of directory entries.
  565. /// </param>
  566. public void WriteEntry(TarEntry sourceEntry, bool recurse)
  567. {
  568. if ( sourceEntry == null ) {
  569. throw new ArgumentNullException("sourceEntry");
  570. }
  571. if ( isDisposed ) {
  572. throw new ObjectDisposedException("TarArchive");
  573. }
  574. try
  575. {
  576. if ( recurse ) {
  577. TarHeader.SetValueDefaults(sourceEntry.UserId, sourceEntry.UserName,
  578. sourceEntry.GroupId, sourceEntry.GroupName);
  579. }
  580. WriteEntryCore(sourceEntry, recurse);
  581. }
  582. finally
  583. {
  584. if ( recurse ) {
  585. TarHeader.RestoreSetValues();
  586. }
  587. }
  588. }
  589. /// <summary>
  590. /// Write an entry to the archive. This method will call the putNextEntry
  591. /// and then write the contents of the entry, and finally call closeEntry()
  592. /// for entries that are files. For directories, it will call putNextEntry(),
  593. /// and then, if the recurse flag is true, process each entry that is a
  594. /// child of the directory.
  595. /// </summary>
  596. /// <param name="sourceEntry">
  597. /// The TarEntry representing the entry to write to the archive.
  598. /// </param>
  599. /// <param name="recurse">
  600. /// If true, process the children of directory entries.
  601. /// </param>
  602. void WriteEntryCore(TarEntry sourceEntry, bool recurse)
  603. {
  604. string tempFileName = null;
  605. string entryFilename = sourceEntry.File;
  606. TarEntry entry = (TarEntry)sourceEntry.Clone();
  607. if ( applyUserInfoOverrides ) {
  608. entry.GroupId = groupId;
  609. entry.GroupName = groupName;
  610. entry.UserId = userId;
  611. entry.UserName = userName;
  612. }
  613. OnProgressMessageEvent(entry, null);
  614. if (asciiTranslate && !entry.IsDirectory) {
  615. if (!IsBinary(entryFilename)) {
  616. tempFileName = Path.GetTempFileName();
  617. using (StreamReader inStream = File.OpenText(entryFilename)) {
  618. using (Stream outStream = File.Create(tempFileName)) {
  619. while (true) {
  620. string line = inStream.ReadLine();
  621. if (line == null) {
  622. break;
  623. }
  624. byte[] data = Encoding.ASCII.GetBytes(line);
  625. outStream.Write(data, 0, data.Length);
  626. outStream.WriteByte((byte)'\n');
  627. }
  628. outStream.Flush();
  629. }
  630. }
  631. entry.Size = new FileInfo(tempFileName).Length;
  632. entryFilename = tempFileName;
  633. }
  634. }
  635. string newName = null;
  636. if (rootPath != null) {
  637. if (entry.Name.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase)) {
  638. newName = entry.Name.Substring(rootPath.Length + 1 );
  639. }
  640. }
  641. if (pathPrefix != null) {
  642. newName = (newName == null) ? pathPrefix + "/" + entry.Name : pathPrefix + "/" + newName;
  643. }
  644. if (newName != null) {
  645. entry.Name = newName;
  646. }
  647. tarOut.PutNextEntry(entry);
  648. if (entry.IsDirectory) {
  649. if (recurse) {
  650. TarEntry[] list = entry.GetDirectoryEntries();
  651. for (int i = 0; i < list.Length; ++i) {
  652. WriteEntryCore(list[i], recurse);
  653. }
  654. }
  655. }
  656. else {
  657. using (Stream inputStream = File.OpenRead(entryFilename)) {
  658. byte[] localBuffer = new byte[32 * 1024];
  659. while (true) {
  660. int numRead = inputStream.Read(localBuffer, 0, localBuffer.Length);
  661. if (numRead <=0) {
  662. break;
  663. }
  664. tarOut.Write(localBuffer, 0, numRead);
  665. }
  666. }
  667. if ( (tempFileName != null) && (tempFileName.Length > 0) ) {
  668. File.Delete(tempFileName);
  669. }
  670. tarOut.CloseEntry();
  671. }
  672. }
  673. /// <summary>
  674. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  675. /// </summary>
  676. public void Dispose()
  677. {
  678. Dispose(true);
  679. GC.SuppressFinalize(this);
  680. }
  681. /// <summary>
  682. /// Releases the unmanaged resources used by the FileStream and optionally releases the managed resources.
  683. /// </summary>
  684. /// <param name="disposing">true to release both managed and unmanaged resources;
  685. /// false to release only unmanaged resources.</param>
  686. protected virtual void Dispose(bool disposing)
  687. {
  688. if ( !isDisposed ) {
  689. isDisposed = true;
  690. if ( disposing ) {
  691. if ( tarOut != null ) {
  692. tarOut.Flush();
  693. tarOut.Close();
  694. }
  695. if ( tarIn != null ) {
  696. tarIn.Close();
  697. }
  698. }
  699. }
  700. }
  701. /// <summary>
  702. /// Closes the archive and releases any associated resources.
  703. /// </summary>
  704. public virtual void Close()
  705. {
  706. Dispose(true);
  707. }
  708. /// <summary>
  709. /// Ensures that resources are freed and other cleanup operations are performed
  710. /// when the garbage collector reclaims the <see cref="TarArchive"/>.
  711. /// </summary>
  712. ~TarArchive()
  713. {
  714. Dispose(false);
  715. }
  716. static void EnsureDirectoryExists(string directoryName)
  717. {
  718. if (!Directory.Exists(directoryName)) {
  719. try {
  720. Directory.CreateDirectory(directoryName);
  721. }
  722. catch (Exception e) {
  723. throw new TarException("Exception creating directory '" + directoryName + "', " + e.Message, e);
  724. }
  725. }
  726. }
  727. // TODO: TarArchive - Is there a better way to test for a text file?
  728. // It no longer reads entire files into memory but is still a weak test!
  729. // This assumes that byte values 0-7, 14-31 or 255 are binary
  730. // and that all non text files contain one of these values
  731. static bool IsBinary(string filename)
  732. {
  733. using (FileStream fs = File.OpenRead(filename))
  734. {
  735. int sampleSize = Math.Min(4096, (int)fs.Length);
  736. byte[] content = new byte[sampleSize];
  737. int bytesRead = fs.Read(content, 0, sampleSize);
  738. for (int i = 0; i < bytesRead; ++i) {
  739. byte b = content[i];
  740. if ( (b < 8) || ((b > 13) && (b < 32)) || (b == 255) ) {
  741. return true;
  742. }
  743. }
  744. }
  745. return false;
  746. }
  747. #region Instance Fields
  748. bool keepOldFiles;
  749. bool asciiTranslate;
  750. int userId;
  751. string userName = string.Empty;
  752. int groupId;
  753. string groupName = string.Empty;
  754. string rootPath;
  755. string pathPrefix;
  756. bool applyUserInfoOverrides;
  757. TarInputStream tarIn;
  758. TarOutputStream tarOut;
  759. bool isDisposed;
  760. #endregion
  761. }
  762. }
  763. /* The original Java file had this header:
  764. ** Authored by Timothy Gerard Endres
  765. ** <mailto:time@gjt.org> <http://www.trustice.com>
  766. **
  767. ** This work has been placed into the public domain.
  768. ** You may use this work in any way and for any purpose you wish.
  769. **
  770. ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
  771. ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
  772. ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
  773. ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
  774. ** REDISTRIBUTION OF THIS SOFTWARE.
  775. **
  776. */