3 // Copyright (C) 2001 Mike Krueger
\r
5 // This program is free software; you can redistribute it and/or
\r
6 // modify it under the terms of the GNU General Public License
\r
7 // as published by the Free Software Foundation; either version 2
\r
8 // of the License, or (at your option) any later version.
\r
10 // This program is distributed in the hope that it will be useful,
\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 // GNU General Public License for more details.
\r
15 // You should have received a copy of the GNU General Public License
\r
16 // along with this program; if not, write to the Free Software
\r
17 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
\r
19 // Linking this library statically or dynamically with other modules is
\r
20 // making a combined work based on this library. Thus, the terms and
\r
21 // conditions of the GNU General Public License cover the whole
\r
24 // As a special exception, the copyright holders of this library give you
\r
25 // permission to link this library with independent modules to produce an
\r
26 // executable, regardless of the license terms of these independent
\r
27 // modules, and to copy and distribute the resulting executable under
\r
28 // terms of your choice, provided that you also meet, for each linked
\r
29 // independent module, the terms and conditions of the license of that
\r
30 // module. An independent module is a module which is not derived from
\r
31 // or based on this library. If you modify this library, you may extend
\r
32 // this exception to your version of the library, but you are not
\r
33 // obligated to do so. If you do not wish to do so, delete this
\r
34 // exception statement from your version.
\r
37 /* The tar format and its POSIX successor PAX have a long history which makes for compatability
\r
38 issues when creating and reading files...
\r
40 This is the ustar (Posix 1003.1) header.
\r
44 char t_name[100]; // 0 Filename
\r
45 char t_mode[8]; // 100 Permissions
\r
46 char t_uid[8]; // 108 Numerical User ID
\r
47 char t_gid[8]; // 116 Numerical Group ID
\r
48 char t_size[12]; // 124 Filesize
\r
49 char t_mtime[12]; // 136 st_mtime
\r
50 char t_chksum[8]; // 148 Checksum
\r
51 char t_typeflag; // 156 Type of File
\r
52 char t_linkname[100]; // 157 Target of Links
\r
53 char t_magic[6]; // 257 "ustar"
\r
54 char t_version[2]; // 263 Version fixed to 00
\r
55 char t_uname[32]; // 265 User Name
\r
56 char t_gname[32]; // 297 Group Name
\r
57 char t_devmajor[8]; // 329 Major for devices
\r
58 char t_devminor[8]; // 337 Minor for devices
\r
59 char t_prefix[155]; // 345 Prefix for t_name
\r
61 char t_mfill[12]; // 500 Filler up to 512
\r
69 namespace ICSharpCode.SharpZipLib.Tar
\r
74 /// This class encapsulates the Tar Entry Header used in Tar Archives.
\r
75 /// The class also holds a number of tar constants, used mostly in headers.
\r
77 public class TarHeader : ICloneable
\r
80 /// The length of the name field in a header buffer.
\r
82 public readonly static int NAMELEN = 100;
\r
85 /// The length of the mode field in a header buffer.
\r
87 public readonly static int MODELEN = 8;
\r
90 /// The length of the user id field in a header buffer.
\r
92 public readonly static int UIDLEN = 8;
\r
95 /// The length of the group id field in a header buffer.
\r
97 public readonly static int GIDLEN = 8;
\r
100 /// The length of the checksum field in a header buffer.
\r
102 public readonly static int CHKSUMLEN = 8;
\r
105 /// The length of the size field in a header buffer.
\r
107 public readonly static int SIZELEN = 12;
\r
110 /// The length of the magic field in a header buffer.
\r
112 public readonly static int MAGICLEN = 6;
\r
115 /// The length of the version field in a header buffer.
\r
117 public readonly static int VERSIONLEN = 2;
\r
120 /// The length of the modification time field in a header buffer.
\r
122 public readonly static int MODTIMELEN = 12;
\r
125 /// The length of the user name field in a header buffer.
\r
127 public readonly static int UNAMELEN = 32;
\r
130 /// The length of the group name field in a header buffer.
\r
132 public readonly static int GNAMELEN = 32;
\r
135 /// The length of the devices field in a header buffer.
\r
137 public readonly static int DEVLEN = 8;
\r
140 /// LF_ constants represents the "type" of an entry
\r
145 /// This is the "old way" of indicating a normal file.
\r
147 public const byte LF_OLDNORM = 0;
\r
150 /// Normal file type.
\r
152 public const byte LF_NORMAL = (byte) '0';
\r
155 /// Link file type.
\r
157 public const byte LF_LINK = (byte) '1';
\r
160 /// Symbolic link file type.
\r
162 public const byte LF_SYMLINK = (byte) '2';
\r
165 /// Character device file type.
\r
167 public const byte LF_CHR = (byte) '3';
\r
170 /// Block device file type.
\r
172 public const byte LF_BLK = (byte) '4';
\r
175 /// Directory file type.
\r
177 public const byte LF_DIR = (byte) '5';
\r
180 /// FIFO (pipe) file type.
\r
182 public const byte LF_FIFO = (byte) '6';
\r
185 /// Contiguous file type.
\r
187 public const byte LF_CONTIG = (byte) '7';
\r
190 /// Posix.1 2001 global extended header
\r
193 public const byte LF_GHDR = (byte) 'g';
\r
196 /// Posix.1 2001 extended header
\r
198 public readonly static byte LF_XHDR = (byte) 'x';
\r
203 // POSIX allows for upper case ascii type as extensions
\r
205 // Solaris access control list
\r
206 public const byte LF_ACL = (byte) 'A';
\r
208 // This is a dir entry that contains the names of files that were in the
\r
209 // dir at the time the dump was made
\r
210 public const byte LF_GNU_DUMPDIR = (byte) 'D';
\r
212 // Solaris Extended Attribute File
\r
213 public const byte LF_EXTATTR = (byte) 'E' ;
\r
215 // Inode (metadata only) no file content
\r
216 public const byte LF_META = (byte) 'I';
\r
218 // Identifies the next file on the tape as having a long link name
\r
219 public const byte LF_GNU_LONGLINK = (byte) 'K';
\r
221 // Identifies the next file on the tape as having a long name
\r
222 public const byte LF_GNU_LONGNAME = (byte) 'L';
\r
224 // Continuation of a file that began on another volume
\r
225 public const byte LF_GNU_MULTIVOL = (byte) 'M';
\r
227 // For storing filenames that dont fit in the main header (old GNU)
\r
228 public const byte LF_GNU_NAMES = (byte) 'N';
\r
231 public const byte LF_GNU_SPARSE = (byte) 'S';
\r
233 // Tape/volume header ignore on extraction
\r
234 public const byte LF_GNU_VOLHDR = (byte) 'V';
\r
237 /// The magic tag representing a POSIX tar archive. (includes trailing NULL)
\r
239 public readonly static string TMAGIC = "ustar ";
\r
242 /// The magic tag representing an old GNU tar archive where version is included in magic and overwrites it
\r
244 public readonly static string GNU_TMAGIC = "ustar ";
\r
247 /// The entry's name.
\r
249 public StringBuilder name;
\r
252 /// The entry's permission mode.
\r
257 /// The entry's user id.
\r
262 /// The entry's group id.
\r
264 public int groupId;
\r
267 /// The entry's size.
\r
272 /// The entry's modification time.
\r
274 public DateTime modTime;
\r
277 /// The entry's checksum.
\r
279 public int checkSum;
\r
282 /// The entry's type flag.
\r
284 public byte typeFlag;
\r
287 /// The entry's link name.
\r
289 public StringBuilder linkName;
\r
292 /// The entry's magic tag.
\r
294 public StringBuilder magic;
\r
297 /// The entry's version.
\r
299 public StringBuilder version;
\r
302 /// The entry's user name.
\r
304 public StringBuilder userName;
\r
307 /// The entry's group name.
\r
309 public StringBuilder groupName;
\r
312 /// The entry's major device number.
\r
314 public int devMajor;
\r
317 /// The entry's minor device number.
\r
319 public int devMinor;
\r
323 this.magic = new StringBuilder(TarHeader.TMAGIC);
\r
324 this.version = new StringBuilder(" ");
\r
326 this.name = new StringBuilder();
\r
327 this.linkName = new StringBuilder();
\r
329 string user = Environment.UserName;
\r
330 // string user = "PocketPC";
\r
331 // string user = "Everyone";
\r
333 if (user.Length > 31) {
\r
334 user = user.Substring(0, 31);
\r
337 this.userId = 1003; // -jr- was 0
\r
338 this.groupId = 513; // -jr- was 0
\r
339 this.userName = new StringBuilder(user);
\r
341 // this.groupName = new StringBuilder(String.Empty);
\r
342 // this.groupName = new StringBuilder("Everyone"); Attempt2
\r
343 this.groupName = new StringBuilder("None"); // Gnu compatible
\r
348 /// TarHeaders can be cloned.
\r
350 public object Clone()
\r
352 TarHeader hdr = new TarHeader();
\r
354 hdr.name = (this.name == null) ? null : new StringBuilder(this.name.ToString());
\r
355 hdr.mode = this.mode;
\r
356 hdr.userId = this.userId;
\r
357 hdr.groupId = this.groupId;
\r
358 hdr.size = this.size;
\r
359 hdr.modTime = this.modTime;
\r
360 hdr.checkSum = this.checkSum;
\r
361 hdr.typeFlag = this.typeFlag;
\r
362 hdr.linkName = (this.linkName == null) ? null : new StringBuilder(this.linkName.ToString());
\r
363 hdr.magic = (this.magic == null) ? null : new StringBuilder(this.magic.ToString());
\r
364 hdr.version = (this.version == null) ? null : new StringBuilder(this.version.ToString());
\r
365 hdr.userName = (this.userName == null) ? null : new StringBuilder(this.userName.ToString());
\r
366 hdr.groupName = (this.groupName == null) ? null : new StringBuilder(this.groupName.ToString());
\r
367 hdr.devMajor = this.devMajor;
\r
368 hdr.devMinor = this.devMinor;
\r
374 /// Get the name of this entry.
\r
377 /// The entry's name.
\r
379 public string GetName()
\r
381 return this.name.ToString();
\r
385 /// Parse an octal string from a header buffer. This is used for the
\r
386 /// file permission mode value.
\r
388 /// <param name = "header">
\r
389 /// The header buffer from which to parse.
\r
391 /// <param name = "offset">
\r
392 /// The offset into the buffer from which to parse.
\r
394 /// <param name = "length">
\r
395 /// The number of header bytes to parse.
\r
398 /// The long value of the octal string.
\r
400 public static long ParseOctal(byte[] header, int offset, int length)
\r
403 bool stillPadding = true;
\r
405 int end = offset + length;
\r
406 for (int i = offset; i < end ; ++i)
\r
408 if (header[i] == 0)
\r
413 if (header[i] == (byte)' ' || header[i] == '0')
\r
420 if (header[i] == (byte)' ')
\r
426 stillPadding = false;
\r
428 result = (result << 3) + (header[i] - '0');
\r
435 /// Parse an entry name from a header buffer.
\r
437 /// <param name="header">
\r
438 /// The header buffer from which to parse.
\r
440 /// <param name="offset">
\r
441 /// The offset into the buffer from which to parse.
\r
443 /// <param name="length">
\r
444 /// The number of header bytes to parse.
\r
447 /// The header's entry name.
\r
449 public static StringBuilder ParseName(byte[] header, int offset, int length)
\r
451 StringBuilder result = new StringBuilder(length);
\r
453 for (int i = offset; i < offset + length; ++i)
\r
455 if (header[i] == 0)
\r
459 result.Append((char)header[i]);
\r
465 public static int GetNameBytes(StringBuilder name, int nameOffset, byte[] buf, int bufferOffset, int length)
\r
469 for (i = 0 ; i < length && nameOffset + i < name.Length; ++i)
\r
471 buf[bufferOffset + i] = (byte)name[nameOffset + i];
\r
474 for (; i < length ; ++i)
\r
476 buf[bufferOffset + i] = 0;
\r
479 return bufferOffset + length;
\r
483 /// Determine the number of bytes in an entry name.
\r
485 /// <param name="name">
\r
487 /// <param name="buf">
\r
488 /// The header buffer from which to parse.
\r
490 /// <param name="offset">
\r
491 /// The offset into the buffer from which to parse.
\r
493 /// <param name="length">
\r
494 /// The number of header bytes to parse.
\r
497 /// The number of bytes in a header's entry name.
\r
499 public static int GetNameBytes(StringBuilder name, byte[] buf, int offset, int length)
\r
501 return GetNameBytes(name, 0, buf, offset, length);
\r
505 /// Parse an octal integer from a header buffer.
\r
507 /// <param name = "val">
\r
509 /// <param name = "buf">
\r
510 /// The header buffer from which to parse.
\r
512 /// <param name = "offset">
\r
513 /// The offset into the buffer from which to parse.
\r
515 /// <param name = "length">
\r
516 /// The number of header bytes to parse.
\r
519 /// The integer value of the octal bytes.
\r
521 public static int GetOctalBytes(long val, byte[] buf, int offset, int length)
\r
523 // TODO check for values too large...
\r
525 int idx = length - 1;
\r
527 // Either a space or null is valid here. We use NULL as per GNUTar
\r
528 buf[offset + idx] = 0;
\r
533 for (long v = val; idx >= 0 && v > 0; --idx)
\r
535 buf[offset + idx] = (byte)((byte)'0' + (byte)(v & 7));
\r
540 for (; idx >= 0; --idx)
\r
542 buf[offset + idx] = (byte)'0';
\r
545 return offset + length;
\r
549 /// Parse an octal long integer from a header buffer.
\r
551 /// <param name = "val">
\r
553 /// <param name = "buf">
\r
554 /// The header buffer from which to parse.
\r
556 /// <param name = "offset">
\r
557 /// The offset into the buffer from which to parse.
\r
559 /// <param name = "length">
\r
560 /// The number of header bytes to parse.
\r
563 /// The long value of the octal bytes.
\r
565 public static int GetLongOctalBytes(long val, byte[] buf, int offset, int length)
\r
567 return GetOctalBytes(val, buf, offset, length);
\r
571 /// Add the checksum octal integer to header buffer.
\r
573 /// <param name = "val">
\r
575 /// <param name = "buf">
\r
576 /// The header buffer to set the checksum for
\r
578 /// <param name = "offset">
\r
579 /// The offset into the buffer for the checksum
\r
581 /// <param name = "length">
\r
582 /// The number of header bytes to update.
\r
583 /// It's formatted differently from the other fields: it has 6 digits, a
\r
584 /// null, then a space -- rather than digits, a space, then a null.
\r
585 /// The final space is already there, from checksumming
\r
589 /// The modified buffer offset
\r
591 private static int GetCheckSumOctalBytes(long val, byte[] buf, int offset, int length)
\r
593 TarHeader.GetOctalBytes(val, buf, offset, length - 1);
\r
594 // buf[offset + length - 1] = (byte)' '; -jr- 23-Jan-2004 this causes failure!!!
\r
595 // buf[offset + length - 2] = 0;
\r
596 return offset + length;
\r
600 /// Compute the checksum for a tar entry header.
\r
601 /// The checksum field must be all spaces prior to this happening
\r
603 /// <param name = "buf">
\r
604 /// The tar entry's header buffer.
\r
607 /// The computed checksum.
\r
609 private static long ComputeCheckSum(byte[] buf)
\r
612 for (int i = 0; i < buf.Length; ++i)
\r
619 readonly static long timeConversionFactor = 10000000L; // -jr- 1 tick == 100 nanoseconds
\r
620 readonly static DateTime datetTime1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0);
\r
621 // 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?
\r
623 static int GetCTime(System.DateTime dateTime)
\r
625 return (int)((dateTime.Ticks - datetTime1970.Ticks) / timeConversionFactor);
\r
628 static DateTime GetDateTimeFromCTime(long ticks)
\r
630 return new DateTime(datetTime1970.Ticks + ticks * timeConversionFactor);
\r
634 /// Parse TarHeader information from a header buffer.
\r
636 /// <param name = "header">
\r
637 /// The tar entry header buffer to get information from.
\r
639 public void ParseBuffer(byte[] header)
\r
643 name = TarHeader.ParseName(header, offset, TarHeader.NAMELEN);
\r
644 offset += TarHeader.NAMELEN;
\r
646 mode = (int)TarHeader.ParseOctal(header, offset, TarHeader.MODELEN);
\r
647 offset += TarHeader.MODELEN;
\r
649 userId = (int)TarHeader.ParseOctal(header, offset, TarHeader.UIDLEN);
\r
650 offset += TarHeader.UIDLEN;
\r
652 groupId = (int)TarHeader.ParseOctal(header, offset, TarHeader.GIDLEN);
\r
653 offset += TarHeader.GIDLEN;
\r
655 size = TarHeader.ParseOctal(header, offset, TarHeader.SIZELEN);
\r
656 offset += TarHeader.SIZELEN;
\r
658 modTime = GetDateTimeFromCTime(TarHeader.ParseOctal(header, offset, TarHeader.MODTIMELEN));
\r
659 offset += TarHeader.MODTIMELEN;
\r
661 checkSum = (int)TarHeader.ParseOctal(header, offset, TarHeader.CHKSUMLEN);
\r
662 offset += TarHeader.CHKSUMLEN;
\r
664 typeFlag = header[ offset++ ];
\r
666 linkName = TarHeader.ParseName(header, offset, TarHeader.NAMELEN);
\r
667 offset += TarHeader.NAMELEN;
\r
669 magic = TarHeader.ParseName(header, offset, TarHeader.MAGICLEN);
\r
670 offset += TarHeader.MAGICLEN;
\r
672 version = TarHeader.ParseName(header, offset, TarHeader.VERSIONLEN);
\r
673 offset += TarHeader.VERSIONLEN;
\r
675 userName = TarHeader.ParseName(header, offset, TarHeader.UNAMELEN);
\r
676 offset += TarHeader.UNAMELEN;
\r
678 groupName = TarHeader.ParseName(header, offset, TarHeader.GNAMELEN);
\r
679 offset += TarHeader.GNAMELEN;
\r
681 devMajor = (int)TarHeader.ParseOctal(header, offset, TarHeader.DEVLEN);
\r
682 offset += TarHeader.DEVLEN;
\r
684 devMinor = (int)TarHeader.ParseOctal(header, offset, TarHeader.DEVLEN);
\r
686 // Fields past this point not currently parsed or used...
\r
690 /// 'Write' header information to buffer provided
\r
692 /// <param name="outbuf">output buffer for header information</param>
\r
693 public void WriteHeader(byte[] outbuf)
\r
697 offset = GetNameBytes(this.name, outbuf, offset, TarHeader.NAMELEN);
\r
698 offset = GetOctalBytes(this.mode, outbuf, offset, TarHeader.MODELEN);
\r
699 offset = GetOctalBytes(this.userId, outbuf, offset, TarHeader.UIDLEN);
\r
700 offset = GetOctalBytes(this.groupId, outbuf, offset, TarHeader.GIDLEN);
\r
702 long size = this.size;
\r
704 offset = GetLongOctalBytes(size, outbuf, offset, TarHeader.SIZELEN);
\r
705 offset = GetLongOctalBytes(GetCTime(this.modTime), outbuf, offset, TarHeader.MODTIMELEN);
\r
707 int csOffset = offset;
\r
708 for (int c = 0; c < TarHeader.CHKSUMLEN; ++c)
\r
710 outbuf[offset++] = (byte)' ';
\r
713 outbuf[offset++] = this.typeFlag;
\r
715 offset = GetNameBytes(this.linkName, outbuf, offset, NAMELEN);
\r
716 offset = GetNameBytes(this.magic, outbuf, offset, MAGICLEN);
\r
717 offset = GetNameBytes(this.version, outbuf, offset, VERSIONLEN);
\r
718 offset = GetNameBytes(this.userName, outbuf, offset, UNAMELEN);
\r
719 offset = GetNameBytes(this.groupName, outbuf, offset, GNAMELEN);
\r
721 if (this.typeFlag == LF_CHR || this.typeFlag == LF_BLK)
\r
723 offset = GetOctalBytes(this.devMajor, outbuf, offset, DEVLEN);
\r
724 offset = GetOctalBytes(this.devMinor, outbuf, offset, DEVLEN);
\r
727 for ( ; offset < outbuf.Length; )
\r
729 outbuf[offset++] = 0;
\r
732 long checkSum = ComputeCheckSum(outbuf);
\r
734 GetCheckSumOctalBytes(checkSum, outbuf, csOffset, CHKSUMLEN);
\r
739 /* The original Java file had this header:
\r
741 ** Authored by Timothy Gerard Endres
\r
742 ** <mailto:time@gjt.org> <http://www.trustice.com>
\r
744 ** This work has been placed into the public domain.
\r
745 ** You may use this work in any way and for any purpose you wish.
\r
747 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
\r
748 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
\r
749 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
\r
750 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
\r
751 ** REDISTRIBUTION OF THIS SOFTWARE.
\r