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