// 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 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"
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
// 500 End
char t_mfill[12]; // 500 Filler up to 512
};
*/
using System;
using System.Text;
namespace ICSharpCode.SharpZipLib.Tar
{
///
/// This class encapsulates the Tar Entry Header used in Tar Archives.
/// The class also holds a number of tar constants, used mostly in headers.
///
public class TarHeader : ICloneable
{
///
/// The length of the name field in a header buffer.
///
public readonly static int NAMELEN = 100;
///
/// The length of the mode field in a header buffer.
///
public readonly static int MODELEN = 8;
///
/// The length of the user id field in a header buffer.
///
public readonly static int UIDLEN = 8;
///
/// The length of the group id field in a header buffer.
///
public readonly static int GIDLEN = 8;
///
/// The length of the checksum field in a header buffer.
///
public readonly static int CHKSUMLEN = 8;
///
/// The length of the size field in a header buffer.
///
public readonly static int SIZELEN = 12;
///
/// The length of the magic field in a header buffer.
///
public readonly static int MAGICLEN = 6;
///
/// The length of the version field in a header buffer.
///
public readonly static int VERSIONLEN = 2;
///
/// The length of the modification time field in a header buffer.
///
public readonly static int MODTIMELEN = 12;
///
/// The length of the user name field in a header buffer.
///
public readonly static int UNAMELEN = 32;
///
/// The length of the group name field in a header buffer.
///
public readonly static int GNAMELEN = 32;
///
/// The length of the devices field in a header buffer.
///
public readonly static int DEVLEN = 8;
///
/// LF_ constants represents the "type" of an entry
///
///
///
/// This is the "old way" of indicating a normal file.
///
public const byte LF_OLDNORM = 0;
///
/// Normal file type.
///
public const byte LF_NORMAL = (byte) '0';
///
/// Link file type.
///
public const byte LF_LINK = (byte) '1';
///
/// Symbolic link file type.
///
public const byte LF_SYMLINK = (byte) '2';
///
/// Character device file type.
///
public const byte LF_CHR = (byte) '3';
///
/// Block device file type.
///
public const byte LF_BLK = (byte) '4';
///
/// Directory file type.
///
public const byte LF_DIR = (byte) '5';
///
/// FIFO (pipe) file type.
///
public const byte LF_FIFO = (byte) '6';
///
/// Contiguous file type.
///
public const byte LF_CONTIG = (byte) '7';
///
/// Posix.1 2001 global extended header
///
///
public const byte LF_GHDR = (byte) 'g';
///
/// Posix.1 2001 extended header
///
public readonly static byte LF_XHDR = (byte) 'x';
// POSIX allows for upper case ascii type as extensions
// Solaris access control list
public const byte LF_ACL = (byte) 'A';
// This is a dir entry that contains the names of files that were in the
// dir at the time the dump was made
public const byte LF_GNU_DUMPDIR = (byte) 'D';
// Solaris Extended Attribute File
public const byte LF_EXTATTR = (byte) 'E' ;
// Inode (metadata only) no file content
public const byte LF_META = (byte) 'I';
// Identifies the next file on the tape as having a long link name
public const byte LF_GNU_LONGLINK = (byte) 'K';
// Identifies the next file on the tape as having a long name
public const byte LF_GNU_LONGNAME = (byte) 'L';
// Continuation of a file that began on another volume
public const byte LF_GNU_MULTIVOL = (byte) 'M';
// For storing filenames that dont fit in the main header (old GNU)
public const byte LF_GNU_NAMES = (byte) 'N';
// Sparse file
public const byte LF_GNU_SPARSE = (byte) 'S';
// Tape/volume header ignore on extraction
public const byte LF_GNU_VOLHDR = (byte) 'V';
///
/// The magic tag representing a POSIX tar archive. (includes trailing NULL)
///
public readonly static string TMAGIC = "ustar ";
///
/// The magic tag representing an old GNU tar archive where version is included in magic and overwrites it
///
public readonly static string GNU_TMAGIC = "ustar ";
///
/// The entry's name.
///
public StringBuilder name;
///
/// The entry's permission mode.
///
public int mode;
///
/// The entry's user id.
///
public int userId;
///
/// The entry's group id.
///
public int groupId;
///
/// The entry's size.
///
public long size;
///
/// The entry's modification time.
///
public DateTime modTime;
///
/// The entry's checksum.
///
public int checkSum;
///
/// The entry's type flag.
///
public byte typeFlag;
///
/// The entry's link name.
///
public StringBuilder linkName;
///
/// The entry's magic tag.
///
public StringBuilder magic;
///
/// The entry's version.
///
public StringBuilder version;
///
/// The entry's user name.
///
public StringBuilder userName;
///
/// The entry's group name.
///
public StringBuilder groupName;
///
/// The entry's major device number.
///
public int devMajor;
///
/// The entry's minor device number.
///
public int devMinor;
public TarHeader()
{
this.magic = new StringBuilder(TarHeader.TMAGIC);
this.version = new StringBuilder(" ");
this.name = new StringBuilder();
this.linkName = new StringBuilder();
string user = Environment.UserName;
// string user = "PocketPC";
// string user = "Everyone";
if (user.Length > 31) {
user = user.Substring(0, 31);
}
this.userId = 1003; // -jr- was 0
this.groupId = 513; // -jr- was 0
this.userName = new StringBuilder(user);
// -jr-
// this.groupName = new StringBuilder(String.Empty);
// this.groupName = new StringBuilder("Everyone"); Attempt2
this.groupName = new StringBuilder("None"); // Gnu compatible
this.size = 0;
}
///
/// TarHeaders can be cloned.
///
public object Clone()
{
TarHeader hdr = new TarHeader();
hdr.name = (this.name == null) ? null : new StringBuilder(this.name.ToString());
hdr.mode = this.mode;
hdr.userId = this.userId;
hdr.groupId = this.groupId;
hdr.size = this.size;
hdr.modTime = this.modTime;
hdr.checkSum = this.checkSum;
hdr.typeFlag = this.typeFlag;
hdr.linkName = (this.linkName == null) ? null : new StringBuilder(this.linkName.ToString());
hdr.magic = (this.magic == null) ? null : new StringBuilder(this.magic.ToString());
hdr.version = (this.version == null) ? null : new StringBuilder(this.version.ToString());
hdr.userName = (this.userName == null) ? null : new StringBuilder(this.userName.ToString());
hdr.groupName = (this.groupName == null) ? null : new StringBuilder(this.groupName.ToString());
hdr.devMajor = this.devMajor;
hdr.devMinor = this.devMinor;
return hdr;
}
///
/// Get the name of this entry.
///
///
/// The entry's name.
///
public string GetName()
{
return this.name.ToString();
}
///
/// Parse an octal string from a header buffer. This is used for the
/// file permission mode value.
///
///
/// The header buffer from which to parse.
///
///
/// The offset into the buffer from which to parse.
///
///
/// The number of header bytes to parse.
///
///
/// The long value of the octal string.
///
public static long ParseOctal(byte[] header, int offset, int length)
{
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;
}
///
/// Parse an entry name from a header buffer.
///
///
/// The header buffer from which to parse.
///
///
/// The offset into the buffer from which to parse.
///
///
/// The number of header bytes to parse.
///
///
/// The header's entry name.
///
public static StringBuilder ParseName(byte[] header, int offset, int 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;
}
public static int GetNameBytes(StringBuilder name, int nameOffset, byte[] buf, int bufferOffset, int length)
{
int i;
for (i = 0 ; i < length && nameOffset + i < name.Length; ++i)
{
buf[bufferOffset + i] = (byte)name[nameOffset + i];
}
for (; i < length ; ++i)
{
buf[bufferOffset + i] = 0;
}
return bufferOffset + length;
}
///
/// Determine the number of bytes in an entry name.
///
///
///
///
/// The header buffer from which to parse.
///
///
/// The offset into the buffer from which to parse.
///
///
/// The number of header bytes to parse.
///
///
/// The number of bytes in a header's entry name.
///
public static int GetNameBytes(StringBuilder name, byte[] buf, int offset, int length)
{
return GetNameBytes(name, 0, buf, offset, length);
}
///
/// Parse an octal integer from a header buffer.
///
///
///
///
/// The header buffer from which to parse.
///
///
/// The offset into the buffer from which to parse.
///
///
/// The number of header bytes to parse.
///
///
/// The integer value of the octal bytes.
///
public static int GetOctalBytes(long val, byte[] buf, int offset, int length)
{
// TODO check for values too large...
int idx = length - 1;
// Either a space or null is valid here. We use NULL as per GNUTar
buf[offset + idx] = 0;
--idx;
if (val > 0)
{
for (long v = val; idx >= 0 && v > 0; --idx)
{
buf[offset + idx] = (byte)((byte)'0' + (byte)(v & 7));
v >>= 3;
}
}
for (; idx >= 0; --idx)
{
buf[offset + idx] = (byte)'0';
}
return offset + length;
}
///
/// Parse an octal long integer from a header buffer.
///
///
///
///
/// The header buffer from which to parse.
///
///
/// The offset into the buffer from which to parse.
///
///
/// The number of header bytes to parse.
///
///
/// The long value of the octal bytes.
///
public static int GetLongOctalBytes(long val, byte[] buf, int offset, int length)
{
return GetOctalBytes(val, buf, offset, length);
}
///
/// Add the checksum octal integer to header buffer.
///
///
///
///
/// The header buffer to set the checksum for
///
///
/// The offset into the buffer for the checksum
///
///
/// 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
///
///
/// The modified buffer offset
///
private static int GetCheckSumOctalBytes(long val, byte[] buf, int offset, int length)
{
TarHeader.GetOctalBytes(val, buf, offset, length - 1);
// buf[offset + length - 1] = (byte)' '; -jr- 23-Jan-2004 this causes failure!!!
// buf[offset + length - 2] = 0;
return offset + length;
}
///
/// Compute the checksum for a tar entry header.
/// The checksum field must be all spaces prior to this happening
///
///
/// The tar entry's header buffer.
///
///
/// The computed checksum.
///
private static long ComputeCheckSum(byte[] buf)
{
long sum = 0;
for (int i = 0; i < buf.Length; ++i)
{
sum += buf[i];
}
return sum;
}
readonly static long timeConversionFactor = 10000000L; // -jr- 1 tick == 100 nanoseconds
readonly static DateTime datetTime1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0);
// readonly static DateTime datetTime1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0).ToUniversalTime(); // -jr- Should be UTC? doesnt match Gnutar if this is so though, why?
static int GetCTime(System.DateTime dateTime)
{
return (int)((dateTime.Ticks - datetTime1970.Ticks) / timeConversionFactor);
}
static DateTime GetDateTimeFromCTime(long ticks)
{
return new DateTime(datetTime1970.Ticks + ticks * timeConversionFactor);
}
///
/// Parse TarHeader information from a header buffer.
///
///
/// The tar entry header buffer to get information from.
///
public void ParseBuffer(byte[] header)
{
int offset = 0;
name = TarHeader.ParseName(header, offset, TarHeader.NAMELEN);
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);
offset += TarHeader.NAMELEN;
magic = TarHeader.ParseName(header, offset, TarHeader.MAGICLEN);
offset += TarHeader.MAGICLEN;
version = TarHeader.ParseName(header, offset, TarHeader.VERSIONLEN);
offset += TarHeader.VERSIONLEN;
userName = TarHeader.ParseName(header, offset, TarHeader.UNAMELEN);
offset += TarHeader.UNAMELEN;
groupName = TarHeader.ParseName(header, offset, TarHeader.GNAMELEN);
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...
}
///
/// 'Write' header information to buffer provided
///
/// output buffer for header information
public void WriteHeader(byte[] outbuf)
{
int offset = 0;
offset = GetNameBytes(this.name, outbuf, offset, TarHeader.NAMELEN);
offset = GetOctalBytes(this.mode, outbuf, offset, TarHeader.MODELEN);
offset = GetOctalBytes(this.userId, outbuf, offset, TarHeader.UIDLEN);
offset = GetOctalBytes(this.groupId, outbuf, offset, TarHeader.GIDLEN);
long size = this.size;
offset = GetLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN);
offset = GetLongOctalBytes(GetCTime(this.modTime), outbuf, offset, TarHeader.MODTIMELEN);
int csOffset = offset;
for (int c = 0; c < TarHeader.CHKSUMLEN; ++c)
{
outbuf[offset++] = (byte)' ';
}
outbuf[offset++] = this.typeFlag;
offset = GetNameBytes(this.linkName, outbuf, offset, NAMELEN);
offset = GetNameBytes(this.magic, outbuf, offset, MAGICLEN);
offset = GetNameBytes(this.version, outbuf, offset, VERSIONLEN);
offset = GetNameBytes(this.userName, outbuf, offset, UNAMELEN);
offset = GetNameBytes(this.groupName, outbuf, offset, GNAMELEN);
if (this.typeFlag == LF_CHR || this.typeFlag == LF_BLK)
{
offset = GetOctalBytes(this.devMajor, outbuf, offset, DEVLEN);
offset = GetOctalBytes(this.devMinor, outbuf, offset, DEVLEN);
}
for ( ; offset < outbuf.Length; )
{
outbuf[offset++] = 0;
}
long checkSum = ComputeCheckSum(outbuf);
GetCheckSumOctalBytes(checkSum, outbuf, csOffset, CHKSUMLEN);
}
}
}
/* The original Java file had this header:
*
** Authored by Timothy Gerard Endres
**
**
** 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.
**
*/