Statistics
| Branch: | Revision:

root / trunk / hammock / src / net35 / ICSharpCode.SharpZipLib.Silverlight / Tar / TarHeader.cs @ 0eea575a

History | View | Annotate | Download (36.5 kB)

1
// TarHeader.cs
2
//
3
// Copyright (C) 2001 Mike Krueger
4
//
5
// This program is free software; you can redistribute it and/or
6
// modify it under the terms of the GNU General Public License
7
// as published by the Free Software Foundation; either version 2
8
// of the License, or (at your option) any later version.
9
//
10
// This program is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
// GNU General Public License for more details.
14
//
15
// You should have received a copy of the GNU General Public License
16
// along with this program; if not, write to the Free Software
17
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
//
19
// Linking this library statically or dynamically with other modules is
20
// making a combined work based on this library.  Thus, the terms and
21
// conditions of the GNU General Public License cover the whole
22
// combination.
23
// 
24
// As a special exception, the copyright holders of this library give you
25
// permission to link this library with independent modules to produce an
26
// executable, regardless of the license terms of these independent
27
// modules, and to copy and distribute the resulting executable under
28
// terms of your choice, provided that you also meet, for each linked
29
// independent module, the terms and conditions of the license of that
30
// module.  An independent module is a module which is not derived from
31
// or based on this library.  If you modify this library, you may extend
32
// this exception to your version of the library, but you are not
33
// obligated to do so.  If you do not wish to do so, delete this
34
// exception statement from your version.
35

    
36

    
37
/* The tar format and its POSIX successor PAX have a long history which makes for compatability
38
   issues when creating and reading files.
39

    
40
   This is further complicated by a large number of programs with variations on formats
41
   One common issue is the handling of names longer than 100 characters.
42
   GNU style long names are currently supported.
43

    
44
This is the ustar (Posix 1003.1) header.
45

    
46
struct header 
47
{
48
	char t_name[100];          //   0 Filename
49
	char t_mode[8];            // 100 Permissions
50
	char t_uid[8];             // 108 Numerical User ID
51
	char t_gid[8];             // 116 Numerical Group ID
52
	char t_size[12];           // 124 Filesize
53
	char t_mtime[12];          // 136 st_mtime
54
	char t_chksum[8];          // 148 Checksum
55
	char t_typeflag;           // 156 Type of File
56
	char t_linkname[100];      // 157 Target of Links
57
	char t_magic[6];           // 257 "ustar" or other...
58
	char t_version[2];         // 263 Version fixed to 00
59
	char t_uname[32];          // 265 User Name
60
	char t_gname[32];          // 297 Group Name
61
	char t_devmajor[8];        // 329 Major for devices
62
	char t_devminor[8];        // 337 Minor for devices
63
	char t_prefix[155];        // 345 Prefix for t_name
64
	char t_mfill[12];          // 500 Filler up to 512
65
};
66

    
67
*/
68

    
69
using System;
70
using System.Text;
71

    
72
namespace ICSharpCode.SharpZipLib.Silverlight.Tar
73
{
74
    /// <summary>
75
    /// This class encapsulates the Tar Entry Header used in Tar Archives.
76
    /// The class also holds a number of tar constants, used mostly in headers.
77
    /// </summary>
78
    public class TarHeader
79
    {
80
        #region Constants
81

    
82
        /// <summary>
83
        /// The length of the checksum field in a header buffer.
84
        /// </summary>
85
        public const int CHKSUMLEN = 8;
86

    
87
        /// <summary>
88
        /// Offset of checksum in a header buffer.
89
        /// </summary>
90
        public const int CHKSUMOFS = 148;
91

    
92
        /// <summary>
93
        /// The length of the devices field in a header buffer.
94
        /// </summary>
95
        public const int DEVLEN = 8;
96

    
97
        /// <summary>
98
        /// The length of the group id field in a header buffer.
99
        /// </summary>
100
        public const int GIDLEN = 8;
101

    
102
        /// <summary>
103
        /// The length of the group name field in a header buffer.
104
        /// </summary>
105
        public const int GNAMELEN = 32;
106

    
107
        /// <summary>
108
        /// The magic tag representing an old GNU tar archive where version is included in magic and overwrites it
109
        /// </summary>
110
        public const string GNU_TMAGIC = "ustar  ";
111

    
112
        /// <summary>
113
        /// Solaris access control list file type
114
        /// </summary>
115
        public const byte LF_ACL = (byte) 'A';
116

    
117
        //
118
        // LF_ constants represent the "type" of an entry
119
        //
120

    
121
        /// <summary>
122
        /// Block device file type.
123
        /// </summary>
124
        public const byte LF_BLK = (byte) '4';
125

    
126
        /// <summary>
127
        /// Character device file type.
128
        /// </summary>
129
        public const byte LF_CHR = (byte) '3';
130

    
131
        /// <summary>
132
        /// Contiguous file type.
133
        /// </summary>
134
        public const byte LF_CONTIG = (byte) '7';
135

    
136
        /// <summary>
137
        /// Directory file type.
138
        /// </summary>
139
        public const byte LF_DIR = (byte) '5';
140

    
141
        /// <summary>
142
        /// Solaris Extended Attribute File
143
        /// </summary>
144
        public const byte LF_EXTATTR = (byte) 'E';
145

    
146
        /// <summary>
147
        /// FIFO (pipe) file type.
148
        /// </summary>
149
        public const byte LF_FIFO = (byte) '6';
150

    
151
        /// <summary>
152
        /// Posix.1 2001 global extended header
153
        /// </summary>
154
        public const byte LF_GHDR = (byte) 'g';
155

    
156
        /// <summary>
157
        /// GNU dir dump file type
158
        /// This is a dir entry that contains the names of files that were in the
159
        /// dir at the time the dump was made
160
        /// </summary>
161
        public const byte LF_GNU_DUMPDIR = (byte) 'D';
162

    
163
        /// <summary>
164
        /// Identifies the next file on the tape as having a long link name
165
        /// </summary>
166
        public const byte LF_GNU_LONGLINK = (byte) 'K';
167

    
168
        /// <summary>
169
        /// Identifies the next file on the tape as having a long name
170
        /// </summary>
171
        public const byte LF_GNU_LONGNAME = (byte) 'L';
172

    
173
        /// <summary>
174
        /// Continuation of a file that began on another volume
175
        /// </summary>
176
        public const byte LF_GNU_MULTIVOL = (byte) 'M';
177

    
178
        /// <summary>
179
        /// For storing filenames that dont fit in the main header (old GNU)
180
        /// </summary>
181
        public const byte LF_GNU_NAMES = (byte) 'N';
182

    
183
        /// <summary>
184
        /// GNU Sparse file
185
        /// </summary>
186
        public const byte LF_GNU_SPARSE = (byte) 'S';
187

    
188
        /// <summary>
189
        /// GNU Tape/volume header ignore on extraction
190
        /// </summary>
191
        public const byte LF_GNU_VOLHDR = (byte) 'V';
192

    
193
        /// <summary>
194
        /// Link file type.
195
        /// </summary>
196
        public const byte LF_LINK = (byte) '1';
197

    
198
        /// <summary>
199
        /// Inode (metadata only) no file content
200
        /// </summary>
201
        public const byte LF_META = (byte) 'I';
202

    
203
        /// <summary>
204
        /// Normal file type.
205
        /// </summary>
206
        public const byte LF_NORMAL = (byte) '0';
207

    
208
        /// <summary>
209
        ///  The "old way" of indicating a normal file.
210
        /// </summary>
211
        public const byte LF_OLDNORM = 0;
212

    
213
        /// <summary>
214
        /// Symbolic link file type.
215
        /// </summary>
216
        public const byte LF_SYMLINK = (byte) '2';
217

    
218
        /// <summary>
219
        /// Posix.1 2001 extended header
220
        /// </summary>
221
        public const byte LF_XHDR = (byte) 'x';
222

    
223
        /// <summary>
224
        /// The length of the magic field in a header buffer.
225
        /// </summary>
226
        public const int MAGICLEN = 6;
227

    
228
        /// <summary>
229
        /// The length of the mode field in a header buffer.
230
        /// </summary>
231
        public const int MODELEN = 8;
232

    
233
        /// <summary>
234
        /// The length of the modification time field in a header buffer.
235
        /// </summary>
236
        public const int MODTIMELEN = 12;
237

    
238
        /// <summary>
239
        /// The length of the name field in a header buffer.
240
        /// </summary>
241
        public const int NAMELEN = 100;
242

    
243
        /// <summary>
244
        /// The length of the size field in a header buffer.
245
        /// </summary>
246
        public const int SIZELEN = 12;
247

    
248
        private const long timeConversionFactor = 10000000L; // 1 tick == 100 nanoseconds
249

    
250
        /// <summary>
251
        /// The magic tag representing a POSIX tar archive.  (includes trailing NULL)
252
        /// </summary>
253
        public const string TMAGIC = "ustar ";
254

    
255
        /// <summary>
256
        /// The length of the user id field in a header buffer.
257
        /// </summary>
258
        public const int UIDLEN = 8;
259

    
260
        /// <summary>
261
        /// The length of the user name field in a header buffer.
262
        /// </summary>
263
        public const int UNAMELEN = 32;
264

    
265
        /// <summary>
266
        /// The length of the version field in a header buffer.
267
        /// </summary>
268
        public const int VERSIONLEN = 2;
269

    
270
        private static readonly DateTime dateTime1970 = new DateTime(1970, 1, 1, 0, 0, 0, 0);
271

    
272
        #endregion
273

    
274
        #region Constructors
275

    
276
        /// <summary>
277
        /// Initialise a default TarHeader instance
278
        /// </summary>
279
        public TarHeader()
280
        {
281
            Magic = TMAGIC;
282
            Version = " ";
283

    
284
            Name = "";
285
            LinkName = "";
286

    
287
            UserId = defaultUserId;
288
            GroupId = defaultGroupId;
289
            UserName = defaultUser;
290
            GroupName = defaultGroupName;
291
            Size = 0;
292
        }
293

    
294
        #endregion
295

    
296
        #region Properties
297

    
298
        /// <summary>
299
        /// Get/set the name for this tar entry.
300
        /// </summary>
301
        /// <exception cref="ArgumentNullException">Thrown when attempting to set the property to null.</exception>
302
        public string Name
303
        {
304
            get { return name; }
305
            set
306
            {
307
                if (value == null)
308
                {
309
                    throw new ArgumentNullException("value");
310
                }
311
                name = value;
312
            }
313
        }
314

    
315
        /// <summary>
316
        /// Get/set the entry's Unix style permission mode.
317
        /// </summary>
318
        public int Mode
319
        {
320
            get { return mode; }
321
            set { mode = value; }
322
        }
323

    
324

    
325
        /// <summary>
326
        /// The entry's user id.
327
        /// </summary>
328
        /// <remarks>
329
        /// This is only directly relevant to unix systems.
330
        /// The default is zero.
331
        /// </remarks>
332
        public int UserId { get; set; }
333

    
334

    
335
        /// <summary>
336
        /// Get/set the entry's group id.
337
        /// </summary>
338
        /// <remarks>
339
        /// This is only directly relevant to linux/unix systems.
340
        /// The default value is zero.
341
        /// </remarks>
342
        public int GroupId { get; set; }
343

    
344

    
345
        /// <summary>
346
        /// Get/set the entry's size.
347
        /// </summary>
348
        /// <exception cref="ArgumentOutOfRangeException">Thrown when setting the size to less than zero.</exception>
349
        public long Size
350
        {
351
            get { return _size; }
352
            set
353
            {
354
                if (value < 0)
355
                {
356
                    throw new ArgumentOutOfRangeException("value", "Cannot be less than zero");
357
                }
358
                _size = value;
359
            }
360
        }
361

    
362

    
363
        /// <summary>
364
        /// Get/set the entry's modification time.
365
        /// </summary>
366
        /// <remarks>
367
        /// The modification time is only accurate to within a second.
368
        /// </remarks>
369
        /// <exception cref="ArgumentOutOfRangeException">Thrown when setting the date time to less than 1/1/1970.</exception>
370
        public DateTime ModTime
371
        {
372
            get { return modTime; }
373
            set
374
            {
375
                if (value < dateTime1970)
376
                {
377
                    throw new ArgumentOutOfRangeException("value", "ModTime cannot be before Jan 1st 1970");
378
                }
379
                modTime = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second);
380
            }
381
        }
382

    
383

    
384
        /// <summary>
385
        /// Get the entry's checksum.  This is only valid/updated after writing or reading an entry.
386
        /// </summary>
387
        public int Checksum { get; private set; }
388

    
389
        /// <summary>
390
        /// Get value of true if the header checksum is valid, false otherwise.
391
        /// </summary>
392
        public bool IsChecksumValid { get; private set; }
393

    
394
        /// <summary>
395
        /// Get/set the entry's type flag.
396
        /// </summary>
397
        public byte TypeFlag { get; set; }
398

    
399
        /// <summary>
400
        /// The entry's link name.
401
        /// </summary>
402
        /// <exception cref="ArgumentNullException">Thrown when attempting to set LinkName to null.</exception>
403
        public string LinkName
404
        {
405
            get { return linkName; }
406
            set
407
            {
408
                if (value == null)
409
                {
410
                    throw new ArgumentNullException("value");
411
                }
412
                linkName = value;
413
            }
414
        }
415

    
416

    
417
        /// <summary>
418
        /// Get/set the entry's magic tag.
419
        /// </summary>
420
        /// <exception cref="ArgumentNullException">Thrown when attempting to set Magic to null.</exception>
421
        public string Magic
422
        {
423
            get { return magic; }
424
            set
425
            {
426
                if (value == null)
427
                {
428
                    throw new ArgumentNullException("value");
429
                }
430
                magic = value;
431
            }
432
        }
433

    
434

    
435
        /// <summary>
436
        /// The entry's version.
437
        /// </summary>
438
        /// <exception cref="ArgumentNullException">Thrown when attempting to set Version to null.</exception>
439
        public string Version
440
        {
441
            get { return version; }
442

    
443
            set
444
            {
445
                if (value == null)
446
                {
447
                    throw new ArgumentNullException("value");
448
                }
449
                version = value;
450
            }
451
        }
452

    
453

    
454
        /// <summary>
455
        /// The entry's user name.
456
        /// </summary>
457
        public string UserName
458
        {
459
            get { return userName; }
460
            set
461
            {
462
                if (value != null)
463
                {
464
                    userName = value.Substring(0, Math.Min(UNAMELEN, value.Length));
465
                }
466
                else
467
                {
468
                    var currentUser = "Silverlight"; //Environment.UserName;
469
                    if (currentUser.Length > UNAMELEN)
470
                    {
471
                        currentUser = currentUser.Substring(0, UNAMELEN);
472
                    }
473
                    userName = currentUser;
474
                }
475
            }
476
        }
477

    
478
        /// <summary>
479
        /// Get/set the entry's group name.
480
        /// </summary>
481
        /// <remarks>
482
        /// This is only directly relevant to unix systems.
483
        /// </remarks>
484
        public string GroupName
485
        {
486
            get { return groupName; }
487
            set
488
            {
489
                groupName = value ?? "None";
490
            }
491
        }
492

    
493

    
494
        /// <summary>
495
        /// Get/set the entry's major device number.
496
        /// </summary>
497
        public int DevMajor { get; set; }
498

    
499

    
500
        /// <summary>
501
        /// Get/set the entry's minor device number.
502
        /// </summary>
503
        public int DevMinor { get; set; }
504

    
505
        /// <summary>
506
        /// Get the name of this entry.
507
        /// </summary>
508
        /// <returns>The entry's name.</returns>
509
        [Obsolete("Use the Name property instead", true)]
510
        public string GetName()
511
        {
512
            return name;
513
        }
514

    
515
        #endregion
516

    
517
        /// <summary>
518
        /// Create a new <see cref="TarHeader"/> that is a copy of the current instance.
519
        /// </summary>
520
        /// <returns>A new <see cref="Object"/> that is a copy of the current instance.</returns>
521
        public object Clone()
522
        {
523
            return MemberwiseClone();
524
        }
525

    
526
        /// <summary>
527
        /// Parse TarHeader information from a header buffer.
528
        /// </summary>
529
        /// <param name = "header">
530
        /// The tar entry header buffer to get information from.
531
        /// </param>
532
        public void ParseBuffer(byte[] header)
533
        {
534
            if (header == null)
535
            {
536
                throw new ArgumentNullException("header");
537
            }
538

    
539
            var offset = 0;
540

    
541
            name = ParseName(header, offset, NAMELEN).ToString();
542
            offset += NAMELEN;
543

    
544
            mode = (int) ParseOctal(header, offset, MODELEN);
545
            offset += MODELEN;
546

    
547
            UserId = (int) ParseOctal(header, offset, UIDLEN);
548
            offset += UIDLEN;
549

    
550
            GroupId = (int) ParseOctal(header, offset, GIDLEN);
551
            offset += GIDLEN;
552

    
553
            Size = ParseOctal(header, offset, SIZELEN);
554
            offset += SIZELEN;
555

    
556
            ModTime = GetDateTimeFromCTime(ParseOctal(header, offset, MODTIMELEN));
557
            offset += MODTIMELEN;
558

    
559
            Checksum = (int) ParseOctal(header, offset, CHKSUMLEN);
560
            offset += CHKSUMLEN;
561

    
562
            TypeFlag = header[offset++];
563

    
564
            LinkName = ParseName(header, offset, NAMELEN).ToString();
565
            offset += NAMELEN;
566

    
567
            Magic = ParseName(header, offset, MAGICLEN).ToString();
568
            offset += MAGICLEN;
569

    
570
            Version = ParseName(header, offset, VERSIONLEN).ToString();
571
            offset += VERSIONLEN;
572

    
573
            UserName = ParseName(header, offset, UNAMELEN).ToString();
574
            offset += UNAMELEN;
575

    
576
            GroupName = ParseName(header, offset, GNAMELEN).ToString();
577
            offset += GNAMELEN;
578

    
579
            DevMajor = (int) ParseOctal(header, offset, DEVLEN);
580
            offset += DEVLEN;
581

    
582
            DevMinor = (int) ParseOctal(header, offset, DEVLEN);
583

    
584
            // Fields past this point not currently parsed or used...
585

    
586
            IsChecksumValid = Checksum == MakeCheckSum(header);
587
        }
588

    
589
        /// <summary>
590
        /// 'Write' header information to buffer provided, updating the <see cref="Checksum">check sum</see>.
591
        /// </summary>
592
        /// <param name="outBuffer">output buffer for header information</param>
593
        public void WriteHeader(byte[] outBuffer)
594
        {
595
            if (outBuffer == null)
596
            {
597
                throw new ArgumentNullException("outBuffer");
598
            }
599

    
600
            var offset = 0;
601

    
602
            offset = GetNameBytes(Name, outBuffer, offset, NAMELEN);
603
            offset = GetOctalBytes(mode, outBuffer, offset, MODELEN);
604
            offset = GetOctalBytes(UserId, outBuffer, offset, UIDLEN);
605
            offset = GetOctalBytes(GroupId, outBuffer, offset, GIDLEN);
606

    
607
            var size = Size;
608

    
609
            offset = GetLongOctalBytes(size, outBuffer, offset, SIZELEN);
610
            offset = GetLongOctalBytes(GetCTime(ModTime), outBuffer, offset, MODTIMELEN);
611

    
612
            var csOffset = offset;
613
            for (var c = 0; c < CHKSUMLEN; ++c)
614
            {
615
                outBuffer[offset++] = (byte) ' ';
616
            }
617

    
618
            outBuffer[offset++] = TypeFlag;
619

    
620
            offset = GetNameBytes(LinkName, outBuffer, offset, NAMELEN);
621
            offset = GetAsciiBytes(Magic, 0, outBuffer, offset, MAGICLEN);
622
            offset = GetNameBytes(Version, outBuffer, offset, VERSIONLEN);
623
            offset = GetNameBytes(UserName, outBuffer, offset, UNAMELEN);
624
            offset = GetNameBytes(GroupName, outBuffer, offset, GNAMELEN);
625

    
626
            if (TypeFlag == LF_CHR || TypeFlag == LF_BLK)
627
            {
628
                offset = GetOctalBytes(DevMajor, outBuffer, offset, DEVLEN);
629
                offset = GetOctalBytes(DevMinor, outBuffer, offset, DEVLEN);
630
            }
631

    
632
            for (; offset < outBuffer.Length;)
633
            {
634
                outBuffer[offset++] = 0;
635
            }
636

    
637
            Checksum = ComputeCheckSum(outBuffer);
638

    
639
            GetCheckSumOctalBytes(Checksum, outBuffer, csOffset, CHKSUMLEN);
640
            IsChecksumValid = true;
641
        }
642

    
643
        /// <summary>
644
        /// Get a hash code for the current object.
645
        /// </summary>
646
        /// <returns>A hash code for the current object.</returns>
647
        public override int GetHashCode()
648
        {
649
            return Name.GetHashCode();
650
        }
651

    
652
        /// <summary>
653
        /// Determines if this instance is equal to the specified object.
654
        /// </summary>
655
        /// <param name="obj">The object to compare with.</param>
656
        /// <returns>true if the objects are equal, false otherwise.</returns>
657
        public override bool Equals(object obj)
658
        {
659
            var localHeader = obj as TarHeader;
660

    
661
            if (localHeader != null)
662
            {
663
                return name == localHeader.name
664
                       && mode == localHeader.mode
665
                       && UserId == localHeader.UserId
666
                       && GroupId == localHeader.GroupId
667
                       && Size == localHeader.Size
668
                       && ModTime == localHeader.ModTime
669
                       && Checksum == localHeader.Checksum
670
                       && TypeFlag == localHeader.TypeFlag
671
                       && LinkName == localHeader.LinkName
672
                       && Magic == localHeader.Magic
673
                       && Version == localHeader.Version
674
                       && UserName == localHeader.UserName
675
                       && GroupName == localHeader.GroupName
676
                       && DevMajor == localHeader.DevMajor
677
                       && DevMinor == localHeader.DevMinor;
678
            }
679
            return false;
680
        }
681

    
682
        /// <summary>
683
        /// Set defaults for values used when constructing a TarHeader instance.
684
        /// </summary>
685
        /// <param name="userId">Value to apply as a default for userId.</param>
686
        /// <param name="userName">Value to apply as a default for userName.</param>
687
        /// <param name="groupId">Value to apply as a default for groupId.</param>
688
        /// <param name="groupName">Value to apply as a default for groupName.</param>
689
        internal static void SetValueDefaults(int userId, string userName, int groupId, string groupName)
690
        {
691
            defaultUserId = userIdAsSet = userId;
692
            defaultUser = userNameAsSet = userName;
693
            defaultGroupId = groupIdAsSet = groupId;
694
            defaultGroupName = groupNameAsSet = groupName;
695
        }
696

    
697
        internal static void RestoreSetValues()
698
        {
699
            defaultUserId = userIdAsSet;
700
            defaultUser = userNameAsSet;
701
            defaultGroupId = groupIdAsSet;
702
            defaultGroupName = groupNameAsSet;
703
        }
704

    
705
        /// <summary>
706
        /// Parse an octal string from a header buffer.
707
        /// </summary>
708
        /// <param name = "header">The header buffer from which to parse.</param>
709
        /// <param name = "offset">The offset into the buffer from which to parse.</param>
710
        /// <param name = "length">The number of header bytes to parse.</param>
711
        /// <returns>The long equivalent of the octal string.</returns>
712
        public static long ParseOctal(byte[] header, int offset, int length)
713
        {
714
            if (header == null)
715
            {
716
                throw new NullReferenceException("header");
717
            }
718

    
719
            long result = 0;
720
            var stillPadding = true;
721

    
722
            var end = offset + length;
723
            for (var i = offset; i < end; ++i)
724
            {
725
                if (header[i] == 0)
726
                {
727
                    break;
728
                }
729

    
730
                if (header[i] == (byte) ' ' || header[i] == '0')
731
                {
732
                    if (stillPadding)
733
                    {
734
                        continue;
735
                    }
736

    
737
                    if (header[i] == (byte) ' ')
738
                    {
739
                        break;
740
                    }
741
                }
742

    
743
                stillPadding = false;
744

    
745
                result = (result << 3) + (header[i] - '0');
746
            }
747

    
748
            return result;
749
        }
750

    
751
        /// <summary>
752
        /// Parse a name from a header buffer.
753
        /// </summary>
754
        /// <param name="header">
755
        /// The header buffer from which to parse.
756
        /// </param>
757
        /// <param name="offset">
758
        /// The offset into the buffer from which to parse.
759
        /// </param>
760
        /// <param name="length">
761
        /// The number of header bytes to parse.
762
        /// </param>
763
        /// <returns>
764
        /// The name parsed.
765
        /// </returns>
766
        public static StringBuilder ParseName(byte[] header, int offset, int length)
767
        {
768
            if (header == null)
769
            {
770
                throw new ArgumentNullException("header");
771
            }
772

    
773
            if (offset < 0)
774
            {
775
                throw new ArgumentOutOfRangeException("offset", "Cannot be less than zero");
776
            }
777

    
778
            if (length < 0)
779
            {
780
                throw new ArgumentOutOfRangeException("length", "Cannot be less than zero");
781
            }
782

    
783
            if (offset + length > header.Length)
784
            {
785
                throw new ArgumentException("Exceeds header size", "length");
786
            }
787

    
788
            var result = new StringBuilder(length);
789

    
790
            for (var i = offset; i < offset + length; ++i)
791
            {
792
                if (header[i] == 0)
793
                {
794
                    break;
795
                }
796
                result.Append((char) header[i]);
797
            }
798

    
799
            return result;
800
        }
801

    
802
        /// <summary>
803
        /// Add <paramref name="name">name</paramref> to the buffer as a collection of bytes
804
        /// </summary>
805
        /// <param name="name">The name to add</param>
806
        /// <param name="nameOffset">The offset of the first character</param>
807
        /// <param name="buffer">The buffer to add to</param>
808
        /// <param name="bufferOffset">The index of the first byte to add</param>
809
        /// <param name="length">The number of characters/bytes to add</param>
810
        /// <returns>The next free index in the <paramref name="buffer">buffer</paramref></returns>
811
        public static int GetNameBytes(StringBuilder name, int nameOffset, byte[] buffer, int bufferOffset, int length)
812
        {
813
            if (name == null)
814
            {
815
                throw new ArgumentNullException("name");
816
            }
817

    
818
            if (buffer == null)
819
            {
820
                throw new ArgumentNullException("buffer");
821
            }
822

    
823
            return GetNameBytes(name.ToString(), nameOffset, buffer, bufferOffset, length);
824
        }
825

    
826
        /// <summary>
827
        /// Add <paramref name="name">name</paramref> to the buffer as a collection of bytes
828
        /// </summary>
829
        /// <param name="name">The name to add</param>
830
        /// <param name="nameOffset">The offset of the first character</param>
831
        /// <param name="buffer">The buffer to add to</param>
832
        /// <param name="bufferOffset">The index of the first byte to add</param>
833
        /// <param name="length">The number of characters/bytes to add</param>
834
        /// <returns>The next free index in the <paramref name="buffer">buffer</paramref></returns>
835
        public static int GetNameBytes(string name, int nameOffset, byte[] buffer, int bufferOffset, int length)
836
        {
837
            if (name == null)
838
            {
839
                throw new ArgumentNullException("name");
840
            }
841

    
842
            if (buffer == null)
843
            {
844
                throw new ArgumentNullException("buffer");
845
            }
846

    
847
            int i;
848

    
849
            for (i = 0; i < length - 1 && nameOffset + i < name.Length; ++i)
850
            {
851
                buffer[bufferOffset + i] = (byte) name[nameOffset + i];
852
            }
853

    
854
            for (; i < length; ++i)
855
            {
856
                buffer[bufferOffset + i] = 0;
857
            }
858

    
859
            return bufferOffset + length;
860
        }
861

    
862
        /// <summary>
863
        /// Add an entry name to the buffer
864
        /// </summary>
865
        /// <param name="name">
866
        /// The name to add
867
        /// </param>
868
        /// <param name="buffer">
869
        /// The buffer to add to
870
        /// </param>
871
        /// <param name="offset">
872
        /// The offset into the buffer from which to start adding
873
        /// </param>
874
        /// <param name="length">
875
        /// The number of header bytes to add
876
        /// </param>
877
        /// <returns>
878
        /// The index of the next free byte in the buffer
879
        /// </returns>
880
        public static int GetNameBytes(StringBuilder name, byte[] buffer, int offset, int length)
881
        {
882
            if (name == null)
883
            {
884
                throw new ArgumentNullException("name");
885
            }
886

    
887
            if (buffer == null)
888
            {
889
                throw new ArgumentNullException("buffer");
890
            }
891

    
892
            return GetNameBytes(name.ToString(), 0, buffer, offset, length);
893
        }
894

    
895
        /// <summary>
896
        /// Add an entry name to the buffer
897
        /// </summary>
898
        /// <param name="name">The name to add</param>
899
        /// <param name="buffer">The buffer to add to</param>
900
        /// <param name="offset">The offset into the buffer from which to start adding</param>
901
        /// <param name="length">The number of header bytes to add</param>
902
        /// <returns>The index of the next free byte in the buffer</returns>
903
        public static int GetNameBytes(string name, byte[] buffer, int offset, int length)
904
        {
905
            if (name == null)
906
            {
907
                throw new ArgumentNullException("name");
908
            }
909

    
910
            if (buffer == null)
911
            {
912
                throw new ArgumentNullException("buffer");
913
            }
914

    
915
            return GetNameBytes(name, 0, buffer, offset, length);
916
        }
917

    
918
        /// <summary>
919
        /// Add a string to a buffer as a collection of ascii bytes.
920
        /// </summary>
921
        /// <param name="toAdd">The string to add</param>
922
        /// <param name="nameOffset">The offset of the first character to add.</param>
923
        /// <param name="buffer">The buffer to add to.</param>
924
        /// <param name="bufferOffset">The offset to start adding at.</param>
925
        /// <param name="length">The number of ascii characters to add.</param>
926
        /// <returns>The next free index in the buffer.</returns>
927
        public static int GetAsciiBytes(string toAdd, int nameOffset, byte[] buffer, int bufferOffset, int length)
928
        {
929
            if (toAdd == null)
930
            {
931
                throw new ArgumentNullException("toAdd");
932
            }
933

    
934
            if (buffer == null)
935
            {
936
                throw new ArgumentNullException("buffer");
937
            }
938

    
939
            for (var i = 0; i < length && nameOffset + i < toAdd.Length; ++i)
940
            {
941
                buffer[bufferOffset + i] = (byte) toAdd[nameOffset + i];
942
            }
943
            return bufferOffset + length;
944
        }
945

    
946
        /// <summary>
947
        /// Put an octal representation of a value into a buffer
948
        /// </summary>
949
        /// <param name = "value">
950
        /// the value to be converted to octal
951
        /// </param>
952
        /// <param name = "buffer">
953
        /// buffer to store the octal string
954
        /// </param>
955
        /// <param name = "offset">
956
        /// The offset into the buffer where the value starts
957
        /// </param>
958
        /// <param name = "length">
959
        /// The length of the octal string to create
960
        /// </param>
961
        /// <returns>
962
        /// The offset of the character next byte after the octal string
963
        /// </returns>
964
        public static int GetOctalBytes(long value, byte[] buffer, int offset, int length)
965
        {
966
            if (buffer == null)
967
            {
968
                throw new ArgumentNullException("buffer");
969
            }
970

    
971
            var localIndex = length - 1;
972

    
973
            // Either a space or null is valid here.  We use NULL as per GNUTar
974
            buffer[offset + localIndex] = 0;
975
            --localIndex;
976

    
977
            if (value > 0)
978
            {
979
                for (var v = value; (localIndex >= 0) && (v > 0); --localIndex)
980
                {
981
                    buffer[offset + localIndex] = (byte) ((byte) '0' + (byte) (v & 7));
982
                    v >>= 3;
983
                }
984
            }
985

    
986
            for (; localIndex >= 0; --localIndex)
987
            {
988
                buffer[offset + localIndex] = (byte) '0';
989
            }
990

    
991
            return offset + length;
992
        }
993

    
994
        /// <summary>
995
        /// Put an octal representation of a value into a buffer
996
        /// </summary>
997
        /// <param name = "value">Value to be convert to octal</param>
998
        /// <param name = "buffer">The buffer to update</param>
999
        /// <param name = "offset">The offset into the buffer to store the value</param>
1000
        /// <param name = "length">The length of the octal string</param>
1001
        /// <returns>Index of next byte</returns>
1002
        public static int GetLongOctalBytes(long value, byte[] buffer, int offset, int length)
1003
        {
1004
            return GetOctalBytes(value, buffer, offset, length);
1005
        }
1006

    
1007
        /// <summary>
1008
        /// Add the checksum integer to header buffer.
1009
        /// </summary>
1010
        /// <param name = "value"></param>
1011
        /// <param name = "buffer">The header buffer to set the checksum for</param>
1012
        /// <param name = "offset">The offset into the buffer for the checksum</param>
1013
        /// <param name = "length">The number of header bytes to update.
1014
        /// It's formatted differently from the other fields: it has 6 digits, a
1015
        /// null, then a space -- rather than digits, a space, then a null.
1016
        /// The final space is already there, from checksumming
1017
        /// </param>
1018
        /// <returns>The modified buffer offset</returns>
1019
        private static int GetCheckSumOctalBytes(long value, byte[] buffer, int offset, int length)
1020
        {
1021
            GetOctalBytes(value, buffer, offset, length - 1);
1022
            return offset + length;
1023
        }
1024

    
1025
        /// <summary>
1026
        /// Compute the checksum for a tar entry header.  
1027
        /// The checksum field must be all spaces prior to this happening
1028
        /// </summary>
1029
        /// <param name = "buffer">The tar entry's header buffer.</param>
1030
        /// <returns>The computed checksum.</returns>
1031
        private static int ComputeCheckSum(byte[] buffer)
1032
        {
1033
            var sum = 0;
1034
            for (var i = 0; i < buffer.Length; ++i)
1035
            {
1036
                sum += buffer[i];
1037
            }
1038
            return sum;
1039
        }
1040

    
1041
        /// <summary>
1042
        /// Make a checksum for a tar entry ignoring the checksum contents.
1043
        /// </summary>
1044
        /// <param name = "buffer">The tar entry's header buffer.</param>
1045
        /// <returns>The checksum for the buffer</returns>
1046
        private static int MakeCheckSum(byte[] buffer)
1047
        {
1048
            var sum = 0;
1049
            for (var i = 0; i < CHKSUMOFS; ++i)
1050
            {
1051
                sum += buffer[i];
1052
            }
1053

    
1054
            for (var i = 0; i < CHKSUMLEN; ++i)
1055
            {
1056
                sum += (byte) ' ';
1057
            }
1058

    
1059
            for (var i = CHKSUMOFS + CHKSUMLEN; i < buffer.Length; ++i)
1060
            {
1061
                sum += buffer[i];
1062
            }
1063
            return sum;
1064
        }
1065

    
1066
        private static int GetCTime(DateTime dateTime)
1067
        {
1068
            return unchecked((int) ((dateTime.Ticks - dateTime1970.Ticks)/timeConversionFactor));
1069
        }
1070

    
1071
        private static DateTime GetDateTimeFromCTime(long ticks)
1072
        {
1073
            DateTime result;
1074

    
1075
            try
1076
            {
1077
                result = new DateTime(dateTime1970.Ticks + ticks*timeConversionFactor);
1078
            }
1079
            catch (ArgumentOutOfRangeException)
1080
            {
1081
                result = dateTime1970;
1082
            }
1083
            return result;
1084
        }
1085

    
1086
        #region Instance Fields
1087

    
1088
        private string groupName;
1089
        private string linkName;
1090
        private string magic;
1091
        private int mode;
1092
        private DateTime modTime;
1093
        private string name;
1094
        private long _size;
1095
        private string userName;
1096
        private string version;
1097

    
1098
        #endregion
1099

    
1100
        #region Class Fields
1101

    
1102
        // Values used during recursive operations.
1103
        internal static int defaultGroupId;
1104
        internal static string defaultGroupName = "None";
1105
        internal static string defaultUser;
1106
        internal static int defaultUserId;
1107
        internal static int groupIdAsSet;
1108
        internal static string groupNameAsSet = "None";
1109
        internal static int userIdAsSet;
1110
        internal static string userNameAsSet;
1111

    
1112
        #endregion
1113
    }
1114
}
1115

    
1116
/* The original Java file had this header:
1117
 * 
1118
** Authored by Timothy Gerard Endres
1119
** <mailto:time@gjt.org>  <http://www.trustice.com>
1120
** 
1121
** This work has been placed into the public domain.
1122
** You may use this work in any way and for any purpose you wish.
1123
**
1124
** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
1125
** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
1126
** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
1127
** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
1128
** REDISTRIBUTION OF THIS SOFTWARE. 
1129
** 
1130
*/