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