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