1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.util.zip;
19
20import java.io.EOFException;
21import java.io.IOException;
22import java.io.InputStream;
23import java.io.RandomAccessFile;
24import java.io.UnsupportedEncodingException;
25import java.util.Calendar;
26import java.util.Date;
27import java.util.GregorianCalendar;
28
29/**
30 * An instance of {@code ZipEntry} represents an entry within a <i>ZIP-archive</i>.
31 * An entry has attributes such as name (= path) or the size of its data. While
32 * an entry identifies data stored in an archive, it does not hold the data
33 * itself. For example when reading a <i>ZIP-file</i> you will first retrieve
34 * all its entries in a collection and then read the data for a specific entry
35 * through an input stream.
36 *
37 * @see ZipFile
38 * @see ZipOutputStream
39 */
40public class ZipEntry implements ZipConstants, Cloneable {
41    String name, comment;
42
43    long compressedSize = -1, crc = -1, size = -1;
44
45    int compressionMethod = -1, time = -1, modDate = -1;
46
47    byte[] extra;
48
49    int nameLen = -1;
50    long mLocalHeaderRelOffset = -1;
51
52    /**
53     * Zip entry state: Deflated.
54     */
55    public static final int DEFLATED = 8;
56
57    /**
58     * Zip entry state: Stored.
59     */
60    public static final int STORED = 0;
61
62    /**
63     * Constructs a new {@code ZipEntry} with the specified name.
64     *
65     * @param name
66     *            the name of the ZIP entry.
67     * @throws IllegalArgumentException
68     *             if the name length is outside the range (> 0xFFFF).
69     */
70    public ZipEntry(String name) {
71        if (name == null) {
72            throw new NullPointerException();
73        }
74        if (name.length() > 0xFFFF) {
75            throw new IllegalArgumentException();
76        }
77        this.name = name;
78    }
79
80    /**
81     * Gets the comment for this {@code ZipEntry}.
82     *
83     * @return the comment for this {@code ZipEntry}, or {@code null} if there
84     *         is no comment. If we're reading an archive with
85     *         {@code ZipInputStream} the comment is not available.
86     */
87    public String getComment() {
88        return comment;
89    }
90
91    /**
92     * Gets the compressed size of this {@code ZipEntry}.
93     *
94     * @return the compressed size, or -1 if the compressed size has not been
95     *         set.
96     */
97    public long getCompressedSize() {
98        return compressedSize;
99    }
100
101    /**
102     * Gets the checksum for this {@code ZipEntry}.
103     *
104     * @return the checksum, or -1 if the checksum has not been set.
105     */
106    public long getCrc() {
107        return crc;
108    }
109
110    /**
111     * Gets the extra information for this {@code ZipEntry}.
112     *
113     * @return a byte array containing the extra information, or {@code null} if
114     *         there is none.
115     */
116    public byte[] getExtra() {
117        return extra;
118    }
119
120    /**
121     * Gets the compression method for this {@code ZipEntry}.
122     *
123     * @return the compression method, either {@code DEFLATED}, {@code STORED}
124     *         or -1 if the compression method has not been set.
125     */
126    public int getMethod() {
127        return compressionMethod;
128    }
129
130    /**
131     * Gets the name of this {@code ZipEntry}.
132     *
133     * @return the entry name.
134     */
135    public String getName() {
136        return name;
137    }
138
139    /**
140     * Gets the uncompressed size of this {@code ZipEntry}.
141     *
142     * @return the uncompressed size, or {@code -1} if the size has not been
143     *         set.
144     */
145    public long getSize() {
146        return size;
147    }
148
149    /**
150     * Gets the last modification time of this {@code ZipEntry}.
151     *
152     * @return the last modification time as the number of milliseconds since
153     *         Jan. 1, 1970.
154     */
155    public long getTime() {
156        if (time != -1) {
157            GregorianCalendar cal = new GregorianCalendar();
158            cal.set(Calendar.MILLISECOND, 0);
159            cal.set(1980 + ((modDate >> 9) & 0x7f), ((modDate >> 5) & 0xf) - 1,
160                    modDate & 0x1f, (time >> 11) & 0x1f, (time >> 5) & 0x3f,
161                    (time & 0x1f) << 1);
162            return cal.getTime().getTime();
163        }
164        return -1;
165    }
166
167    /**
168     * Determine whether or not this {@code ZipEntry} is a directory.
169     *
170     * @return {@code true} when this {@code ZipEntry} is a directory, {@code
171     *         false} otherwise.
172     */
173    public boolean isDirectory() {
174        return name.charAt(name.length() - 1) == '/';
175    }
176
177    /**
178     * Sets the comment for this {@code ZipEntry}.
179     *
180     * @param string
181     *            the comment for this entry.
182     */
183    public void setComment(String string) {
184        if (string == null || string.length() <= 0xFFFF) {
185            comment = string;
186        } else {
187            throw new IllegalArgumentException();
188        }
189    }
190
191    /**
192     * Sets the compressed size for this {@code ZipEntry}.
193     *
194     * @param value
195     *            the compressed size (in bytes).
196     */
197    public void setCompressedSize(long value) {
198        compressedSize = value;
199    }
200
201    /**
202     * Sets the checksum for this {@code ZipEntry}.
203     *
204     * @param value
205     *            the checksum for this entry.
206     * @throws IllegalArgumentException
207     *             if {@code value} is < 0 or > 0xFFFFFFFFL.
208     */
209    public void setCrc(long value) {
210        if (value >= 0 && value <= 0xFFFFFFFFL) {
211            crc = value;
212        } else {
213            throw new IllegalArgumentException();
214        }
215    }
216
217    /**
218     * Sets the extra information for this {@code ZipEntry}.
219     *
220     * @param data
221     *            a byte array containing the extra information.
222     * @throws IllegalArgumentException
223     *             when the length of data is greater than 0xFFFF bytes.
224     */
225    public void setExtra(byte[] data) {
226        if (data == null || data.length <= 0xFFFF) {
227            extra = data;
228        } else {
229            throw new IllegalArgumentException();
230        }
231    }
232
233    /**
234     * Sets the compression method for this {@code ZipEntry}.
235     *
236     * @param value
237     *            the compression method, either {@code DEFLATED} or {@code
238     *            STORED}.
239     * @throws IllegalArgumentException
240     *             when value is not {@code DEFLATED} or {@code STORED}.
241     */
242    public void setMethod(int value) {
243        if (value != STORED && value != DEFLATED) {
244            throw new IllegalArgumentException();
245        }
246        compressionMethod = value;
247    }
248
249    /**
250     * Sets the uncompressed size of this {@code ZipEntry}.
251     *
252     * @param value
253     *            the uncompressed size for this entry.
254     * @throws IllegalArgumentException
255     *             if {@code value} < 0 or {@code value} > 0xFFFFFFFFL.
256     */
257    public void setSize(long value) {
258        if (value >= 0 && value <= 0xFFFFFFFFL) {
259            size = value;
260        } else {
261            throw new IllegalArgumentException();
262        }
263    }
264
265    /**
266     * Sets the modification time of this {@code ZipEntry}.
267     *
268     * @param value
269     *            the modification time as the number of milliseconds since Jan.
270     *            1, 1970.
271     */
272    public void setTime(long value) {
273        GregorianCalendar cal = new GregorianCalendar();
274        cal.setTime(new Date(value));
275        int year = cal.get(Calendar.YEAR);
276        if (year < 1980) {
277            modDate = 0x21;
278            time = 0;
279        } else {
280            modDate = cal.get(Calendar.DATE);
281            modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate;
282            modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate;
283            time = cal.get(Calendar.SECOND) >> 1;
284            time = (cal.get(Calendar.MINUTE) << 5) | time;
285            time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time;
286        }
287    }
288
289    /**
290     * Returns the string representation of this {@code ZipEntry}.
291     *
292     * @return the string representation of this {@code ZipEntry}.
293     */
294    @Override
295    public String toString() {
296        return name;
297    }
298
299    /**
300     * Constructs a new {@code ZipEntry} using the values obtained from {@code
301     * ze}.
302     *
303     * @param ze
304     *            the {@code ZipEntry} from which to obtain values.
305     */
306    public ZipEntry(ZipEntry ze) {
307        name = ze.name;
308        comment = ze.comment;
309        time = ze.time;
310        size = ze.size;
311        compressedSize = ze.compressedSize;
312        crc = ze.crc;
313        compressionMethod = ze.compressionMethod;
314        modDate = ze.modDate;
315        extra = ze.extra;
316        nameLen = ze.nameLen;
317        mLocalHeaderRelOffset = ze.mLocalHeaderRelOffset;
318    }
319
320    /**
321     * Returns a shallow copy of this entry.
322     *
323     * @return a copy of this entry.
324     */
325    @Override
326    public Object clone() {
327        return new ZipEntry(this);
328    }
329
330    /**
331     * Returns the hash code for this {@code ZipEntry}.
332     *
333     * @return the hash code of the entry.
334     */
335    @Override
336    public int hashCode() {
337        return name.hashCode();
338    }
339
340    /*
341     * Internal constructor.  Creates a new ZipEntry by reading the
342     * Central Directory Entry from "in", which must be positioned at
343     * the CDE signature.
344     *
345     * On exit, "in" will be positioned at the start of the next entry.
346     */
347    ZipEntry(LittleEndianReader ler, InputStream in) throws IOException {
348
349        /*
350         * We're seeing performance issues when we call readShortLE and
351         * readIntLE, so we're going to read the entire header at once
352         * and then parse the results out without using any function calls.
353         * Uglier, but should be much faster.
354         *
355         * Note that some lines look a bit different, because the corresponding
356         * fields or locals are long and so we need to do & 0xffffffffl to avoid
357         * problems induced by sign extension.
358         */
359
360        byte[] hdrBuf = ler.hdrBuf;
361        myReadFully(in, hdrBuf);
362
363        long sig = (hdrBuf[0] & 0xff) | ((hdrBuf[1] & 0xff) << 8) |
364            ((hdrBuf[2] & 0xff) << 16) | ((hdrBuf[3] << 24) & 0xffffffffL);
365        if (sig != CENSIG) {
366             throw new ZipException("Central Directory Entry not found");
367        }
368
369        compressionMethod = (hdrBuf[10] & 0xff) | ((hdrBuf[11] & 0xff) << 8);
370        time = (hdrBuf[12] & 0xff) | ((hdrBuf[13] & 0xff) << 8);
371        modDate = (hdrBuf[14] & 0xff) | ((hdrBuf[15] & 0xff) << 8);
372        crc = (hdrBuf[16] & 0xff) | ((hdrBuf[17] & 0xff) << 8)
373                | ((hdrBuf[18] & 0xff) << 16)
374                | ((hdrBuf[19] << 24) & 0xffffffffL);
375        compressedSize = (hdrBuf[20] & 0xff) | ((hdrBuf[21] & 0xff) << 8)
376                | ((hdrBuf[22] & 0xff) << 16)
377                | ((hdrBuf[23] << 24) & 0xffffffffL);
378        size = (hdrBuf[24] & 0xff) | ((hdrBuf[25] & 0xff) << 8)
379                | ((hdrBuf[26] & 0xff) << 16)
380                | ((hdrBuf[27] << 24) & 0xffffffffL);
381        nameLen = (hdrBuf[28] & 0xff) | ((hdrBuf[29] & 0xff) << 8);
382        int extraLen = (hdrBuf[30] & 0xff) | ((hdrBuf[31] & 0xff) << 8);
383        int commentLen = (hdrBuf[32] & 0xff) | ((hdrBuf[33] & 0xff) << 8);
384        mLocalHeaderRelOffset = (hdrBuf[42] & 0xff) | ((hdrBuf[43] & 0xff) << 8)
385                | ((hdrBuf[44] & 0xff) << 16)
386                | ((hdrBuf[45] << 24) & 0xffffffffL);
387
388        byte[] nameBytes = new byte[nameLen];
389        myReadFully(in, nameBytes);
390
391        byte[] commentBytes = null;
392        if (commentLen > 0) {
393            commentBytes = new byte[commentLen];
394            myReadFully(in, commentBytes);
395        }
396
397        if (extraLen > 0) {
398            extra = new byte[extraLen];
399            myReadFully(in, extra);
400        }
401
402        try {
403            /*
404             * The actual character set is "IBM Code Page 437".  As of
405             * Sep 2006, the Zip spec (APPNOTE.TXT) supports UTF-8.  When
406             * bit 11 of the GP flags field is set, the file name and
407             * comment fields are UTF-8.
408             *
409             * TODO: add correct UTF-8 support.
410             */
411            name = new String(nameBytes, "ISO-8859-1");
412            if (commentBytes != null) {
413                comment = new String(commentBytes, "ISO-8859-1");
414            } else {
415                comment = null;
416            }
417        } catch (UnsupportedEncodingException uee) {
418            throw new InternalError(uee.getMessage());
419        }
420    }
421
422    private void myReadFully(InputStream in, byte[] b) throws IOException {
423        int len = b.length;
424        int off = 0;
425
426        while (len > 0) {
427            int count = in.read(b, off, len);
428            if (count <= 0) {
429                throw new EOFException();
430            }
431            off += count;
432            len -= count;
433        }
434    }
435
436    /*
437     * Read a four-byte int in little-endian order.
438     */
439    static long readIntLE(RandomAccessFile raf) throws IOException {
440        int b0 = raf.read();
441        int b1 = raf.read();
442        int b2 = raf.read();
443        int b3 = raf.read();
444
445        if (b3 < 0) {
446            throw new EOFException("in ZipEntry.readIntLE(RandomAccessFile)");
447        }
448        return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); // ATTENTION: DOES SIGN EXTENSION: IS THIS WANTED?
449    }
450
451    static class LittleEndianReader {
452        private byte[] b = new byte[4];
453        byte[] hdrBuf = new byte[CENHDR];
454
455        /*
456         * Read a two-byte short in little-endian order.
457         */
458        int readShortLE(InputStream in) throws IOException {
459            if (in.read(b, 0, 2) == 2) {
460                return (b[0] & 0XFF) | ((b[1] & 0XFF) << 8);
461            } else {
462                throw new EOFException("in ZipEntry.readShortLE(InputStream)");
463            }
464        }
465
466        /*
467         * Read a four-byte int in little-endian order.
468         */
469        long readIntLE(InputStream in) throws IOException {
470            if (in.read(b, 0, 4) == 4) {
471                return (   ((b[0] & 0XFF))
472                         | ((b[1] & 0XFF) << 8)
473                         | ((b[2] & 0XFF) << 16)
474                         | ((b[3] & 0XFF) << 24))
475                       & 0XFFFFFFFFL; // Here for sure NO sign extension is wanted.
476            } else {
477                throw new EOFException("in ZipEntry.readIntLE(InputStream)");
478            }
479        }
480    }
481}
482