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 */
17package org.apache.harmony.tests.java.util.zip;
18
19import java.io.ByteArrayInputStream;
20import java.io.ByteArrayOutputStream;
21import java.io.File;
22import java.io.FileOutputStream;
23import java.io.IOException;
24import java.lang.reflect.Field;
25import java.nio.file.attribute.FileTime;
26import java.util.ArrayList;
27import java.util.List;
28import java.util.zip.CRC32;
29import java.util.zip.ZipEntry;
30import java.util.zip.ZipException;
31import java.util.zip.ZipInputStream;
32import java.util.zip.ZipOutputStream;
33import libcore.junit.junit3.TestCaseWithRules;
34import libcore.junit.util.ResourceLeakageDetector.DisableResourceLeakageDetection;
35import libcore.junit.util.ResourceLeakageDetector;
36import org.junit.Rule;
37import org.junit.rules.TestRule;
38
39public class ZipOutputStreamTest extends TestCaseWithRules {
40    @Rule
41    public TestRule guardRule = ResourceLeakageDetector.getRule();
42
43    ZipOutputStream zos;
44
45    ByteArrayOutputStream bos;
46
47    ZipInputStream zis;
48
49    static final String data = "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld";
50
51    /**
52     * java.util.zip.ZipOutputStream#close()
53     */
54    public void test_close() throws Exception {
55        zos.putNextEntry(new ZipEntry("XX"));
56        zos.closeEntry();
57        zos.close();
58
59        // Regression for HARMONY-97
60        ZipOutputStream zos = new ZipOutputStream(new ByteArrayOutputStream());
61        zos.putNextEntry(new ZipEntry("myFile"));
62        zos.close();
63        zos.close(); // Should be a no-op
64    }
65
66    /**
67     * java.util.zip.ZipOutputStream#closeEntry()
68     */
69    public void test_closeEntry() throws IOException {
70        ZipEntry ze = new ZipEntry("testEntry");
71        ze.setTime(System.currentTimeMillis());
72        zos.putNextEntry(ze);
73        zos.write("Hello World".getBytes("UTF-8"));
74        zos.closeEntry();
75        assertTrue("closeEntry failed to update required fields",
76                ze.getSize() == 11 && ze.getCompressedSize() == 13);
77
78    }
79
80    /**
81     * java.util.zip.ZipOutputStream#finish()
82     */
83    public void test_finish() throws Exception {
84        ZipEntry ze = new ZipEntry("test");
85        zos.putNextEntry(ze);
86        zos.write("Hello World".getBytes());
87        zos.finish();
88        assertEquals("Finish failed to closeCurrentEntry", 11, ze.getSize());
89
90        ZipOutputStream zos = new ZipOutputStream(new ByteArrayOutputStream());
91        zos.putNextEntry(new ZipEntry("myFile"));
92        zos.finish();
93        zos.close();
94        try {
95            zos.finish();
96            fail("Assert 0: Expected IOException");
97        } catch (IOException e) {
98            // Expected
99        }
100    }
101
102    /**
103     * java.util.zip.ZipOutputStream#putNextEntry(java.util.zip.ZipEntry)
104     */
105    public void test_putNextEntryLjava_util_zip_ZipEntry() throws IOException {
106        ZipEntry ze = new ZipEntry("testEntry");
107        ze.setTime(System.currentTimeMillis());
108        zos.putNextEntry(ze);
109        zos.write("Hello World".getBytes());
110        zos.closeEntry();
111        zos.close();
112        zis = new ZipInputStream(new ByteArrayInputStream(bos.toByteArray()));
113        ZipEntry ze2 = zis.getNextEntry();
114        zis.closeEntry();
115        assertEquals("Failed to write correct entry", ze.getName(), ze2.getName());
116        assertEquals("Failed to write correct entry", ze.getCrc(), ze2.getCrc());
117        try {
118            zos.putNextEntry(ze);
119            fail("Entry with incorrect setting failed to throw exception");
120        } catch (IOException e) {
121            // expected
122        }
123    }
124
125    /**
126     * java.util.zip.ZipOutputStream#setComment(java.lang.String)
127     */
128    @DisableResourceLeakageDetection(
129            why = "InflaterOutputStream.close() does not work properly if finish() throws an"
130                    + " exception; finish() throws an exception if the output is invalid; this is"
131                    + " an issue with the ZipOutputStream created in setUp()",
132            bug = "31797037")
133    public void test_setCommentLjava_lang_String() {
134        // There is no way to get the comment back, so no way to determine if
135        // the comment is set correct
136        zos.setComment("test setComment");
137
138        try {
139            zos.setComment(new String(new byte[0xFFFF + 1]));
140            fail("Comment over 0xFFFF in length should throw exception");
141        } catch (IllegalArgumentException e) {
142            // Passed
143        }
144    }
145
146    /**
147     * java.util.zip.ZipOutputStream#setLevel(int)
148     */
149    public void test_setLevelI() throws IOException {
150        ZipEntry ze = new ZipEntry("test");
151        zos.putNextEntry(ze);
152        zos.write(data.getBytes());
153        zos.closeEntry();
154        long csize = ze.getCompressedSize();
155        zos.setLevel(9); // Max Compression
156        zos.putNextEntry(ze = new ZipEntry("test2"));
157        zos.write(data.getBytes());
158        zos.closeEntry();
159        assertTrue("setLevel failed", csize <= ze.getCompressedSize());
160    }
161
162    /**
163     * java.util.zip.ZipOutputStream#setMethod(int)
164     */
165    public void test_setMethodI() throws IOException {
166        ZipEntry ze = new ZipEntry("test");
167        zos.setMethod(ZipOutputStream.STORED);
168        CRC32 tempCrc = new CRC32();
169        tempCrc.update(data.getBytes());
170        ze.setCrc(tempCrc.getValue());
171        ze.setSize(new String(data).length());
172        zos.putNextEntry(ze);
173        zos.write(data.getBytes());
174        zos.closeEntry();
175        long csize = ze.getCompressedSize();
176        zos.setMethod(ZipOutputStream.DEFLATED);
177        zos.putNextEntry(ze = new ZipEntry("test2"));
178        zos.write(data.getBytes());
179        zos.closeEntry();
180        assertTrue("setLevel failed", csize >= ze.getCompressedSize());
181    }
182
183    /**
184     * java.util.zip.ZipOutputStream#write(byte[], int, int)
185     */
186    @DisableResourceLeakageDetection(
187            why = "InflaterOutputStream.close() does not work properly if finish() throws an"
188                    + " exception; finish() throws an exception if the output is invalid.",
189            bug = "31797037")
190    public void test_write$BII() throws IOException {
191        ZipEntry ze = new ZipEntry("test");
192        zos.putNextEntry(ze);
193        zos.write(data.getBytes());
194        zos.closeEntry();
195        zos.close();
196        zos = null;
197        zis = new ZipInputStream(new ByteArrayInputStream(bos.toByteArray()));
198        zis.getNextEntry();
199        byte[] b = new byte[data.length()];
200        int r = 0;
201        int count = 0;
202        while (count != b.length && (r = zis.read(b, count, b.length)) != -1) {
203            count += r;
204        }
205        zis.closeEntry();
206        assertEquals("Write failed to write correct bytes", new String(b), data);
207
208        File f = File.createTempFile("testZip", "tst");
209        f.deleteOnExit();
210        FileOutputStream stream = new FileOutputStream(f);
211        ZipOutputStream zip = new ZipOutputStream(stream);
212        zip.setMethod(ZipEntry.STORED);
213
214        try {
215            zip.putNextEntry(new ZipEntry("Second"));
216            fail("Not set an entry. Should have thrown ZipException.");
217        } catch (ZipException e) {
218            // expected -- We have not set an entry
219        }
220
221        try {
222            // We try to write data without entry
223            zip.write(new byte[2]);
224            fail("Writing data without an entry. Should have thrown IOException");
225        } catch (IOException e) {
226            // expected
227        }
228
229        try {
230            // Try to write without an entry and with nonsense offset and
231            // length
232            zip.write(new byte[2], 0, 12);
233            fail("Writing data without an entry. Should have thrown IndexOutOfBoundsException");
234        } catch (IndexOutOfBoundsException e) {
235            // expected
236        }
237
238        // Regression for HARMONY-4405
239        try {
240            zip.write(null, 0, -2);
241            fail();
242        } catch (NullPointerException expected) {
243        } catch (IndexOutOfBoundsException expected) {
244        }
245        try {
246            zip.write(null, 0, 2);
247            fail();
248        } catch (NullPointerException expected) {
249        }
250        try {
251            zip.write(new byte[2], 0, -2);
252            fail();
253        } catch (IndexOutOfBoundsException expected) {
254        }
255
256        // Close stream because ZIP is invalid
257        stream.close();
258    }
259
260    /**
261     * java.util.zip.ZipOutputStream#write(byte[], int, int)
262     */
263    @DisableResourceLeakageDetection(
264            why = "InflaterOutputStream.close() does not work properly if finish() throws an"
265                    + " exception; finish() throws an exception if the output is invalid; this is"
266                    + " an issue with the ZipOutputStream created in setUp()",
267            bug = "31797037")
268    public void test_write$BII_2() throws IOException {
269        // Regression for HARMONY-577
270        File f1 = File.createTempFile("testZip1", "tst");
271        f1.deleteOnExit();
272        FileOutputStream stream1 = new FileOutputStream(f1);
273        ZipOutputStream zip1 = new ZipOutputStream(stream1);
274        zip1.putNextEntry(new ZipEntry("one"));
275        zip1.setMethod(ZipOutputStream.STORED);
276        zip1.setMethod(ZipEntry.STORED);
277
278        zip1.write(new byte[2]);
279
280        try {
281            zip1.putNextEntry(new ZipEntry("Second"));
282            fail("ZipException expected");
283        } catch (ZipException e) {
284            // expected - We have not set an entry
285        }
286
287        try {
288            zip1.write(new byte[2]); // try to write data without entry
289            fail("expected IOE there");
290        } catch (IOException e2) {
291            // expected
292        }
293
294        zip1.close();
295    }
296    /**
297     * Test standard and info-zip-extended timestamp rounding
298     */
299    public void test_timeSerializationRounding() throws Exception {
300        List<ZipEntry> entries = new ArrayList<>();
301        ZipEntry zipEntry;
302
303        entries.add(zipEntry = new ZipEntry("test1"));
304        final long someTimestamp = 1479139143200L;
305        zipEntry.setTime(someTimestamp);
306
307        entries.add(zipEntry = new ZipEntry("test2"));
308        zipEntry.setLastModifiedTime(FileTime.fromMillis(someTimestamp));
309
310        for (ZipEntry entry : entries) {
311            zos.putNextEntry(entry);
312            zos.write(data.getBytes());
313            zos.closeEntry();
314        }
315        zos.close();
316
317        try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
318           // getTime should be rounded down to a multiple of 2s
319            ZipEntry readEntry = zis.getNextEntry();
320            assertEquals((someTimestamp / 2000) * 2000,
321                         readEntry.getTime());
322
323            // With extended timestamp getTime&getLastModifiedTime should berounded down to a
324            // multiple of 1s
325            readEntry = zis.getNextEntry();
326            assertEquals((someTimestamp / 1000) * 1000,
327                         readEntry.getLastModifiedTime().toMillis());
328            assertEquals((someTimestamp / 1000) * 1000,
329                         readEntry.getTime());
330        }
331    }
332
333    /**
334     * Test info-zip extended timestamp support
335     */
336    public void test_exttSupport() throws Exception {
337        List<ZipEntry> entries = new ArrayList<>();
338
339        ZipEntry zipEntry;
340
341        // There's no sane way to access ONLY mtime
342        Field mtimeField = ZipEntry.class.getDeclaredField("mtime");
343        mtimeField.setAccessible(true);
344
345        // Serialized DOS timestamp resolution is 2s. Serialized extended
346        // timestamp resolution is 1s. If we won't use rounded values then
347        // asserting time equality would be more complicated (resolution of
348        // getTime depends weather we use extended timestamp).
349        //
350        // We have to call setTime on all entries. If it's not set then
351        // ZipOutputStream will call setTime(System.currentTimeMillis()) on it.
352        // I will use this as a excuse to test whether setting particular time
353        // values (~< 1980 ~> 2099) triggers use of the extended last-modified
354        // timestamp.
355        final long timestampWithinDostimeBound = ZipEntry.UPPER_DOSTIME_BOUND;
356        assertEquals(0, timestampWithinDostimeBound % 1000);
357        final long timestampBeyondDostimeBound = ZipEntry.UPPER_DOSTIME_BOUND + 2000;
358        assertEquals(0, timestampBeyondDostimeBound % 1000);
359
360        // This will set both dos timestamp and last-modified timestamp (because < 1980)
361        entries.add(zipEntry = new ZipEntry("test_setTime"));
362        zipEntry.setTime(0);
363        assertNotNull(mtimeField.get(zipEntry));
364
365        // Explicitly set info-zip last-modified extended timestamp
366        entries.add(zipEntry = new ZipEntry("test_setLastModifiedTime"));
367        zipEntry.setLastModifiedTime(FileTime.fromMillis(1000));
368
369        // Set creation time and (since we have to call setTime on ZipEntry, otherwise
370        // ZipOutputStream will call setTime(System.currentTimeMillis()) and the getTime()
371        // assert will fail due to low serialization resolution) test that calling
372        // setTime with value <= ZipEntry.UPPER_DOSTIME_BOUND won't set the info-zip
373        // last-modified extended timestamp.
374        entries.add(zipEntry = new ZipEntry("test_setCreationTime"));
375        zipEntry.setCreationTime(FileTime.fromMillis(1000));
376        zipEntry.setTime(timestampWithinDostimeBound);
377        assertNull(mtimeField.get(zipEntry));
378
379        // Set last access time and test that calling setTime with value >
380        // ZipEntry.UPPER_DOSTIME_BOUND will set the info-zip last-modified extended
381        // timestamp
382        entries.add(zipEntry = new ZipEntry("test_setLastAccessTime"));
383        zipEntry.setLastAccessTime(FileTime.fromMillis(3000));
384        zipEntry.setTime(timestampBeyondDostimeBound);
385        assertNotNull(mtimeField.get(zipEntry));
386
387        for (ZipEntry entry : entries) {
388            zos.putNextEntry(entry);
389            zos.write(data.getBytes());
390            zos.closeEntry();
391        }
392        zos.close();
393
394        try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
395            for (ZipEntry entry : entries) {
396                ZipEntry readEntry = zis.getNextEntry();
397                assertEquals(entry.getName(), readEntry.getName());
398                assertEquals(entry.getName(), entry.getTime(), readEntry.getTime());
399                assertEquals(entry.getName(), entry.getLastModifiedTime(), readEntry.getLastModifiedTime());
400                assertEquals(entry.getLastAccessTime(), readEntry.getLastAccessTime());
401                assertEquals(entry.getCreationTime(), readEntry.getCreationTime());
402            }
403        }
404    }
405
406
407    @Override
408    protected void setUp() throws Exception {
409        super.setUp();
410        zos = new ZipOutputStream(bos = new ByteArrayOutputStream());
411    }
412
413    @Override
414    protected void tearDown() throws Exception {
415        try {
416            // Close the ZipInputStream first as that does not fail.
417            if (zis != null) {
418                zis.close();
419            }
420            if (zos != null) {
421                // This will throw a ZipException if nothing is written to the ZipOutputStream.
422                zos.close();
423            }
424        } catch (Exception e) {
425        }
426        super.tearDown();
427    }
428}
429