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