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.IOException; 25 import java.io.InputStream; 26 import java.io.OutputStream; 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 libcore.junit.junit3.TestCaseWithRules; 36 import libcore.junit.util.ResourceLeakageDetector; 37 import org.junit.Rule; 38 import org.junit.rules.TestRule; 39 import tests.support.resource.Support_Resources; 40 41 public abstract class AbstractZipFileTest extends TestCaseWithRules { 42 @Rule 43 public TestRule resourceLeakageDetectorRule = ResourceLeakageDetector.getRule(); 44 45 /** 46 * Exercise Inflater's ability to refill the zlib's input buffer. As of this 47 * writing, this buffer's max size is 64KiB compressed bytes. We'll write a 48 * full megabyte of uncompressed data, which should be sufficient to exhaust 49 * the buffer. http://b/issue?id=2734751 50 */ testInflatingFilesRequiringZipRefill()51 public void testInflatingFilesRequiringZipRefill() throws IOException { 52 int originalSize = 1024 * 1024; 53 byte[] readBuffer = new byte[8192]; 54 final File f = createTemporaryZipFile(); 55 writeEntries(createZipOutputStream(f), 1, originalSize, false /* setEntrySize */); 56 ZipFile zipFile = new ZipFile(f); 57 for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { 58 ZipEntry zipEntry = e.nextElement(); 59 assertTrue("This test needs >64 KiB of compressed data to exercise Inflater", 60 zipEntry.getCompressedSize() > (64 * 1024)); 61 InputStream is = zipFile.getInputStream(zipEntry); 62 while (is.read(readBuffer, 0, readBuffer.length) != -1) {} 63 is.close(); 64 } 65 zipFile.close(); 66 } 67 replaceBytes(byte[] buffer, byte[] original, byte[] replacement)68 private static void replaceBytes(byte[] buffer, byte[] original, byte[] replacement) { 69 // Gotcha here: original and replacement must be the same length 70 assertEquals(original.length, replacement.length); 71 boolean found; 72 for(int i=0; i < buffer.length - original.length; i++) { 73 found = false; 74 if (buffer[i] == original[0]) { 75 found = true; 76 for (int j=0; j < original.length; j++) { 77 if (buffer[i+j] != original[j]) { 78 found = false; 79 break; 80 } 81 } 82 } 83 if (found) { 84 for (int j=0; j < original.length; j++) { 85 buffer[i+j] = replacement[j]; 86 } 87 } 88 } 89 } 90 writeBytes(File f, byte[] bytes)91 private static void writeBytes(File f, byte[] bytes) throws IOException { 92 FileOutputStream out = new FileOutputStream(f); 93 out.write(bytes); 94 out.close(); 95 } 96 97 /** 98 * Make sure we don't fail silently for duplicate entries. 99 * b/8219321 100 */ testDuplicateEntries()101 public void testDuplicateEntries() throws Exception { 102 String name1 = "test_file_name1"; 103 String name2 = "test_file_name2"; 104 105 // Create the good zip file. 106 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 107 ZipOutputStream out = createZipOutputStream(baos); 108 out.putNextEntry(new ZipEntry(name2)); 109 out.closeEntry(); 110 out.putNextEntry(new ZipEntry(name1)); 111 out.closeEntry(); 112 out.close(); 113 114 // Rewrite one of the filenames. 115 byte[] buffer = baos.toByteArray(); 116 replaceBytes(buffer, name2.getBytes(), name1.getBytes()); 117 118 // Write the result to a file. 119 File badZip = createTemporaryZipFile(); 120 writeBytes(badZip, buffer); 121 122 // Check that we refuse to load the modified file. 123 try { 124 ZipFile bad = new ZipFile(badZip); 125 fail(); 126 } catch (ZipException expected) { 127 } 128 } 129 130 /** 131 * Make sure the size used for stored zip entires is the uncompressed size. 132 * b/10227498 133 */ testStoredEntrySize()134 public void testStoredEntrySize() throws Exception { 135 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 136 ZipOutputStream out = createZipOutputStream(baos); 137 138 // Set up a single stored entry. 139 String name = "test_file"; 140 int expectedLength = 5; 141 ZipEntry outEntry = new ZipEntry(name); 142 byte[] buffer = new byte[expectedLength]; 143 outEntry.setMethod(ZipEntry.STORED); 144 CRC32 crc = new CRC32(); 145 crc.update(buffer); 146 outEntry.setCrc(crc.getValue()); 147 outEntry.setSize(buffer.length); 148 149 out.putNextEntry(outEntry); 150 out.write(buffer); 151 out.closeEntry(); 152 out.close(); 153 154 // Write the result to a file. 155 byte[] outBuffer = baos.toByteArray(); 156 File zipFile = createTemporaryZipFile(); 157 writeBytes(zipFile, outBuffer); 158 159 ZipFile zip = new ZipFile(zipFile); 160 // Set up the zip entry to have different compressed/uncompressed sizes. 161 ZipEntry ze = zip.getEntry(name); 162 ze.setCompressedSize(expectedLength - 1); 163 // Read the contents of the stream and verify uncompressed size was used. 164 InputStream stream = zip.getInputStream(ze); 165 int count = 0; 166 int read; 167 while ((read = stream.read(buffer)) != -1) { 168 count += read; 169 } 170 171 assertEquals(expectedLength, count); 172 zip.close(); 173 } 174 testInflatingStreamsRequiringZipRefill()175 public void testInflatingStreamsRequiringZipRefill() throws IOException { 176 int originalSize = 1024 * 1024; 177 byte[] readBuffer = new byte[8192]; 178 final File f = createTemporaryZipFile(); 179 writeEntries(createZipOutputStream(f), 1, originalSize, false /* setEntrySize */); 180 181 ZipInputStream in = new ZipInputStream(new FileInputStream(f)); 182 while (in.getNextEntry() != null) { 183 while (in.read(readBuffer, 0, readBuffer.length) != -1) {} 184 } 185 in.close(); 186 } 187 testZipFileWithLotsOfEntries()188 public void testZipFileWithLotsOfEntries() throws IOException { 189 int expectedEntryCount = 64*1024 - 1; 190 final File f = createTemporaryZipFile(); 191 writeEntries(createZipOutputStream(f), expectedEntryCount, 0, false /* setEntrySize */); 192 ZipFile zipFile = new ZipFile(f); 193 int entryCount = 0; 194 for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { 195 ZipEntry zipEntry = e.nextElement(); 196 ++entryCount; 197 } 198 assertEquals(expectedEntryCount, entryCount); 199 zipFile.close(); 200 } 201 202 // http://code.google.com/p/android/issues/detail?id=36187 testZipFileLargerThan2GiB()203 public void testZipFileLargerThan2GiB() throws IOException { 204 if (false) { // TODO: this test requires too much time and too much disk space! 205 final File f = createTemporaryZipFile(); 206 writeEntries(createZipOutputStream(f), 1024, 3*1024*1024, false /* setEntrySize */); 207 ZipFile zipFile = new ZipFile(f); 208 int entryCount = 0; 209 for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements(); ) { 210 e.nextElement(); 211 ++entryCount; 212 } 213 assertEquals(1024, entryCount); 214 zipFile.close(); 215 } 216 } 217 218 /** 219 * Compresses the given number of files, each of the given size, into a .zip archive. 220 */ writeEntries(ZipOutputStream out, int entryCount, long entrySize, boolean setEntrySize)221 protected void writeEntries(ZipOutputStream out, int entryCount, long entrySize, 222 boolean setEntrySize) 223 throws IOException { 224 byte[] writeBuffer = new byte[8192]; 225 Random random = new Random(); 226 try { 227 for (int entry = 0; entry < entryCount; ++entry) { 228 ZipEntry ze = new ZipEntry(Integer.toHexString(entry)); 229 if (setEntrySize) { 230 ze.setSize(entrySize); 231 } 232 out.putNextEntry(ze); 233 234 for (long i = 0; i < entrySize; i += writeBuffer.length) { 235 random.nextBytes(writeBuffer); 236 int byteCount = (int) Math.min(writeBuffer.length, entrySize - i); 237 out.write(writeBuffer, 0, byteCount); 238 } 239 240 out.closeEntry(); 241 } 242 } finally { 243 out.close(); 244 } 245 } 246 createTemporaryZipFile()247 static File createTemporaryZipFile() throws IOException { 248 File result = File.createTempFile("ZipFileTest", ".zip"); 249 result.deleteOnExit(); 250 return result; 251 } 252 createZipOutputStream(File f)253 private ZipOutputStream createZipOutputStream(File f) throws IOException { 254 return createZipOutputStream(new BufferedOutputStream(new FileOutputStream(f))); 255 } 256 createZipOutputStream(OutputStream wrapped)257 protected abstract ZipOutputStream createZipOutputStream(OutputStream wrapped); 258 testSTORED()259 public void testSTORED() throws IOException { 260 ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); 261 CRC32 crc = new CRC32(); 262 263 // Missing CRC, size, and compressed size => failure. 264 try { 265 ZipEntry ze = new ZipEntry("a"); 266 ze.setMethod(ZipEntry.STORED); 267 out.putNextEntry(ze); 268 fail(); 269 } catch (ZipException expected) { 270 } 271 272 // Missing CRC and compressed size => failure. 273 try { 274 ZipEntry ze = new ZipEntry("a"); 275 ze.setMethod(ZipEntry.STORED); 276 ze.setSize(0); 277 out.putNextEntry(ze); 278 fail(); 279 } catch (ZipException expected) { 280 } 281 282 // Missing CRC and size => failure. 283 try { 284 ZipEntry ze = new ZipEntry("a"); 285 ze.setMethod(ZipEntry.STORED); 286 ze.setSize(0); 287 ze.setCompressedSize(0); 288 out.putNextEntry(ze); 289 fail(); 290 } catch (ZipException expected) { 291 } 292 293 // Missing size and compressed size => failure. 294 try { 295 ZipEntry ze = new ZipEntry("a"); 296 ze.setMethod(ZipEntry.STORED); 297 ze.setCrc(crc.getValue()); 298 out.putNextEntry(ze); 299 fail(); 300 } catch (ZipException expected) { 301 } 302 303 // Missing size is copied from compressed size. 304 { 305 ZipEntry ze = new ZipEntry("okay1"); 306 ze.setMethod(ZipEntry.STORED); 307 ze.setCrc(crc.getValue()); 308 309 assertEquals(-1, ze.getSize()); 310 assertEquals(-1, ze.getCompressedSize()); 311 312 ze.setCompressedSize(0); 313 314 assertEquals(-1, ze.getSize()); 315 assertEquals(0, ze.getCompressedSize()); 316 317 out.putNextEntry(ze); 318 319 assertEquals(0, ze.getSize()); 320 assertEquals(0, ze.getCompressedSize()); 321 } 322 323 // Missing compressed size is copied from size. 324 { 325 ZipEntry ze = new ZipEntry("okay2"); 326 ze.setMethod(ZipEntry.STORED); 327 ze.setCrc(crc.getValue()); 328 329 assertEquals(-1, ze.getSize()); 330 assertEquals(-1, ze.getCompressedSize()); 331 332 ze.setSize(0); 333 334 assertEquals(0, ze.getSize()); 335 assertEquals(-1, ze.getCompressedSize()); 336 337 out.putNextEntry(ze); 338 339 assertEquals(0, ze.getSize()); 340 assertEquals(0, ze.getCompressedSize()); 341 } 342 343 // Mismatched size and compressed size => failure. 344 try { 345 ZipEntry ze = new ZipEntry("a"); 346 ze.setMethod(ZipEntry.STORED); 347 ze.setCrc(crc.getValue()); 348 ze.setCompressedSize(1); 349 ze.setSize(0); 350 out.putNextEntry(ze); 351 fail(); 352 } catch (ZipException expected) { 353 } 354 355 // Everything present => success. 356 ZipEntry ze = new ZipEntry("okay"); 357 ze.setMethod(ZipEntry.STORED); 358 ze.setCrc(crc.getValue()); 359 ze.setSize(0); 360 ze.setCompressedSize(0); 361 out.putNextEntry(ze); 362 363 out.close(); 364 } 365 makeString(int count, String ch)366 private String makeString(int count, String ch) { 367 StringBuilder sb = new StringBuilder(); 368 for (int i = 0; i < count; ++i) { 369 sb.append(ch); 370 } 371 return sb.toString(); 372 } 373 testComments()374 public void testComments() throws Exception { 375 String expectedFileComment = "1 \u0666 2"; 376 String expectedEntryComment = "a \u0666 b"; 377 378 File file = createTemporaryZipFile(); 379 ZipOutputStream out = createZipOutputStream(file); 380 381 // Is file comment length checking done on bytes or characters? (Should be bytes.) 382 out.setComment(null); 383 out.setComment(makeString(0xffff, "a")); 384 try { 385 out.setComment(makeString(0xffff + 1, "a")); 386 fail(); 387 } catch (IllegalArgumentException expected) { 388 } 389 try { 390 out.setComment(makeString(0xffff, "\u0666")); 391 fail(); 392 } catch (IllegalArgumentException expected) { 393 } 394 395 ZipEntry ze = new ZipEntry("a"); 396 397 // Is entry comment length checking done on bytes or characters? (Should be bytes.) 398 ze.setComment(null); 399 ze.setComment(makeString(0xffff, "a")); 400 try { 401 ze.setComment(makeString(0xffff + 1, "a")); 402 fail(); 403 } catch (IllegalArgumentException expected) { 404 } 405 try { 406 ze.setComment(makeString(0xffff, "\u0666")); 407 fail(); 408 } catch (IllegalArgumentException expected) { 409 } 410 411 ze.setComment(expectedEntryComment); 412 out.putNextEntry(ze); 413 out.closeEntry(); 414 415 out.setComment(expectedFileComment); 416 out.close(); 417 418 ZipFile zipFile = new ZipFile(file); 419 assertEquals(expectedFileComment, zipFile.getComment()); 420 assertEquals(expectedEntryComment, zipFile.getEntry("a").getComment()); 421 zipFile.close(); 422 } 423 test_getComment_unset()424 public void test_getComment_unset() throws Exception { 425 File file = createTemporaryZipFile(); 426 ZipOutputStream out = createZipOutputStream(file); 427 ZipEntry ze = new ZipEntry("test entry"); 428 ze.setComment("per-entry comment"); 429 out.putNextEntry(ze); 430 out.close(); 431 432 try (ZipFile zipFile = new ZipFile(file)) { 433 assertEquals(null, zipFile.getComment()); 434 } 435 } 436 437 // https://code.google.com/p/android/issues/detail?id=58465 test_NUL_in_filename()438 public void test_NUL_in_filename() throws Exception { 439 File file = createTemporaryZipFile(); 440 441 // We allow creation of a ZipEntry whose name contains a NUL byte, 442 // mainly because it's not likely to happen by accident and it's useful for testing. 443 ZipOutputStream out = createZipOutputStream(file); 444 out.putNextEntry(new ZipEntry("hello")); 445 out.putNextEntry(new ZipEntry("hello\u0000")); 446 out.close(); 447 448 // But you can't open a ZIP file containing such an entry, because we reject it 449 // when we find it in the central directory. 450 try { 451 ZipFile zipFile = new ZipFile(file); 452 fail(); 453 } catch (ZipException expected) { 454 } 455 } 456 testCrc()457 public void testCrc() throws IOException { 458 ZipEntry ze = new ZipEntry("test"); 459 ze.setMethod(ZipEntry.STORED); 460 ze.setSize(4); 461 462 // setCrc takes a long, not an int, so -1 isn't a valid CRC32 (because it's 64 bits). 463 try { 464 ze.setCrc(-1); 465 fail(); 466 } catch (IllegalArgumentException expected) { 467 } 468 469 // You can set the CRC32 to 0xffffffff if you're slightly more careful though... 470 ze.setCrc(0xffffffffL); 471 assertEquals(0xffffffffL, ze.getCrc()); 472 473 // And it actually works, even though we use -1L to mean "no CRC set"... 474 ZipOutputStream out = createZipOutputStream(createTemporaryZipFile()); 475 out.putNextEntry(ze); 476 out.write(-1); 477 out.write(-1); 478 out.write(-1); 479 out.write(-1); 480 out.closeEntry(); 481 out.close(); 482 } 483 484 /** 485 * RI does allow reading of an empty zip using a {@link ZipFile}. 486 */ testConstructorWorksWhenReadingEmptyZipArchive()487 public void testConstructorWorksWhenReadingEmptyZipArchive() throws IOException { 488 489 File resources = Support_Resources.createTempFolder(); 490 File emptyZip = Support_Resources.copyFile( 491 resources, "java/util/zip", "EmptyArchive.zip"); 492 493 try (ZipFile zipFile = new ZipFile(emptyZip)) { 494 assertEquals(0, zipFile.size()); 495 } 496 } 497 498 // http://b/65491407 testReadMoreThan8kInOneRead()499 public void testReadMoreThan8kInOneRead() throws IOException { 500 // Create a zip file with 1mb entry 501 final File f = createTemporaryZipFile(); 502 writeEntries(createZipOutputStream(f), 1, 1024 * 1024, true /* setEntrySize */); 503 504 // Create a ~64kb read buffer (-32 bytes for a slack, inflater wont fill it completly) 505 byte[] readBuffer = new byte[1024 * 64 - 32]; 506 507 // Read the data to read buffer 508 ZipFile zipFile = new ZipFile(f); 509 InputStream is = zipFile.getInputStream(zipFile.entries().nextElement()); 510 int read = is.read(readBuffer, 0, readBuffer.length); 511 512 // Assert that whole buffer been filled. Due to openJdk choice of buffer size, read 513 // never returned more than 8k of data. 514 assertEquals(readBuffer.length, read); 515 is.close(); 516 zipFile.close(); 517 } 518 519 // http://b/65491407 testReadWithOffset()520 public void testReadWithOffset() throws IOException { 521 // Create a zip file with 1mb entry 522 final File f = createTemporaryZipFile(); 523 writeEntries(createZipOutputStream(f), 1, 1024 * 1024, true /* setEntrySize */); 524 525 int bufferSize = 128; 526 byte[] readBuffer = new byte[bufferSize]; 527 528 // Read the data to read buffer 529 ZipFile zipFile = new ZipFile(f); 530 InputStream is = zipFile.getInputStream(zipFile.entries().nextElement()); 531 532 // Read data (Random bytes sting) to last 32 bit 533 int read = is.read(readBuffer, bufferSize - 32, 32); 534 535 // Check if buffer looks like expected 536 assertEquals(32, read); 537 for (int i = 0; i < bufferSize - 32; i++) { 538 assertEquals(0, readBuffer[i]); 539 } 540 541 is.close(); 542 zipFile.close(); 543 } 544 545 // http://b/65491407 testReadWithOffsetInvalid()546 public void testReadWithOffsetInvalid() throws IOException { 547 // Create a zip file with 1mb entry 548 final File f = createTemporaryZipFile(); 549 writeEntries(createZipOutputStream(f), 1, 1024 * 1024, true /* setEntrySize */); 550 551 int bufferSize = 128; 552 byte[] readBuffer = new byte[bufferSize]; 553 554 // Read the data to read buffer 555 ZipFile zipFile = new ZipFile(f); 556 InputStream is = zipFile.getInputStream(zipFile.entries().nextElement()); 557 558 try { 559 is.read(readBuffer, bufferSize - 32, 33); 560 fail(); 561 } catch(IndexOutOfBoundsException expect) {} 562 try { 563 is.read(readBuffer, -1, 32); 564 fail(); 565 } catch(IndexOutOfBoundsException expect) {} 566 try { 567 is.read(readBuffer, 32, -1); 568 fail(); 569 } catch(IndexOutOfBoundsException expect) {} 570 try { 571 is.read(readBuffer, bufferSize, 1); 572 fail(); 573 } catch(IndexOutOfBoundsException expect) {} 574 575 is.close(); 576 zipFile.close(); 577 } 578 testReadTruncatedZipFile()579 public void testReadTruncatedZipFile() throws IOException { 580 final File f = createTemporaryZipFile(); 581 try (FileOutputStream fos = new FileOutputStream(f)) { 582 // Byte representation of lower 4 bytes of ZipConstants.LOCSIG in little endian order. 583 byte[] bytes = new byte[] {0x50, 0x4b, 0x03, 0x04}; 584 fos.write(bytes); 585 } 586 587 try (ZipFile zipFile = new ZipFile(f)) { 588 fail("Should not be possible to open the ZipFile as it is too short"); 589 } catch (ZipException e) { 590 // expected 591 } 592 } 593 } 594