1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.java.util.zip;
18
19import java.io.BufferedOutputStream;
20import java.io.File;
21import java.io.FileInputStream;
22import java.io.FileOutputStream;
23import java.io.IOException;
24import java.time.LocalDate;
25import java.time.ZoneId;
26import java.time.temporal.ChronoUnit;
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.List;
30import java.util.jar.JarEntry;
31import java.util.zip.ZipEntry;
32import java.util.zip.ZipFile;
33import java.util.zip.ZipInputStream;
34import java.util.zip.ZipOutputStream;
35
36public class ZipEntryTest extends junit.framework.TestCase {
37  // The zip format differentiates between times before and after 1/1/1980. A timestamp before 1980
38  // will produce a different zip binary. ZipOutputStream.putNextEntry defaults the entry times to
39  // the current system clock value. This time can be used explicitly to ensure the behavior of most
40  // tests is independent of the system clock.
41  private static final long ENTRY_TIME = 1262304000000L; //  January 1, 2010 12:00:00 AM GMT
42
43  private static ZipOutputStream createZipOutputStream(File f) throws IOException {
44    return new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
45  }
46
47  private static String makeString(int count, String s) {
48    StringBuilder sb = new StringBuilder();
49    for (int i = 0; i < count; ++i) {
50      sb.append(s);
51    }
52    return sb.toString();
53  }
54
55  private List<File> temporaryFiles = new ArrayList<>();
56
57  private File createTemporaryZipFile() throws IOException {
58    File result = File.createTempFile("ZipFileTest", "zip");
59    temporaryFiles.add(result);
60    return result;
61  }
62
63  @Override
64  public void tearDown() throws Exception {
65    for (File file : temporaryFiles) {
66      file.delete();
67    }
68    temporaryFiles.clear();
69    super.tearDown();
70  }
71
72  // http://code.google.com/p/android/issues/detail?id=4690
73  public void test_utf8FileNames() throws Exception {
74    // Create a zip file containing non-ASCII filenames.
75    File f = File.createTempFile("your", "mum");
76    List<String> filenames = Arrays.asList("us-ascii",
77                                           "\u043c\u0430\u0440\u0442\u0430", // russian
78                                           "\u1f00\u03c0\u1f78", // greek
79                                           "\u30b3\u30f3\u30cb\u30c1\u30cf"); // japanese
80    ZipOutputStream out = new ZipOutputStream(new FileOutputStream(f));
81    for (String filename : filenames) {
82      out.putNextEntry(new ZipEntry(filename));
83      out.closeEntry(); // Empty files are fine.
84    }
85    out.close();
86    // Read it back, and check we find all those names.
87    // This failed when we were mangling the encoding.
88    ZipFile zipFile = new ZipFile(f);
89    for (String filename : filenames) {
90      assertNotNull(filename, zipFile.getEntry(filename));
91    }
92    // Check that ZipInputStream works too.
93    ZipInputStream in = new ZipInputStream(new FileInputStream(f));
94    ZipEntry entry;
95    int entryCount = 0;
96    while ((entry = in.getNextEntry()) != null) {
97      assertTrue(entry.getName(), filenames.contains(entry.getName()));
98      ++entryCount;
99    }
100    assertEquals(filenames.size(), entryCount);
101    in.close();
102  }
103
104  // http://b/2099615
105  public void testClone() {
106    byte[] extra = { 5, 7, 9 };
107    JarEntry jarEntry = new JarEntry("foo");
108    jarEntry.setExtra(extra);
109    assertSame("Expected no defensive copy of extra", extra, jarEntry.getExtra());
110
111    ZipEntry clone = (ZipEntry) jarEntry.clone();
112    assertEquals(JarEntry.class, clone.getClass());
113    assertNotSame(extra, clone.getExtra());
114  }
115
116  public void testTooLongName() throws Exception {
117    String tooLongName = makeString(65536, "z");
118    try {
119      new ZipEntry(tooLongName);
120      fail();
121    } catch (IllegalArgumentException expected) {
122    }
123  }
124
125  public void testMaxLengthName() throws Exception {
126    String maxLengthName = makeString(65535, "z");
127
128    File f = createTemporaryZipFile();
129    ZipOutputStream out = createZipOutputStream(f);
130    out.putNextEntry(new ZipEntry(maxLengthName));
131    out.closeEntry();
132    out.close();
133
134    // Read it back, and check that we see the entry.
135    ZipFile zipFile = new ZipFile(f);
136    assertNotNull(zipFile.getEntry(maxLengthName));
137    zipFile.close();
138  }
139
140  public void testTooLongExtra() throws Exception {
141    byte[] tooLongExtra = new byte[65536];
142    ZipEntry ze = new ZipEntry("x");
143    try {
144      ze.setExtra(tooLongExtra);
145      fail();
146    } catch (IllegalArgumentException expected) {
147    }
148  }
149
150  public void testMaxLengthExtra() throws Exception {
151    byte[] maxLengthExtra = new byte[65535];
152
153    File f = createTemporaryZipFile();
154    ZipOutputStream out = createZipOutputStream(f);
155    ZipEntry ze = new ZipEntry("x");
156    ze.setSize(0);
157    ze.setTime(ENTRY_TIME);
158    ze.setExtra(maxLengthExtra);
159    out.putNextEntry(ze);
160    out.closeEntry();
161    out.close();
162
163    // Read it back, and check that we see the entry.
164    ZipFile zipFile = new ZipFile(f);
165    assertEquals(maxLengthExtra.length, zipFile.getEntry("x").getExtra().length);
166    zipFile.close();
167  }
168
169  public void testSetTime() throws Exception {
170    // Set a time before the lower bound of dos time, year 1980
171    checkSetTime(0L); // January 1, 1970 12:00:00 AM GMT
172    checkSetTime(31536000000L); // January 1, 1971 12:00:00 AM GMT
173    checkSetTime(315187200000L); // December 28, 1979 12:00:00 AM GMT
174    // December 31, 1979 11:59:59 AM Local time
175    checkSetTime(LocalDate.of(1980, 1, 1).atStartOfDay().minus(1, ChronoUnit.SECONDS)
176                    .atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
177
178    // January 1, 1980 12:00:00 AM Local time
179    checkSetTime(LocalDate.of(1980, 1, 1).atStartOfDay().atZone(ZoneId.systemDefault())
180            .toInstant().toEpochMilli());
181    // Set a time after the lower bound of dos time, year 1980
182    checkSetTime(315705600000L); // January 3, 1980 12:00:00 AM GMT
183    checkSetTime(ENTRY_TIME); // January 1, 2010 12:00:00 AM
184
185    // Set a time after upper bound of dos time.
186    checkSetTime(4134153600000L); // January 3, 2101 12:00:00 AM GMT
187  }
188
189  private void checkSetTime(long time) throws IOException {
190    File f = createTemporaryZipFile();
191    ZipOutputStream out = createZipOutputStream(f);
192    ZipEntry ze = new ZipEntry("x");
193    ze.setSize(0);
194    ze.setTime(time);
195    out.putNextEntry(ze);
196    out.closeEntry();
197    out.close();
198
199    // Read it back, and check that we see the entry.
200    ZipFile zipFile = new ZipFile(f);
201    assertEquals(time, zipFile.getEntry("x").getTime());
202    zipFile.close();
203  }
204
205  // TODO: This test does not compile because we need to add a ZipOutputStream constructor
206  // that forces zip64. This also needs followup changes in ZipInputStream et al. to assume zip64
207  // if the header says so, and to not depend purely on the entry length.
208  //
209  // public void testMaxLengthExtra_zip64() throws Exception {
210  //   // Not quite the max length (65535), but large enough that there's no space
211  //   // for the zip64 extended info header.
212  //   byte[] maxLengthExtra = new byte[65530];
213  //
214  //   File f = createTemporaryZipFile();
215  //   ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f)),
216  //           true /* forceZip64 */);
217  //   ZipEntry ze = new ZipEntry("x");
218  //
219  //   ze.setExtra(maxLengthExtra);
220  //   try {
221  //     out.putNextEntry(ze);
222  //     fail();
223  //   } catch (ZipException expected) {
224  //   }
225  // }
226
227
228  public void testTooLongComment() throws Exception {
229    String tooLongComment = makeString(65536, "z");
230    ZipEntry ze = new ZipEntry("x");
231    try {
232      ze.setComment(tooLongComment);
233      fail();
234    } catch (IllegalArgumentException expected) {
235    }
236  }
237
238  public void testMaxLengthComment() throws Exception {
239    String maxLengthComment = makeString(65535, "z");
240
241    File f = createTemporaryZipFile();
242    ZipOutputStream out = createZipOutputStream(f);
243    ZipEntry ze = new ZipEntry("x");
244    ze.setComment(maxLengthComment);
245    out.putNextEntry(ze);
246    out.closeEntry();
247    out.close();
248
249    // Read it back, and check that we see the entry.
250    ZipFile zipFile = new ZipFile(f);
251    assertEquals(maxLengthComment, zipFile.getEntry("x").getComment());
252    zipFile.close();
253  }
254
255  public void testCommentAndExtraInSameOrder() throws Exception {
256    String comment = makeString(17, "z");
257    byte[] extra = makeString(11, "a").getBytes();
258
259    File f = createTemporaryZipFile();
260    ZipOutputStream out = createZipOutputStream(f);
261
262    // Regular (non zip64) format.
263    ZipEntry ze = new ZipEntry("x");
264    ze.setSize(0);
265    ze.setTime(ENTRY_TIME);
266    ze.setExtra(extra);
267    ze.setComment(comment);
268    out.putNextEntry(ze);
269    out.closeEntry();
270
271    // An entry without a length is assumed to be zip64.
272    ze = new ZipEntry("y");
273    ze.setTime(ENTRY_TIME);
274    ze.setExtra(extra);
275    ze.setComment(comment);
276    out.putNextEntry(ze);
277    out.closeEntry();
278    out.close();
279
280    // Read it back and make sure comments and extra are in the right order
281    ZipFile zipFile = new ZipFile(f);
282    try {
283      assertEquals(comment, zipFile.getEntry("x").getComment());
284      assertTrue(Arrays.equals(extra, zipFile.getEntry("x").getExtra()));
285
286      assertEquals(comment, zipFile.getEntry("y").getComment());
287      assertTrue(Arrays.equals(extra, zipFile.getEntry("y").getExtra()));
288    } finally {
289      zipFile.close();
290    }
291  }
292}
293