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 */ 019package org.apache.commons.compress.archivers.tar; 020 021import java.io.File; 022import java.io.IOException; 023import java.util.Collections; 024import java.util.Date; 025import java.util.HashMap; 026import java.util.Locale; 027import java.util.Map; 028import org.apache.commons.compress.archivers.ArchiveEntry; 029import org.apache.commons.compress.archivers.zip.ZipEncoding; 030import org.apache.commons.compress.utils.ArchiveUtils; 031 032/** 033 * This class represents an entry in a Tar archive. It consists 034 * of the entry's header, as well as the entry's File. Entries 035 * can be instantiated in one of three ways, depending on how 036 * they are to be used. 037 * <p> 038 * TarEntries that are created from the header bytes read from 039 * an archive are instantiated with the TarEntry( byte[] ) 040 * constructor. These entries will be used when extracting from 041 * or listing the contents of an archive. These entries have their 042 * header filled in using the header bytes. They also set the File 043 * to null, since they reference an archive entry not a file. 044 * <p> 045 * TarEntries that are created from Files that are to be written 046 * into an archive are instantiated with the TarEntry( File ) 047 * constructor. These entries have their header filled in using 048 * the File's information. They also keep a reference to the File 049 * for convenience when writing entries. 050 * <p> 051 * Finally, TarEntries can be constructed from nothing but a name. 052 * This allows the programmer to construct the entry by hand, for 053 * instance when only an InputStream is available for writing to 054 * the archive, and the header information is constructed from 055 * other information. In this case the header fields are set to 056 * defaults and the File is set to null. 057 * 058 * <p> 059 * The C structure for a Tar Entry's header is: 060 * <pre> 061 * struct header { 062 * char name[100]; // TarConstants.NAMELEN - offset 0 063 * char mode[8]; // TarConstants.MODELEN - offset 100 064 * char uid[8]; // TarConstants.UIDLEN - offset 108 065 * char gid[8]; // TarConstants.GIDLEN - offset 116 066 * char size[12]; // TarConstants.SIZELEN - offset 124 067 * char mtime[12]; // TarConstants.MODTIMELEN - offset 136 068 * char chksum[8]; // TarConstants.CHKSUMLEN - offset 148 069 * char linkflag[1]; // - offset 156 070 * char linkname[100]; // TarConstants.NAMELEN - offset 157 071 * The following fields are only present in new-style POSIX tar archives: 072 * char magic[6]; // TarConstants.MAGICLEN - offset 257 073 * char version[2]; // TarConstants.VERSIONLEN - offset 263 074 * char uname[32]; // TarConstants.UNAMELEN - offset 265 075 * char gname[32]; // TarConstants.GNAMELEN - offset 297 076 * char devmajor[8]; // TarConstants.DEVLEN - offset 329 077 * char devminor[8]; // TarConstants.DEVLEN - offset 337 078 * char prefix[155]; // TarConstants.PREFIXLEN - offset 345 079 * // Used if "name" field is not long enough to hold the path 080 * char pad[12]; // NULs - offset 500 081 * } header; 082 * All unused bytes are set to null. 083 * New-style GNU tar files are slightly different from the above. 084 * For values of size larger than 077777777777L (11 7s) 085 * or uid and gid larger than 07777777L (7 7s) 086 * the sign bit of the first byte is set, and the rest of the 087 * field is the binary representation of the number. 088 * See TarUtils.parseOctalOrBinary. 089 * </pre> 090 * 091 * <p> 092 * The C structure for a old GNU Tar Entry's header is: 093 * <pre> 094 * struct oldgnu_header { 095 * char unused_pad1[345]; // TarConstants.PAD1LEN_GNU - offset 0 096 * char atime[12]; // TarConstants.ATIMELEN_GNU - offset 345 097 * char ctime[12]; // TarConstants.CTIMELEN_GNU - offset 357 098 * char offset[12]; // TarConstants.OFFSETLEN_GNU - offset 369 099 * char longnames[4]; // TarConstants.LONGNAMESLEN_GNU - offset 381 100 * char unused_pad2; // TarConstants.PAD2LEN_GNU - offset 385 101 * struct sparse sp[4]; // TarConstants.SPARSELEN_GNU - offset 386 102 * char isextended; // TarConstants.ISEXTENDEDLEN_GNU - offset 482 103 * char realsize[12]; // TarConstants.REALSIZELEN_GNU - offset 483 104 * char unused_pad[17]; // TarConstants.PAD3LEN_GNU - offset 495 105 * }; 106 * </pre> 107 * Whereas, "struct sparse" is: 108 * <pre> 109 * struct sparse { 110 * char offset[12]; // offset 0 111 * char numbytes[12]; // offset 12 112 * }; 113 * </pre> 114 * 115 * <p> 116 * The C structure for a xstar (Jörg Schilling star) Tar Entry's header is: 117 * <pre> 118 * struct star_header { 119 * char name[100]; // offset 0 120 * char mode[8]; // offset 100 121 * char uid[8]; // offset 108 122 * char gid[8]; // offset 116 123 * char size[12]; // offset 124 124 * char mtime[12]; // offset 136 125 * char chksum[8]; // offset 148 126 * char typeflag; // offset 156 127 * char linkname[100]; // offset 157 128 * char magic[6]; // offset 257 129 * char version[2]; // offset 263 130 * char uname[32]; // offset 265 131 * char gname[32]; // offset 297 132 * char devmajor[8]; // offset 329 133 * char devminor[8]; // offset 337 134 * char prefix[131]; // offset 345 135 * char atime[12]; // offset 476 136 * char ctime[12]; // offset 488 137 * char mfill[8]; // offset 500 138 * char xmagic[4]; // offset 508 "tar" 139 * }; 140 * </pre> 141 * <p>which is identical to new-style POSIX up to the first 130 bytes of the prefix.</p> 142 * 143 * @NotThreadSafe 144 */ 145 146public class TarArchiveEntry implements ArchiveEntry, TarConstants { 147 private static final TarArchiveEntry[] EMPTY_TAR_ARCHIVE_ENTRIES = new TarArchiveEntry[0]; 148 149 /** The entry's name. */ 150 private String name = ""; 151 152 /** Whether to enforce leading slashes on the name */ 153 private boolean preserveLeadingSlashes; 154 155 /** The entry's permission mode. */ 156 private int mode; 157 158 /** The entry's user id. */ 159 private long userId = 0; 160 161 /** The entry's group id. */ 162 private long groupId = 0; 163 164 /** The entry's size. */ 165 private long size = 0; 166 167 /** The entry's modification time. */ 168 private long modTime; 169 170 /** If the header checksum is reasonably correct. */ 171 private boolean checkSumOK; 172 173 /** The entry's link flag. */ 174 private byte linkFlag; 175 176 /** The entry's link name. */ 177 private String linkName = ""; 178 179 /** The entry's magic tag. */ 180 private String magic = MAGIC_POSIX; 181 /** The version of the format */ 182 private String version = VERSION_POSIX; 183 184 /** The entry's user name. */ 185 private String userName; 186 187 /** The entry's group name. */ 188 private String groupName = ""; 189 190 /** The entry's major device number. */ 191 private int devMajor = 0; 192 193 /** The entry's minor device number. */ 194 private int devMinor = 0; 195 196 /** If an extension sparse header follows. */ 197 private boolean isExtended; 198 199 /** The entry's real size in case of a sparse file. */ 200 private long realSize; 201 202 /** is this entry a GNU sparse entry using one of the PAX formats? */ 203 private boolean paxGNUSparse; 204 205 /** is this entry a star sparse entry using the PAX header? */ 206 private boolean starSparse; 207 208 /** The entry's file reference */ 209 private final File file; 210 211 /** Extra, user supplied pax headers */ 212 private final Map<String,String> extraPaxHeaders = new HashMap<>(); 213 214 /** Maximum length of a user's name in the tar file */ 215 public static final int MAX_NAMELEN = 31; 216 217 /** Default permissions bits for directories */ 218 public static final int DEFAULT_DIR_MODE = 040755; 219 220 /** Default permissions bits for files */ 221 public static final int DEFAULT_FILE_MODE = 0100644; 222 223 /** Convert millis to seconds */ 224 public static final int MILLIS_PER_SECOND = 1000; 225 226 227 /** 228 * Construct an empty entry and prepares the header values. 229 */ 230 private TarArchiveEntry() { 231 String user = System.getProperty("user.name", ""); 232 233 if (user.length() > MAX_NAMELEN) { 234 user = user.substring(0, MAX_NAMELEN); 235 } 236 237 this.userName = user; 238 this.file = null; 239 } 240 241 /** 242 * Construct an entry with only a name. This allows the programmer 243 * to construct the entry's header "by hand". File is set to null. 244 * 245 * @param name the entry name 246 */ 247 public TarArchiveEntry(final String name) { 248 this(name, false); 249 } 250 251 /** 252 * Construct an entry with only a name. This allows the programmer 253 * to construct the entry's header "by hand". File is set to null. 254 * 255 * @param name the entry name 256 * @param preserveLeadingSlashes whether to allow leading slashes 257 * in the name. 258 * 259 * @since 1.1 260 */ 261 public TarArchiveEntry(String name, final boolean preserveLeadingSlashes) { 262 this(); 263 264 this.preserveLeadingSlashes = preserveLeadingSlashes; 265 266 name = normalizeFileName(name, preserveLeadingSlashes); 267 final boolean isDir = name.endsWith("/"); 268 269 this.name = name; 270 this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE; 271 this.linkFlag = isDir ? LF_DIR : LF_NORMAL; 272 this.modTime = new Date().getTime() / MILLIS_PER_SECOND; 273 this.userName = ""; 274 } 275 276 /** 277 * Construct an entry with a name and a link flag. 278 * 279 * @param name the entry name 280 * @param linkFlag the entry link flag. 281 */ 282 public TarArchiveEntry(final String name, final byte linkFlag) { 283 this(name, linkFlag, false); 284 } 285 286 /** 287 * Construct an entry with a name and a link flag. 288 * 289 * @param name the entry name 290 * @param linkFlag the entry link flag. 291 * @param preserveLeadingSlashes whether to allow leading slashes 292 * in the name. 293 * 294 * @since 1.5 295 */ 296 public TarArchiveEntry(final String name, final byte linkFlag, final boolean preserveLeadingSlashes) { 297 this(name, preserveLeadingSlashes); 298 this.linkFlag = linkFlag; 299 if (linkFlag == LF_GNUTYPE_LONGNAME) { 300 magic = MAGIC_GNU; 301 version = VERSION_GNU_SPACE; 302 } 303 } 304 305 /** 306 * Construct an entry for a file. File is set to file, and the 307 * header is constructed from information from the file. 308 * The name is set from the normalized file path. 309 * 310 * @param file The file that the entry represents. 311 */ 312 public TarArchiveEntry(final File file) { 313 this(file, file.getPath()); 314 } 315 316 /** 317 * Construct an entry for a file. File is set to file, and the 318 * header is constructed from information from the file. 319 * 320 * @param file The file that the entry represents. 321 * @param fileName the name to be used for the entry. 322 */ 323 public TarArchiveEntry(final File file, final String fileName) { 324 final String normalizedName = normalizeFileName(fileName, false); 325 this.file = file; 326 327 if (file.isDirectory()) { 328 this.mode = DEFAULT_DIR_MODE; 329 this.linkFlag = LF_DIR; 330 331 final int nameLength = normalizedName.length(); 332 if (nameLength == 0 || normalizedName.charAt(nameLength - 1) != '/') { 333 this.name = normalizedName + "/"; 334 } else { 335 this.name = normalizedName; 336 } 337 } else { 338 this.mode = DEFAULT_FILE_MODE; 339 this.linkFlag = LF_NORMAL; 340 this.size = file.length(); 341 this.name = normalizedName; 342 } 343 344 this.modTime = file.lastModified() / MILLIS_PER_SECOND; 345 this.userName = ""; 346 } 347 348 /** 349 * Construct an entry from an archive's header bytes. File is set 350 * to null. 351 * 352 * @param headerBuf The header bytes from a tar archive entry. 353 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 354 */ 355 public TarArchiveEntry(final byte[] headerBuf) { 356 this(); 357 parseTarHeader(headerBuf); 358 } 359 360 /** 361 * Construct an entry from an archive's header bytes. File is set 362 * to null. 363 * 364 * @param headerBuf The header bytes from a tar archive entry. 365 * @param encoding encoding to use for file names 366 * @since 1.4 367 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 368 * @throws IOException on error 369 */ 370 public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding) 371 throws IOException { 372 this(); 373 parseTarHeader(headerBuf, encoding); 374 } 375 376 /** 377 * Determine if the two entries are equal. Equality is determined 378 * by the header names being equal. 379 * 380 * @param it Entry to be checked for equality. 381 * @return True if the entries are equal. 382 */ 383 public boolean equals(final TarArchiveEntry it) { 384 return it != null && getName().equals(it.getName()); 385 } 386 387 /** 388 * Determine if the two entries are equal. Equality is determined 389 * by the header names being equal. 390 * 391 * @param it Entry to be checked for equality. 392 * @return True if the entries are equal. 393 */ 394 @Override 395 public boolean equals(final Object it) { 396 if (it == null || getClass() != it.getClass()) { 397 return false; 398 } 399 return equals((TarArchiveEntry) it); 400 } 401 402 /** 403 * Hashcodes are based on entry names. 404 * 405 * @return the entry hashcode 406 */ 407 @Override 408 public int hashCode() { 409 return getName().hashCode(); 410 } 411 412 /** 413 * Determine if the given entry is a descendant of this entry. 414 * Descendancy is determined by the name of the descendant 415 * starting with this entry's name. 416 * 417 * @param desc Entry to be checked as a descendent of this. 418 * @return True if entry is a descendant of this. 419 */ 420 public boolean isDescendent(final TarArchiveEntry desc) { 421 return desc.getName().startsWith(getName()); 422 } 423 424 /** 425 * Get this entry's name. 426 * 427 * @return This entry's name. 428 */ 429 @Override 430 public String getName() { 431 return name; 432 } 433 434 /** 435 * Set this entry's name. 436 * 437 * @param name This entry's new name. 438 */ 439 public void setName(final String name) { 440 this.name = normalizeFileName(name, this.preserveLeadingSlashes); 441 } 442 443 /** 444 * Set the mode for this entry 445 * 446 * @param mode the mode for this entry 447 */ 448 public void setMode(final int mode) { 449 this.mode = mode; 450 } 451 452 /** 453 * Get this entry's link name. 454 * 455 * @return This entry's link name. 456 */ 457 public String getLinkName() { 458 return linkName; 459 } 460 461 /** 462 * Set this entry's link name. 463 * 464 * @param link the link name to use. 465 * 466 * @since 1.1 467 */ 468 public void setLinkName(final String link) { 469 this.linkName = link; 470 } 471 472 /** 473 * Get this entry's user id. 474 * 475 * @return This entry's user id. 476 * @deprecated use #getLongUserId instead as user ids can be 477 * bigger than {@link Integer#MAX_VALUE} 478 */ 479 @Deprecated 480 public int getUserId() { 481 return (int) (userId & 0xffffffff); 482 } 483 484 /** 485 * Set this entry's user id. 486 * 487 * @param userId This entry's new user id. 488 */ 489 public void setUserId(final int userId) { 490 setUserId((long) userId); 491 } 492 493 /** 494 * Get this entry's user id. 495 * 496 * @return This entry's user id. 497 * @since 1.10 498 */ 499 public long getLongUserId() { 500 return userId; 501 } 502 503 /** 504 * Set this entry's user id. 505 * 506 * @param userId This entry's new user id. 507 * @since 1.10 508 */ 509 public void setUserId(final long userId) { 510 this.userId = userId; 511 } 512 513 /** 514 * Get this entry's group id. 515 * 516 * @return This entry's group id. 517 * @deprecated use #getLongGroupId instead as group ids can be 518 * bigger than {@link Integer#MAX_VALUE} 519 */ 520 @Deprecated 521 public int getGroupId() { 522 return (int) (groupId & 0xffffffff); 523 } 524 525 /** 526 * Set this entry's group id. 527 * 528 * @param groupId This entry's new group id. 529 */ 530 public void setGroupId(final int groupId) { 531 setGroupId((long) groupId); 532 } 533 534 /** 535 * Get this entry's group id. 536 * 537 * @since 1.10 538 * @return This entry's group id. 539 */ 540 public long getLongGroupId() { 541 return groupId; 542 } 543 544 /** 545 * Set this entry's group id. 546 * 547 * @since 1.10 548 * @param groupId This entry's new group id. 549 */ 550 public void setGroupId(final long groupId) { 551 this.groupId = groupId; 552 } 553 554 /** 555 * Get this entry's user name. 556 * 557 * @return This entry's user name. 558 */ 559 public String getUserName() { 560 return userName; 561 } 562 563 /** 564 * Set this entry's user name. 565 * 566 * @param userName This entry's new user name. 567 */ 568 public void setUserName(final String userName) { 569 this.userName = userName; 570 } 571 572 /** 573 * Get this entry's group name. 574 * 575 * @return This entry's group name. 576 */ 577 public String getGroupName() { 578 return groupName; 579 } 580 581 /** 582 * Set this entry's group name. 583 * 584 * @param groupName This entry's new group name. 585 */ 586 public void setGroupName(final String groupName) { 587 this.groupName = groupName; 588 } 589 590 /** 591 * Convenience method to set this entry's group and user ids. 592 * 593 * @param userId This entry's new user id. 594 * @param groupId This entry's new group id. 595 */ 596 public void setIds(final int userId, final int groupId) { 597 setUserId(userId); 598 setGroupId(groupId); 599 } 600 601 /** 602 * Convenience method to set this entry's group and user names. 603 * 604 * @param userName This entry's new user name. 605 * @param groupName This entry's new group name. 606 */ 607 public void setNames(final String userName, final String groupName) { 608 setUserName(userName); 609 setGroupName(groupName); 610 } 611 612 /** 613 * Set this entry's modification time. The parameter passed 614 * to this method is in "Java time". 615 * 616 * @param time This entry's new modification time. 617 */ 618 public void setModTime(final long time) { 619 modTime = time / MILLIS_PER_SECOND; 620 } 621 622 /** 623 * Set this entry's modification time. 624 * 625 * @param time This entry's new modification time. 626 */ 627 public void setModTime(final Date time) { 628 modTime = time.getTime() / MILLIS_PER_SECOND; 629 } 630 631 /** 632 * Set this entry's modification time. 633 * 634 * @return time This entry's new modification time. 635 */ 636 public Date getModTime() { 637 return new Date(modTime * MILLIS_PER_SECOND); 638 } 639 640 @Override 641 public Date getLastModifiedDate() { 642 return getModTime(); 643 } 644 645 /** 646 * Get this entry's checksum status. 647 * 648 * @return if the header checksum is reasonably correct 649 * @see TarUtils#verifyCheckSum(byte[]) 650 * @since 1.5 651 */ 652 public boolean isCheckSumOK() { 653 return checkSumOK; 654 } 655 656 /** 657 * Get this entry's file. 658 * 659 * <p>This method is only useful for entries created from a {@code 660 * File} but not for entries read from an archive.</p> 661 * 662 * @return This entry's file. 663 */ 664 public File getFile() { 665 return file; 666 } 667 668 /** 669 * Get this entry's mode. 670 * 671 * @return This entry's mode. 672 */ 673 public int getMode() { 674 return mode; 675 } 676 677 /** 678 * Get this entry's file size. 679 * 680 * @return This entry's file size. 681 */ 682 @Override 683 public long getSize() { 684 return size; 685 } 686 687 /** 688 * Set this entry's file size. 689 * 690 * @param size This entry's new file size. 691 * @throws IllegalArgumentException if the size is < 0. 692 */ 693 public void setSize(final long size) { 694 if (size < 0){ 695 throw new IllegalArgumentException("Size is out of range: "+size); 696 } 697 this.size = size; 698 } 699 700 /** 701 * Get this entry's major device number. 702 * 703 * @return This entry's major device number. 704 * @since 1.4 705 */ 706 public int getDevMajor() { 707 return devMajor; 708 } 709 710 /** 711 * Set this entry's major device number. 712 * 713 * @param devNo This entry's major device number. 714 * @throws IllegalArgumentException if the devNo is < 0. 715 * @since 1.4 716 */ 717 public void setDevMajor(final int devNo) { 718 if (devNo < 0){ 719 throw new IllegalArgumentException("Major device number is out of " 720 + "range: " + devNo); 721 } 722 this.devMajor = devNo; 723 } 724 725 /** 726 * Get this entry's minor device number. 727 * 728 * @return This entry's minor device number. 729 * @since 1.4 730 */ 731 public int getDevMinor() { 732 return devMinor; 733 } 734 735 /** 736 * Set this entry's minor device number. 737 * 738 * @param devNo This entry's minor device number. 739 * @throws IllegalArgumentException if the devNo is < 0. 740 * @since 1.4 741 */ 742 public void setDevMinor(final int devNo) { 743 if (devNo < 0){ 744 throw new IllegalArgumentException("Minor device number is out of " 745 + "range: " + devNo); 746 } 747 this.devMinor = devNo; 748 } 749 750 /** 751 * Indicates in case of an oldgnu sparse file if an extension 752 * sparse header follows. 753 * 754 * @return true if an extension oldgnu sparse header follows. 755 */ 756 public boolean isExtended() { 757 return isExtended; 758 } 759 760 /** 761 * Get this entry's real file size in case of a sparse file. 762 * 763 * @return This entry's real file size. 764 */ 765 public long getRealSize() { 766 return realSize; 767 } 768 769 /** 770 * Indicate if this entry is a GNU sparse block. 771 * 772 * @return true if this is a sparse extension provided by GNU tar 773 */ 774 public boolean isGNUSparse() { 775 return isOldGNUSparse() || isPaxGNUSparse(); 776 } 777 778 /** 779 * Indicate if this entry is a GNU or star sparse block using the 780 * oldgnu format. 781 * 782 * @return true if this is a sparse extension provided by GNU tar or star 783 * @since 1.11 784 */ 785 public boolean isOldGNUSparse() { 786 return linkFlag == LF_GNUTYPE_SPARSE; 787 } 788 789 /** 790 * Indicate if this entry is a GNU sparse block using one of the 791 * PAX formats. 792 * 793 * @return true if this is a sparse extension provided by GNU tar 794 * @since 1.11 795 */ 796 public boolean isPaxGNUSparse() { 797 return paxGNUSparse; 798 } 799 800 /** 801 * Indicate if this entry is a star sparse block using PAX headers. 802 * 803 * @return true if this is a sparse extension provided by star 804 * @since 1.11 805 */ 806 public boolean isStarSparse() { 807 return starSparse; 808 } 809 810 /** 811 * Indicate if this entry is a GNU long linkname block 812 * 813 * @return true if this is a long name extension provided by GNU tar 814 */ 815 public boolean isGNULongLinkEntry() { 816 return linkFlag == LF_GNUTYPE_LONGLINK; 817 } 818 819 /** 820 * Indicate if this entry is a GNU long name block 821 * 822 * @return true if this is a long name extension provided by GNU tar 823 */ 824 public boolean isGNULongNameEntry() { 825 return linkFlag == LF_GNUTYPE_LONGNAME; 826 } 827 828 /** 829 * Check if this is a Pax header. 830 * 831 * @return {@code true} if this is a Pax header. 832 * 833 * @since 1.1 834 * 835 */ 836 public boolean isPaxHeader() { 837 return linkFlag == LF_PAX_EXTENDED_HEADER_LC 838 || linkFlag == LF_PAX_EXTENDED_HEADER_UC; 839 } 840 841 /** 842 * Check if this is a Pax header. 843 * 844 * @return {@code true} if this is a Pax header. 845 * 846 * @since 1.1 847 */ 848 public boolean isGlobalPaxHeader() { 849 return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER; 850 } 851 852 /** 853 * Return whether or not this entry represents a directory. 854 * 855 * @return True if this entry is a directory. 856 */ 857 @Override 858 public boolean isDirectory() { 859 if (file != null) { 860 return file.isDirectory(); 861 } 862 863 if (linkFlag == LF_DIR) { 864 return true; 865 } 866 867 if (!isPaxHeader() && !isGlobalPaxHeader() && getName().endsWith("/")) { 868 return true; 869 } 870 871 return false; 872 } 873 874 /** 875 * Check if this is a "normal file" 876 * 877 * @since 1.2 878 * @return whether this is a "normal file" 879 */ 880 public boolean isFile() { 881 if (file != null) { 882 return file.isFile(); 883 } 884 if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) { 885 return true; 886 } 887 return !getName().endsWith("/"); 888 } 889 890 /** 891 * Check if this is a symbolic link entry. 892 * 893 * @since 1.2 894 * @return whether this is a symbolic link 895 */ 896 public boolean isSymbolicLink() { 897 return linkFlag == LF_SYMLINK; 898 } 899 900 /** 901 * Check if this is a link entry. 902 * 903 * @since 1.2 904 * @return whether this is a link entry 905 */ 906 public boolean isLink() { 907 return linkFlag == LF_LINK; 908 } 909 910 /** 911 * Check if this is a character device entry. 912 * 913 * @since 1.2 914 * @return whether this is a character device 915 */ 916 public boolean isCharacterDevice() { 917 return linkFlag == LF_CHR; 918 } 919 920 /** 921 * Check if this is a block device entry. 922 * 923 * @since 1.2 924 * @return whether this is a block device 925 */ 926 public boolean isBlockDevice() { 927 return linkFlag == LF_BLK; 928 } 929 930 /** 931 * Check if this is a FIFO (pipe) entry. 932 * 933 * @since 1.2 934 * @return whether this is a FIFO entry 935 */ 936 public boolean isFIFO() { 937 return linkFlag == LF_FIFO; 938 } 939 940 /** 941 * Check whether this is a sparse entry. 942 * 943 * @return whether this is a sparse entry 944 * @since 1.11 945 */ 946 public boolean isSparse() { 947 return isGNUSparse() || isStarSparse(); 948 } 949 950 /** 951 * get extra PAX Headers 952 * @return read-only map containing any extra PAX Headers 953 * @since 1.15 954 */ 955 public Map<String, String> getExtraPaxHeaders() { 956 return Collections.unmodifiableMap(extraPaxHeaders); 957 } 958 959 /** 960 * clear all extra PAX headers. 961 * @since 1.15 962 */ 963 public void clearExtraPaxHeaders() { 964 extraPaxHeaders.clear(); 965 } 966 967 /** 968 * add a PAX header to this entry. If the header corresponds to an existing field in the entry, 969 * that field will be set; otherwise the header will be added to the extraPaxHeaders Map 970 * @param name The full name of the header to set. 971 * @param value value of header. 972 * @since 1.15 973 */ 974 public void addPaxHeader(String name,String value) { 975 processPaxHeader(name,value); 976 } 977 978 /** 979 * get named extra PAX header 980 * @param name The full name of an extended PAX header to retrieve 981 * @return The value of the header, if any. 982 * @since 1.15 983 */ 984 public String getExtraPaxHeader(String name) { 985 return extraPaxHeaders.get(name); 986 } 987 988 /** 989 * Update the entry using a map of pax headers. 990 * @param headers 991 * @since 1.15 992 */ 993 void updateEntryFromPaxHeaders(Map<String, String> headers) { 994 for (final Map.Entry<String, String> ent : headers.entrySet()) { 995 final String key = ent.getKey(); 996 final String val = ent.getValue(); 997 processPaxHeader(key, val, headers); 998 } 999 } 1000 1001 /** 1002 * process one pax header, using the entries extraPaxHeaders map as source for extra headers 1003 * used when handling entries for sparse files. 1004 * @param key 1005 * @param val 1006 * @since 1.15 1007 */ 1008 private void processPaxHeader(String key, String val) { 1009 processPaxHeader(key,val,extraPaxHeaders); 1010 } 1011 1012 /** 1013 * Process one pax header, using the supplied map as source for extra headers to be used when handling 1014 * entries for sparse files 1015 * 1016 * @param key the header name. 1017 * @param val the header value. 1018 * @param headers map of headers used for dealing with sparse file. 1019 * @since 1.15 1020 */ 1021 private void processPaxHeader(String key, String val, Map<String, String> headers) { 1022 /* 1023 * The following headers are defined for Pax. 1024 * atime, ctime, charset: cannot use these without changing TarArchiveEntry fields 1025 * mtime 1026 * comment 1027 * gid, gname 1028 * linkpath 1029 * size 1030 * uid,uname 1031 * SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those 1032 * 1033 * GNU sparse files use additional members, we use 1034 * GNU.sparse.size to detect the 0.0 and 0.1 versions and 1035 * GNU.sparse.realsize for 1.0. 1036 * 1037 * star files use additional members of which we use 1038 * SCHILY.filetype in order to detect star sparse files. 1039 * 1040 * If called from addExtraPaxHeader, these additional headers must be already present . 1041 */ 1042 switch (key) { 1043 case "path": 1044 setName(val); 1045 break; 1046 case "linkpath": 1047 setLinkName(val); 1048 break; 1049 case "gid": 1050 setGroupId(Long.parseLong(val)); 1051 break; 1052 case "gname": 1053 setGroupName(val); 1054 break; 1055 case "uid": 1056 setUserId(Long.parseLong(val)); 1057 break; 1058 case "uname": 1059 setUserName(val); 1060 break; 1061 case "size": 1062 setSize(Long.parseLong(val)); 1063 break; 1064 case "mtime": 1065 setModTime((long) (Double.parseDouble(val) * 1000)); 1066 break; 1067 case "SCHILY.devminor": 1068 setDevMinor(Integer.parseInt(val)); 1069 break; 1070 case "SCHILY.devmajor": 1071 setDevMajor(Integer.parseInt(val)); 1072 break; 1073 case "GNU.sparse.size": 1074 fillGNUSparse0xData(headers); 1075 break; 1076 case "GNU.sparse.realsize": 1077 fillGNUSparse1xData(headers); 1078 break; 1079 case "SCHILY.filetype": 1080 if ("sparse".equals(val)) { 1081 fillStarSparseData(headers); 1082 } 1083 break; 1084 default: 1085 extraPaxHeaders.put(key,val); 1086 } 1087 } 1088 1089 1090 1091 /** 1092 * If this entry represents a file, and the file is a directory, return 1093 * an array of TarEntries for this entry's children. 1094 * 1095 * <p>This method is only useful for entries created from a {@code 1096 * File} but not for entries read from an archive.</p> 1097 * 1098 * @return An array of TarEntry's for this entry's children. 1099 */ 1100 public TarArchiveEntry[] getDirectoryEntries() { 1101 if (file == null || !file.isDirectory()) { 1102 return EMPTY_TAR_ARCHIVE_ENTRIES; 1103 } 1104 1105 final String[] list = file.list(); 1106 if (list == null) { 1107 return EMPTY_TAR_ARCHIVE_ENTRIES; 1108 } 1109 final TarArchiveEntry[] result = new TarArchiveEntry[list.length]; 1110 1111 for (int i = 0; i < result.length; ++i) { 1112 result[i] = new TarArchiveEntry(new File(file, list[i])); 1113 } 1114 1115 return result; 1116 } 1117 1118 /** 1119 * Write an entry's header information to a header buffer. 1120 * 1121 * <p>This method does not use the star/GNU tar/BSD tar extensions.</p> 1122 * 1123 * @param outbuf The tar entry header buffer to fill in. 1124 */ 1125 public void writeEntryHeader(final byte[] outbuf) { 1126 try { 1127 writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false); 1128 } catch (final IOException ex) { 1129 try { 1130 writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false); 1131 } catch (final IOException ex2) { 1132 // impossible 1133 throw new RuntimeException(ex2); //NOSONAR 1134 } 1135 } 1136 } 1137 1138 /** 1139 * Write an entry's header information to a header buffer. 1140 * 1141 * @param outbuf The tar entry header buffer to fill in. 1142 * @param encoding encoding to use when writing the file name. 1143 * @param starMode whether to use the star/GNU tar/BSD tar 1144 * extension for numeric fields if their value doesn't fit in the 1145 * maximum size of standard tar archives 1146 * @since 1.4 1147 * @throws IOException on error 1148 */ 1149 public void writeEntryHeader(final byte[] outbuf, final ZipEncoding encoding, 1150 final boolean starMode) throws IOException { 1151 int offset = 0; 1152 1153 offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN, 1154 encoding); 1155 offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode); 1156 offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN, 1157 starMode); 1158 offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN, 1159 starMode); 1160 offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode); 1161 offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN, 1162 starMode); 1163 1164 final int csOffset = offset; 1165 1166 for (int c = 0; c < CHKSUMLEN; ++c) { 1167 outbuf[offset++] = (byte) ' '; 1168 } 1169 1170 outbuf[offset++] = linkFlag; 1171 offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN, 1172 encoding); 1173 offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN); 1174 offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN); 1175 offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN, 1176 encoding); 1177 offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN, 1178 encoding); 1179 offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN, 1180 starMode); 1181 offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN, 1182 starMode); 1183 1184 while (offset < outbuf.length) { 1185 outbuf[offset++] = 0; 1186 } 1187 1188 final long chk = TarUtils.computeCheckSum(outbuf); 1189 1190 TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN); 1191 } 1192 1193 private int writeEntryHeaderField(final long value, final byte[] outbuf, final int offset, 1194 final int length, final boolean starMode) { 1195 if (!starMode && (value < 0 1196 || value >= 1l << 3 * (length - 1))) { 1197 // value doesn't fit into field when written as octal 1198 // number, will be written to PAX header or causes an 1199 // error 1200 return TarUtils.formatLongOctalBytes(0, outbuf, offset, length); 1201 } 1202 return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset, 1203 length); 1204 } 1205 1206 /** 1207 * Parse an entry's header information from a header buffer. 1208 * 1209 * @param header The tar entry header buffer to get information from. 1210 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 1211 */ 1212 public void parseTarHeader(final byte[] header) { 1213 try { 1214 parseTarHeader(header, TarUtils.DEFAULT_ENCODING); 1215 } catch (final IOException ex) { 1216 try { 1217 parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true); 1218 } catch (final IOException ex2) { 1219 // not really possible 1220 throw new RuntimeException(ex2); //NOSONAR 1221 } 1222 } 1223 } 1224 1225 /** 1226 * Parse an entry's header information from a header buffer. 1227 * 1228 * @param header The tar entry header buffer to get information from. 1229 * @param encoding encoding to use for file names 1230 * @since 1.4 1231 * @throws IllegalArgumentException if any of the numeric fields 1232 * have an invalid format 1233 * @throws IOException on error 1234 */ 1235 public void parseTarHeader(final byte[] header, final ZipEncoding encoding) 1236 throws IOException { 1237 parseTarHeader(header, encoding, false); 1238 } 1239 1240 private void parseTarHeader(final byte[] header, final ZipEncoding encoding, 1241 final boolean oldStyle) 1242 throws IOException { 1243 int offset = 0; 1244 1245 name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) 1246 : TarUtils.parseName(header, offset, NAMELEN, encoding); 1247 offset += NAMELEN; 1248 mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN); 1249 offset += MODELEN; 1250 userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN); 1251 offset += UIDLEN; 1252 groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN); 1253 offset += GIDLEN; 1254 size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN); 1255 offset += SIZELEN; 1256 modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN); 1257 offset += MODTIMELEN; 1258 checkSumOK = TarUtils.verifyCheckSum(header); 1259 offset += CHKSUMLEN; 1260 linkFlag = header[offset++]; 1261 linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) 1262 : TarUtils.parseName(header, offset, NAMELEN, encoding); 1263 offset += NAMELEN; 1264 magic = TarUtils.parseName(header, offset, MAGICLEN); 1265 offset += MAGICLEN; 1266 version = TarUtils.parseName(header, offset, VERSIONLEN); 1267 offset += VERSIONLEN; 1268 userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN) 1269 : TarUtils.parseName(header, offset, UNAMELEN, encoding); 1270 offset += UNAMELEN; 1271 groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN) 1272 : TarUtils.parseName(header, offset, GNAMELEN, encoding); 1273 offset += GNAMELEN; 1274 if (linkFlag == LF_CHR || linkFlag == LF_BLK) { 1275 devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN); 1276 offset += DEVLEN; 1277 devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN); 1278 offset += DEVLEN; 1279 } else { 1280 offset += 2 * DEVLEN; 1281 } 1282 1283 final int type = evaluateType(header); 1284 switch (type) { 1285 case FORMAT_OLDGNU: { 1286 offset += ATIMELEN_GNU; 1287 offset += CTIMELEN_GNU; 1288 offset += OFFSETLEN_GNU; 1289 offset += LONGNAMESLEN_GNU; 1290 offset += PAD2LEN_GNU; 1291 offset += SPARSELEN_GNU; 1292 isExtended = TarUtils.parseBoolean(header, offset); 1293 offset += ISEXTENDEDLEN_GNU; 1294 realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU); 1295 offset += REALSIZELEN_GNU; // NOSONAR - assignment as documentation 1296 break; 1297 } 1298 case FORMAT_XSTAR: { 1299 final String xstarPrefix = oldStyle 1300 ? TarUtils.parseName(header, offset, PREFIXLEN_XSTAR) 1301 : TarUtils.parseName(header, offset, PREFIXLEN_XSTAR, encoding); 1302 if (xstarPrefix.length() > 0) { 1303 name = xstarPrefix + "/" + name; 1304 } 1305 break; 1306 } 1307 case FORMAT_POSIX: 1308 default: { 1309 final String prefix = oldStyle 1310 ? TarUtils.parseName(header, offset, PREFIXLEN) 1311 : TarUtils.parseName(header, offset, PREFIXLEN, encoding); 1312 // SunOS tar -E does not add / to directory names, so fix 1313 // up to be consistent 1314 if (isDirectory() && !name.endsWith("/")){ 1315 name = name + "/"; 1316 } 1317 if (prefix.length() > 0){ 1318 name = prefix + "/" + name; 1319 } 1320 } 1321 } 1322 } 1323 1324 /** 1325 * Strips Windows' drive letter as well as any leading slashes, 1326 * turns path separators into forward slahes. 1327 */ 1328 private static String normalizeFileName(String fileName, 1329 final boolean preserveLeadingSlashes) { 1330 final String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); 1331 1332 if (osname != null) { 1333 1334 // Strip off drive letters! 1335 // REVIEW Would a better check be "(File.separator == '\')"? 1336 1337 if (osname.startsWith("windows")) { 1338 if (fileName.length() > 2) { 1339 final char ch1 = fileName.charAt(0); 1340 final char ch2 = fileName.charAt(1); 1341 1342 if (ch2 == ':' 1343 && (ch1 >= 'a' && ch1 <= 'z' 1344 || ch1 >= 'A' && ch1 <= 'Z')) { 1345 fileName = fileName.substring(2); 1346 } 1347 } 1348 } else if (osname.contains("netware")) { 1349 final int colon = fileName.indexOf(':'); 1350 if (colon != -1) { 1351 fileName = fileName.substring(colon + 1); 1352 } 1353 } 1354 } 1355 1356 fileName = fileName.replace(File.separatorChar, '/'); 1357 1358 // No absolute pathnames 1359 // Windows (and Posix?) paths can start with "\\NetworkDrive\", 1360 // so we loop on starting /'s. 1361 while (!preserveLeadingSlashes && fileName.startsWith("/")) { 1362 fileName = fileName.substring(1); 1363 } 1364 return fileName; 1365 } 1366 1367 /** 1368 * Evaluate an entry's header format from a header buffer. 1369 * 1370 * @param header The tar entry header buffer to evaluate the format for. 1371 * @return format type 1372 */ 1373 private int evaluateType(final byte[] header) { 1374 if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) { 1375 return FORMAT_OLDGNU; 1376 } 1377 if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) { 1378 if (ArchiveUtils.matchAsciiBuffer(MAGIC_XSTAR, header, XSTAR_MAGIC_OFFSET, 1379 XSTAR_MAGIC_LEN)) { 1380 return FORMAT_XSTAR; 1381 } 1382 return FORMAT_POSIX; 1383 } 1384 return 0; 1385 } 1386 1387 void fillGNUSparse0xData(final Map<String, String> headers) { 1388 paxGNUSparse = true; 1389 realSize = Integer.parseInt(headers.get("GNU.sparse.size")); 1390 if (headers.containsKey("GNU.sparse.name")) { 1391 // version 0.1 1392 name = headers.get("GNU.sparse.name"); 1393 } 1394 } 1395 1396 void fillGNUSparse1xData(final Map<String, String> headers) { 1397 paxGNUSparse = true; 1398 realSize = Integer.parseInt(headers.get("GNU.sparse.realsize")); 1399 name = headers.get("GNU.sparse.name"); 1400 } 1401 1402 void fillStarSparseData(final Map<String, String> headers) { 1403 starSparse = true; 1404 if (headers.containsKey("SCHILY.realsize")) { 1405 realSize = Long.parseLong(headers.get("SCHILY.realsize")); 1406 } 1407 } 1408} 1409