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