1 /*
2 ** Authored by Timothy Gerard Endres
3 ** <mailto:time@gjt.org>  <http://www.trustice.com>
4 **
5 ** This work has been placed into the public domain.
6 ** You may use this work in any way and for any purpose you wish.
7 **
8 ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
9 ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
10 ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
11 ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
12 ** REDISTRIBUTION OF THIS SOFTWARE.
13 **
14 */
15 
16 package com.ice.tar;
17 
18 import java.io.*;
19 import java.util.Date;
20 
21 
22 /**
23  *
24  * This class represents an entry in a Tar archive. It consists
25  * of the entry's header, as well as the entry's File. Entries
26  * can be instantiated in one of three ways, depending on how
27  * they are to be used.
28  * <p>
29  * TarEntries that are created from the header bytes read from
30  * an archive are instantiated with the TarEntry( byte[] )
31  * constructor. These entries will be used when extracting from
32  * or listing the contents of an archive. These entries have their
33  * header filled in using the header bytes. They also set the File
34  * to null, since they reference an archive entry not a file.
35  * <p>
36  * TarEntries that are created from Files that are to be written
37  * into an archive are instantiated with the TarEntry( File )
38  * constructor. These entries have their header filled in using
39  * the File's information. They also keep a reference to the File
40  * for convenience when writing entries.
41  * <p>
42  * Finally, TarEntries can be constructed from nothing but a name.
43  * This allows the programmer to construct the entry by hand, for
44  * instance when only an InputStream is available for writing to
45  * the archive, and the header information is constructed from
46  * other information. In this case the header fields are set to
47  * defaults and the File is set to null.
48  *
49  * <pre>
50  *
51  * Original Unix Tar Header:
52  *
53  * Field  Field     Field
54  * Width  Name      Meaning
55  * -----  --------- ---------------------------
56  *   100  name      name of file
57  *     8  mode      file mode
58  *     8  uid       owner user ID
59  *     8  gid       owner group ID
60  *    12  size      length of file in bytes
61  *    12  mtime     modify time of file
62  *     8  chksum    checksum for header
63  *     1  link      indicator for links
64  *   100  linkname  name of linked file
65  *
66  * </pre>
67  *
68  * <pre>
69  *
70  * POSIX "ustar" Style Tar Header:
71  *
72  * Field  Field     Field
73  * Width  Name      Meaning
74  * -----  --------- ---------------------------
75  *   100  name      name of file
76  *     8  mode      file mode
77  *     8  uid       owner user ID
78  *     8  gid       owner group ID
79  *    12  size      length of file in bytes
80  *    12  mtime     modify time of file
81  *     8  chksum    checksum for header
82  *     1  typeflag  type of file
83  *   100  linkname  name of linked file
84  *     6  magic     USTAR indicator
85  *     2  version   USTAR version
86  *    32  uname     owner user name
87  *    32  gname     owner group name
88  *     8  devmajor  device major number
89  *     8  devminor  device minor number
90  *   155  prefix    prefix for file name
91  *
92  * struct posix_header
93  *   {                     byte offset
94  *   char name[100];            0
95  *   char mode[8];            100
96  *   char uid[8];             108
97  *   char gid[8];             116
98  *   char size[12];           124
99  *   char mtime[12];          136
100  *   char chksum[8];          148
101  *   char typeflag;           156
102  *   char linkname[100];      157
103  *   char magic[6];           257
104  *   char version[2];         263
105  *   char uname[32];          265
106  *   char gname[32];          297
107  *   char devmajor[8];        329
108  *   char devminor[8];        337
109  *   char prefix[155];        345
110  *   };                       500
111  *
112  * </pre>
113  *
114  * Note that while the class does recognize GNU formatted headers,
115  * it does not perform proper processing of GNU archives. I hope
116  * to add the GNU support someday.
117  *
118  * Directory "size" fix contributed by:
119  * Bert Becker <becker@informatik.hu-berlin.de>
120  *
121  * @see TarHeader
122  * @author Timothy Gerard Endres, <time@gjt.org>
123  */
124 
125 public
126 class		TarEntry
127 extends		Object
128 implements	Cloneable
129 	{
130 	/**
131 	 * If this entry represents a File, this references it.
132 	 */
133 	protected File				file;
134 
135 	/**
136 	 * This is the entry's header information.
137 	 */
138 	protected TarHeader			header;
139 
140 	/**
141 	 * Set to true if this is a "old-unix" format entry.
142 	 */
143 	protected boolean			unixFormat;
144 
145 	/**
146 	 * Set to true if this is a 'ustar' format entry.
147 	 */
148 	protected boolean			ustarFormat;
149 
150 	/**
151 	 * Set to true if this is a GNU 'ustar' format entry.
152 	 */
153 	protected boolean			gnuFormat;
154 
155 
156 	/**
157 	 * The default constructor is protected for use only by subclasses.
158 	 */
159 	protected
TarEntry()160 	TarEntry()
161 		{
162 		}
163 
164 	/**
165 	 * Construct an entry with only a name. This allows the programmer
166 	 * to construct the entry's header "by hand". File is set to null.
167 	 */
168 	public
TarEntry( String name )169 	TarEntry( String name )
170 		{
171 		this.initialize();
172 		this.nameTarHeader( this.header, name );
173 		}
174 
175 	/**
176 	 * Construct an entry for a file. File is set to file, and the
177 	 * header is constructed from information from the file.
178 	 *
179 	 * @param file The file that the entry represents.
180 	 */
181 	public
TarEntry( File file )182 	TarEntry( File file )
183 		throws InvalidHeaderException
184 		{
185 		this.initialize();
186 		this.getFileTarHeader( this.header, file );
187 		}
188 
189 	/**
190 	 * Construct an entry from an archive's header bytes. File is set
191 	 * to null.
192 	 *
193 	 * @param headerBuf The header bytes from a tar archive entry.
194 	 */
195 	public
TarEntry( byte[] headerBuf )196 	TarEntry( byte[] headerBuf )
197 		throws InvalidHeaderException
198 		{
199 		this.initialize();
200 		this.parseTarHeader( this.header, headerBuf );
201 		}
202 
203 	/**
204 	 * Initialization code common to all constructors.
205 	 */
206 	private void
initialize()207 	initialize()
208 		{
209 		this.file = null;
210 		this.header = new TarHeader();
211 
212 		this.gnuFormat = false;
213 		this.ustarFormat = true; // REVIEW What we prefer to use...
214 		this.unixFormat = false;
215 		}
216 
217 	/**
218 	 * Clone the entry.
219 	 */
220 	public Object
clone()221 	clone()
222 		{
223 		TarEntry entry = null;
224 
225 		try {
226 			entry = (TarEntry) super.clone();
227 
228 			if ( this.header != null )
229 				{
230 				entry.header = (TarHeader) this.header.clone();
231 				}
232 
233 			if ( this.file != null )
234 				{
235 				entry.file = new File( this.file.getAbsolutePath() );
236 				}
237 			}
238 		catch ( CloneNotSupportedException ex )
239 			{
240 			ex.printStackTrace( System.err );
241 			}
242 
243 		return entry;
244 		}
245 
246 	/**
247 	 * Returns true if this entry's header is in "ustar" format.
248 	 *
249 	 * @return True if the entry's header is in "ustar" format.
250 	 */
251 	public boolean
isUSTarFormat()252 	isUSTarFormat()
253 		{
254 		return this.ustarFormat;
255 		}
256 
257 	/**
258 	 * Sets this entry's header format to "ustar".
259 	 */
260 	public void
setUSTarFormat()261 	setUSTarFormat()
262 		{
263 		this.ustarFormat = true;
264 		this.gnuFormat = false;
265 		this.unixFormat = false;
266 		}
267 
268 	/**
269 	 * Returns true if this entry's header is in the GNU 'ustar' format.
270 	 *
271 	 * @return True if the entry's header is in the GNU 'ustar' format.
272 	 */
273 	public boolean
isGNUTarFormat()274 	isGNUTarFormat()
275 		{
276 		return this.gnuFormat;
277 		}
278 
279 	/**
280 	 * Sets this entry's header format to GNU "ustar".
281 	 */
282 	public void
setGNUTarFormat()283 	setGNUTarFormat()
284 		{
285 		this.gnuFormat = true;
286 		this.ustarFormat = false;
287 		this.unixFormat = false;
288 		}
289 
290 	/**
291 	 * Returns true if this entry's header is in the old "unix-tar" format.
292 	 *
293 	 * @return True if the entry's header is in the old "unix-tar" format.
294 	 */
295 	public boolean
isUnixTarFormat()296 	isUnixTarFormat()
297 		{
298 		return this.unixFormat;
299 		}
300 
301 	/**
302 	 * Sets this entry's header format to "unix-style".
303 	 */
304 	public void
setUnixTarFormat()305 	setUnixTarFormat()
306 		{
307 		this.unixFormat = true;
308 		this.ustarFormat = false;
309 		this.gnuFormat = false;
310 		}
311 
312 	/**
313 	 * Determine if the two entries are equal. Equality is determined
314 	 * by the header names being equal.
315 	 *
316 	 * @return it Entry to be checked for equality.
317 	 * @return True if the entries are equal.
318 	 */
319 	public boolean
equals( TarEntry it )320 	equals( TarEntry it )
321 		{
322 		return
323 			this.header.name.toString().equals
324 				( it.header.name.toString() );
325 		}
326 
327 	/**
328 	 * Determine if the given entry is a descendant of this entry.
329 	 * Descendancy is determined by the name of the descendant
330 	 * starting with this entry's name.
331 	 *
332 	 * @param desc Entry to be checked as a descendent of this.
333 	 * @return True if entry is a descendant of this.
334 	 */
335 	public boolean
isDescendent( TarEntry desc )336 	isDescendent( TarEntry desc )
337 		{
338 		return
339 			desc.header.name.toString().startsWith
340 				( this.header.name.toString() );
341 		}
342 
343 	/**
344 	 * Get this entry's header.
345 	 *
346 	 * @return This entry's TarHeader.
347 	 */
348 	public TarHeader
getHeader()349 	getHeader()
350 		{
351 		return this.header;
352 		}
353 
354 	/**
355 	 * Get this entry's name.
356 	 *
357 	 * @return This entry's name.
358 	 */
359 	public String
getName()360 	getName()
361 		{
362 		return this.header.name.toString();
363 		}
364 
365 	/**
366 	 * Set this entry's name.
367 	 *
368 	 * @param name This entry's new name.
369 	 */
370 	public void
setName( String name )371 	setName( String name )
372 		{
373 		this.header.name =
374 			new StringBuffer( name );
375 		}
376 
377 	/**
378 	 * Get this entry's user id.
379 	 *
380 	 * @return This entry's user id.
381 	 */
382 	public int
getUserId()383 	getUserId()
384 		{
385 		return this.header.userId;
386 		}
387 
388 	/**
389 	 * Set this entry's user id.
390 	 *
391 	 * @param userId This entry's new user id.
392 	 */
393 	public void
setUserId( int userId )394 	setUserId( int userId )
395 		{
396 		this.header.userId = userId;
397 		}
398 
399 	/**
400 	 * Get this entry's group id.
401 	 *
402 	 * @return This entry's group id.
403 	 */
404 	public int
getGroupId()405 	getGroupId()
406 		{
407 		return this.header.groupId;
408 		}
409 
410 	/**
411 	 * Set this entry's group id.
412 	 *
413 	 * @param groupId This entry's new group id.
414 	 */
415 	public void
setGroupId( int groupId )416 	setGroupId( int groupId )
417 		{
418 		this.header.groupId = groupId;
419 		}
420 
421 	/**
422 	 * Get this entry's user name.
423 	 *
424 	 * @return This entry's user name.
425 	 */
426 	public String
getUserName()427 	getUserName()
428 		{
429 		return this.header.userName.toString();
430 		}
431 
432 	/**
433 	 * Set this entry's user name.
434 	 *
435 	 * @param userName This entry's new user name.
436 	 */
437 	public void
setUserName( String userName )438 	setUserName( String userName )
439 		{
440 		this.header.userName =
441 			new StringBuffer( userName );
442 		}
443 
444 	/**
445 	 * Get this entry's group name.
446 	 *
447 	 * @return This entry's group name.
448 	 */
449 	public String
getGroupName()450 	getGroupName()
451 		{
452 		return this.header.groupName.toString();
453 		}
454 
455 	/**
456 	 * Set this entry's group name.
457 	 *
458 	 * @param groupName This entry's new group name.
459 	 */
460 	public void
setGroupName( String groupName )461 	setGroupName( String groupName )
462 		{
463 		this.header.groupName =
464 			new StringBuffer( groupName );
465 		}
466 
467 	/**
468 	 * Convenience method to set this entry's group and user ids.
469 	 *
470 	 * @param userId This entry's new user id.
471 	 * @param groupId This entry's new group id.
472 	 */
473 	public void
setIds( int userId, int groupId )474 	setIds( int userId, int groupId )
475 		{
476 		this.setUserId( userId );
477 		this.setGroupId( groupId );
478 		}
479 
480 	/**
481 	 * Convenience method to set this entry's group and user names.
482 	 *
483 	 * @param userName This entry's new user name.
484 	 * @param groupName This entry's new group name.
485 	 */
486 	public void
setNames( String userName, String groupName )487 	setNames( String userName, String groupName )
488 		{
489 		this.setUserName( userName );
490 		this.setGroupName( groupName );
491 		}
492 
493 	/**
494 	 * Set this entry's modification time. The parameter passed
495 	 * to this method is in "Java time".
496 	 *
497 	 * @param time This entry's new modification time.
498 	 */
499 	public void
setModTime( long time )500 	setModTime( long time )
501 		{
502 		this.header.modTime = time / 1000;
503 		}
504 
505 	/**
506 	 * Set this entry's modification time.
507 	 *
508 	 * @param time This entry's new modification time.
509 	 */
510 	public void
setModTime( Date time )511 	setModTime( Date time )
512 		{
513 		this.header.modTime = time.getTime() / 1000;
514 		}
515 
516 	/**
517 	 * Set this entry's modification time.
518 	 *
519 	 * @param time This entry's new modification time.
520 	 */
521 	public Date
getModTime()522 	getModTime()
523 		{
524 		return new Date( this.header.modTime * 1000 );
525 		}
526 
527 	/**
528 	 * Get this entry's file.
529 	 *
530 	 * @return This entry's file.
531 	 */
532 	public File
getFile()533 	getFile()
534 		{
535 		return this.file;
536 		}
537 
538 	/**
539 	 * Get this entry's file size.
540 	 *
541 	 * @return This entry's file size.
542 	 */
543 	public long
getSize()544 	getSize()
545 		{
546 		return this.header.size;
547 		}
548 
549 	/**
550 	 * Set this entry's file size.
551 	 *
552 	 * @param size This entry's new file size.
553 	 */
554 	public void
setSize( long size )555 	setSize( long size )
556 		{
557 		this.header.size = size;
558 		}
559 
560 	/**
561 	 * Return whether or not this entry represents a directory.
562 	 *
563 	 * @return True if this entry is a directory.
564 	 */
565 	public boolean
isDirectory()566 	isDirectory()
567 		{
568 		if ( this.file != null )
569 			return this.file.isDirectory();
570 
571 		if ( this.header != null )
572 			{
573 			if ( this.header.linkFlag == TarHeader.LF_DIR )
574 				return true;
575 
576 			if ( this.header.name.toString().endsWith( "/" ) )
577 				return true;
578 			}
579 
580 		return false;
581 		}
582 
583 	/**
584 	 * Fill in a TarHeader with information from a File.
585 	 *
586 	 * @param hdr The TarHeader to fill in.
587 	 * @param file The file from which to get the header information.
588 	 */
589 	public void
getFileTarHeader( TarHeader hdr, File file )590 	getFileTarHeader( TarHeader hdr, File file )
591 		throws InvalidHeaderException
592 		{
593 		this.file = file;
594 
595 		String name = file.getPath();
596 		String osname = System.getProperty( "os.name" );
597 		if ( osname != null )
598 			{
599 			// Strip off drive letters!
600 			// REVIEW Would a better check be "(File.separator == '\')"?
601 
602 			// String Win32Prefix = "Windows";
603 			// String prefix = osname.substring( 0, Win32Prefix.length() );
604 			// if ( prefix.equalsIgnoreCase( Win32Prefix ) )
605 
606 			// if ( File.separatorChar == '\\' )
607 
608 			// Windows OS check was contributed by
609 			// Patrick Beard <beard@netscape.com>
610 			String Win32Prefix = "windows";
611 			if ( osname.toLowerCase().startsWith( Win32Prefix ) )
612 				{
613 				if ( name.length() > 2 )
614 					{
615 					char ch1 = name.charAt(0);
616 					char ch2 = name.charAt(1);
617 					if ( ch2 == ':'
618 						&& ( (ch1 >= 'a' && ch1 <= 'z')
619 							|| (ch1 >= 'A' && ch1 <= 'Z') ) )
620 						{
621 						name = name.substring( 2 );
622 						}
623 					}
624 				}
625 			}
626 
627 		name = name.replace( File.separatorChar, '/' );
628 
629 		// No absolute pathnames
630 		// Windows (and Posix?) paths can start with "\\NetworkDrive\",
631 		// so we loop on starting /'s.
632 
633 		for ( ; name.startsWith( "/" ) ; )
634 			name = name.substring( 1 );
635 
636  		hdr.linkName = new StringBuffer( "" );
637 
638 		hdr.name = new StringBuffer( name );
639 
640 		if ( file.isDirectory() )
641 			{
642 			hdr.size = 0;
643 			hdr.mode = 040755;
644 			hdr.linkFlag = TarHeader.LF_DIR;
645 			if ( hdr.name.charAt( hdr.name.length() - 1 ) != '/' )
646 				hdr.name.append( "/" );
647 			}
648 		else
649 			{
650 			hdr.size = file.length();
651 			hdr.mode = 0100644;
652 			hdr.linkFlag = TarHeader.LF_NORMAL;
653 			}
654 
655 		// UNDONE When File lets us get the userName, use it!
656 
657 		hdr.modTime = file.lastModified() / 1000;
658 		hdr.checkSum = 0;
659 		hdr.devMajor = 0;
660 		hdr.devMinor = 0;
661 		}
662 
663 	/**
664 	 * If this entry represents a file, and the file is a directory, return
665 	 * an array of TarEntries for this entry's children.
666 	 *
667 	 * @return An array of TarEntry's for this entry's children.
668 	 */
669 	public TarEntry[]
getDirectoryEntries()670 	getDirectoryEntries()
671 		throws InvalidHeaderException
672 		{
673 		if ( this.file == null
674 				|| ! this.file.isDirectory() )
675 			{
676 			return new TarEntry[0];
677 			}
678 
679 		String[] list = this.file.list();
680 
681 		TarEntry[] result = new TarEntry[ list.length ];
682 
683 		for ( int i = 0 ; i < list.length ; ++i )
684 			{
685 			result[i] =
686 				new TarEntry
687 					( new File( this.file, list[i] ) );
688 			}
689 
690 		return result;
691 		}
692 
693 	/**
694 	 * Compute the checksum of a tar entry header.
695 	 *
696 	 * @param buf The tar entry's header buffer.
697 	 * @return The computed checksum.
698 	 */
699 	public long
computeCheckSum( byte[] buf )700 	computeCheckSum( byte[] buf )
701 		{
702 		long sum = 0;
703 
704 		for ( int i = 0 ; i < buf.length ; ++i )
705 			{
706 			sum += 255 & buf[ i ];
707 			}
708 
709 		return sum;
710 		}
711 
712 	/**
713 	 * Write an entry's header information to a header buffer.
714 	 * This method can throw an InvalidHeaderException
715 	 *
716 	 * @param outbuf The tar entry header buffer to fill in.
717 	 * @throws InvalidHeaderException If the name will not fit in the header.
718 	 */
719 	public void
writeEntryHeader( byte[] outbuf )720 	writeEntryHeader( byte[] outbuf )
721 		throws InvalidHeaderException
722 		{
723 		int offset = 0;
724 
725 		if ( this.isUnixTarFormat() )
726 			{
727 			if ( this.header.name.length() > 100 )
728 				throw new InvalidHeaderException
729 					( "file path is greater than 100 characters, "
730 						+ this.header.name );
731 			}
732 
733 		offset = TarHeader.getFileNameBytes( this.header.name.toString(), outbuf );
734 
735 		offset = TarHeader.getOctalBytes
736 			( this.header.mode, outbuf, offset, TarHeader.MODELEN );
737 
738 		offset = TarHeader.getOctalBytes
739 			( this.header.userId, outbuf, offset, TarHeader.UIDLEN );
740 
741 		offset = TarHeader.getOctalBytes
742 			( this.header.groupId, outbuf, offset, TarHeader.GIDLEN );
743 
744 		long size = this.header.size;
745 
746 		offset = TarHeader.getLongOctalBytes
747 			( size, outbuf, offset, TarHeader.SIZELEN );
748 
749 		offset = TarHeader.getLongOctalBytes
750 			( this.header.modTime, outbuf, offset, TarHeader.MODTIMELEN );
751 
752 		int csOffset = offset;
753 		for ( int c = 0 ; c < TarHeader.CHKSUMLEN ; ++c )
754 			outbuf[ offset++ ] = (byte) ' ';
755 
756 		outbuf[ offset++ ] = this.header.linkFlag;
757 
758 		offset = TarHeader.getNameBytes
759 			( this.header.linkName, outbuf, offset, TarHeader.NAMELEN );
760 
761 		if ( this.unixFormat )
762 			{
763 			for ( int i = 0 ; i < TarHeader.MAGICLEN ; ++i )
764 				outbuf[ offset++ ] = 0;
765 			}
766 		else
767 			{
768 			offset = TarHeader.getNameBytes
769 				( this.header.magic, outbuf, offset, TarHeader.MAGICLEN );
770 			}
771 
772 		offset = TarHeader.getNameBytes
773 			( this.header.userName, outbuf, offset, TarHeader.UNAMELEN );
774 
775 		offset = TarHeader.getNameBytes
776 			( this.header.groupName, outbuf, offset, TarHeader.GNAMELEN );
777 
778 		offset = TarHeader.getOctalBytes
779 			( this.header.devMajor, outbuf, offset, TarHeader.DEVLEN );
780 
781 		offset = TarHeader.getOctalBytes
782 			( this.header.devMinor, outbuf, offset, TarHeader.DEVLEN );
783 
784 		for ( ; offset < outbuf.length ; )
785 			outbuf[ offset++ ] = 0;
786 
787 		long checkSum = this.computeCheckSum( outbuf );
788 
789 		TarHeader.getCheckSumOctalBytes
790 			( checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN );
791 		}
792 
793 	/**
794 	 * Parse an entry's TarHeader information from a header buffer.
795 	 *
796 	 * Old unix-style code contributed by David Mehringer <dmehring@astro.uiuc.edu>.
797 	 *
798 	 * @param hdr The TarHeader to fill in from the buffer information.
799 	 * @param header The tar entry header buffer to get information from.
800 	 */
801 	public void
parseTarHeader( TarHeader hdr, byte[] headerBuf )802 	parseTarHeader( TarHeader hdr, byte[] headerBuf )
803 		throws InvalidHeaderException
804 		{
805 		int offset = 0;
806 
807 		//
808 		// NOTE Recognize archive header format.
809 		//
810 		if (       headerBuf[257] == 0
811 				&& headerBuf[258] == 0
812 				&& headerBuf[259] == 0
813 				&& headerBuf[260] == 0
814 				&& headerBuf[261] == 0 )
815 			{
816 			this.unixFormat = true;
817 			this.ustarFormat = false;
818 			this.gnuFormat = false;
819 			}
820 		else if (  headerBuf[257] == 'u'
821 				&& headerBuf[258] == 's'
822 				&& headerBuf[259] == 't'
823 				&& headerBuf[260] == 'a'
824 				&& headerBuf[261] == 'r'
825 				&& headerBuf[262] == 0 )
826 			{
827 			this.ustarFormat = true;
828 			this.gnuFormat = false;
829 			this.unixFormat = false;
830 			}
831 		else if (  headerBuf[257] == 'u'
832 				&& headerBuf[258] == 's'
833 				&& headerBuf[259] == 't'
834 				&& headerBuf[260] == 'a'
835 				&& headerBuf[261] == 'r'
836 				&& headerBuf[262] != 0
837 				&& headerBuf[263] != 0 )
838 			{
839 			// REVIEW
840 			this.gnuFormat = true;
841 			this.unixFormat = false;
842 			this.ustarFormat = false;
843 			}
844 		else
845 			{
846 			StringBuffer buf = new StringBuffer( 128 );
847 
848 			buf.append( "header magic is not 'ustar' or unix-style zeros, it is '" );
849 			buf.append( headerBuf[257] );
850 			buf.append( headerBuf[258] );
851 			buf.append( headerBuf[259] );
852 			buf.append( headerBuf[260] );
853 			buf.append( headerBuf[261] );
854 			buf.append( headerBuf[262] );
855 			buf.append( headerBuf[263] );
856 			buf.append( "', or (dec) " );
857 			buf.append( (int)headerBuf[257] );
858 			buf.append( ", " );
859 			buf.append( (int)headerBuf[258] );
860 			buf.append( ", " );
861 			buf.append( (int)headerBuf[259] );
862 			buf.append( ", " );
863 			buf.append( (int)headerBuf[260] );
864 			buf.append( ", " );
865 			buf.append( (int)headerBuf[261] );
866 			buf.append( ", " );
867 			buf.append( (int)headerBuf[262] );
868 			buf.append( ", " );
869 			buf.append( (int)headerBuf[263] );
870 
871 			throw new InvalidHeaderException( buf.toString() );
872 			}
873 
874 		hdr.name = TarHeader.parseFileName( headerBuf );
875 
876 		offset = TarHeader.NAMELEN;
877 
878 		hdr.mode = (int)
879 			TarHeader.parseOctal( headerBuf, offset, TarHeader.MODELEN );
880 
881 		offset += TarHeader.MODELEN;
882 
883 		hdr.userId = (int)
884 			TarHeader.parseOctal( headerBuf, offset, TarHeader.UIDLEN );
885 
886 		offset += TarHeader.UIDLEN;
887 
888 		hdr.groupId = (int)
889 			TarHeader.parseOctal( headerBuf, offset, TarHeader.GIDLEN );
890 
891 		offset += TarHeader.GIDLEN;
892 
893 		hdr.size =
894 			TarHeader.parseOctal( headerBuf, offset, TarHeader.SIZELEN );
895 
896 		offset += TarHeader.SIZELEN;
897 
898 		hdr.modTime =
899 			TarHeader.parseOctal( headerBuf, offset, TarHeader.MODTIMELEN );
900 
901 		offset += TarHeader.MODTIMELEN;
902 
903 		hdr.checkSum = (int)
904 			TarHeader.parseOctal( headerBuf, offset, TarHeader.CHKSUMLEN );
905 
906 		offset += TarHeader.CHKSUMLEN;
907 
908 		hdr.linkFlag = headerBuf[ offset++ ];
909 
910 		hdr.linkName =
911 			TarHeader.parseName( headerBuf, offset, TarHeader.NAMELEN );
912 
913 		offset += TarHeader.NAMELEN;
914 
915 		if ( this.ustarFormat )
916 			{
917 			hdr.magic =
918 				TarHeader.parseName( headerBuf, offset, TarHeader.MAGICLEN );
919 
920 			offset += TarHeader.MAGICLEN;
921 
922 			hdr.userName =
923 				TarHeader.parseName( headerBuf, offset, TarHeader.UNAMELEN );
924 
925 			offset += TarHeader.UNAMELEN;
926 
927 			hdr.groupName =
928 				TarHeader.parseName( headerBuf, offset, TarHeader.GNAMELEN );
929 
930 			offset += TarHeader.GNAMELEN;
931 
932 			hdr.devMajor = (int)
933 				TarHeader.parseOctal( headerBuf, offset, TarHeader.DEVLEN );
934 
935 			offset += TarHeader.DEVLEN;
936 
937 			hdr.devMinor = (int)
938 				TarHeader.parseOctal( headerBuf, offset, TarHeader.DEVLEN );
939 			}
940 		else
941 			{
942 			hdr.devMajor = 0;
943 			hdr.devMinor = 0;
944 			hdr.magic = new StringBuffer( "" );
945 			hdr.userName = new StringBuffer( "" );
946 			hdr.groupName = new StringBuffer( "" );
947 			}
948 		}
949 
950 	/**
951 	 * Fill in a TarHeader given only the entry's name.
952 	 *
953 	 * @param hdr The TarHeader to fill in.
954 	 * @param name The tar entry name.
955 	 */
956 	public void
nameTarHeader( TarHeader hdr, String name )957 	nameTarHeader( TarHeader hdr, String name )
958 		{
959 		boolean isDir = name.endsWith( "/" );
960 
961 		this.gnuFormat = false;
962 		this.ustarFormat = true;
963 		this.unixFormat = false;
964 
965 		hdr.checkSum = 0;
966 		hdr.devMajor = 0;
967 		hdr.devMinor = 0;
968 
969 		hdr.name = new StringBuffer( name );
970 		hdr.mode = isDir ? 040755 : 0100644;
971 		hdr.userId = 0;
972 		hdr.groupId = 0;
973 		hdr.size = 0;
974 		hdr.checkSum = 0;
975 
976 		hdr.modTime =
977 			(new java.util.Date()).getTime() / 1000;
978 
979 		hdr.linkFlag =
980 			isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL;
981 
982 		hdr.linkName = new StringBuffer( "" );
983 		hdr.userName = new StringBuffer( "" );
984 		hdr.groupName = new StringBuffer( "" );
985 
986 		hdr.devMajor = 0;
987 		hdr.devMinor = 0;
988 		}
989 
990 	public String
toString()991 	toString()
992 		{
993 		StringBuffer result = new StringBuffer( 128 );
994 		return result.
995 			append( "[TarEntry name=" ).
996 			append( this.getName() ).
997 			append( ", isDir=" ).
998 			append( this.isDirectory() ).
999 			append( ", size=" ).
1000 			append( this.getSize() ).
1001 			append( ", userId=" ).
1002 			append( this.getUserId() ).
1003 			append( ", user=" ).
1004 			append( this.getUserName() ).
1005 			append( ", groupId=" ).
1006 			append( this.getGroupId() ).
1007 			append( ", group=" ).
1008 			append( this.getGroupName() ).
1009 			append( "]" ).
1010 			toString();
1011 		}
1012 
1013 	}
1014 
1015