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