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