    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.ar;
    020
    021import java.io.File;
    022import java.io.IOException;
    023import java.nio.file.Files;
    024import java.nio.file.LinkOption;
    025import java.nio.file.Path;
    026import java.util.Date;
    027import java.util.Objects;
    028
    029import org.apache.commons.compress.archivers.ArchiveEntry;
    030
    031/**
    032 * Represents an archive entry in the "ar" format.
    033 * <p>
    034 * Each AR archive starts with "!&lt;arch&gt;" followed by a LF. After these 8 bytes
    035 * the archive entries are listed. The format of an entry header is as it follows:
    036 * </p>
    037 * <pre>
    038 * START BYTE   END BYTE    NAME                    FORMAT      LENGTH
    039 * 0            15          File name               ASCII       16
    040 * 16           27          Modification timestamp  Decimal     12
    041 * 28           33          Owner ID                Decimal     6
    042 * 34           39          Group ID                Decimal     6
    043 * 40           47          File mode               Octal       8
    044 * 48           57          File size (bytes)       Decimal     10
    045 * 58           59          File magic              \140\012    2
    046 * </pre>
    047 * <p>
    048 * This specifies that an ar archive entry header contains 60 bytes.
    049 * </p>
    050 * <p>
    051 * Due to the limitation of the file name length to 16 bytes GNU and
    052 * BSD has their own variants of this format. Currently Commons
    053 * Compress can read but not write the GNU variant.  It fully supports
    054 * the BSD variant.
    055 * </p>
    056 *
    057 * @see <a href="https://www.freebsd.org/cgi/man.cgi?query=ar&sektion=5">ar man page</a>
    058 * @Immutable
    059 */
    060public class ArArchiveEntry implements ArchiveEntry {
    061
    062    /** The header for each entry */
    063    public static final String HEADER = "!<arch>\n";
    064
    065    /** The trailer for each entry */
    066    public static final String TRAILER = "`\012";
    067
    068    private static final int DEFAULT_MODE = 33188; // = (octal) 0100644
    069
    070    /**
    071     * SVR4/GNU adds a trailing / to names; BSD does not.
    072     * They also vary in how names longer than 16 characters are represented.
    073     * (Not yet fully supported by this implementation)
    074     */
    075    private final String name;
    076    private final int userId;
    077    private final int groupId;
    078    private final int mode;
    079    private final long lastModified;
    080    private final long length;
    081
    082    /**
    083     * Creates a new instance using the attributes of the given file
    084     * @param inputFile the file to create an entry from
    085     * @param entryName the name of the entry
    086     */
    087    public ArArchiveEntry(final File inputFile, final String entryName) {
    088        // TODO sort out mode
    089        this(entryName, inputFile.isFile() ? inputFile.length() : 0,
    090             0, 0, DEFAULT_MODE, inputFile.lastModified() / 1000);
    091    }
    092
    093    /**
    094     * Creates a new instance using the attributes of the given file
    095     * @param inputPath the file to create an entry from
    096     * @param entryName the name of the entry
    097     * @param options options indicating how symbolic links are handled.
    098     * @throws IOException if an I/O error occurs.
    099     * @since 1.21
    100     */
    101    public ArArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
    102        this(entryName, Files.isRegularFile(inputPath, options) ? Files.size(inputPath) : 0, 0, 0, DEFAULT_MODE,
    103            Files.getLastModifiedTime(inputPath, options).toMillis() / 1000);
    104    }
    105
    106    /**
    107     * Create a new instance using a couple of default values.
    108     *
    109     * <p>Sets userId and groupId to 0, the octal file mode to 644 and
    110     * the last modified time to the current time.</p>
    111     *
    112     * @param name name of the entry
    113     * @param length length of the entry in bytes
    114     */
    115    public ArArchiveEntry(final String name, final long length) {
    116        this(name, length, 0, 0, DEFAULT_MODE,
    117             System.currentTimeMillis() / 1000);
    118    }
    119
    120    /**
    121     * Create a new instance.
    122     *
    123     * @param name name of the entry
    124     * @param length length of the entry in bytes
    125     * @param userId numeric user id
    126     * @param groupId numeric group id
    127     * @param mode file mode
    128     * @param lastModified last modified time in seconds since the epoch
    129     */
    130    public ArArchiveEntry(final String name, final long length, final int userId, final int groupId,
    131                          final int mode, final long lastModified) {
    132        this.name = name;
    133        if (length < 0) {
    134            throw new IllegalArgumentException("length must not be negative");
    135        }
    136        this.length = length;
    137        this.userId = userId;
    138        this.groupId = groupId;
    139        this.mode = mode;
    140        this.lastModified = lastModified;
    141    }
    142
    143    @Override
    144    public boolean equals(final Object obj) {
    145        if (this == obj) {
    146            return true;
    147        }
    148        if (obj == null || getClass() != obj.getClass()) {
    149            return false;
    150        }
    151        final ArArchiveEntry other = (ArArchiveEntry) obj;
    152        if (name == null) {
    153            return other.name == null;
    154        }
    155        return name.equals(other.name);
    156    }
    157
    158    /**
    159     * Gets the group ID.
    160     *
    161     * @return the group ID.
    162     */
    163    public int getGroupId() {
    164        return groupId;
    165    }
    166
    167    /**
    168     * Gets the last modified time in seconds since the epoch.
    169     *
    170     * @return the last modified date.
    171     */
    172    public long getLastModified() {
    173        return lastModified;
    174    }
    175
    176    @Override
    177    public Date getLastModifiedDate() {
    178        return new Date(1000 * getLastModified());
    179    }
    180
    181    /**
    182     * Gets the length.
    183     *
    184     * @return the length.
    185     */
    186    public long getLength() {
    187        return length;
    188    }
    189
    190    /**
    191     * Gets the mode.
    192     *
    193     * @return the mode.
    194     */
    195    public int getMode() {
    196        return mode;
    197    }
    198
    199    @Override
    200    public String getName() {
    201        return name;
    202    }
    203
    204    @Override
    205    public long getSize() {
    206        return this.getLength();
    207    }
    208
    209    /**
    210     * Gets the user ID.
    211     *
    212     * @return the user ID.
    213     */
    214    public int getUserId() {
    215        return userId;
    216    }
    217
    218    @Override
    219    public int hashCode() {
    220        return Objects.hash(name);
    221    }
    222
    223    @Override
    224    public boolean isDirectory() {
    225        return false;
    226    }
    227}