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 17 package libcore.java.util.zip; 18 19 import java.io.BufferedOutputStream; 20 import java.io.ByteArrayOutputStream; 21 import java.io.File; 22 import java.io.FileInputStream; 23 import java.io.FileOutputStream; 24 import java.io.FileWriter; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.util.Enumeration; 28 import java.util.Random; 29 import java.util.zip.CRC32; 30 import java.util.zip.ZipEntry; 31 import java.util.zip.ZipException; 32 import java.util.zip.ZipFile; 33 import java.util.zip.ZipInputStream; 34 import java.util.zip.ZipOutputStream; 35 import junit.framework.TestCase; 36 import libcore.io.IoUtils; 37 38 public final class ZipFileTest extends TestCase { 39 /** 40 * Exercise Inflater's ability to refill the zlib's input buffer. As of this 41 * writing, this buffer's max size is 64KiB compressed bytes. We'll write a 42 * full megabyte of uncompressed data, which should be sufficient to exhaust 43 * the buffer. http://b/issue?id=2734751 44 */ 45 public void testInflatingFilesRequiringZipRefill() throws IOException { 46 int originalSize = 1024 * 1024; 47 byte[] readBuffer = new byte[8192]; 48 ZipFile zipFile = new ZipFile(createZipFile(1, originalSize)); 49 for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { 50 ZipEntry zipEntry = e.nextElement(); 51 assertTrue("This test needs >64 KiB of compressed data to exercise Inflater", 52 zipEntry.getCompressedSize() > (64 * 1024)); 53 InputStream is = zipFile.getInputStream(zipEntry); 54 while (is.read(readBuffer, 0, readBuffer.length) != -1) {} 55 is.close(); 56 } 57 zipFile.close(); 58 } 59 60 private static void replaceBytes(byte[] original, byte[] replacement, byte[] buffer) { 61 // Gotcha here: original and replacement must be the same length 62 assertEquals(original.length, replacement.length); 63 boolean found; 64 for(int i=0; i < buffer.length - original.length; i++) { 65 found = false; 66 if (buffer[i] == original[0]) { 67 found = true; 68 for (int j=0; j < original.length; j++) { 69 if (buffer[i+j] != original[j]) { 70 found = false; 71 break; 72 } 73 } 74 } 75 if (found) { 76 for (int j=0; j < original.length; j++) { 77 buffer[i+j] = replacement[j]; 78 } 79 } 80 } 81 } 82 83 /** 84 * Make sure we don't fail silently for duplicate entries. 85 * b/8219321 86 */ 87 public void testDuplicateEntries() throws IOException { 88 String entryName = "test_file_name1"; 89 String tmpName = "test_file_name2"; 90 91 // create the template data 92 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); 93 ZipOutputStream out = new ZipOutputStream(bytesOut); 94 ZipEntry ze1 = new ZipEntry(tmpName); 95 out.putNextEntry(ze1); 96 out.closeEntry(); 97 ZipEntry ze2 = new ZipEntry(entryName); 98 out.putNextEntry(ze2); 99 out.closeEntry(); 100 out.close(); 101 102 // replace the bytes we don't like 103 byte[] buf = bytesOut.toByteArray(); 104 replaceBytes(tmpName.getBytes(), entryName.getBytes(), buf); 105 106 // write the result to a file 107 File badZip = File.createTempFile("badzip", "zip"); 108 badZip.deleteOnExit(); 109 FileOutputStream outstream = new FileOutputStream(badZip); 110 outstream.write(buf); 111 outstream.close(); 112 113 // see if we can still handle it 114 try { 115 ZipFile bad = new ZipFile(badZip); 116 fail(); 117 } catch (ZipException expected) { 118 } 119 } 120 121 public void testInflatingStreamsRequiringZipRefill() throws IOException { 122 int originalSize = 1024 * 1024; 123 byte[] readBuffer = new byte[8192]; 124 ZipInputStream in = new ZipInputStream(new FileInputStream(createZipFile(1, originalSize))); 125 while (in.getNextEntry() != null) { 126 while (in.read(readBuffer, 0, readBuffer.length) != -1) {} 127 } 128 in.close(); 129 } 130 131 public void testZipFileWithLotsOfEntries() throws IOException { 132 int expectedEntryCount = 64*1024 - 1; 133 File f = createZipFile(expectedEntryCount, 0); 134 ZipFile zipFile = new ZipFile(f); 135 int entryCount = 0; 136 for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { 137 ZipEntry zipEntry = e.nextElement(); 138 ++entryCount; 139 } 140 assertEquals(expectedEntryCount, entryCount); 141 zipFile.close(); 142 } 143 144 // http://code.google.com/p/android/issues/detail?id=36187 145 public void testZipFileLargerThan2GiB() throws IOException { 146 if (false) { // TODO: this test requires too much time and too much disk space! 147 File f = createZipFile(1024, 3*1024*1024); 148 ZipFile zipFile = new ZipFile(f); 149 int entryCount = 0; 150 for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { 151 ZipEntry zipEntry = e.nextElement(); 152 ++entryCount; 153 } 154 assertEquals(1024, entryCount); 155 zipFile.close(); 156 } 157 } 158 159 public void testZip64Support() throws IOException { 160 try { 161 createZipFile(64*1024, 0); 162 fail(); // Make this test more like testHugeZipFile when we have Zip64 support. 163 } catch (ZipException expected) { 164 } 165 } 166 167 /** 168 * Compresses the given number of files, each of the given size, into a .zip archive. 169 */ 170 private File createZipFile(int entryCount, int entrySize) throws IOException { 171 File result = createTemporaryZipFile(); 172 173 byte[] writeBuffer = new byte[8192]; 174 Random random = new Random(); 175 176 ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(result))); 177 for (int entry = 0; entry < entryCount; ++entry) { 178 ZipEntry ze = new ZipEntry(Integer.toHexString(entry)); 179 out.putNextEntry(ze); 180 181 for (int i = 0; i < entrySize; i += writeBuffer.length) { 182 random.nextBytes(writeBuffer); 183 int byteCount = Math.min(writeBuffer.length, entrySize - i); 184 out.write(writeBuffer, 0, byteCount); 185 } 186 187 out.closeEntry(); 188 } 189 190 out.close(); 191 return result; 192 } 193 194 private File createTemporaryZipFile() throws IOException { 195 File result = File.createTempFile("ZipFileTest", "zip"); 196 result.deleteOnExit(); 197 return result; 198 } 199 200 private ZipOutputStream createZipOutputStream(File f) throws IOException { 201 return new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(f))); 202 } 203 204 public void testSTORED() throws IOException { 205 ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); 206 CRC32 crc = new CRC32(); 207 208 // Missing CRC, size, and compressed size => failure. 209 try { 210 ZipEntry ze = new ZipEntry("a"); 211 ze.setMethod(ZipEntry.STORED); 212 out.putNextEntry(ze); 213 fail(); 214 } catch (ZipException expected) { 215 } 216 217 // Missing CRC and compressed size => failure. 218 try { 219 ZipEntry ze = new ZipEntry("a"); 220 ze.setMethod(ZipEntry.STORED); 221 ze.setSize(0); 222 out.putNextEntry(ze); 223 fail(); 224 } catch (ZipException expected) { 225 } 226 227 // Missing CRC and size => failure. 228 try { 229 ZipEntry ze = new ZipEntry("a"); 230 ze.setMethod(ZipEntry.STORED); 231 ze.setSize(0); 232 ze.setCompressedSize(0); 233 out.putNextEntry(ze); 234 fail(); 235 } catch (ZipException expected) { 236 } 237 238 // Missing size and compressed size => failure. 239 try { 240 ZipEntry ze = new ZipEntry("a"); 241 ze.setMethod(ZipEntry.STORED); 242 ze.setCrc(crc.getValue()); 243 out.putNextEntry(ze); 244 fail(); 245 } catch (ZipException expected) { 246 } 247 248 // Missing size is copied from compressed size. 249 { 250 ZipEntry ze = new ZipEntry("okay1"); 251 ze.setMethod(ZipEntry.STORED); 252 ze.setCrc(crc.getValue()); 253 254 assertEquals(-1, ze.getSize()); 255 assertEquals(-1, ze.getCompressedSize()); 256 257 ze.setCompressedSize(0); 258 259 assertEquals(-1, ze.getSize()); 260 assertEquals(0, ze.getCompressedSize()); 261 262 out.putNextEntry(ze); 263 264 assertEquals(0, ze.getSize()); 265 assertEquals(0, ze.getCompressedSize()); 266 } 267 268 // Missing compressed size is copied from size. 269 { 270 ZipEntry ze = new ZipEntry("okay2"); 271 ze.setMethod(ZipEntry.STORED); 272 ze.setCrc(crc.getValue()); 273 274 assertEquals(-1, ze.getSize()); 275 assertEquals(-1, ze.getCompressedSize()); 276 277 ze.setSize(0); 278 279 assertEquals(0, ze.getSize()); 280 assertEquals(-1, ze.getCompressedSize()); 281 282 out.putNextEntry(ze); 283 284 assertEquals(0, ze.getSize()); 285 assertEquals(0, ze.getCompressedSize()); 286 } 287 288 // Mismatched size and compressed size => failure. 289 try { 290 ZipEntry ze = new ZipEntry("a"); 291 ze.setMethod(ZipEntry.STORED); 292 ze.setCrc(crc.getValue()); 293 ze.setCompressedSize(1); 294 ze.setSize(0); 295 out.putNextEntry(ze); 296 fail(); 297 } catch (ZipException expected) { 298 } 299 300 // Everything present => success. 301 ZipEntry ze = new ZipEntry("okay"); 302 ze.setMethod(ZipEntry.STORED); 303 ze.setCrc(crc.getValue()); 304 ze.setSize(0); 305 ze.setCompressedSize(0); 306 out.putNextEntry(ze); 307 308 out.close(); 309 } 310 311 private String makeString(int count, String ch) { 312 StringBuilder sb = new StringBuilder(); 313 for (int i = 0; i < count; ++i) { 314 sb.append(ch); 315 } 316 return sb.toString(); 317 } 318 319 public void testComment() throws Exception { 320 String expectedFileComment = "1 \u0666 2"; 321 String expectedEntryComment = "a \u0666 b"; 322 323 File file = createTemporaryZipFile(); 324 ZipOutputStream out = createZipOutputStream(file); 325 326 // Is file comment length checking done on bytes or characters? (Should be bytes.) 327 out.setComment(null); 328 out.setComment(makeString(0xffff, "a")); 329 try { 330 out.setComment(makeString(0xffff + 1, "a")); 331 fail(); 332 } catch (IllegalArgumentException expected) { 333 } 334 try { 335 out.setComment(makeString(0xffff, "\u0666")); 336 fail(); 337 } catch (IllegalArgumentException expected) { 338 } 339 340 ZipEntry ze = new ZipEntry("a"); 341 342 // Is entry comment length checking done on bytes or characters? (Should be bytes.) 343 ze.setComment(null); 344 ze.setComment(makeString(0xffff, "a")); 345 try { 346 ze.setComment(makeString(0xffff + 1, "a")); 347 fail(); 348 } catch (IllegalArgumentException expected) { 349 } 350 try { 351 ze.setComment(makeString(0xffff, "\u0666")); 352 fail(); 353 } catch (IllegalArgumentException expected) { 354 } 355 356 ze.setComment(expectedEntryComment); 357 out.putNextEntry(ze); 358 out.closeEntry(); 359 360 out.setComment(expectedFileComment); 361 out.close(); 362 363 ZipFile zipFile = new ZipFile(file); 364 // TODO: there's currently no API for reading the file comment --- strings(1) the file? 365 assertEquals(expectedEntryComment, zipFile.getEntry("a").getComment()); 366 zipFile.close(); 367 } 368 369 public void testNameLengthChecks() throws IOException { 370 // Is entry name length checking done on bytes or characters? 371 // Really it should be bytes, but the RI only checks characters at construction time. 372 // Android does the same, because it's cheap... 373 try { 374 new ZipEntry((String) null); 375 fail(); 376 } catch (NullPointerException expected) { 377 } 378 new ZipEntry(makeString(0xffff, "a")); 379 try { 380 new ZipEntry(makeString(0xffff + 1, "a")); 381 fail(); 382 } catch (IllegalArgumentException expected) { 383 } 384 385 // ...but Android won't let you create a zip file with a truncated name. 386 ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); 387 ZipEntry ze = new ZipEntry(makeString(0xffff, "\u0666")); 388 try { 389 out.putNextEntry(ze); 390 fail(); // The RI fails this test; it just checks the character count at construction time. 391 } catch (IllegalArgumentException expected) { 392 } 393 out.closeEntry(); 394 out.putNextEntry(new ZipEntry("okay")); // ZipOutputStream.close throws if you add nothing! 395 out.close(); 396 } 397 398 public void testCrc() throws IOException { 399 ZipEntry ze = new ZipEntry("test"); 400 ze.setMethod(ZipEntry.STORED); 401 ze.setSize(4); 402 403 // setCrc takes a long, not an int, so -1 isn't a valid CRC32 (because it's 64 bits). 404 try { 405 ze.setCrc(-1); 406 } catch (IllegalArgumentException expected) { 407 } 408 409 // You can set the CRC32 to 0xffffffff if you're slightly more careful though... 410 ze.setCrc(0xffffffffL); 411 assertEquals(0xffffffffL, ze.getCrc()); 412 413 // And it actually works, even though we use -1L to mean "no CRC set"... 414 ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); 415 out.putNextEntry(ze); 416 out.write(-1); 417 out.write(-1); 418 out.write(-1); 419 out.write(-1); 420 out.closeEntry(); 421 out.close(); 422 } 423 } 424