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 */ 322 public TarArchiveEntry(byte[] headerBuf, ZipEncoding encoding) 323 throws IOException { 324 this(); 325 parseTarHeader(headerBuf, encoding); 326 } 327 328 /** 329 * Determine if the two entries are equal. Equality is determined 330 * by the header names being equal. 331 * 332 * @param it Entry to be checked for equality. 333 * @return True if the entries are equal. 334 */ 335 public boolean equals(TarArchiveEntry it) { 336 return getName().equals(it.getName()); 337 } 338 339 /** 340 * Determine if the two entries are equal. Equality is determined 341 * by the header names being equal. 342 * 343 * @param it Entry to be checked for equality. 344 * @return True if the entries are equal. 345 */ 346 @Override 347 public boolean equals(Object it) { 348 if (it == null || getClass() != it.getClass()) { 349 return false; 350 } 351 return equals((TarArchiveEntry) it); 352 } 353 354 /** 355 * Hashcodes are based on entry names. 356 * 357 * @return the entry hashcode 358 */ 359 @Override 360 public int hashCode() { 361 return getName().hashCode(); 362 } 363 364 /** 365 * Determine if the given entry is a descendant of this entry. 366 * Descendancy is determined by the name of the descendant 367 * starting with this entry's name. 368 * 369 * @param desc Entry to be checked as a descendent of this. 370 * @return True if entry is a descendant of this. 371 */ 372 public boolean isDescendent(TarArchiveEntry desc) { 373 return desc.getName().startsWith(getName()); 374 } 375 376 /** 377 * Get this entry's name. 378 * 379 * @return This entry's name. 380 */ 381 public String getName() { 382 return name; 383 } 384 385 /** 386 * Set this entry's name. 387 * 388 * @param name This entry's new name. 389 */ 390 public void setName(String name) { 391 this.name = normalizeFileName(name, false); 392 } 393 394 /** 395 * Set the mode for this entry 396 * 397 * @param mode the mode for this entry 398 */ 399 public void setMode(int mode) { 400 this.mode = mode; 401 } 402 403 /** 404 * Get this entry's link name. 405 * 406 * @return This entry's link name. 407 */ 408 public String getLinkName() { 409 return linkName; 410 } 411 412 /** 413 * Set this entry's link name. 414 * 415 * @param link the link name to use. 416 * 417 * @since 1.1 418 */ 419 public void setLinkName(String link) { 420 this.linkName = link; 421 } 422 423 /** 424 * Get this entry's user id. 425 * 426 * @return This entry's user id. 427 * @deprecated use #getLongUserId instead as user ids can be 428 * bigger than {@link Integer.MAX_INT} 429 */ 430 @Deprecated 431 public int getUserId() { 432 return (int) (userId & 0xffffffff); 433 } 434 435 /** 436 * Set this entry's user id. 437 * 438 * @param userId This entry's new user id. 439 */ 440 public void setUserId(int userId) { 441 setUserId((long) userId); 442 } 443 444 /** 445 * Get this entry's user id. 446 * 447 * @return This entry's user id. 448 * @since 1.10 449 */ 450 public long getLongUserId() { 451 return userId; 452 } 453 454 /** 455 * Set this entry's user id. 456 * 457 * @param userId This entry's new user id. 458 * @since 1.10 459 */ 460 public void setUserId(long userId) { 461 this.userId = userId; 462 } 463 464 /** 465 * Get this entry's group id. 466 * 467 * @return This entry's group id. 468 * @deprecated use #getLongGroupId instead as group ids can be 469 * bigger than {@link Integer.MAX_INT} 470 */ 471 @Deprecated 472 public int getGroupId() { 473 return (int) (groupId & 0xffffffff); 474 } 475 476 /** 477 * Set this entry's group id. 478 * 479 * @param groupId This entry's new group id. 480 */ 481 public void setGroupId(int groupId) { 482 setGroupId((long) groupId); 483 } 484 485 /** 486 * Get this entry's group id. 487 * 488 * @since 1.10 489 * @return This entry's group id. 490 */ 491 public long getLongGroupId() { 492 return groupId; 493 } 494 495 /** 496 * Set this entry's group id. 497 * 498 * @since 1.10 499 * @param groupId This entry's new group id. 500 */ 501 public void setGroupId(long groupId) { 502 this.groupId = groupId; 503 } 504 505 /** 506 * Get this entry's user name. 507 * 508 * @return This entry's user name. 509 */ 510 public String getUserName() { 511 return userName; 512 } 513 514 /** 515 * Set this entry's user name. 516 * 517 * @param userName This entry's new user name. 518 */ 519 public void setUserName(String userName) { 520 this.userName = userName; 521 } 522 523 /** 524 * Get this entry's group name. 525 * 526 * @return This entry's group name. 527 */ 528 public String getGroupName() { 529 return groupName; 530 } 531 532 /** 533 * Set this entry's group name. 534 * 535 * @param groupName This entry's new group name. 536 */ 537 public void setGroupName(String groupName) { 538 this.groupName = groupName; 539 } 540 541 /** 542 * Convenience method to set this entry's group and user ids. 543 * 544 * @param userId This entry's new user id. 545 * @param groupId This entry's new group id. 546 */ 547 public void setIds(int userId, int groupId) { 548 setUserId(userId); 549 setGroupId(groupId); 550 } 551 552 /** 553 * Convenience method to set this entry's group and user names. 554 * 555 * @param userName This entry's new user name. 556 * @param groupName This entry's new group name. 557 */ 558 public void setNames(String userName, String groupName) { 559 setUserName(userName); 560 setGroupName(groupName); 561 } 562 563 /** 564 * Set this entry's modification time. The parameter passed 565 * to this method is in "Java time". 566 * 567 * @param time This entry's new modification time. 568 */ 569 public void setModTime(long time) { 570 modTime = time / MILLIS_PER_SECOND; 571 } 572 573 /** 574 * Set this entry's modification time. 575 * 576 * @param time This entry's new modification time. 577 */ 578 public void setModTime(Date time) { 579 modTime = time.getTime() / MILLIS_PER_SECOND; 580 } 581 582 /** 583 * Set this entry's modification time. 584 * 585 * @return time This entry's new modification time. 586 */ 587 public Date getModTime() { 588 return new Date(modTime * MILLIS_PER_SECOND); 589 } 590 591 public Date getLastModifiedDate() { 592 return getModTime(); 593 } 594 595 /** 596 * Get this entry's checksum status. 597 * 598 * @return if the header checksum is reasonably correct 599 * @see TarUtils#verifyCheckSum(byte[]) 600 * @since 1.5 601 */ 602 public boolean isCheckSumOK() { 603 return checkSumOK; 604 } 605 606 /** 607 * Get this entry's file. 608 * 609 * @return This entry's file. 610 */ 611 public File getFile() { 612 return file; 613 } 614 615 /** 616 * Get this entry's mode. 617 * 618 * @return This entry's mode. 619 */ 620 public int getMode() { 621 return mode; 622 } 623 624 /** 625 * Get this entry's file size. 626 * 627 * @return This entry's file size. 628 */ 629 public long getSize() { 630 return size; 631 } 632 633 /** 634 * Set this entry's file size. 635 * 636 * @param size This entry's new file size. 637 * @throws IllegalArgumentException if the size is < 0. 638 */ 639 public void setSize(long size) { 640 if (size < 0){ 641 throw new IllegalArgumentException("Size is out of range: "+size); 642 } 643 this.size = size; 644 } 645 646 /** 647 * Get this entry's major device number. 648 * 649 * @return This entry's major device number. 650 * @since 1.4 651 */ 652 public int getDevMajor() { 653 return devMajor; 654 } 655 656 /** 657 * Set this entry's major device number. 658 * 659 * @param devNo This entry's major device number. 660 * @throws IllegalArgumentException if the devNo is < 0. 661 * @since 1.4 662 */ 663 public void setDevMajor(int devNo) { 664 if (devNo < 0){ 665 throw new IllegalArgumentException("Major device number is out of " 666 + "range: " + devNo); 667 } 668 this.devMajor = devNo; 669 } 670 671 /** 672 * Get this entry's minor device number. 673 * 674 * @return This entry's minor device number. 675 * @since 1.4 676 */ 677 public int getDevMinor() { 678 return devMinor; 679 } 680 681 /** 682 * Set this entry's minor device number. 683 * 684 * @param devNo This entry's minor device number. 685 * @throws IllegalArgumentException if the devNo is < 0. 686 * @since 1.4 687 */ 688 public void setDevMinor(int devNo) { 689 if (devNo < 0){ 690 throw new IllegalArgumentException("Minor device number is out of " 691 + "range: " + devNo); 692 } 693 this.devMinor = devNo; 694 } 695 696 /** 697 * Indicates in case of a sparse file if an extension sparse header 698 * follows. 699 * 700 * @return true if an extension sparse header follows. 701 */ 702 public boolean isExtended() { 703 return isExtended; 704 } 705 706 /** 707 * Get this entry's real file size in case of a sparse file. 708 * 709 * @return This entry's real file size. 710 */ 711 public long getRealSize() { 712 return realSize; 713 } 714 715 /** 716 * Indicate if this entry is a GNU sparse block 717 * 718 * @return true if this is a sparse extension provided by GNU tar 719 */ 720 public boolean isGNUSparse() { 721 return linkFlag == LF_GNUTYPE_SPARSE; 722 } 723 724 /** 725 * Indicate if this entry is a GNU long linkname block 726 * 727 * @return true if this is a long name extension provided by GNU tar 728 */ 729 public boolean isGNULongLinkEntry() { 730 return linkFlag == LF_GNUTYPE_LONGLINK 731 && name.equals(GNU_LONGLINK); 732 } 733 734 /** 735 * Indicate if this entry is a GNU long name block 736 * 737 * @return true if this is a long name extension provided by GNU tar 738 */ 739 public boolean isGNULongNameEntry() { 740 return linkFlag == LF_GNUTYPE_LONGNAME 741 && name.equals(GNU_LONGLINK); 742 } 743 744 /** 745 * Check if this is a Pax header. 746 * 747 * @return {@code true} if this is a Pax header. 748 * 749 * @since 1.1 750 * 751 */ 752 public boolean isPaxHeader(){ 753 return linkFlag == LF_PAX_EXTENDED_HEADER_LC 754 || linkFlag == LF_PAX_EXTENDED_HEADER_UC; 755 } 756 757 /** 758 * Check if this is a Pax header. 759 * 760 * @return {@code true} if this is a Pax header. 761 * 762 * @since 1.1 763 */ 764 public boolean isGlobalPaxHeader(){ 765 return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER; 766 } 767 768 /** 769 * Return whether or not this entry represents a directory. 770 * 771 * @return True if this entry is a directory. 772 */ 773 public boolean isDirectory() { 774 if (file != null) { 775 return file.isDirectory(); 776 } 777 778 if (linkFlag == LF_DIR) { 779 return true; 780 } 781 782 if (getName().endsWith("/")) { 783 return true; 784 } 785 786 return false; 787 } 788 789 /** 790 * Check if this is a "normal file" 791 * 792 * @since 1.2 793 */ 794 public boolean isFile() { 795 if (file != null) { 796 return file.isFile(); 797 } 798 if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) { 799 return true; 800 } 801 return !getName().endsWith("/"); 802 } 803 804 /** 805 * Check if this is a symbolic link entry. 806 * 807 * @since 1.2 808 */ 809 public boolean isSymbolicLink() { 810 return linkFlag == LF_SYMLINK; 811 } 812 813 /** 814 * Check if this is a link entry. 815 * 816 * @since 1.2 817 */ 818 public boolean isLink() { 819 return linkFlag == LF_LINK; 820 } 821 822 /** 823 * Check if this is a character device entry. 824 * 825 * @since 1.2 826 */ 827 public boolean isCharacterDevice() { 828 return linkFlag == LF_CHR; 829 } 830 831 /** 832 * Check if this is a block device entry. 833 * 834 * @since 1.2 835 */ 836 public boolean isBlockDevice() { 837 return linkFlag == LF_BLK; 838 } 839 840 /** 841 * Check if this is a FIFO (pipe) entry. 842 * 843 * @since 1.2 844 */ 845 public boolean isFIFO() { 846 return linkFlag == LF_FIFO; 847 } 848 849 /** 850 * If this entry represents a file, and the file is a directory, return 851 * an array of TarEntries for this entry's children. 852 * 853 * @return An array of TarEntry's for this entry's children. 854 */ 855 public TarArchiveEntry[] getDirectoryEntries() { 856 if (file == null || !file.isDirectory()) { 857 return new TarArchiveEntry[0]; 858 } 859 860 String[] list = file.list(); 861 TarArchiveEntry[] result = new TarArchiveEntry[list.length]; 862 863 for (int i = 0; i < list.length; ++i) { 864 result[i] = new TarArchiveEntry(new File(file, list[i])); 865 } 866 867 return result; 868 } 869 870 /** 871 * Write an entry's header information to a header buffer. 872 * 873 * <p>This method does not use the star/GNU tar/BSD tar extensions.</p> 874 * 875 * @param outbuf The tar entry header buffer to fill in. 876 */ 877 public void writeEntryHeader(byte[] outbuf) { 878 try { 879 writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false); 880 } catch (IOException ex) { 881 try { 882 writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false); 883 } catch (IOException ex2) { 884 // impossible 885 throw new RuntimeException(ex2); 886 } 887 } 888 } 889 890 /** 891 * Write an entry's header information to a header buffer. 892 * 893 * @param outbuf The tar entry header buffer to fill in. 894 * @param encoding encoding to use when writing the file name. 895 * @param starMode whether to use the star/GNU tar/BSD tar 896 * extension for numeric fields if their value doesn't fit in the 897 * maximum size of standard tar archives 898 * @since 1.4 899 */ 900 public void writeEntryHeader(byte[] outbuf, ZipEncoding encoding, 901 boolean starMode) throws IOException { 902 int offset = 0; 903 904 offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN, 905 encoding); 906 offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode); 907 offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN, 908 starMode); 909 offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN, 910 starMode); 911 offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode); 912 offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN, 913 starMode); 914 915 int csOffset = offset; 916 917 for (int c = 0; c < CHKSUMLEN; ++c) { 918 outbuf[offset++] = (byte) ' '; 919 } 920 921 outbuf[offset++] = linkFlag; 922 offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN, 923 encoding); 924 offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN); 925 offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN); 926 offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN, 927 encoding); 928 offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN, 929 encoding); 930 offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN, 931 starMode); 932 offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN, 933 starMode); 934 935 while (offset < outbuf.length) { 936 outbuf[offset++] = 0; 937 } 938 939 long chk = TarUtils.computeCheckSum(outbuf); 940 941 TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN); 942 } 943 944 private int writeEntryHeaderField(long value, byte[] outbuf, int offset, 945 int length, boolean starMode) { 946 if (!starMode && (value < 0 947 || value >= 1l << 3 * (length - 1))) { 948 // value doesn't fit into field when written as octal 949 // number, will be written to PAX header or causes an 950 // error 951 return TarUtils.formatLongOctalBytes(0, outbuf, offset, length); 952 } 953 return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset, 954 length); 955 } 956 957 /** 958 * Parse an entry's header information from a header buffer. 959 * 960 * @param header The tar entry header buffer to get information from. 961 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 962 */ 963 public void parseTarHeader(byte[] header) { 964 try { 965 parseTarHeader(header, TarUtils.DEFAULT_ENCODING); 966 } catch (IOException ex) { 967 try { 968 parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true); 969 } catch (IOException ex2) { 970 // not really possible 971 throw new RuntimeException(ex2); 972 } 973 } 974 } 975 976 /** 977 * Parse an entry's header information from a header buffer. 978 * 979 * @param header The tar entry header buffer to get information from. 980 * @param encoding encoding to use for file names 981 * @since 1.4 982 * @throws IllegalArgumentException if any of the numeric fields 983 * have an invalid format 984 */ 985 public void parseTarHeader(byte[] header, ZipEncoding encoding) 986 throws IOException { 987 parseTarHeader(header, encoding, false); 988 } 989 990 private void parseTarHeader(byte[] header, ZipEncoding encoding, 991 final boolean oldStyle) 992 throws IOException { 993 int offset = 0; 994 995 name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) 996 : TarUtils.parseName(header, offset, NAMELEN, encoding); 997 offset += NAMELEN; 998 mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN); 999 offset += MODELEN; 1000 userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN); 1001 offset += UIDLEN; 1002 groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN); 1003 offset += GIDLEN; 1004 size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN); 1005 offset += SIZELEN; 1006 modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN); 1007 offset += MODTIMELEN; 1008 checkSumOK = TarUtils.verifyCheckSum(header); 1009 offset += CHKSUMLEN; 1010 linkFlag = header[offset++]; 1011 linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) 1012 : TarUtils.parseName(header, offset, NAMELEN, encoding); 1013 offset += NAMELEN; 1014 magic = TarUtils.parseName(header, offset, MAGICLEN); 1015 offset += MAGICLEN; 1016 version = TarUtils.parseName(header, offset, VERSIONLEN); 1017 offset += VERSIONLEN; 1018 userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN) 1019 : TarUtils.parseName(header, offset, UNAMELEN, encoding); 1020 offset += UNAMELEN; 1021 groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN) 1022 : TarUtils.parseName(header, offset, GNAMELEN, encoding); 1023 offset += GNAMELEN; 1024 devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN); 1025 offset += DEVLEN; 1026 devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN); 1027 offset += DEVLEN; 1028 1029 int type = evaluateType(header); 1030 switch (type) { 1031 case FORMAT_OLDGNU: { 1032 offset += ATIMELEN_GNU; 1033 offset += CTIMELEN_GNU; 1034 offset += OFFSETLEN_GNU; 1035 offset += LONGNAMESLEN_GNU; 1036 offset += PAD2LEN_GNU; 1037 offset += SPARSELEN_GNU; 1038 isExtended = TarUtils.parseBoolean(header, offset); 1039 offset += ISEXTENDEDLEN_GNU; 1040 realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU); 1041 offset += REALSIZELEN_GNU; 1042 break; 1043 } 1044 case FORMAT_POSIX: 1045 default: { 1046 String prefix = oldStyle 1047 ? TarUtils.parseName(header, offset, PREFIXLEN) 1048 : TarUtils.parseName(header, offset, PREFIXLEN, encoding); 1049 // SunOS tar -E does not add / to directory names, so fix 1050 // up to be consistent 1051 if (isDirectory() && !name.endsWith("/")){ 1052 name = name + "/"; 1053 } 1054 if (prefix.length() > 0){ 1055 name = prefix + "/" + name; 1056 } 1057 } 1058 } 1059 } 1060 1061 /** 1062 * Strips Windows' drive letter as well as any leading slashes, 1063 * turns path separators into forward slahes. 1064 */ 1065 private static String normalizeFileName(String fileName, 1066 boolean preserveLeadingSlashes) { 1067 String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); 1068 1069 if (osname != null) { 1070 1071 // Strip off drive letters! 1072 // REVIEW Would a better check be "(File.separator == '\')"? 1073 1074 if (osname.startsWith("windows")) { 1075 if (fileName.length() > 2) { 1076 char ch1 = fileName.charAt(0); 1077 char ch2 = fileName.charAt(1); 1078 1079 if (ch2 == ':' 1080 && (ch1 >= 'a' && ch1 <= 'z' 1081 || ch1 >= 'A' && ch1 <= 'Z')) { 1082 fileName = fileName.substring(2); 1083 } 1084 } 1085 } else if (osname.contains("netware")) { 1086 int colon = fileName.indexOf(':'); 1087 if (colon != -1) { 1088 fileName = fileName.substring(colon + 1); 1089 } 1090 } 1091 } 1092 1093 fileName = fileName.replace(File.separatorChar, '/'); 1094 1095 // No absolute pathnames 1096 // Windows (and Posix?) paths can start with "\\NetworkDrive\", 1097 // so we loop on starting /'s. 1098 while (!preserveLeadingSlashes && fileName.startsWith("/")) { 1099 fileName = fileName.substring(1); 1100 } 1101 return fileName; 1102 } 1103 1104 /** 1105 * Evaluate an entry's header format from a header buffer. 1106 * 1107 * @param header The tar entry header buffer to evaluate the format for. 1108 * @return format type 1109 */ 1110 private int evaluateType(byte[] header) { 1111 if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) { 1112 return FORMAT_OLDGNU; 1113 } 1114 if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) { 1115 return FORMAT_POSIX; 1116 } 1117 return 0; 1118 } 1119} 1120