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