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