Mercurial > repos > SharpZipLib
diff Tar/TarHeader.cs @ 1:94e25b786321
Re #311: can't read ZIP file packed by Linux app Archive Manager/File Roller
Initial commit of clean SharpZipLib 0860 source. Only change is build paths.
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Sat, 30 Oct 2010 14:03:17 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Tar/TarHeader.cs Sat Oct 30 14:03:17 2010 +0000 @@ -0,0 +1,1156 @@ +// TarHeader.cs +// +// Copyright (C) 2001 Mike Krueger +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + + +/* The tar format and its POSIX successor PAX have a long history which makes for compatability + issues when creating and reading files. + + This is further complicated by a large number of programs with variations on formats + One common issue is the handling of names longer than 100 characters. + GNU style long names are currently supported. + +This is the ustar (Posix 1003.1) header. + +struct header +{ + char t_name[100]; // 0 Filename + char t_mode[8]; // 100 Permissions + char t_uid[8]; // 108 Numerical User ID + char t_gid[8]; // 116 Numerical Group ID + char t_size[12]; // 124 Filesize + char t_mtime[12]; // 136 st_mtime + char t_chksum[8]; // 148 Checksum + char t_typeflag; // 156 Type of File + char t_linkname[100]; // 157 Target of Links + char t_magic[6]; // 257 "ustar" or other... + char t_version[2]; // 263 Version fixed to 00 + char t_uname[32]; // 265 User Name + char t_gname[32]; // 297 Group Name + char t_devmajor[8]; // 329 Major for devices + char t_devminor[8]; // 337 Minor for devices + char t_prefix[155]; // 345 Prefix for t_name + char t_mfill[12]; // 500 Filler up to 512 +}; + +*/ + +using System; +using System.Text; + +namespace ICSharpCode.SharpZipLib.Tar +{ + + + /// <summary> + /// This class encapsulates the Tar Entry Header used in Tar Archives. + /// The class also holds a number of tar constants, used mostly in headers. + /// </summary> + public class TarHeader : ICloneable + { + #region Constants + /// <summary> + /// The length of the name field in a header buffer. + /// </summary> + public const int NAMELEN = 100; + + /// <summary> + /// The length of the mode field in a header buffer. + /// </summary> + public const int MODELEN = 8; + + /// <summary> + /// The length of the user id field in a header buffer. + /// </summary> + public const int UIDLEN = 8; + + /// <summary> + /// The length of the group id field in a header buffer. + /// </summary> + public const int GIDLEN = 8; + + /// <summary> + /// The length of the checksum field in a header buffer. + /// </summary> + public const int CHKSUMLEN = 8; + + /// <summary> + /// Offset of checksum in a header buffer. + /// </summary> + public const int CHKSUMOFS = 148; + + /// <summary> + /// The length of the size field in a header buffer. + /// </summary> + public const int SIZELEN = 12; + + /// <summary> + /// The length of the magic field in a header buffer. + /// </summary> + public const int MAGICLEN = 6; + + /// <summary> + /// The length of the version field in a header buffer. + /// </summary> + public const int VERSIONLEN = 2; + + /// <summary> + /// The length of the modification time field in a header buffer. + /// </summary> + public const int MODTIMELEN = 12; + + /// <summary> + /// The length of the user name field in a header buffer. + /// </summary> + public const int UNAMELEN = 32; + + /// <summary> + /// The length of the group name field in a header buffer. + /// </summary> + public const int GNAMELEN = 32; + + /// <summary> + /// The length of the devices field in a header buffer. + /// </summary> + public const int DEVLEN = 8; + + // + // LF_ constants represent the "type" of an entry + // + + /// <summary> + /// The "old way" of indicating a normal file. + /// </summary> + public const byte LF_OLDNORM = 0; + + /// <summary> + /// Normal file type. + /// </summary> + public const byte LF_NORMAL = (byte) '0'; + + /// <summary> + /// Link file type. + /// </summary> + public const byte LF_LINK = (byte) '1'; + + /// <summary> + /// Symbolic link file type. + /// </summary> + public const byte LF_SYMLINK = (byte) '2'; + + /// <summary> + /// Character device file type. + /// </summary> + public const byte LF_CHR = (byte) '3'; + + /// <summary> + /// Block device file type. + /// </summary> + public const byte LF_BLK = (byte) '4'; + + /// <summary> + /// Directory file type. + /// </summary> + public const byte LF_DIR = (byte) '5'; + + /// <summary> + /// FIFO (pipe) file type. + /// </summary> + public const byte LF_FIFO = (byte) '6'; + + /// <summary> + /// Contiguous file type. + /// </summary> + public const byte LF_CONTIG = (byte) '7'; + + /// <summary> + /// Posix.1 2001 global extended header + /// </summary> + public const byte LF_GHDR = (byte) 'g'; + + /// <summary> + /// Posix.1 2001 extended header + /// </summary> + public const byte LF_XHDR = (byte) 'x'; + + // POSIX allows for upper case ascii type as extensions + + /// <summary> + /// Solaris access control list file type + /// </summary> + public const byte LF_ACL = (byte) 'A'; + + /// <summary> + /// GNU dir dump file type + /// This is a dir entry that contains the names of files that were in the + /// dir at the time the dump was made + /// </summary> + public const byte LF_GNU_DUMPDIR = (byte) 'D'; + + /// <summary> + /// Solaris Extended Attribute File + /// </summary> + public const byte LF_EXTATTR = (byte) 'E' ; + + /// <summary> + /// Inode (metadata only) no file content + /// </summary> + public const byte LF_META = (byte) 'I'; + + /// <summary> + /// Identifies the next file on the tape as having a long link name + /// </summary> + public const byte LF_GNU_LONGLINK = (byte) 'K'; + + /// <summary> + /// Identifies the next file on the tape as having a long name + /// </summary> + public const byte LF_GNU_LONGNAME = (byte) 'L'; + + /// <summary> + /// Continuation of a file that began on another volume + /// </summary> + public const byte LF_GNU_MULTIVOL = (byte) 'M'; + + /// <summary> + /// For storing filenames that dont fit in the main header (old GNU) + /// </summary> + public const byte LF_GNU_NAMES = (byte) 'N'; + + /// <summary> + /// GNU Sparse file + /// </summary> + public const byte LF_GNU_SPARSE = (byte) 'S'; + + /// <summary> + /// GNU Tape/volume header ignore on extraction + /// </summary> + public const byte LF_GNU_VOLHDR = (byte) 'V'; + + /// <summary> + /// The magic tag representing a POSIX tar archive. (includes trailing NULL) + /// </summary> + public const string TMAGIC = "ustar "; + + /// <summary> + /// The magic tag representing an old GNU tar archive where version is included in magic and overwrites it + /// </summary> + public const string GNU_TMAGIC = "ustar "; + + const long timeConversionFactor = 10000000L; // 1 tick == 100 nanoseconds + readonly static DateTime dateTime1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0); + #endregion + + #region Constructors + + /// <summary> + /// Initialise a default TarHeader instance + /// </summary> + public TarHeader() + { + Magic = TMAGIC; + Version = " "; + + Name = ""; + LinkName = ""; + + UserId = defaultUserId; + GroupId = defaultGroupId; + UserName = defaultUser; + GroupName = defaultGroupName; + Size = 0; + } + + #endregion + + #region Properties + /// <summary> + /// Get/set the name for this tar entry. + /// </summary> + /// <exception cref="ArgumentNullException">Thrown when attempting to set the property to null.</exception> + public string Name + { + get { return name; } + set { + if ( value == null ) { + throw new ArgumentNullException("value"); + } + name = value; + } + } + + /// <summary> + /// Get the name of this entry. + /// </summary> + /// <returns>The entry's name.</returns> + [Obsolete("Use the Name property instead", true)] + public string GetName() + { + return name; + } + + /// <summary> + /// Get/set the entry's Unix style permission mode. + /// </summary> + public int Mode + { + get { return mode; } + set { mode = value; } + } + + + /// <summary> + /// The entry's user id. + /// </summary> + /// <remarks> + /// This is only directly relevant to unix systems. + /// The default is zero. + /// </remarks> + public int UserId + { + get { return userId; } + set { userId = value; } + } + + + /// <summary> + /// Get/set the entry's group id. + /// </summary> + /// <remarks> + /// This is only directly relevant to linux/unix systems. + /// The default value is zero. + /// </remarks> + public int GroupId + { + get { return groupId; } + set { groupId = value; } + } + + + /// <summary> + /// Get/set the entry's size. + /// </summary> + /// <exception cref="ArgumentOutOfRangeException">Thrown when setting the size to less than zero.</exception> + public long Size + { + get { return size; } + set { + if ( value < 0 ) { +#if NETCF_1_0 + throw new ArgumentOutOfRangeException("value"); +#else + throw new ArgumentOutOfRangeException("value", "Cannot be less than zero"); +#endif + } + size = value; + } + } + + + /// <summary> + /// Get/set the entry's modification time. + /// </summary> + /// <remarks> + /// The modification time is only accurate to within a second. + /// </remarks> + /// <exception cref="ArgumentOutOfRangeException">Thrown when setting the date time to less than 1/1/1970.</exception> + public DateTime ModTime + { + get { return modTime; } + set { + if ( value < dateTime1970 ) + { +#if NETCF_1_0 + throw new ArgumentOutOfRangeException("value"); +#else + throw new ArgumentOutOfRangeException("value", "ModTime cannot be before Jan 1st 1970"); +#endif + } + modTime = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second); + } + } + + + /// <summary> + /// Get the entry's checksum. This is only valid/updated after writing or reading an entry. + /// </summary> + public int Checksum + { + get { return checksum; } + } + + + /// <summary> + /// Get value of true if the header checksum is valid, false otherwise. + /// </summary> + public bool IsChecksumValid + { + get { return isChecksumValid; } + } + + + /// <summary> + /// Get/set the entry's type flag. + /// </summary> + public byte TypeFlag + { + get { return typeFlag; } + set { typeFlag = value; } + } + + + /// <summary> + /// The entry's link name. + /// </summary> + /// <exception cref="ArgumentNullException">Thrown when attempting to set LinkName to null.</exception> + public string LinkName + { + get { return linkName; } + set { + if ( value == null ) { + throw new ArgumentNullException("value"); + } + linkName = value; + } + } + + + /// <summary> + /// Get/set the entry's magic tag. + /// </summary> + /// <exception cref="ArgumentNullException">Thrown when attempting to set Magic to null.</exception> + public string Magic + { + get { return magic; } + set { + if ( value == null ) { + throw new ArgumentNullException("value"); + } + magic = value; + } + } + + + /// <summary> + /// The entry's version. + /// </summary> + /// <exception cref="ArgumentNullException">Thrown when attempting to set Version to null.</exception> + public string Version + { + get { + return version; + } + + set { + if ( value == null ) { + throw new ArgumentNullException("value"); + } + version = value; + } + } + + + /// <summary> + /// The entry's user name. + /// </summary> + public string UserName + { + get { return userName; } + set { + if (value != null) { + userName = value.Substring(0, Math.Min(UNAMELEN, value.Length)); + } + else { +#if NETCF_1_0 || NETCF_2_0 + string currentUser = "PocketPC"; +#else + string currentUser = Environment.UserName; +#endif + if (currentUser.Length > UNAMELEN) { + currentUser = currentUser.Substring(0, UNAMELEN); + } + userName = currentUser; + } + } + } + + + /// <summary> + /// Get/set the entry's group name. + /// </summary> + /// <remarks> + /// This is only directly relevant to unix systems. + /// </remarks> + public string GroupName + { + get { return groupName; } + set { + if ( value == null ) { + groupName = "None"; + } + else { + groupName = value; + } + } + } + + + /// <summary> + /// Get/set the entry's major device number. + /// </summary> + public int DevMajor + { + get { return devMajor; } + set { devMajor = value; } + } + + + /// <summary> + /// Get/set the entry's minor device number. + /// </summary> + public int DevMinor + { + get { return devMinor; } + set { devMinor = value; } + } + + #endregion + + #region ICloneable Members + /// <summary> + /// Create a new <see cref="TarHeader"/> that is a copy of the current instance. + /// </summary> + /// <returns>A new <see cref="Object"/> that is a copy of the current instance.</returns> + public object Clone() + { + return MemberwiseClone(); + } + #endregion + + /// <summary> + /// Parse TarHeader information from a header buffer. + /// </summary> + /// <param name = "header"> + /// The tar entry header buffer to get information from. + /// </param> + public void ParseBuffer(byte[] header) + { + if ( header == null ) + { + throw new ArgumentNullException("header"); + } + + int offset = 0; + + name = TarHeader.ParseName(header, offset, TarHeader.NAMELEN).ToString(); + offset += TarHeader.NAMELEN; + + mode = (int)TarHeader.ParseOctal(header, offset, TarHeader.MODELEN); + offset += TarHeader.MODELEN; + + UserId = (int)TarHeader.ParseOctal(header, offset, TarHeader.UIDLEN); + offset += TarHeader.UIDLEN; + + GroupId = (int)TarHeader.ParseOctal(header, offset, TarHeader.GIDLEN); + offset += TarHeader.GIDLEN; + + Size = TarHeader.ParseOctal(header, offset, TarHeader.SIZELEN); + offset += TarHeader.SIZELEN; + + ModTime = GetDateTimeFromCTime(TarHeader.ParseOctal(header, offset, TarHeader.MODTIMELEN)); + offset += TarHeader.MODTIMELEN; + + checksum = (int)TarHeader.ParseOctal(header, offset, TarHeader.CHKSUMLEN); + offset += TarHeader.CHKSUMLEN; + + TypeFlag = header[ offset++ ]; + + LinkName = TarHeader.ParseName(header, offset, TarHeader.NAMELEN).ToString(); + offset += TarHeader.NAMELEN; + + Magic = TarHeader.ParseName(header, offset, TarHeader.MAGICLEN).ToString(); + offset += TarHeader.MAGICLEN; + + Version = TarHeader.ParseName(header, offset, TarHeader.VERSIONLEN).ToString(); + offset += TarHeader.VERSIONLEN; + + UserName = TarHeader.ParseName(header, offset, TarHeader.UNAMELEN).ToString(); + offset += TarHeader.UNAMELEN; + + GroupName = TarHeader.ParseName(header, offset, TarHeader.GNAMELEN).ToString(); + offset += TarHeader.GNAMELEN; + + DevMajor = (int)TarHeader.ParseOctal(header, offset, TarHeader.DEVLEN); + offset += TarHeader.DEVLEN; + + DevMinor = (int)TarHeader.ParseOctal(header, offset, TarHeader.DEVLEN); + + // Fields past this point not currently parsed or used... + + isChecksumValid = Checksum == TarHeader.MakeCheckSum(header); + } + + /// <summary> + /// 'Write' header information to buffer provided, updating the <see cref="Checksum">check sum</see>. + /// </summary> + /// <param name="outBuffer">output buffer for header information</param> + public void WriteHeader(byte[] outBuffer) + { + if ( outBuffer == null ) + { + throw new ArgumentNullException("outBuffer"); + } + + int offset = 0; + + offset = GetNameBytes(Name, outBuffer, offset, NAMELEN); + offset = GetOctalBytes(mode, outBuffer, offset, MODELEN); + offset = GetOctalBytes(UserId, outBuffer, offset, UIDLEN); + offset = GetOctalBytes(GroupId, outBuffer, offset, GIDLEN); + + offset = GetLongOctalBytes(Size, outBuffer, offset, SIZELEN); + offset = GetLongOctalBytes(GetCTime(ModTime), outBuffer, offset, MODTIMELEN); + + int csOffset = offset; + for (int c = 0; c < CHKSUMLEN; ++c) + { + outBuffer[offset++] = (byte)' '; + } + + outBuffer[offset++] = TypeFlag; + + offset = GetNameBytes(LinkName, outBuffer, offset, NAMELEN); + offset = GetAsciiBytes(Magic, 0, outBuffer, offset, MAGICLEN); + offset = GetNameBytes(Version, outBuffer, offset, VERSIONLEN); + offset = GetNameBytes(UserName, outBuffer, offset, UNAMELEN); + offset = GetNameBytes(GroupName, outBuffer, offset, GNAMELEN); + + if ((TypeFlag == LF_CHR) || (TypeFlag == LF_BLK)) + { + offset = GetOctalBytes(DevMajor, outBuffer, offset, DEVLEN); + offset = GetOctalBytes(DevMinor, outBuffer, offset, DEVLEN); + } + + for ( ; offset < outBuffer.Length; ) + { + outBuffer[offset++] = 0; + } + + checksum = ComputeCheckSum(outBuffer); + + GetCheckSumOctalBytes(checksum, outBuffer, csOffset, CHKSUMLEN); + isChecksumValid = true; + } + + /// <summary> + /// Get a hash code for the current object. + /// </summary> + /// <returns>A hash code for the current object.</returns> + public override int GetHashCode() + { + return Name.GetHashCode(); + } + + /// <summary> + /// Determines if this instance is equal to the specified object. + /// </summary> + /// <param name="obj">The object to compare with.</param> + /// <returns>true if the objects are equal, false otherwise.</returns> + public override bool Equals(object obj) + { + TarHeader localHeader = obj as TarHeader; + + bool result; + if ( localHeader != null ) + { + result = (name == localHeader.name) + && (mode == localHeader.mode) + && (UserId == localHeader.UserId) + && (GroupId == localHeader.GroupId) + && (Size == localHeader.Size) + && (ModTime == localHeader.ModTime) + && (Checksum == localHeader.Checksum) + && (TypeFlag == localHeader.TypeFlag) + && (LinkName == localHeader.LinkName) + && (Magic == localHeader.Magic) + && (Version == localHeader.Version) + && (UserName == localHeader.UserName) + && (GroupName == localHeader.GroupName) + && (DevMajor == localHeader.DevMajor) + && (DevMinor == localHeader.DevMinor); + } + else + { + result = false; + } + return result; + } + + /// <summary> + /// Set defaults for values used when constructing a TarHeader instance. + /// </summary> + /// <param name="userId">Value to apply as a default for userId.</param> + /// <param name="userName">Value to apply as a default for userName.</param> + /// <param name="groupId">Value to apply as a default for groupId.</param> + /// <param name="groupName">Value to apply as a default for groupName.</param> + static internal void SetValueDefaults(int userId, string userName, int groupId, string groupName) + { + defaultUserId = userIdAsSet = userId; + defaultUser = userNameAsSet = userName; + defaultGroupId = groupIdAsSet = groupId; + defaultGroupName = groupNameAsSet = groupName; + } + + static internal void RestoreSetValues() + { + defaultUserId = userIdAsSet; + defaultUser = userNameAsSet; + defaultGroupId = groupIdAsSet; + defaultGroupName = groupNameAsSet; + } + + /// <summary> + /// Parse an octal string from a header buffer. + /// </summary> + /// <param name = "header">The header buffer from which to parse.</param> + /// <param name = "offset">The offset into the buffer from which to parse.</param> + /// <param name = "length">The number of header bytes to parse.</param> + /// <returns>The long equivalent of the octal string.</returns> + static public long ParseOctal(byte[] header, int offset, int length) + { + if ( header == null ) { + throw new ArgumentNullException("header"); + } + + long result = 0; + bool stillPadding = true; + + int end = offset + length; + for (int i = offset; i < end ; ++i) { + if (header[i] == 0) { + break; + } + + if (header[i] == (byte)' ' || header[i] == '0') { + if (stillPadding) { + continue; + } + + if (header[i] == (byte)' ') { + break; + } + } + + stillPadding = false; + + result = (result << 3) + (header[i] - '0'); + } + + return result; + } + + /// <summary> + /// Parse a name from a header buffer. + /// </summary> + /// <param name="header"> + /// The header buffer from which to parse. + /// </param> + /// <param name="offset"> + /// The offset into the buffer from which to parse. + /// </param> + /// <param name="length"> + /// The number of header bytes to parse. + /// </param> + /// <returns> + /// The name parsed. + /// </returns> + static public StringBuilder ParseName(byte[] header, int offset, int length) + { + if ( header == null ) { + throw new ArgumentNullException("header"); + } + + if ( offset < 0 ) { +#if NETCF_1_0 + throw new ArgumentOutOfRangeException("offset"); +#else + throw new ArgumentOutOfRangeException("offset", "Cannot be less than zero"); +#endif + } + + if ( length < 0 ) + { +#if NETCF_1_0 + throw new ArgumentOutOfRangeException("length"); +#else + throw new ArgumentOutOfRangeException("length", "Cannot be less than zero"); +#endif + } + + if ( offset + length > header.Length ) + { + throw new ArgumentException("Exceeds header size", "length"); + } + + StringBuilder result = new StringBuilder(length); + + for (int i = offset; i < offset + length; ++i) { + if (header[i] == 0) { + break; + } + result.Append((char)header[i]); + } + + return result; + } + + /// <summary> + /// Add <paramref name="name">name</paramref> to the buffer as a collection of bytes + /// </summary> + /// <param name="name">The name to add</param> + /// <param name="nameOffset">The offset of the first character</param> + /// <param name="buffer">The buffer to add to</param> + /// <param name="bufferOffset">The index of the first byte to add</param> + /// <param name="length">The number of characters/bytes to add</param> + /// <returns>The next free index in the <paramref name="buffer"/></returns> + public static int GetNameBytes(StringBuilder name, int nameOffset, byte[] buffer, int bufferOffset, int length) + { + if ( name == null ) { + throw new ArgumentNullException("name"); + } + + if ( buffer == null ) { + throw new ArgumentNullException("buffer"); + } + + return GetNameBytes(name.ToString(), nameOffset, buffer, bufferOffset, length); + } + + /// <summary> + /// Add <paramref name="name">name</paramref> to the buffer as a collection of bytes + /// </summary> + /// <param name="name">The name to add</param> + /// <param name="nameOffset">The offset of the first character</param> + /// <param name="buffer">The buffer to add to</param> + /// <param name="bufferOffset">The index of the first byte to add</param> + /// <param name="length">The number of characters/bytes to add</param> + /// <returns>The next free index in the <paramref name="buffer"/></returns> + public static int GetNameBytes(string name, int nameOffset, byte[] buffer, int bufferOffset, int length) + { + if ( name == null ) + { + throw new ArgumentNullException("name"); + } + + if ( buffer == null ) + { + throw new ArgumentNullException("buffer"); + } + + int i; + + for (i = 0 ; i < length - 1 && nameOffset + i < name.Length; ++i) { + buffer[bufferOffset + i] = (byte)name[nameOffset + i]; + } + + for (; i < length ; ++i) { + buffer[bufferOffset + i] = 0; + } + + return bufferOffset + length; + } + + /// <summary> + /// Add an entry name to the buffer + /// </summary> + /// <param name="name"> + /// The name to add + /// </param> + /// <param name="buffer"> + /// The buffer to add to + /// </param> + /// <param name="offset"> + /// The offset into the buffer from which to start adding + /// </param> + /// <param name="length"> + /// The number of header bytes to add + /// </param> + /// <returns> + /// The index of the next free byte in the buffer + /// </returns> + public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, int length) + { + + if ( name == null ) { + throw new ArgumentNullException("name"); + } + + if ( buffer == null ) { + throw new ArgumentNullException("buffer"); + } + + return GetNameBytes(name.ToString(), 0, buffer, offset, length); + } + + /// <summary> + /// Add an entry name to the buffer + /// </summary> + /// <param name="name">The name to add</param> + /// <param name="buffer">The buffer to add to</param> + /// <param name="offset">The offset into the buffer from which to start adding</param> + /// <param name="length">The number of header bytes to add</param> + /// <returns>The index of the next free byte in the buffer</returns> + public static int GetNameBytes(string name, byte[] buffer, int offset, int length) + { + + if ( name == null ) { + throw new ArgumentNullException("name"); + } + + if ( buffer == null ) + { + throw new ArgumentNullException("buffer"); + } + + return GetNameBytes(name, 0, buffer, offset, length); + } + + /// <summary> + /// Add a string to a buffer as a collection of ascii bytes. + /// </summary> + /// <param name="toAdd">The string to add</param> + /// <param name="nameOffset">The offset of the first character to add.</param> + /// <param name="buffer">The buffer to add to.</param> + /// <param name="bufferOffset">The offset to start adding at.</param> + /// <param name="length">The number of ascii characters to add.</param> + /// <returns>The next free index in the buffer.</returns> + public static int GetAsciiBytes(string toAdd, int nameOffset, byte[] buffer, int bufferOffset, int length ) + { + if ( toAdd == null ) { + throw new ArgumentNullException("toAdd"); + } + + if ( buffer == null ) { + throw new ArgumentNullException("buffer"); + } + + for (int i = 0 ; i < length && nameOffset + i < toAdd.Length; ++i) + { + buffer[bufferOffset + i] = (byte)toAdd[nameOffset + i]; + } + return bufferOffset + length; + } + + /// <summary> + /// Put an octal representation of a value into a buffer + /// </summary> + /// <param name = "value"> + /// the value to be converted to octal + /// </param> + /// <param name = "buffer"> + /// buffer to store the octal string + /// </param> + /// <param name = "offset"> + /// The offset into the buffer where the value starts + /// </param> + /// <param name = "length"> + /// The length of the octal string to create + /// </param> + /// <returns> + /// The offset of the character next byte after the octal string + /// </returns> + public static int GetOctalBytes(long value, byte[] buffer, int offset, int length) + { + if ( buffer == null ) { + throw new ArgumentNullException("buffer"); + } + + int localIndex = length - 1; + + // Either a space or null is valid here. We use NULL as per GNUTar + buffer[offset + localIndex] = 0; + --localIndex; + + if (value > 0) { + for ( long v = value; (localIndex >= 0) && (v > 0); --localIndex ) { + buffer[offset + localIndex] = (byte)((byte)'0' + (byte)(v & 7)); + v >>= 3; + } + } + + for ( ; localIndex >= 0; --localIndex ) { + buffer[offset + localIndex] = (byte)'0'; + } + + return offset + length; + } + + /// <summary> + /// Put an octal representation of a value into a buffer + /// </summary> + /// <param name = "value">Value to be convert to octal</param> + /// <param name = "buffer">The buffer to update</param> + /// <param name = "offset">The offset into the buffer to store the value</param> + /// <param name = "length">The length of the octal string</param> + /// <returns>Index of next byte</returns> + public static int GetLongOctalBytes(long value, byte[] buffer, int offset, int length) + { + return GetOctalBytes(value, buffer, offset, length); + } + + /// <summary> + /// Add the checksum integer to header buffer. + /// </summary> + /// <param name = "value"></param> + /// <param name = "buffer">The header buffer to set the checksum for</param> + /// <param name = "offset">The offset into the buffer for the checksum</param> + /// <param name = "length">The number of header bytes to update. + /// It's formatted differently from the other fields: it has 6 digits, a + /// null, then a space -- rather than digits, a space, then a null. + /// The final space is already there, from checksumming + /// </param> + /// <returns>The modified buffer offset</returns> + static int GetCheckSumOctalBytes(long value, byte[] buffer, int offset, int length) + { + TarHeader.GetOctalBytes(value, buffer, offset, length - 1); + return offset + length; + } + + /// <summary> + /// Compute the checksum for a tar entry header. + /// The checksum field must be all spaces prior to this happening + /// </summary> + /// <param name = "buffer">The tar entry's header buffer.</param> + /// <returns>The computed checksum.</returns> + static int ComputeCheckSum(byte[] buffer) + { + int sum = 0; + for (int i = 0; i < buffer.Length; ++i) { + sum += buffer[i]; + } + return sum; + } + + /// <summary> + /// Make a checksum for a tar entry ignoring the checksum contents. + /// </summary> + /// <param name = "buffer">The tar entry's header buffer.</param> + /// <returns>The checksum for the buffer</returns> + static int MakeCheckSum(byte[] buffer) + { + int sum = 0; + for ( int i = 0; i < CHKSUMOFS; ++i ) + { + sum += buffer[i]; + } + + for ( int i = 0; i < TarHeader.CHKSUMLEN; ++i) + { + sum += (byte)' '; + } + + for (int i = CHKSUMOFS + CHKSUMLEN; i < buffer.Length; ++i) + { + sum += buffer[i]; + } + return sum; + } + + static int GetCTime(System.DateTime dateTime) + { + return unchecked((int)((dateTime.Ticks - dateTime1970.Ticks) / timeConversionFactor)); + } + + static DateTime GetDateTimeFromCTime(long ticks) + { + DateTime result; + + try { + result = new DateTime(dateTime1970.Ticks + ticks * timeConversionFactor); + } + catch(ArgumentOutOfRangeException) { + result = dateTime1970; + } + return result; + } + + #region Instance Fields + string name; + int mode; + int userId; + int groupId; + long size; + DateTime modTime; + int checksum; + bool isChecksumValid; + byte typeFlag; + string linkName; + string magic; + string version; + string userName; + string groupName; + int devMajor; + int devMinor; + #endregion + + #region Class Fields + // Values used during recursive operations. + static internal int userIdAsSet; + static internal int groupIdAsSet; + static internal string userNameAsSet; + static internal string groupNameAsSet = "None"; + + static internal int defaultUserId; + static internal int defaultGroupId; + static internal string defaultGroupName = "None"; + static internal string defaultUser; + #endregion + } +} + +/* The original Java file had this header: + * +** Authored by Timothy Gerard Endres +** <mailto:time@gjt.org> <http://www.trustice.com> +** +** This work has been placed into the public domain. +** You may use this work in any way and for any purpose you wish. +** +** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, +** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR +** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY +** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR +** REDISTRIBUTION OF THIS SOFTWARE. +** +*/