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 @Override
330 public boolean equals(Object it) {
331 if (it == null || getClass() != it.getClass()) {
332 return false;
333 }
334 return equals((TarArchiveEntry) it);
335 }
336
337 /**
338 * Hashcodes are based on entry names.
339 *
340 * @return the entry hashcode
341 */
342 @Override
343 public int hashCode() {
344 return getName().hashCode();
345 }
346
347 /**
348 * Determine if the given entry is a descendant of this entry.
349 * Descendancy is determined by the name of the descendant
350 * starting with this entry's name.
351 *
352 * @param desc Entry to be checked as a descendent of this.
353 * @return True if entry is a descendant of this.
354 */
355 public boolean isDescendent(TarArchiveEntry desc) {
356 return desc.getName().startsWith(getName());
357 }
358
359 /**
360 * Get this entry's name.
361 *
362 * @return This entry's name.
363 */
364 public String getName() {
365 return name.toString();
366 }
367
368 /**
369 * Set this entry's name.
370 *
371 * @param name This entry's new name.
372 */
373 public void setName(String name) {
374 this.name = normalizeFileName(name, false);
375 }
376
377 /**
378 * Set the mode for this entry
379 *
380 * @param mode the mode for this entry
381 */
382 public void setMode(int mode) {
383 this.mode = mode;
384 }
385
386 /**
387 * Get this entry's link name.
388 *
389 * @return This entry's link name.
390 */
391 public String getLinkName() {
392 return linkName.toString();
393 }
394
395 /**
396 * Set this entry's link name.
397 *
398 * @param link the link name to use.
399 *
400 * @since Apache Commons Compress 1.1
401 */
402 public void setLinkName(String link) {
403 this.linkName = link;
404 }
405
406 /**
407 * Get this entry's user id.
408 *
409 * @return This entry's user id.
410 */
411 public int getUserId() {
412 return userId;
413 }
414
415 /**
416 * Set this entry's user id.
417 *
418 * @param userId This entry's new user id.
419 */
420 public void setUserId(int userId) {
421 this.userId = userId;
422 }
423
424 /**
425 * Get this entry's group id.
426 *
427 * @return This entry's group id.
428 */
429 public int getGroupId() {
430 return groupId;
431 }
432
433 /**
434 * Set this entry's group id.
435 *
436 * @param groupId This entry's new group id.
437 */
438 public void setGroupId(int groupId) {
439 this.groupId = groupId;
440 }
441
442 /**
443 * Get this entry's user name.
444 *
445 * @return This entry's user name.
446 */
447 public String getUserName() {
448 return userName.toString();
449 }
450
451 /**
452 * Set this entry's user name.
453 *
454 * @param userName This entry's new user name.
455 */
456 public void setUserName(String userName) {
457 this.userName = userName;
458 }
459
460 /**
461 * Get this entry's group name.
462 *
463 * @return This entry's group name.
464 */
465 public String getGroupName() {
466 return groupName.toString();
467 }
468
469 /**
470 * Set this entry's group name.
471 *
472 * @param groupName This entry's new group name.
473 */
474 public void setGroupName(String groupName) {
475 this.groupName = groupName;
476 }
477
478 /**
479 * Convenience method to set this entry's group and user ids.
480 *
481 * @param userId This entry's new user id.
482 * @param groupId This entry's new group id.
483 */
484 public void setIds(int userId, int groupId) {
485 setUserId(userId);
486 setGroupId(groupId);
487 }
488
489 /**
490 * Convenience method to set this entry's group and user names.
491 *
492 * @param userName This entry's new user name.
493 * @param groupName This entry's new group name.
494 */
495 public void setNames(String userName, String groupName) {
496 setUserName(userName);
497 setGroupName(groupName);
498 }
499
500 /**
501 * Set this entry's modification time. The parameter passed
502 * to this method is in "Java time".
503 *
504 * @param time This entry's new modification time.
505 */
506 public void setModTime(long time) {
507 modTime = time / MILLIS_PER_SECOND;
508 }
509
510 /**
511 * Set this entry's modification time.
512 *
513 * @param time This entry's new modification time.
514 */
515 public void setModTime(Date time) {
516 modTime = time.getTime() / MILLIS_PER_SECOND;
517 }
518
519 /**
520 * Set this entry's modification time.
521 *
522 * @return time This entry's new modification time.
523 */
524 public Date getModTime() {
525 return new Date(modTime * MILLIS_PER_SECOND);
526 }
527
528 /** {@inheritDoc} */
529 public Date getLastModifiedDate() {
530 return getModTime();
531 }
532
533 /**
534 * Get this entry's file.
535 *
536 * @return This entry's file.
537 */
538 public File getFile() {
539 return file;
540 }
541
542 /**
543 * Get this entry's mode.
544 *
545 * @return This entry's mode.
546 */
547 public int getMode() {
548 return mode;
549 }
550
551 /**
552 * Get this entry's file size.
553 *
554 * @return This entry's file size.
555 */
556 public long getSize() {
557 return size;
558 }
559
560 /**
561 * Set this entry's file size.
562 *
563 * @param size This entry's new file size.
564 * @throws IllegalArgumentException if the size is < 0
565 * or > {@link TarConstants#MAXSIZE} (077777777777L).
566 */
567 public void setSize(long size) {
568 if (size > MAXSIZE || size < 0){
569 throw new IllegalArgumentException("Size is out of range: "+size);
570 }
571 this.size = size;
572 }
573
574 /**
575 * Indicates in case of a sparse file if an extension sparse header
576 * follows.
577 *
578 * @return true if an extension sparse header follows.
579 */
580 public boolean isExtended() {
581 return isExtended;
582 }
583
584 /**
585 * Get this entry's real file size in case of a sparse file.
586 *
587 * @return This entry's real file size.
588 */
589 public long getRealSize() {
590 return realSize;
591 }
592
593 /**
594 * Indicate if this entry is a GNU sparse block
595 *
596 * @return true if this is a sparse extension provided by GNU tar
597 */
598 public boolean isGNUSparse() {
599 return linkFlag == LF_GNUTYPE_SPARSE;
600 }
601
602 /**
603 * Indicate if this entry is a GNU long name block
604 *
605 * @return true if this is a long name extension provided by GNU tar
606 */
607 public boolean isGNULongNameEntry() {
608 return linkFlag == LF_GNUTYPE_LONGNAME
609 && name.toString().equals(GNU_LONGLINK);
610 }
611
612 /**
613 * Check if this is a Pax header.
614 *
615 * @return <code>true</code> if this is a Pax header.
616 *
617 * @since Apache Commons Compress 1.1
618 */
619 public boolean isPaxHeader(){
620 return linkFlag == LF_PAX_EXTENDED_HEADER_LC
621 || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
622 }
623
624 /**
625 * Check if this is a Pax header.
626 *
627 * @return <code>true</code> if this is a Pax header.
628 *
629 * @since Apache Commons Compress 1.1
630 */
631 public boolean isGlobalPaxHeader(){
632 return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
633 }
634
635 /**
636 * Return whether or not this entry represents a directory.
637 *
638 * @return True if this entry is a directory.
639 */
640 public boolean isDirectory() {
641 if (file != null) {
642 return file.isDirectory();
643 }
644
645 if (linkFlag == LF_DIR) {
646 return true;
647 }
648
649 if (getName().endsWith("/")) {
650 return true;
651 }
652
653 return false;
654 }
655
656 /**
657 * Check if this is a "normal file"
658 *
659 * @since Apache Commons Compress 1.2
660 */
661 public boolean isFile() {
662 if (file != null) {
663 return file.isFile();
664 }
665 if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
666 return true;
667 }
668 return !getName().endsWith("/");
669 }
670
671 /**
672 * Check if this is a symbolic link entry.
673 *
674 * @since Apache Commons Compress 1.2
675 */
676 public boolean isSymbolicLink() {
677 return linkFlag == LF_SYMLINK;
678 }
679
680 /**
681 * Check if this is a link entry.
682 *
683 * @since Apache Commons Compress 1.2
684 */
685 public boolean isLink() {
686 return linkFlag == LF_LINK;
687 }
688
689 /**
690 * Check if this is a character device entry.
691 *
692 * @since Apache Commons Compress 1.2
693 */
694 public boolean isCharacterDevice() {
695 return linkFlag == LF_CHR;
696 }
697
698 /**
699 * Check if this is a block device entry.
700 *
701 * @since Apache Commons Compress 1.2
702 */
703 public boolean isBlockDevice() {
704 return linkFlag == LF_BLK;
705 }
706
707 /**
708 * Check if this is a FIFO (pipe) entry.
709 *
710 * @since Apache Commons Compress 1.2
711 */
712 public boolean isFIFO() {
713 return linkFlag == LF_FIFO;
714 }
715
716 /**
717 * If this entry represents a file, and the file is a directory, return
718 * an array of TarEntries for this entry's children.
719 *
720 * @return An array of TarEntry's for this entry's children.
721 */
722 public TarArchiveEntry[] getDirectoryEntries() {
723 if (file == null || !file.isDirectory()) {
724 return new TarArchiveEntry[0];
725 }
726
727 String[] list = file.list();
728 TarArchiveEntry[] result = new TarArchiveEntry[list.length];
729
730 for (int i = 0; i < list.length; ++i) {
731 result[i] = new TarArchiveEntry(new File(file, list[i]));
732 }
733
734 return result;
735 }
736
737 /**
738 * Write an entry's header information to a header buffer.
739 *
740 * @param outbuf The tar entry header buffer to fill in.
741 */
742 public void writeEntryHeader(byte[] outbuf) {
743 int offset = 0;
744
745 offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN);
746 offset = TarUtils.formatOctalBytes(mode, outbuf, offset, MODELEN);
747 offset = TarUtils.formatOctalBytes(userId, outbuf, offset, UIDLEN);
748 offset = TarUtils.formatOctalBytes(groupId, outbuf, offset, GIDLEN);
749 offset = TarUtils.formatLongOctalBytes(size, outbuf, offset, SIZELEN);
750 offset = TarUtils.formatLongOctalBytes(modTime, outbuf, offset, MODTIMELEN);
751
752 int csOffset = offset;
753
754 for (int c = 0; c < CHKSUMLEN; ++c) {
755 outbuf[offset++] = (byte) ' ';
756 }
757
758 outbuf[offset++] = linkFlag;
759 offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN);
760 offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
761 offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
762 offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN);
763 offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN);
764 offset = TarUtils.formatOctalBytes(devMajor, outbuf, offset, DEVLEN);
765 offset = TarUtils.formatOctalBytes(devMinor, outbuf, offset, DEVLEN);
766
767 while (offset < outbuf.length) {
768 outbuf[offset++] = 0;
769 }
770
771 long chk = TarUtils.computeCheckSum(outbuf);
772
773 TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
774 }
775
776 /**
777 * Parse an entry's header information from a header buffer.
778 *
779 * @param header The tar entry header buffer to get information from.
780 */
781 public void parseTarHeader(byte[] header) {
782 int offset = 0;
783
784 name = TarUtils.parseName(header, offset, NAMELEN);
785 offset += NAMELEN;
786 mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
787 offset += MODELEN;
788 userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
789 offset += UIDLEN;
790 groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
791 offset += GIDLEN;
792 size = TarUtils.parseOctal(header, offset, SIZELEN);
793 offset += SIZELEN;
794 modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
795 offset += MODTIMELEN;
796 offset += CHKSUMLEN;
797 linkFlag = header[offset++];
798 linkName = TarUtils.parseName(header, offset, NAMELEN);
799 offset += NAMELEN;
800 magic = TarUtils.parseName(header, offset, MAGICLEN);
801 offset += MAGICLEN;
802 version = TarUtils.parseName(header, offset, VERSIONLEN);
803 offset += VERSIONLEN;
804 userName = TarUtils.parseName(header, offset, UNAMELEN);
805 offset += UNAMELEN;
806 groupName = TarUtils.parseName(header, offset, GNAMELEN);
807 offset += GNAMELEN;
808 devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
809 offset += DEVLEN;
810 devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
811 offset += DEVLEN;
812
813 int type = evaluateType(header);
814 switch (type) {
815 case FORMAT_OLDGNU: {
816 offset += ATIMELEN_GNU;
817 offset += CTIMELEN_GNU;
818 offset += OFFSETLEN_GNU;
819 offset += LONGNAMESLEN_GNU;
820 offset += PAD2LEN_GNU;
821 offset += SPARSELEN_GNU;
822 isExtended = TarUtils.parseBoolean(header, offset);
823 offset += ISEXTENDEDLEN_GNU;
824 realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
825 offset += REALSIZELEN_GNU;
826 break;
827 }
828 case FORMAT_POSIX:
829 default: {
830 String prefix = TarUtils.parseName(header, offset, PREFIXLEN);
831 // SunOS tar -E does not add / to directory names, so fix
832 // up to be consistent
833 if (isDirectory() && !name.endsWith("/")){
834 name = name + "/";
835 }
836 if (prefix.length() > 0){
837 name = prefix + "/" + name;
838 }
839 }
840 }
841 }
842
843 /**
844 * Strips Windows' drive letter as well as any leading slashes,
845 * turns path separators into forward slahes.
846 */
847 private static String normalizeFileName(String fileName,
848 boolean preserveLeadingSlashes) {
849 String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
850
851 if (osname != null) {
852
853 // Strip off drive letters!
854 // REVIEW Would a better check be "(File.separator == '\')"?
855
856 if (osname.startsWith("windows")) {
857 if (fileName.length() > 2) {
858 char ch1 = fileName.charAt(0);
859 char ch2 = fileName.charAt(1);
860
861 if (ch2 == ':'
862 && ((ch1 >= 'a' && ch1 <= 'z')
863 || (ch1 >= 'A' && ch1 <= 'Z'))) {
864 fileName = fileName.substring(2);
865 }
866 }
867 } else if (osname.indexOf("netware") > -1) {
868 int colon = fileName.indexOf(':');
869 if (colon != -1) {
870 fileName = fileName.substring(colon + 1);
871 }
872 }
873 }
874
875 fileName = fileName.replace(File.separatorChar, '/');
876
877 // No absolute pathnames
878 // Windows (and Posix?) paths can start with "\\NetworkDrive\",
879 // so we loop on starting /'s.
880 while (!preserveLeadingSlashes && fileName.startsWith("/")) {
881 fileName = fileName.substring(1);
882 }
883 return fileName;
884 }
885
886 /**
887 * Evaluate an entry's header format from a header buffer.
888 *
889 * @param header The tar entry header buffer to evaluate the format for.
890 * @return format type
891 */
892 private int evaluateType(byte[] header) {
893 final ByteBuffer magic = ByteBuffer.wrap(header, MAGIC_OFFSET, MAGICLEN);
894 if (magic.compareTo(ByteBuffer.wrap(MAGIC_GNU.getBytes())) == 0)
895 return FORMAT_OLDGNU;
896 if (magic.compareTo(ByteBuffer.wrap(MAGIC_POSIX.getBytes())) == 0)
897 return FORMAT_POSIX;
898 return 0;
899 }
900 }
901