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