001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.commons.compress.archivers.tar;
020
021 import java.io.File;
022 import java.nio.ByteBuffer;
023 import java.util.Date;
024 import java.util.Locale;
025
026 import org.apache.commons.compress.archivers.ArchiveEntry;
027
028 /**
029 * This class represents an entry in a Tar archive. It consists
030 * of the entry's header, as well as the entry's File. Entries
031 * can be instantiated in one of three ways, depending on how
032 * they are to be used.
033 * <p>
034 * TarEntries that are created from the header bytes read from
035 * an archive are instantiated with the TarEntry( byte[] )
036 * constructor. These entries will be used when extracting from
037 * or listing the contents of an archive. These entries have their
038 * header filled in using the header bytes. They also set the File
039 * to null, since they reference an archive entry not a file.
040 * <p>
041 * TarEntries that are created from Files that are to be written
042 * into an archive are instantiated with the TarEntry( File )
043 * constructor. These entries have their header filled in using
044 * the File's information. They also keep a reference to the File
045 * for convenience when writing entries.
046 * <p>
047 * Finally, TarEntries can be constructed from nothing but a name.
048 * This allows the programmer to construct the entry by hand, for
049 * instance when only an InputStream is available for writing to
050 * the archive, and the header information is constructed from
051 * other information. In this case the header fields are set to
052 * defaults and the File is set to null.
053 *
054 * <p>
055 * The C structure for a Tar Entry's header is:
056 * <pre>
057 * struct header {
058 * char name[100]; // TarConstants.NAMELEN - offset 0
059 * char mode[8]; // TarConstants.MODELEN - offset 100
060 * char uid[8]; // TarConstants.UIDLEN - offset 108
061 * char gid[8]; // TarConstants.GIDLEN - offset 116
062 * char size[12]; // TarConstants.SIZELEN - offset 124
063 * char mtime[12]; // TarConstants.MODTIMELEN - offset 136
064 * char chksum[8]; // TarConstants.CHKSUMLEN - offset 148
065 * char linkflag[1]; // - offset 156
066 * char linkname[100]; // TarConstants.NAMELEN - offset 157
067 * The following fields are only present in new-style POSIX tar archives:
068 * char magic[6]; // TarConstants.MAGICLEN - offset 257
069 * char version[2]; // TarConstants.VERSIONLEN - offset 263
070 * char uname[32]; // TarConstants.UNAMELEN - offset 265
071 * char gname[32]; // TarConstants.GNAMELEN - offset 297
072 * char devmajor[8]; // TarConstants.DEVLEN - offset 329
073 * char devminor[8]; // TarConstants.DEVLEN - offset 337
074 * char prefix[155]; // TarConstants.PREFIXLEN - offset 345
075 * // Used if "name" field is not long enough to hold the path
076 * char pad[12]; // NULs - offset 500
077 * } header;
078 * All unused bytes are set to null.
079 * New-style GNU tar files are slightly different from the above.
080 * </pre>
081 *
082 * <p>
083 * The C structure for a old GNU Tar Entry's header is:
084 * <pre>
085 * struct oldgnu_header {
086 * char unused_pad1[345]; // TarConstants.PAD1LEN_GNU - offset 0
087 * char atime[12]; // TarConstants.ATIMELEN_GNU - offset 345
088 * char ctime[12]; // TarConstants.CTIMELEN_GNU - offset 357
089 * char offset[12]; // TarConstants.OFFSETLEN_GNU - offset 369
090 * char longnames[4]; // TarConstants.LONGNAMESLEN_GNU - offset 381
091 * char unused_pad2; // TarConstants.PAD2LEN_GNU - offset 385
092 * struct sparse sp[4]; // TarConstants.SPARSELEN_GNU - offset 386
093 * char isextended; // TarConstants.ISEXTENDEDLEN_GNU - offset 482
094 * char realsize[12]; // TarConstants.REALSIZELEN_GNU - offset 483
095 * char unused_pad[17]; // TarConstants.PAD3LEN_GNU - offset 495
096 * };
097 * </pre>
098 * Whereas, "struct sparse" is:
099 * <pre>
100 * struct sparse {
101 * char offset[12]; // offset 0
102 * char numbytes[12]; // offset 12
103 * };
104 * </pre>
105 *
106 * @NotThreadSafe
107 */
108
109 public class TarArchiveEntry implements TarConstants, ArchiveEntry {
110 /** The entry's name. */
111 private String name;
112
113 /** The entry's permission mode. */
114 private int mode;
115
116 /** The entry's user id. */
117 private int userId;
118
119 /** The entry's group id. */
120 private int groupId;
121
122 /** The entry's size. */
123 private long size;
124
125 /** The entry's modification time. */
126 private long modTime;
127
128 /** The entry's link flag. */
129 private byte linkFlag;
130
131 /** The entry's link name. */
132 private String linkName;
133
134 /** The entry's magic tag. */
135 private String magic;
136 /** The version of the format */
137 private String version;
138
139 /** The entry's user name. */
140 private String userName;
141
142 /** The entry's group name. */
143 private String groupName;
144
145 /** The entry's major device number. */
146 private int devMajor;
147
148 /** The entry's minor device number. */
149 private int devMinor;
150
151 /** If an extension sparse header follows. */
152 private boolean isExtended;
153
154 /** The entry's real size in case of a sparse file. */
155 private long realSize;
156
157 /** The entry's file reference */
158 private File file;
159
160 /** Maximum length of a user's name in the tar file */
161 public static final int MAX_NAMELEN = 31;
162
163 /** Default permissions bits for directories */
164 public static final int DEFAULT_DIR_MODE = 040755;
165
166 /** Default permissions bits for files */
167 public static final int DEFAULT_FILE_MODE = 0100644;
168
169 /** Convert millis to seconds */
170 public static final int MILLIS_PER_SECOND = 1000;
171
172 /**
173 * Construct an empty entry and prepares the header values.
174 */
175 private TarArchiveEntry () {
176 this.magic = MAGIC_POSIX;
177 this.version = VERSION_POSIX;
178 this.name = "";
179 this.linkName = "";
180
181 String user = System.getProperty("user.name", "");
182
183 if (user.length() > MAX_NAMELEN) {
184 user = user.substring(0, MAX_NAMELEN);
185 }
186
187 this.userId = 0;
188 this.groupId = 0;
189 this.userName = user;
190 this.groupName = "";
191 this.file = null;
192 }
193
194 /**
195 * Construct an entry with only a name. This allows the programmer
196 * to construct the entry's header "by hand". File is set to null.
197 *
198 * @param name the entry name
199 */
200 public TarArchiveEntry(String name) {
201 this(name, false);
202 }
203
204 /**
205 * Construct an entry with only a name. This allows the programmer
206 * to construct the entry's header "by hand". File is set to null.
207 *
208 * @param name the entry name
209 * @param preserveLeadingSlashes whether to allow leading slashes
210 * in the name.
211 *
212 * @since Apache Commons Compress 1.1
213 */
214 public TarArchiveEntry(String name, boolean preserveLeadingSlashes) {
215 this();
216
217 name = normalizeFileName(name, preserveLeadingSlashes);
218 boolean isDir = name.endsWith("/");
219
220 this.devMajor = 0;
221 this.devMinor = 0;
222 this.name = name;
223 this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
224 this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
225 this.userId = 0;
226 this.groupId = 0;
227 this.size = 0;
228 this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND;
229 this.linkName = "";
230 this.userName = "";
231 this.groupName = "";
232 this.devMajor = 0;
233 this.devMinor = 0;
234
235 }
236
237 /**
238 * Construct an entry with a name and a link flag.
239 *
240 * @param name the entry name
241 * @param linkFlag the entry link flag.
242 */
243 public TarArchiveEntry(String name, byte linkFlag) {
244 this(name);
245 this.linkFlag = linkFlag;
246 if (linkFlag == LF_GNUTYPE_LONGNAME) {
247 magic = MAGIC_GNU;
248 version = VERSION_GNU_SPACE;
249 }
250 }
251
252 /**
253 * Construct an entry for a file. File is set to file, and the
254 * header is constructed from information from the file.
255 * The name is set from the normalized file path.
256 *
257 * @param file The file that the entry represents.
258 */
259 public TarArchiveEntry(File file) {
260 this(file, normalizeFileName(file.getPath(), false));
261 }
262
263 /**
264 * Construct an entry for a file. File is set to file, and the
265 * header is constructed from information from the file.
266 *
267 * @param file The file that the entry represents.
268 * @param fileName the name to be used for the entry.
269 */
270 public TarArchiveEntry(File file, String fileName) {
271 this();
272
273 this.file = file;
274
275 this.linkName = "";
276
277 if (file.isDirectory()) {
278 this.mode = DEFAULT_DIR_MODE;
279 this.linkFlag = LF_DIR;
280
281 int nameLength = fileName.length();
282 if (nameLength == 0 || fileName.charAt(nameLength - 1) != '/') {
283 this.name = fileName + "/";
284 } else {
285 this.name = fileName;
286 }
287 this.size = 0;
288 } else {
289 this.mode = DEFAULT_FILE_MODE;
290 this.linkFlag = LF_NORMAL;
291 this.size = file.length();
292 this.name = fileName;
293 }
294
295 this.modTime = file.lastModified() / MILLIS_PER_SECOND;
296 this.devMajor = 0;
297 this.devMinor = 0;
298 }
299
300 /**
301 * Construct an entry from an archive's header bytes. File is set
302 * to null.
303 *
304 * @param headerBuf The header bytes from a tar archive entry.
305 */
306 public TarArchiveEntry(byte[] headerBuf) {
307 this();
308 parseTarHeader(headerBuf);
309 }
310
311 /**
312 * Determine if the two entries are equal. Equality is determined
313 * by the header names being equal.
314 *
315 * @param it Entry to be checked for equality.
316 * @return True if the entries are equal.
317 */
318 public boolean equals(TarArchiveEntry it) {
319 return getName().equals(it.getName());
320 }
321
322 /**
323 * Determine if the two entries are equal. Equality is determined
324 * by the header names being equal.
325 *
326 * @param it Entry to be checked for equality.
327 * @return True if the entries are equal.
328 */
329 public boolean equals(Object it) {
330 if (it == null || getClass() != it.getClass()) {
331 return false;
332 }
333 return equals((TarArchiveEntry) it);
334 }
335
336 /**
337 * Hashcodes are based on entry names.
338 *
339 * @return the entry hashcode
340 */
341 public int hashCode() {
342 return getName().hashCode();
343 }
344
345 /**
346 * Determine if the given entry is a descendant of this entry.
347 * Descendancy is determined by the name of the descendant
348 * starting with this entry's name.
349 *
350 * @param desc Entry to be checked as a descendent of this.
351 * @return True if entry is a descendant of this.
352 */
353 public boolean isDescendent(TarArchiveEntry desc) {
354 return desc.getName().startsWith(getName());
355 }
356
357 /**
358 * Get this entry's name.
359 *
360 * @return This entry's name.
361 */
362 public String getName() {
363 return name.toString();
364 }
365
366 /**
367 * Set this entry's name.
368 *
369 * @param name This entry's new name.
370 */
371 public void setName(String name) {
372 this.name = normalizeFileName(name, false);
373 }
374
375 /**
376 * Set the mode for this entry
377 *
378 * @param mode the mode for this entry
379 */
380 public void setMode(int mode) {
381 this.mode = mode;
382 }
383
384 /**
385 * Get this entry's link name.
386 *
387 * @return This entry's link name.
388 */
389 public String getLinkName() {
390 return linkName.toString();
391 }
392
393 /**
394 * Set this entry's link name.
395 *
396 * @param link the link name to use.
397 *
398 * @since Apache Commons Compress 1.1
399 */
400 public void setLinkName(String link) {
401 this.linkName = link;
402 }
403
404 /**
405 * Get this entry's user id.
406 *
407 * @return This entry's user id.
408 */
409 public int getUserId() {
410 return userId;
411 }
412
413 /**
414 * Set this entry's user id.
415 *
416 * @param userId This entry's new user id.
417 */
418 public void setUserId(int userId) {
419 this.userId = userId;
420 }
421
422 /**
423 * Get this entry's group id.
424 *
425 * @return This entry's group id.
426 */
427 public int getGroupId() {
428 return groupId;
429 }
430
431 /**
432 * Set this entry's group id.
433 *
434 * @param groupId This entry's new group id.
435 */
436 public void setGroupId(int groupId) {
437 this.groupId = groupId;
438 }
439
440 /**
441 * Get this entry's user name.
442 *
443 * @return This entry's user name.
444 */
445 public String getUserName() {
446 return userName.toString();
447 }
448
449 /**
450 * Set this entry's user name.
451 *
452 * @param userName This entry's new user name.
453 */
454 public void setUserName(String userName) {
455 this.userName = userName;
456 }
457
458 /**
459 * Get this entry's group name.
460 *
461 * @return This entry's group name.
462 */
463 public String getGroupName() {
464 return groupName.toString();
465 }
466
467 /**
468 * Set this entry's group name.
469 *
470 * @param groupName This entry's new group name.
471 */
472 public void setGroupName(String groupName) {
473 this.groupName = groupName;
474 }
475
476 /**
477 * Convenience method to set this entry's group and user ids.
478 *
479 * @param userId This entry's new user id.
480 * @param groupId This entry's new group id.
481 */
482 public void setIds(int userId, int groupId) {
483 setUserId(userId);
484 setGroupId(groupId);
485 }
486
487 /**
488 * Convenience method to set this entry's group and user names.
489 *
490 * @param userName This entry's new user name.
491 * @param groupName This entry's new group name.
492 */
493 public void setNames(String userName, String groupName) {
494 setUserName(userName);
495 setGroupName(groupName);
496 }
497
498 /**
499 * Set this entry's modification time. The parameter passed
500 * to this method is in "Java time".
501 *
502 * @param time This entry's new modification time.
503 */
504 public void setModTime(long time) {
505 modTime = time / MILLIS_PER_SECOND;
506 }
507
508 /**
509 * Set this entry's modification time.
510 *
511 * @param time This entry's new modification time.
512 */
513 public void setModTime(Date time) {
514 modTime = time.getTime() / MILLIS_PER_SECOND;
515 }
516
517 /**
518 * Set this entry's modification time.
519 *
520 * @return time This entry's new modification time.
521 */
522 public Date getModTime() {
523 return new Date(modTime * MILLIS_PER_SECOND);
524 }
525
526 /** {@inheritDoc} */
527 public Date getLastModifiedDate() {
528 return getModTime();
529 }
530
531 /**
532 * Get this entry's file.
533 *
534 * @return This entry's file.
535 */
536 public File getFile() {
537 return file;
538 }
539
540 /**
541 * Get this entry's mode.
542 *
543 * @return This entry's mode.
544 */
545 public int getMode() {
546 return mode;
547 }
548
549 /**
550 * Get this entry's file size.
551 *
552 * @return This entry's file size.
553 */
554 public long getSize() {
555 return size;
556 }
557
558 /**
559 * Set this entry's file size.
560 *
561 * @param size This entry's new file size.
562 * @throws IllegalArgumentException if the size is < 0
563 * or > {@link TarConstants#MAXSIZE} (077777777777L).
564 */
565 public void setSize(long size) {
566 if (size > MAXSIZE || size < 0){
567 throw new IllegalArgumentException("Size is out of range: "+size);
568 }
569 this.size = size;
570 }
571
572 /**
573 * Indicates in case of a sparse file if an extension sparse header
574 * follows.
575 *
576 * @return true if an extension sparse header follows.
577 */
578 public boolean isExtended() {
579 return isExtended;
580 }
581
582 /**
583 * Get this entry's real file size in case of a sparse file.
584 *
585 * @return This entry's real file size.
586 */
587 public long getRealSize() {
588 return realSize;
589 }
590
591 /**
592 * Indicate if this entry is a GNU sparse block
593 *
594 * @return true if this is a sparse extension provided by GNU tar
595 */
596 public boolean isGNUSparse() {
597 return linkFlag == LF_GNUTYPE_SPARSE;
598 }
599
600 /**
601 * Indicate if this entry is a GNU long name block
602 *
603 * @return true if this is a long name extension provided by GNU tar
604 */
605 public boolean isGNULongNameEntry() {
606 return linkFlag == LF_GNUTYPE_LONGNAME
607 && name.toString().equals(GNU_LONGLINK);
608 }
609
610 /**
611 * Check if this is a Pax header.
612 *
613 * @return <code>true</code> if this is a Pax header.
614 *
615 * @since Apache Commons Compress 1.1
616 */
617 public boolean isPaxHeader(){
618 return linkFlag == LF_PAX_EXTENDED_HEADER_LC
619 || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
620 }
621
622 /**
623 * Check if this is a Pax header.
624 *
625 * @return <code>true</code> if this is a Pax header.
626 *
627 * @since Apache Commons Compress 1.1
628 */
629 public boolean isGlobalPaxHeader(){
630 return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
631 }
632
633 /**
634 * Return whether or not this entry represents a directory.
635 *
636 * @return True if this entry is a directory.
637 */
638 public boolean isDirectory() {
639 if (file != null) {
640 return file.isDirectory();
641 }
642
643 if (linkFlag == LF_DIR) {
644 return true;
645 }
646
647 if (getName().endsWith("/")) {
648 return true;
649 }
650
651 return false;
652 }
653
654 /**
655 * Check if this is a "normal file"
656 *
657 * @since Apache Commons Compress 1.2
658 */
659 public boolean isFile() {
660 if (file != null) {
661 return file.isFile();
662 }
663 if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
664 return true;
665 }
666 return !getName().endsWith("/");
667 }
668
669 /**
670 * Check if this is a symbolic link entry.
671 *
672 * @since Apache Commons Compress 1.2
673 */
674 public boolean isSymbolicLink() {
675 return linkFlag == LF_SYMLINK;
676 }
677
678 /**
679 * Check if this is a link entry.
680 *
681 * @since Apache Commons Compress 1.2
682 */
683 public boolean isLink() {
684 return linkFlag == LF_LINK;
685 }
686
687 /**
688 * Check if this is a character device entry.
689 *
690 * @since Apache Commons Compress 1.2
691 */
692 public boolean isCharacterDevice() {
693 return linkFlag == LF_CHR;
694 }
695
696 /**
697 * Check if this is a block device entry.
698 *
699 * @since Apache Commons Compress 1.2
700 */
701 public boolean isBlockDevice() {
702 return linkFlag == LF_BLK;
703 }
704
705 /**
706 * Check if this is a FIFO (pipe) entry.
707 *
708 * @since Apache Commons Compress 1.2
709 */
710 public boolean isFIFO() {
711 return linkFlag == LF_FIFO;
712 }
713
714 /**
715 * If this entry represents a file, and the file is a directory, return
716 * an array of TarEntries for this entry's children.
717 *
718 * @return An array of TarEntry's for this entry's children.
719 */
720 public TarArchiveEntry[] getDirectoryEntries() {
721 if (file == null || !file.isDirectory()) {
722 return new TarArchiveEntry[0];
723 }
724
725 String[] list = file.list();
726 TarArchiveEntry[] result = new TarArchiveEntry[list.length];
727
728 for (int i = 0; i < list.length; ++i) {
729 result[i] = new TarArchiveEntry(new File(file, list[i]));
730 }
731
732 return result;
733 }
734
735 /**
736 * Write an entry's header information to a header buffer.
737 *
738 * @param outbuf The tar entry header buffer to fill in.
739 */
740 public void writeEntryHeader(byte[] outbuf) {
741 int offset = 0;
742
743 offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN);
744 offset = TarUtils.formatOctalBytes(mode, outbuf, offset, MODELEN);
745 offset = TarUtils.formatOctalBytes(userId, outbuf, offset, UIDLEN);
746 offset = TarUtils.formatOctalBytes(groupId, outbuf, offset, GIDLEN);
747 offset = TarUtils.formatLongOctalBytes(size, outbuf, offset, SIZELEN);
748 offset = TarUtils.formatLongOctalBytes(modTime, outbuf, offset, MODTIMELEN);
749
750 int csOffset = offset;
751
752 for (int c = 0; c < CHKSUMLEN; ++c) {
753 outbuf[offset++] = (byte) ' ';
754 }
755
756 outbuf[offset++] = linkFlag;
757 offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN);
758 offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
759 offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
760 offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN);
761 offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN);
762 offset = TarUtils.formatOctalBytes(devMajor, outbuf, offset, DEVLEN);
763 offset = TarUtils.formatOctalBytes(devMinor, outbuf, offset, DEVLEN);
764
765 while (offset < outbuf.length) {
766 outbuf[offset++] = 0;
767 }
768
769 long chk = TarUtils.computeCheckSum(outbuf);
770
771 TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
772 }
773
774 /**
775 * Parse an entry's header information from a header buffer.
776 *
777 * @param header The tar entry header buffer to get information from.
778 */
779 public void parseTarHeader(byte[] header) {
780 int offset = 0;
781
782 name = TarUtils.parseName(header, offset, NAMELEN);
783 offset += NAMELEN;
784 mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
785 offset += MODELEN;
786 userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
787 offset += UIDLEN;
788 groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
789 offset += GIDLEN;
790 size = TarUtils.parseOctal(header, offset, SIZELEN);
791 offset += SIZELEN;
792 modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
793 offset += MODTIMELEN;
794 offset += CHKSUMLEN;
795 linkFlag = header[offset++];
796 linkName = TarUtils.parseName(header, offset, NAMELEN);
797 offset += NAMELEN;
798 magic = TarUtils.parseName(header, offset, MAGICLEN);
799 offset += MAGICLEN;
800 version = TarUtils.parseName(header, offset, VERSIONLEN);
801 offset += VERSIONLEN;
802 userName = TarUtils.parseName(header, offset, UNAMELEN);
803 offset += UNAMELEN;
804 groupName = TarUtils.parseName(header, offset, GNAMELEN);
805 offset += GNAMELEN;
806 devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
807 offset += DEVLEN;
808 devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
809 offset += DEVLEN;
810
811 int type = evaluateType(header);
812 switch (type) {
813 case FORMAT_OLDGNU: {
814 offset += ATIMELEN_GNU;
815 offset += CTIMELEN_GNU;
816 offset += OFFSETLEN_GNU;
817 offset += LONGNAMESLEN_GNU;
818 offset += PAD2LEN_GNU;
819 offset += SPARSELEN_GNU;
820 isExtended = TarUtils.parseBoolean(header, offset);
821 offset += ISEXTENDEDLEN_GNU;
822 realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
823 offset += REALSIZELEN_GNU;
824 break;
825 }
826 case FORMAT_POSIX:
827 default: {
828 String prefix = TarUtils.parseName(header, offset, PREFIXLEN);
829 // SunOS tar -E does not add / to directory names, so fix
830 // up to be consistent
831 if (isDirectory() && !name.endsWith("/")){
832 name = name + "/";
833 }
834 if (prefix.length() > 0){
835 name = prefix + "/" + name;
836 }
837 }
838 }
839 }
840
841 /**
842 * Strips Windows' drive letter as well as any leading slashes,
843 * turns path separators into forward slahes.
844 */
845 private static String normalizeFileName(String fileName,
846 boolean preserveLeadingSlashes) {
847 String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
848
849 if (osname != null) {
850
851 // Strip off drive letters!
852 // REVIEW Would a better check be "(File.separator == '\')"?
853
854 if (osname.startsWith("windows")) {
855 if (fileName.length() > 2) {
856 char ch1 = fileName.charAt(0);
857 char ch2 = fileName.charAt(1);
858
859 if (ch2 == ':'
860 && ((ch1 >= 'a' && ch1 <= 'z')
861 || (ch1 >= 'A' && ch1 <= 'Z'))) {
862 fileName = fileName.substring(2);
863 }
864 }
865 } else if (osname.indexOf("netware") > -1) {
866 int colon = fileName.indexOf(':');
867 if (colon != -1) {
868 fileName = fileName.substring(colon + 1);
869 }
870 }
871 }
872
873 fileName = fileName.replace(File.separatorChar, '/');
874
875 // No absolute pathnames
876 // Windows (and Posix?) paths can start with "\\NetworkDrive\",
877 // so we loop on starting /'s.
878 while (!preserveLeadingSlashes && fileName.startsWith("/")) {
879 fileName = fileName.substring(1);
880 }
881 return fileName;
882 }
883
884 /**
885 * Evaluate an entry's header format from a header buffer.
886 *
887 * @param header The tar entry header buffer to evaluate the format for.
888 * @return format type
889 */
890 private int evaluateType(byte[] header) {
891 final ByteBuffer magic = ByteBuffer.wrap(header, MAGIC_OFFSET, MAGICLEN);
892 if (magic.compareTo(ByteBuffer.wrap(MAGIC_GNU.getBytes())) == 0)
893 return FORMAT_OLDGNU;
894 if (magic.compareTo(ByteBuffer.wrap(MAGIC_POSIX.getBytes())) == 0)
895 return FORMAT_POSIX;
896 return 0;
897 }
898 }
899