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