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