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