using System; using System.IO; #if !PORTABLE using System.Net; #endif using System.Text; namespace SharpCompress.Common.Tar.Headers { internal enum EntryType : byte { File = 0, OldFile = (byte)'0', HardLink = (byte)'1', SymLink = (byte)'2', CharDevice = (byte)'3', BlockDevice = (byte)'4', Directory = (byte)'5', Fifo = (byte)'6', LongLink = (byte)'K', LongName = (byte)'L', SparseFile = (byte)'S', VolumeHeader = (byte)'V', } internal class TarHeader { internal static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0); internal string Name { get; set; } //internal int Mode { get; set; } //internal int UserId { get; set; } //internal string UserName { get; set; } //internal int GroupId { get; set; } //internal string GroupName { get; set; } internal long Size { get; set; } internal DateTime LastModifiedTime { get; set; } internal EntryType EntryType { get; set; } internal Stream PackedStream { get; set; } internal void Write(Stream output) { if (Name.Length > 255) { throw new InvalidFormatException("UsTar fileName can not be longer thatn 255 chars"); } byte[] buffer = new byte[512]; string name = Name; if (name.Length > 100) { name = Name.Substring(0, 100); } WriteStringBytes(name, buffer, 0, 100); WriteOctalBytes(511, buffer, 100, 8); WriteOctalBytes(0, buffer, 108, 8); WriteOctalBytes(0, buffer, 116, 8); WriteOctalBytes(Size, buffer, 124, 12); var time = (long)(LastModifiedTime - Epoch).TotalSeconds; WriteOctalBytes(time, buffer, 136, 12); buffer[156] = (byte)EntryType; //Encoding.UTF8.GetBytes("magic").CopyTo(buffer, 257); if (Name.Length > 100) { name = Name.Substring(101, Name.Length); ArchiveEncoding.Default.GetBytes(name).CopyTo(buffer, 345); } if (Size >= 0x1FFFFFFFF) { #if PORTABLE byte[] bytes = BitConverter.GetBytes(Utility.HostToNetworkOrder(Size)); #else byte[] bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Size)); #endif var bytes12 = new byte[12]; bytes.CopyTo(bytes12, 12 - bytes.Length); bytes12[0] |= 0x80; bytes12.CopyTo(buffer, 124); } int crc = RecalculateChecksum(buffer); WriteOctalBytes(crc, buffer, 148, 8); output.Write(buffer, 0, buffer.Length); } internal bool Read(BinaryReader reader) { byte[] buffer = reader.ReadBytes(512); if (buffer.Length == 0) { return false; } if (buffer.Length < 512) { throw new InvalidOperationException(); } Name = ArchiveEncoding.Default.GetString(buffer, 0, 100).TrimNulls(); //Mode = ReadASCIIInt32Base8(buffer, 100, 7); //UserId = ReadASCIIInt32Base8(buffer, 108, 7); //GroupId = ReadASCIIInt32Base8(buffer, 116, 7); EntryType = (EntryType)buffer[156]; if ((buffer[124] & 0x80) == 0x80) // if size in binary { long sizeBigEndian = BitConverter.ToInt64(buffer, 0x80); #if PORTABLE Size = Utility.NetworkToHostOrder(sizeBigEndian); #else Size = IPAddress.NetworkToHostOrder(sizeBigEndian); #endif } else { Size = ReadASCIIInt64Base8(buffer, 124, 11); } long unixTimeStamp = ReadASCIIInt64Base8(buffer, 136, 11); LastModifiedTime = Epoch.AddSeconds(unixTimeStamp); Magic = ArchiveEncoding.Default.GetString(buffer, 257, 6).TrimNulls(); if (!string.IsNullOrEmpty(Magic) && "ustar ".Equals(Magic)) { string namePrefix = ArchiveEncoding.Default.GetString(buffer, 345, 157); namePrefix = namePrefix.TrimNulls(); if (!string.IsNullOrEmpty(namePrefix)) { Name = namePrefix + "/" + Name; } } if (EntryType != EntryType.LongName && Name.Length == 0) { return false; } return true; } private static void WriteStringBytes(string name, byte[] buffer, int offset, int length) { int i; for (i = 0; i < length - 1 && i < name.Length; ++i) { buffer[offset + i] = (byte)name[i]; } for (; i < length; ++i) { buffer[offset + i] = 0; } } private static void WriteOctalBytes(long value, byte[] buffer, int offset, int length) { string val = Convert.ToString(value, 8); int shift = length - val.Length - 1; for (int i = 0; i < shift; i++) { buffer[offset + i] = (byte)' '; } for (int i = 0; i < val.Length; i++) { buffer[offset + i + shift] = (byte)val[i]; } buffer[offset + length] = 0; } private static int ReadASCIIInt32Base8(byte[] buffer, int offset, int count) { string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls(); if (string.IsNullOrEmpty(s)) { return 0; } return Convert.ToInt32(s, 8); } private static long ReadASCIIInt64Base8(byte[] buffer, int offset, int count) { string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls(); if (string.IsNullOrEmpty(s)) { return 0; } return Convert.ToInt64(s, 8); } private static long ReadASCIIInt64(byte[] buffer, int offset, int count) { string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls(); if (string.IsNullOrEmpty(s)) { return 0; } return Convert.ToInt64(s); } internal static int RecalculateChecksum(byte[] buf) { // Set default value for checksum. That is 8 spaces. Encoding.UTF8.GetBytes(" ").CopyTo(buf, 148); // Calculate checksum int headerChecksum = 0; foreach (byte b in buf) { headerChecksum += b; } return headerChecksum; } internal static int RecalculateAltChecksum(byte[] buf) { Encoding.UTF8.GetBytes(" ").CopyTo(buf, 148); int headerChecksum = 0; foreach (byte b in buf) { if ((b & 0x80) == 0x80) { headerChecksum -= b ^ 0x80; } else { headerChecksum += b; } } return headerChecksum; } public long? DataStartPosition { get; set; } public string Magic { get; set; } } }