TarHeader.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. using System;
  2. using System.IO;
  3. using System.Text;
  4. using SharpCompress.Converters;
  5. namespace SharpCompress.Common.Tar.Headers
  6. {
  7. internal class TarHeader
  8. {
  9. internal static readonly DateTime EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
  10. public TarHeader(ArchiveEncoding archiveEncoding)
  11. {
  12. ArchiveEncoding = archiveEncoding;
  13. }
  14. internal string Name { get; set; }
  15. //internal int Mode { get; set; }
  16. //internal int UserId { get; set; }
  17. //internal string UserName { get; set; }
  18. //internal int GroupId { get; set; }
  19. //internal string GroupName { get; set; }
  20. internal long Size { get; set; }
  21. internal DateTime LastModifiedTime { get; set; }
  22. internal EntryType EntryType { get; set; }
  23. internal Stream PackedStream { get; set; }
  24. internal ArchiveEncoding ArchiveEncoding { get; }
  25. internal const int BLOCK_SIZE = 512;
  26. internal void Write(Stream output)
  27. {
  28. byte[] buffer = new byte[BLOCK_SIZE];
  29. WriteOctalBytes(511, buffer, 100, 8); // file mode
  30. WriteOctalBytes(0, buffer, 108, 8); // owner ID
  31. WriteOctalBytes(0, buffer, 116, 8); // group ID
  32. //ArchiveEncoding.UTF8.GetBytes("magic").CopyTo(buffer, 257);
  33. if (Name.Length > 100)
  34. {
  35. // Set mock filename and filetype to indicate the next block is the actual name of the file
  36. WriteStringBytes("././@LongLink", buffer, 0, 100);
  37. buffer[156] = (byte)EntryType.LongName;
  38. WriteOctalBytes(Name.Length + 1, buffer, 124, 12);
  39. }
  40. else
  41. {
  42. WriteStringBytes(Name, buffer, 0, 100);
  43. WriteOctalBytes(Size, buffer, 124, 12);
  44. var time = (long)(LastModifiedTime.ToUniversalTime() - EPOCH).TotalSeconds;
  45. WriteOctalBytes(time, buffer, 136, 12);
  46. buffer[156] = (byte)EntryType;
  47. if (Size >= 0x1FFFFFFFF)
  48. {
  49. byte[] bytes = DataConverter.BigEndian.GetBytes(Size);
  50. var bytes12 = new byte[12];
  51. bytes.CopyTo(bytes12, 12 - bytes.Length);
  52. bytes12[0] |= 0x80;
  53. bytes12.CopyTo(buffer, 124);
  54. }
  55. }
  56. int crc = RecalculateChecksum(buffer);
  57. WriteOctalBytes(crc, buffer, 148, 8);
  58. output.Write(buffer, 0, buffer.Length);
  59. if (Name.Length > 100)
  60. {
  61. WriteLongFilenameHeader(output);
  62. Name = Name.Substring(0, 100);
  63. Write(output);
  64. }
  65. }
  66. private void WriteLongFilenameHeader(Stream output)
  67. {
  68. byte[] nameBytes = ArchiveEncoding.Encode(Name);
  69. output.Write(nameBytes, 0, nameBytes.Length);
  70. // pad to multiple of BlockSize bytes, and make sure a terminating null is added
  71. int numPaddingBytes = BLOCK_SIZE - (nameBytes.Length % BLOCK_SIZE);
  72. if (numPaddingBytes == 0)
  73. {
  74. numPaddingBytes = BLOCK_SIZE;
  75. }
  76. output.Write(new byte[numPaddingBytes], 0, numPaddingBytes);
  77. }
  78. internal bool Read(BinaryReader reader)
  79. {
  80. var buffer = ReadBlock(reader);
  81. if (buffer.Length == 0)
  82. {
  83. return false;
  84. }
  85. if (ReadEntryType(buffer) == EntryType.LongName)
  86. {
  87. Name = ReadLongName(reader, buffer);
  88. buffer = ReadBlock(reader);
  89. }
  90. else
  91. {
  92. Name = ArchiveEncoding.Decode(buffer, 0, 100).TrimNulls();
  93. }
  94. EntryType = ReadEntryType(buffer);
  95. Size = ReadSize(buffer);
  96. //Mode = ReadASCIIInt32Base8(buffer, 100, 7);
  97. //UserId = ReadASCIIInt32Base8(buffer, 108, 7);
  98. //GroupId = ReadASCIIInt32Base8(buffer, 116, 7);
  99. long unixTimeStamp = ReadAsciiInt64Base8(buffer, 136, 11);
  100. LastModifiedTime = EPOCH.AddSeconds(unixTimeStamp).ToLocalTime();
  101. Magic = ArchiveEncoding.Decode(buffer, 257, 6).TrimNulls();
  102. if (!string.IsNullOrEmpty(Magic)
  103. && "ustar".Equals(Magic))
  104. {
  105. string namePrefix = ArchiveEncoding.Decode(buffer, 345, 157);
  106. namePrefix = namePrefix.TrimNulls();
  107. if (!string.IsNullOrEmpty(namePrefix))
  108. {
  109. Name = namePrefix + "/" + Name;
  110. }
  111. }
  112. if (EntryType != EntryType.LongName
  113. && Name.Length == 0)
  114. {
  115. return false;
  116. }
  117. return true;
  118. }
  119. private string ReadLongName(BinaryReader reader, byte[] buffer)
  120. {
  121. var size = ReadSize(buffer);
  122. var nameLength = (int)size;
  123. var nameBytes = reader.ReadBytes(nameLength);
  124. var remainingBytesToRead = BLOCK_SIZE - (nameLength % BLOCK_SIZE);
  125. // Read the rest of the block and discard the data
  126. if (remainingBytesToRead < BLOCK_SIZE)
  127. {
  128. reader.ReadBytes(remainingBytesToRead);
  129. }
  130. return ArchiveEncoding.Decode(nameBytes, 0, nameBytes.Length).TrimNulls();
  131. }
  132. private static EntryType ReadEntryType(byte[] buffer)
  133. {
  134. return (EntryType)buffer[156];
  135. }
  136. private long ReadSize(byte[] buffer)
  137. {
  138. if ((buffer[124] & 0x80) == 0x80) // if size in binary
  139. {
  140. return DataConverter.BigEndian.GetInt64(buffer, 0x80);
  141. }
  142. return ReadAsciiInt64Base8(buffer, 124, 11);
  143. }
  144. private static byte[] ReadBlock(BinaryReader reader)
  145. {
  146. byte[] buffer = reader.ReadBytes(BLOCK_SIZE);
  147. if (buffer.Length != 0 && buffer.Length < BLOCK_SIZE)
  148. {
  149. throw new InvalidOperationException("Buffer is invalid size");
  150. }
  151. return buffer;
  152. }
  153. private static void WriteStringBytes(string name, byte[] buffer, int offset, int length)
  154. {
  155. int i;
  156. for (i = 0; i < length - 1 && i < name.Length; ++i)
  157. {
  158. buffer[offset + i] = (byte)name[i];
  159. }
  160. for (; i < length; ++i)
  161. {
  162. buffer[offset + i] = 0;
  163. }
  164. }
  165. private static void WriteOctalBytes(long value, byte[] buffer, int offset, int length)
  166. {
  167. string val = Convert.ToString(value, 8);
  168. int shift = length - val.Length - 1;
  169. for (int i = 0; i < shift; i++)
  170. {
  171. buffer[offset + i] = (byte)' ';
  172. }
  173. for (int i = 0; i < val.Length; i++)
  174. {
  175. buffer[offset + i + shift] = (byte)val[i];
  176. }
  177. }
  178. private static int ReadAsciiInt32Base8(byte[] buffer, int offset, int count)
  179. {
  180. string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls();
  181. if (string.IsNullOrEmpty(s))
  182. {
  183. return 0;
  184. }
  185. return Convert.ToInt32(s, 8);
  186. }
  187. private static long ReadAsciiInt64Base8(byte[] buffer, int offset, int count)
  188. {
  189. string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls();
  190. if (string.IsNullOrEmpty(s))
  191. {
  192. return 0;
  193. }
  194. return Convert.ToInt64(s, 8);
  195. }
  196. private static long ReadAsciiInt64(byte[] buffer, int offset, int count)
  197. {
  198. string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls();
  199. if (string.IsNullOrEmpty(s))
  200. {
  201. return 0;
  202. }
  203. return Convert.ToInt64(s);
  204. }
  205. internal static int RecalculateChecksum(byte[] buf)
  206. {
  207. // Set default value for checksum. That is 8 spaces.
  208. Encoding.UTF8.GetBytes(" ").CopyTo(buf, 148);
  209. // Calculate checksum
  210. int headerChecksum = 0;
  211. foreach (byte b in buf)
  212. {
  213. headerChecksum += b;
  214. }
  215. return headerChecksum;
  216. }
  217. internal static int RecalculateAltChecksum(byte[] buf)
  218. {
  219. Encoding.UTF8.GetBytes(" ").CopyTo(buf, 148);
  220. int headerChecksum = 0;
  221. foreach (byte b in buf)
  222. {
  223. if ((b & 0x80) == 0x80)
  224. {
  225. headerChecksum -= b ^ 0x80;
  226. }
  227. else
  228. {
  229. headerChecksum += b;
  230. }
  231. }
  232. return headerChecksum;
  233. }
  234. public long? DataStartPosition { get; set; }
  235. public string Magic { get; set; }
  236. }
  237. }