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