    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.OutputStream;
    024import java.io.StringWriter;
    025import java.nio.ByteBuffer;
    026import java.util.Arrays;
    027import java.util.Date;
    028import java.util.HashMap;
    029import java.util.Map;
    030import org.apache.commons.compress.archivers.ArchiveEntry;
    031import org.apache.commons.compress.archivers.ArchiveOutputStream;
    032import org.apache.commons.compress.archivers.zip.ZipEncoding;
    033import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
    034import org.apache.commons.compress.utils.CharsetNames;
    035import org.apache.commons.compress.utils.CountingOutputStream;
    036
    037/**
    038 * The TarOutputStream writes a UNIX tar archive as an OutputStream.
    039 * Methods are provided to put entries, and then write their contents
    040 * by writing to this stream using write().
    041 * @NotThreadSafe
    042 */
    043public class TarArchiveOutputStream extends ArchiveOutputStream {
    044    /** Fail if a long file name is required in the archive. */
    045    public static final int LONGFILE_ERROR = 0;
    046
    047    /** Long paths will be truncated in the archive. */
    048    public static final int LONGFILE_TRUNCATE = 1;
    049
    050    /** GNU tar extensions are used to store long file names in the archive. */
    051    public static final int LONGFILE_GNU = 2;
    052
    053    /** POSIX/PAX extensions are used to store long file names in the archive. */
    054    public static final int LONGFILE_POSIX = 3;
    055
    056    /** Fail if a big number (e.g. size &gt; 8GiB) is required in the archive. */
    057    public static final int BIGNUMBER_ERROR = 0;
    058
    059    /** star/GNU tar/BSD tar extensions are used to store big number in the archive. */
    060    public static final int BIGNUMBER_STAR = 1;
    061
    062    /** POSIX/PAX extensions are used to store big numbers in the archive. */
    063    public static final int BIGNUMBER_POSIX = 2;
    064
    065    private long      currSize;
    066    private String    currName;
    067    private long      currBytes;
    068    private final byte[]    recordBuf;
    069    private int       assemLen;
    070    private final byte[]    assemBuf;
    071    private int       longFileMode = LONGFILE_ERROR;
    072    private int       bigNumberMode = BIGNUMBER_ERROR;
    073    private int recordsWritten;
    074    private final int recordsPerBlock;
    075    private final int recordSize;
    076
    077    private boolean closed = false;
    078
    079    /** Indicates if putArchiveEntry has been called without closeArchiveEntry */
    080    private boolean haveUnclosedEntry = false;
    081
    082    /** indicates if this archive is finished */
    083    private boolean finished = false;
    084
    085    private final OutputStream out;
    086
    087    private final ZipEncoding zipEncoding;
    088
    089    // the provided encoding (for unit tests)
    090    final String encoding;
    091
    092    private boolean addPaxHeadersForNonAsciiNames = false;
    093    private static final ZipEncoding ASCII =
    094        ZipEncodingHelper.getZipEncoding("ASCII");
    095
    096    /**
    097     * Constructor for TarInputStream.
    098     * @param os the output stream to use
    099     */
    100    public TarArchiveOutputStream(final OutputStream os) {
    101        this(os, TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE);
    102    }
    103
    104    /**
    105     * Constructor for TarInputStream.
    106     * @param os the output stream to use
    107     * @param encoding name of the encoding to use for file names
    108     * @since 1.4
    109     */
    110    public TarArchiveOutputStream(final OutputStream os, final String encoding) {
    111        this(os, TarConstants.DEFAULT_BLKSIZE, TarConstants.DEFAULT_RCDSIZE, encoding);
    112    }
    113
    114    /**
    115     * Constructor for TarInputStream.
    116     * @param os the output stream to use
    117     * @param blockSize the block size to use
    118     */
    119    public TarArchiveOutputStream(final OutputStream os, final int blockSize) {
    120        this(os, blockSize, TarConstants.DEFAULT_RCDSIZE);
    121    }
    122
    123    /**
    124     * Constructor for TarInputStream.
    125     * @param os the output stream to use
    126     * @param blockSize the block size to use
    127     * @param encoding name of the encoding to use for file names
    128     * @since 1.4
    129     */
    130    public TarArchiveOutputStream(final OutputStream os, final int blockSize,
    131                                  final String encoding) {
    132        this(os, blockSize, TarConstants.DEFAULT_RCDSIZE, encoding);
    133    }
    134
    135    /**
    136     * Constructor for TarInputStream.
    137     * @param os the output stream to use
    138     * @param blockSize the block size to use
    139     * @param recordSize the record size to use
    140     */
    141    public TarArchiveOutputStream(final OutputStream os, final int blockSize, final int recordSize) {
    142        this(os, blockSize, recordSize, null);
    143    }
    144
    145    /**
    146     * Constructor for TarInputStream.
    147     * @param os the output stream to use
    148     * @param blockSize the block size to use
    149     * @param recordSize the record size to use
    150     * @param encoding name of the encoding to use for file names
    151     * @since 1.4
    152     */
    153    public TarArchiveOutputStream(final OutputStream os, final int blockSize,
    154                                  final int recordSize, final String encoding) {
    155        out = new CountingOutputStream(os);
    156        this.encoding = encoding;
    157        this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
    158
    159        this.assemLen = 0;
    160        this.assemBuf = new byte[recordSize];
    161        this.recordBuf = new byte[recordSize];
    162        this.recordSize = recordSize;
    163        this.recordsPerBlock = blockSize / recordSize;
    164    }
    165
    166    /**
    167     * Set the long file mode.
    168     * This can be LONGFILE_ERROR(0), LONGFILE_TRUNCATE(1) or LONGFILE_GNU(2).
    169     * This specifies the treatment of long file names (names &gt;= TarConstants.NAMELEN).
    170     * Default is LONGFILE_ERROR.
    171     * @param longFileMode the mode to use
    172     */
    173    public void setLongFileMode(final int longFileMode) {
    174        this.longFileMode = longFileMode;
    175    }
    176
    177    /**
    178     * Set the big number mode.
    179     * This can be BIGNUMBER_ERROR(0), BIGNUMBER_POSIX(1) or BIGNUMBER_STAR(2).
    180     * This specifies the treatment of big files (sizes &gt; TarConstants.MAXSIZE) and other numeric values to big to fit into a traditional tar header.
    181     * Default is BIGNUMBER_ERROR.
    182     * @param bigNumberMode the mode to use
    183     * @since 1.4
    184     */
    185    public void setBigNumberMode(final int bigNumberMode) {
    186        this.bigNumberMode = bigNumberMode;
    187    }
    188
    189    /**
    190     * Whether to add a PAX extension header for non-ASCII file names.
    191     * @since 1.4
    192     * @param b whether to add a PAX extension header for non-ASCII file names.
    193     */
    194    public void setAddPaxHeadersForNonAsciiNames(final boolean b) {
    195        addPaxHeadersForNonAsciiNames = b;
    196    }
    197
    198    @Deprecated
    199    @Override
    200    public int getCount() {
    201        return (int) getBytesWritten();
    202    }
    203
    204    @Override
    205    public long getBytesWritten() {
    206        return ((CountingOutputStream) out).getBytesWritten();
    207    }
    208
    209    /**
    210     * Ends the TAR archive without closing the underlying OutputStream.
    211     *
    212     * An archive consists of a series of file entries terminated by an
    213     * end-of-archive entry, which consists of two 512 blocks of zero bytes.
    214     * POSIX.1 requires two EOF records, like some other implementations.
    215     *
    216     * @throws IOException on error
    217     */
    218    @Override
    219    public void finish() throws IOException {
    220        if (finished) {
    221            throw new IOException("This archive has already been finished");
    222        }
    223
    224        if (haveUnclosedEntry) {
    225            throw new IOException("This archives contains unclosed entries.");
    226        }
    227        writeEOFRecord();
    228        writeEOFRecord();
    229        padAsNeeded();
    230        out.flush();
    231        finished = true;
    232    }
    233
    234    /**
    235     * Closes the underlying OutputStream.
    236     * @throws IOException on error
    237     */
    238    @Override
    239    public void close() throws IOException {
    240        if (!finished) {
    241            finish();
    242        }
    243
    244        if (!closed) {
    245            out.close();
    246            closed = true;
    247        }
    248    }
    249
    250    /**
    251     * Get the record size being used by this stream's TarBuffer.
    252     *
    253     * @return The TarBuffer record size.
    254     */
    255    public int getRecordSize() {
    256        return this.recordSize;
    257    }
    258
    259    /**
    260     * Put an entry on the output stream. This writes the entry's
    261     * header record and positions the output stream for writing
    262     * the contents of the entry. Once this method is called, the
    263     * stream is ready for calls to write() to write the entry's
    264     * contents. Once the contents are written, closeArchiveEntry()
    265     * <B>MUST</B> be called to ensure that all buffered data
    266     * is completely written to the output stream.
    267     *
    268     * @param archiveEntry The TarEntry to be written to the archive.
    269     * @throws IOException on error
    270     * @throws ClassCastException if archiveEntry is not an instance of TarArchiveEntry
    271     */
    272    @Override
    273    public void putArchiveEntry(final ArchiveEntry archiveEntry) throws IOException {
    274        if (finished) {
    275            throw new IOException("Stream has already been finished");
    276        }
    277        final TarArchiveEntry entry = (TarArchiveEntry) archiveEntry;
    278        final Map<String, String> paxHeaders = new HashMap<>();
    279        final String entryName = entry.getName();
    280        final boolean paxHeaderContainsPath = handleLongName(entry, entryName, paxHeaders, "path",
    281                                                       TarConstants.LF_GNUTYPE_LONGNAME, "file name");
    282
    283        final String linkName = entry.getLinkName();
    284        final boolean paxHeaderContainsLinkPath = linkName != null && linkName.length() > 0
    285            && handleLongName(entry, linkName, paxHeaders, "linkpath",
    286                              TarConstants.LF_GNUTYPE_LONGLINK, "link name");
    287
    288        if (bigNumberMode == BIGNUMBER_POSIX) {
    289            addPaxHeadersForBigNumbers(paxHeaders, entry);
    290        } else if (bigNumberMode != BIGNUMBER_STAR) {
    291            failForBigNumbers(entry);
    292        }
    293
    294        if (addPaxHeadersForNonAsciiNames && !paxHeaderContainsPath
    295            && !ASCII.canEncode(entryName)) {
    296            paxHeaders.put("path", entryName);
    297        }
    298
    299        if (addPaxHeadersForNonAsciiNames && !paxHeaderContainsLinkPath
    300            && (entry.isLink() || entry.isSymbolicLink())
    301            && !ASCII.canEncode(linkName)) {
    302            paxHeaders.put("linkpath", linkName);
    303        }
    304
    305        if (paxHeaders.size() > 0) {
    306            writePaxHeaders(entry, entryName, paxHeaders);
    307        }
    308
    309        entry.writeEntryHeader(recordBuf, zipEncoding,
    310                               bigNumberMode == BIGNUMBER_STAR);
    311        writeRecord(recordBuf);
    312
    313        currBytes = 0;
    314
    315        if (entry.isDirectory()) {
    316            currSize = 0;
    317        } else {
    318            currSize = entry.getSize();
    319        }
    320        currName = entryName;
    321        haveUnclosedEntry = true;
    322    }
    323
    324    /**
    325     * Close an entry. This method MUST be called for all file
    326     * entries that contain data. The reason is that we must
    327     * buffer data written to the stream in order to satisfy
    328     * the buffer's record based writes. Thus, there may be
    329     * data fragments still being assembled that must be written
    330     * to the output stream before this entry is closed and the
    331     * next entry written.
    332     * @throws IOException on error
    333     */
    334    @Override
    335    public void closeArchiveEntry() throws IOException {
    336        if (finished) {
    337            throw new IOException("Stream has already been finished");
    338        }
    339        if (!haveUnclosedEntry){
    340            throw new IOException("No current entry to close");
    341        }
    342        if (assemLen > 0) {
    343            for (int i = assemLen; i < assemBuf.length; ++i) {
    344                assemBuf[i] = 0;
    345            }
    346
    347            writeRecord(assemBuf);
    348
    349            currBytes += assemLen;
    350            assemLen = 0;
    351        }
    352
    353        if (currBytes < currSize) {
    354            throw new IOException("entry '" + currName + "' closed at '"
    355                                  + currBytes
    356                                  + "' before the '" + currSize
    357                                  + "' bytes specified in the header were written");
    358        }
    359        haveUnclosedEntry = false;
    360    }
    361
    362    /**
    363     * Writes bytes to the current tar archive entry. This method
    364     * is aware of the current entry and will throw an exception if
    365     * you attempt to write bytes past the length specified for the
    366     * current entry. The method is also (painfully) aware of the
    367     * record buffering required by TarBuffer, and manages buffers
    368     * that are not a multiple of recordsize in length, including
    369     * assembling records from small buffers.
    370     *
    371     * @param wBuf The buffer to write to the archive.
    372     * @param wOffset The offset in the buffer from which to get bytes.
    373     * @param numToWrite The number of bytes to write.
    374     * @throws IOException on error
    375     */
    376    @Override
    377    public void write(final byte[] wBuf, int wOffset, int numToWrite) throws IOException {
    378        if (!haveUnclosedEntry) {
    379            throw new IllegalStateException("No current tar entry");
    380        }
    381        if (currBytes + numToWrite > currSize) {
    382            throw new IOException("request to write '" + numToWrite
    383                                  + "' bytes exceeds size in header of '"
    384                                  + currSize + "' bytes for entry '"
    385                                  + currName + "'");
    386
    387            //
    388            // We have to deal with assembly!!!
    389            // The programmer can be writing little 32 byte chunks for all
    390            // we know, and we must assemble complete records for writing.
    391            // REVIEW Maybe this should be in TarBuffer? Could that help to
    392            // eliminate some of the buffer copying.
    393            //
    394        }
    395
    396        if (assemLen > 0) {
    397            if (assemLen + numToWrite >= recordBuf.length) {
    398                final int aLen = recordBuf.length - assemLen;
    399
    400                System.arraycopy(assemBuf, 0, recordBuf, 0,
    401                                 assemLen);
    402                System.arraycopy(wBuf, wOffset, recordBuf,
    403                                 assemLen, aLen);
    404                writeRecord(recordBuf);
    405
    406                currBytes += recordBuf.length;
    407                wOffset += aLen;
    408                numToWrite -= aLen;
    409                assemLen = 0;
    410            } else {
    411                System.arraycopy(wBuf, wOffset, assemBuf, assemLen,
    412                                 numToWrite);
    413
    414                wOffset += numToWrite;
    415                assemLen += numToWrite;
    416                numToWrite = 0;
    417            }
    418        }
    419
    420        //
    421        // When we get here we have EITHER:
    422        // o An empty "assemble" buffer.
    423        // o No bytes to write (numToWrite == 0)
    424        //
    425        while (numToWrite > 0) {
    426            if (numToWrite < recordBuf.length) {
    427                System.arraycopy(wBuf, wOffset, assemBuf, assemLen,
    428                                 numToWrite);
    429
    430                assemLen += numToWrite;
    431
    432                break;
    433            }
    434
    435            writeRecord(wBuf, wOffset);
    436
    437            final int num = recordBuf.length;
    438
    439            currBytes += num;
    440            numToWrite -= num;
    441            wOffset += num;
    442        }
    443    }
    444
    445    /**
    446     * Writes a PAX extended header with the given map as contents.
    447     * @since 1.4
    448     */
    449    void writePaxHeaders(final TarArchiveEntry entry,
    450                         final String entryName,
    451                         final Map<String, String> headers) throws IOException {
    452        String name = "./PaxHeaders.X/" + stripTo7Bits(entryName);
    453        if (name.length() >= TarConstants.NAMELEN) {
    454            name = name.substring(0, TarConstants.NAMELEN - 1);
    455        }
    456        final TarArchiveEntry pex = new TarArchiveEntry(name,
    457                                                  TarConstants.LF_PAX_EXTENDED_HEADER_LC);
    458        transferModTime(entry, pex);
    459
    460        final StringWriter w = new StringWriter();
    461        for (final Map.Entry<String, String> h : headers.entrySet()) {
    462            final String key = h.getKey();
    463            final String value = h.getValue();
    464            int len = key.length() + value.length()
    465                + 3 /* blank, equals and newline */
    466                + 2 /* guess 9 < actual length < 100 */;
    467            String line = len + " " + key + "=" + value + "\n";
    468            int actualLength = line.getBytes(CharsetNames.UTF_8).length;
    469            while (len != actualLength) {
    470                // Adjust for cases where length < 10 or > 100
    471                // or where UTF-8 encoding isn't a single octet
    472                // per character.
    473                // Must be in loop as size may go from 99 to 100 in
    474                // first pass so we'd need a second.
    475                len = actualLength;
    476                line = len + " " + key + "=" + value + "\n";
    477                actualLength = line.getBytes(CharsetNames.UTF_8).length;
    478            }
    479            w.write(line);
    480        }
    481        final byte[] data = w.toString().getBytes(CharsetNames.UTF_8);
    482        pex.setSize(data.length);
    483        putArchiveEntry(pex);
    484        write(data);
    485        closeArchiveEntry();
    486    }
    487
    488    private String stripTo7Bits(final String name) {
    489        final int length = name.length();
    490        final StringBuilder result = new StringBuilder(length);
    491        for (int i = 0; i < length; i++) {
    492            final char stripped = (char) (name.charAt(i) & 0x7F);
    493            if (shouldBeReplaced(stripped)) {
    494                result.append("_");
    495            } else {
    496                result.append(stripped);
    497            }
    498        }
    499        return result.toString();
    500    }
    501
    502    /**
    503     * @return true if the character could lead to problems when used
    504     * inside a TarArchiveEntry name for a PAX header.
    505     */
    506    private boolean shouldBeReplaced(final char c) {
    507        return c == 0 // would be read as Trailing null
    508            || c == '/' // when used as last character TAE will consider the PAX header a directory
    509            || c == '\\'; // same as '/' as slashes get "normalized" on Windows
    510    }
    511
    512    /**
    513     * Write an EOF (end of archive) record to the tar archive.
    514     * An EOF record consists of a record of all zeros.
    515     */
    516    private void writeEOFRecord() throws IOException {
    517        Arrays.fill(recordBuf, (byte) 0);
    518        writeRecord(recordBuf);
    519    }
    520
    521    @Override
    522    public void flush() throws IOException {
    523        out.flush();
    524    }
    525
    526    @Override
    527    public ArchiveEntry createArchiveEntry(final File inputFile, final String entryName)
    528            throws IOException {
    529        if(finished) {
    530            throw new IOException("Stream has already been finished");
    531        }
    532        return new TarArchiveEntry(inputFile, entryName);
    533    }
    534
    535    /**
    536     * Write an archive record to the archive.
    537     *
    538     * @param record The record data to write to the archive.
    539     * @throws IOException on error
    540     */
    541    private void writeRecord(final byte[] record) throws IOException {
    542        if (record.length != recordSize) {
    543            throw new IOException("record to write has length '"
    544                                  + record.length
    545                                  + "' which is not the record size of '"
    546                                  + recordSize + "'");
    547        }
    548
    549        out.write(record);
    550        recordsWritten++;
    551    }
    552
    553    /**
    554     * Write an archive record to the archive, where the record may be
    555     * inside of a larger array buffer. The buffer must be "offset plus
    556     * record size" long.
    557     *
    558     * @param buf The buffer containing the record data to write.
    559     * @param offset The offset of the record data within buf.
    560     * @throws IOException on error
    561     */
    562    private void writeRecord(final byte[] buf, final int offset) throws IOException {
    563
    564        if (offset + recordSize > buf.length) {
    565            throw new IOException("record has length '" + buf.length
    566                                  + "' with offset '" + offset
    567                                  + "' which is less than the record size of '"
    568                                  + recordSize + "'");
    569        }
    570
    571        out.write(buf, offset, recordSize);
    572        recordsWritten++;
    573    }
    574
    575    private void padAsNeeded() throws IOException {
    576        final int start = recordsWritten % recordsPerBlock;
    577        if (start != 0) {
    578            for (int i = start; i < recordsPerBlock; i++) {
    579                writeEOFRecord();
    580            }
    581        }
    582    }
    583
    584    private void addPaxHeadersForBigNumbers(final Map<String, String> paxHeaders,
    585                                            final TarArchiveEntry entry) {
    586        addPaxHeaderForBigNumber(paxHeaders, "size", entry.getSize(),
    587                                 TarConstants.MAXSIZE);
    588        addPaxHeaderForBigNumber(paxHeaders, "gid", entry.getLongGroupId(),
    589                                 TarConstants.MAXID);
    590        addPaxHeaderForBigNumber(paxHeaders, "mtime",
    591                                 entry.getModTime().getTime() / 1000,
    592                                 TarConstants.MAXSIZE);
    593        addPaxHeaderForBigNumber(paxHeaders, "uid", entry.getLongUserId(),
    594                                 TarConstants.MAXID);
    595        // star extensions by J\u00f6rg Schilling
    596        addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devmajor",
    597                                 entry.getDevMajor(), TarConstants.MAXID);
    598        addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devminor",
    599                                 entry.getDevMinor(), TarConstants.MAXID);
    600        // there is no PAX header for file mode
    601        failForBigNumber("mode", entry.getMode(), TarConstants.MAXID);
    602    }
    603
    604    private void addPaxHeaderForBigNumber(final Map<String, String> paxHeaders,
    605                                          final String header, final long value,
    606                                          final long maxValue) {
    607        if (value < 0 || value > maxValue) {
    608            paxHeaders.put(header, String.valueOf(value));
    609        }
    610    }
    611
    612    private void failForBigNumbers(final TarArchiveEntry entry) {
    613        failForBigNumber("entry size", entry.getSize(), TarConstants.MAXSIZE);
    614        failForBigNumberWithPosixMessage("group id", entry.getLongGroupId(), TarConstants.MAXID);
    615        failForBigNumber("last modification time",
    616                         entry.getModTime().getTime() / 1000,
    617                         TarConstants.MAXSIZE);
    618        failForBigNumber("user id", entry.getLongUserId(), TarConstants.MAXID);
    619        failForBigNumber("mode", entry.getMode(), TarConstants.MAXID);
    620        failForBigNumber("major device number", entry.getDevMajor(),
    621                         TarConstants.MAXID);
    622        failForBigNumber("minor device number", entry.getDevMinor(),
    623                         TarConstants.MAXID);
    624    }
    625
    626    private void failForBigNumber(final String field, final long value, final long maxValue) {
    627        failForBigNumber(field, value, maxValue, "");
    628    }
    629
    630    private void failForBigNumberWithPosixMessage(final String field, final long value, final long maxValue) {
    631        failForBigNumber(field, value, maxValue, " Use STAR or POSIX extensions to overcome this limit");
    632    }
    633
    634    private void failForBigNumber(final String field, final long value, final long maxValue, final String additionalMsg) {
    635        if (value < 0 || value > maxValue) {
    636            throw new RuntimeException(field + " '" + value //NOSONAR
    637                    + "' is too big ( > "
    638                    + maxValue + " )." + additionalMsg);
    639        }
    640    }
    641
    642    /**
    643     * Handles long file or link names according to the longFileMode setting.
    644     *
    645     * <p>I.e. if the given name is too long to be written to a plain
    646     * tar header then
    647     * <ul>
    648     *   <li>it creates a pax header who's name is given by the
    649     *   paxHeaderName parameter if longFileMode is POSIX</li>
    650     *   <li>it creates a GNU longlink entry who's type is given by
    651     *   the linkType parameter if longFileMode is GNU</li>
    652     *   <li>it throws an exception if longFileMode is ERROR</li>
    653     *   <li>it truncates the name if longFileMode is TRUNCATE</li>
    654     * </ul></p>
    655     *
    656     * @param entry entry the name belongs to
    657     * @param name the name to write
    658     * @param paxHeaders current map of pax headers
    659     * @param paxHeaderName name of the pax header to write
    660     * @param linkType type of the GNU entry to write
    661     * @param fieldName the name of the field
    662     * @return whether a pax header has been written.
    663     */
    664    private boolean handleLongName(final TarArchiveEntry entry , final String name,
    665                                   final Map<String, String> paxHeaders,
    666                                   final String paxHeaderName, final byte linkType, final String fieldName)
    667        throws IOException {
    668        final ByteBuffer encodedName = zipEncoding.encode(name);
    669        final int len = encodedName.limit() - encodedName.position();
    670        if (len >= TarConstants.NAMELEN) {
    671
    672            if (longFileMode == LONGFILE_POSIX) {
    673                paxHeaders.put(paxHeaderName, name);
    674                return true;
    675            } else if (longFileMode == LONGFILE_GNU) {
    676                // create a TarEntry for the LongLink, the contents
    677                // of which are the link's name
    678                final TarArchiveEntry longLinkEntry = new TarArchiveEntry(TarConstants.GNU_LONGLINK, linkType);
    679
    680                longLinkEntry.setSize(len + 1l); // +1 for NUL
    681                transferModTime(entry, longLinkEntry);
    682                putArchiveEntry(longLinkEntry);
    683                write(encodedName.array(), encodedName.arrayOffset(), len);
    684                write(0); // NUL terminator
    685                closeArchiveEntry();
    686            } else if (longFileMode != LONGFILE_TRUNCATE) {
    687                throw new RuntimeException(fieldName + " '" + name //NOSONAR
    688                                           + "' is too long ( > "
    689                                           + TarConstants.NAMELEN + " bytes)");
    690            }
    691        }
    692        return false;
    693    }
    694
    695    private void transferModTime(final TarArchiveEntry from, final TarArchiveEntry to) {
    696        Date fromModTime = from.getModTime();
    697        final long fromModTimeSeconds = fromModTime.getTime() / 1000;
    698        if (fromModTimeSeconds < 0 || fromModTimeSeconds > TarConstants.MAXSIZE) {
    699            fromModTime = new Date(0);
    700        }
    701        to.setModTime(fromModTime);
    702    }
    703}