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.zip;
020
021import java.util.zip.CRC32;
022import java.util.zip.ZipException;
023
024/**
025 * Adds Unix file permission and UID/GID fields as well as symbolic
026 * link handling.
027 *
028 * <p>This class uses the ASi extra field in the format:</p>
029 * <pre>
030 *         Value         Size            Description
031 *         -----         ----            -----------
032 * (Unix3) 0x756e        Short           tag for this extra block type
033 *         TSize         Short           total data size for this block
034 *         CRC           Long            CRC-32 of the remaining data
035 *         Mode          Short           file permissions
036 *         SizDev        Long            symlink'd size OR major/minor dev num
037 *         UID           Short           user ID
038 *         GID           Short           group ID
039 *         (var.)        variable        symbolic link filename
040 * </pre>
041 * <p>taken from appnote.iz (Info-ZIP note, 981119) found at <a
042 * href="ftp://ftp.uu.net/pub/archiving/zip/doc/">ftp://ftp.uu.net/pub/archiving/zip/doc/</a></p>
043 *
044 * <p>Short is two bytes and Long is four bytes in big endian byte and
045 * word order, device numbers are currently not supported.</p>
046 * @NotThreadSafe
047 *
048 * <p>Since the documentation this class is based upon doesn't mention
049 * the character encoding of the file name at all, it is assumed that
050 * it uses the current platform's default encoding.</p>
051 */
052public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable {
053
054    private static final ZipShort HEADER_ID = new ZipShort(0x756E);
055    private static final int      WORD = 4;
056    /**
057     * Standard Unix stat(2) file mode.
058     */
059    private int mode = 0;
060    /**
061     * User ID.
062     */
063    private int uid = 0;
064    /**
065     * Group ID.
066     */
067    private int gid = 0;
068    /**
069     * File this entry points to, if it is a symbolic link.
070     *
071     * <p>empty string - if entry is not a symbolic link.</p>
072     */
073    private String link = "";
074    /**
075     * Is this an entry for a directory?
076     */
077    private boolean dirFlag = false;
078
079    /**
080     * Instance used to calculate checksums.
081     */
082    private CRC32 crc = new CRC32();
083
084    /** Constructor for AsiExtraField. */
085    public AsiExtraField() {
086    }
087
088    /**
089     * The Header-ID.
090     * @return the value for the header id for this extrafield
091     */
092    @Override
093    public ZipShort getHeaderId() {
094        return HEADER_ID;
095    }
096
097    /**
098     * Length of the extra field in the local file data - without
099     * Header-ID or length specifier.
100     * @return a <code>ZipShort</code> for the length of the data of this extra field
101     */
102    @Override
103    public ZipShort getLocalFileDataLength() {
104        return new ZipShort(WORD         // CRC
105                          + 2         // Mode
106                          + WORD         // SizDev
107                          + 2         // UID
108                          + 2         // GID
109                          + getLinkedFile().getBytes().length);
110                          // Uses default charset - see class Javadoc
111    }
112
113    /**
114     * Delegate to local file data.
115     * @return the centralDirectory length
116     */
117    @Override
118    public ZipShort getCentralDirectoryLength() {
119        return getLocalFileDataLength();
120    }
121
122    /**
123     * The actual data to put into local file data - without Header-ID
124     * or length specifier.
125     * @return get the data
126     */
127    @Override
128    public byte[] getLocalFileDataData() {
129        // CRC will be added later
130        final byte[] data = new byte[getLocalFileDataLength().getValue() - WORD];
131        System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2);
132
133        final byte[] linkArray = getLinkedFile().getBytes(); // Uses default charset - see class Javadoc
134        // CheckStyle:MagicNumber OFF
135        System.arraycopy(ZipLong.getBytes(linkArray.length),
136                         0, data, 2, WORD);
137
138        System.arraycopy(ZipShort.getBytes(getUserId()),
139                         0, data, 6, 2);
140        System.arraycopy(ZipShort.getBytes(getGroupId()),
141                         0, data, 8, 2);
142
143        System.arraycopy(linkArray, 0, data, 10, linkArray.length);
144        // CheckStyle:MagicNumber ON
145
146        crc.reset();
147        crc.update(data);
148        final long checksum = crc.getValue();
149
150        final byte[] result = new byte[data.length + WORD];
151        System.arraycopy(ZipLong.getBytes(checksum), 0, result, 0, WORD);
152        System.arraycopy(data, 0, result, WORD, data.length);
153        return result;
154    }
155
156    /**
157     * Delegate to local file data.
158     * @return the local file data
159     */
160    @Override
161    public byte[] getCentralDirectoryData() {
162        return getLocalFileDataData();
163    }
164
165    /**
166     * Set the user id.
167     * @param uid the user id
168     */
169    public void setUserId(final int uid) {
170        this.uid = uid;
171    }
172
173    /**
174     * Get the user id.
175     * @return the user id
176     */
177    public int getUserId() {
178        return uid;
179    }
180
181    /**
182     * Set the group id.
183     * @param gid the group id
184     */
185    public void setGroupId(final int gid) {
186        this.gid = gid;
187    }
188
189    /**
190     * Get the group id.
191     * @return the group id
192     */
193    public int getGroupId() {
194        return gid;
195    }
196
197    /**
198     * Indicate that this entry is a symbolic link to the given filename.
199     *
200     * @param name Name of the file this entry links to, empty String
201     *             if it is not a symbolic link.
202     */
203    public void setLinkedFile(final String name) {
204        link = name;
205        mode = getMode(mode);
206    }
207
208    /**
209     * Name of linked file
210     *
211     * @return name of the file this entry links to if it is a
212     *         symbolic link, the empty string otherwise.
213     */
214    public String getLinkedFile() {
215        return link;
216    }
217
218    /**
219     * Is this entry a symbolic link?
220     * @return true if this is a symbolic link
221     */
222    public boolean isLink() {
223        return getLinkedFile().length() != 0;
224    }
225
226    /**
227     * File mode of this file.
228     * @param mode the file mode
229     */
230    public void setMode(final int mode) {
231        this.mode = getMode(mode);
232    }
233
234    /**
235     * File mode of this file.
236     * @return the file mode
237     */
238    public int getMode() {
239        return mode;
240    }
241
242    /**
243     * Indicate whether this entry is a directory.
244     * @param dirFlag if true, this entry is a directory
245     */
246    public void setDirectory(final boolean dirFlag) {
247        this.dirFlag = dirFlag;
248        mode = getMode(mode);
249    }
250
251    /**
252     * Is this entry a directory?
253     * @return true if this entry is a directory
254     */
255    public boolean isDirectory() {
256        return dirFlag && !isLink();
257    }
258
259    /**
260     * Populate data from this array as if it was in local file data.
261     * @param data an array of bytes
262     * @param offset the start offset
263     * @param length the number of bytes in the array from offset
264     * @throws ZipException on error
265     */
266    @Override
267    public void parseFromLocalFileData(final byte[] data, final int offset, final int length)
268        throws ZipException {
269
270        final long givenChecksum = ZipLong.getValue(data, offset);
271        final byte[] tmp = new byte[length - WORD];
272        System.arraycopy(data, offset + WORD, tmp, 0, length - WORD);
273        crc.reset();
274        crc.update(tmp);
275        final long realChecksum = crc.getValue();
276        if (givenChecksum != realChecksum) {
277            throw new ZipException("bad CRC checksum "
278                                   + Long.toHexString(givenChecksum)
279                                   + " instead of "
280                                   + Long.toHexString(realChecksum));
281        }
282
283        final int newMode = ZipShort.getValue(tmp, 0);
284        // CheckStyle:MagicNumber OFF
285        final byte[] linkArray = new byte[(int) ZipLong.getValue(tmp, 2)];
286        uid = ZipShort.getValue(tmp, 6);
287        gid = ZipShort.getValue(tmp, 8);
288
289        if (linkArray.length == 0) {
290            link = "";
291        } else {
292            System.arraycopy(tmp, 10, linkArray, 0, linkArray.length);
293            link = new String(linkArray); // Uses default charset - see class Javadoc
294        }
295        // CheckStyle:MagicNumber ON
296        setDirectory((newMode & DIR_FLAG) != 0);
297        setMode(newMode);
298    }
299
300    /**
301     * Doesn't do anything special since this class always uses the
302     * same data in central directory and local file data.
303     */
304    @Override
305    public void parseFromCentralDirectoryData(final byte[] buffer, final int offset,
306                                              final int length)
307        throws ZipException {
308        parseFromLocalFileData(buffer, offset, length);
309    }
310
311    /**
312     * Get the file mode for given permissions with the correct file type.
313     * @param mode the mode
314     * @return the type with the mode
315     */
316    protected int getMode(final int mode) {
317        int type = FILE_FLAG;
318        if (isLink()) {
319            type = LINK_FLAG;
320        } else if (isDirectory()) {
321            type = DIR_FLAG;
322        }
323        return type | (mode & PERM_MASK);
324    }
325
326    @Override
327    public Object clone() {
328        try {
329            final AsiExtraField cloned = (AsiExtraField) super.clone();
330            cloned.crc = new CRC32();
331            return cloned;
332        } catch (final CloneNotSupportedException cnfe) {
333            // impossible
334            throw new RuntimeException(cnfe); //NOSONAR
335        }
336    }
337}