1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.harmony.tests.java.util.zip; 18 19 import java.io.ByteArrayInputStream; 20 import java.io.ByteArrayOutputStream; 21 import java.io.File; 22 import java.io.FileOutputStream; 23 import java.io.IOException; 24 import java.lang.reflect.Field; 25 import java.nio.file.attribute.FileTime; 26 import java.time.Duration; 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.zip.CRC32; 30 import java.util.zip.ZipEntry; 31 import java.util.zip.ZipException; 32 import java.util.zip.ZipInputStream; 33 import java.util.zip.ZipOutputStream; 34 import libcore.junit.junit3.TestCaseWithRules; 35 import libcore.junit.util.ResourceLeakageDetector.DisableResourceLeakageDetection; 36 import libcore.junit.util.ResourceLeakageDetector; 37 import libcore.test.annotation.NonCts; 38 import libcore.test.reasons.NonCtsReasons; 39 40 import org.junit.Rule; 41 import org.junit.rules.TestRule; 42 43 public class ZipOutputStreamTest extends TestCaseWithRules { 44 @Rule 45 public TestRule guardRule = ResourceLeakageDetector.getRule(); 46 47 ZipOutputStream zos; 48 49 ByteArrayOutputStream bos; 50 51 ZipInputStream zis; 52 53 static final String data = "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld"; 54 55 /** 56 * java.util.zip.ZipOutputStream#close() 57 */ test_close()58 public void test_close() throws Exception { 59 zos.putNextEntry(new ZipEntry("XX")); 60 zos.closeEntry(); 61 zos.close(); 62 63 // Regression for HARMONY-97 64 ZipOutputStream zos = new ZipOutputStream(new ByteArrayOutputStream()); 65 zos.putNextEntry(new ZipEntry("myFile")); 66 zos.close(); 67 zos.close(); // Should be a no-op 68 } 69 70 /** 71 * java.util.zip.ZipOutputStream#closeEntry() 72 */ test_closeEntry()73 public void test_closeEntry() throws IOException { 74 ZipEntry ze = new ZipEntry("testEntry"); 75 ze.setTime(System.currentTimeMillis()); 76 zos.putNextEntry(ze); 77 zos.write("Hello World".getBytes("UTF-8")); 78 zos.closeEntry(); 79 assertTrue("closeEntry failed to update required fields", 80 ze.getSize() == 11 && ze.getCompressedSize() == 13); 81 82 } 83 84 /** 85 * java.util.zip.ZipOutputStream#finish() 86 */ test_finish()87 public void test_finish() throws Exception { 88 ZipEntry ze = new ZipEntry("test"); 89 zos.putNextEntry(ze); 90 zos.write("Hello World".getBytes()); 91 zos.finish(); 92 assertEquals("Finish failed to closeCurrentEntry", 11, ze.getSize()); 93 94 ZipOutputStream zos = new ZipOutputStream(new ByteArrayOutputStream()); 95 zos.putNextEntry(new ZipEntry("myFile")); 96 zos.finish(); 97 zos.close(); 98 try { 99 zos.finish(); 100 fail("Assert 0: Expected IOException"); 101 } catch (IOException e) { 102 // Expected 103 } 104 } 105 106 /** 107 * java.util.zip.ZipOutputStream#putNextEntry(java.util.zip.ZipEntry) 108 */ test_putNextEntryLjava_util_zip_ZipEntry()109 public void test_putNextEntryLjava_util_zip_ZipEntry() throws IOException { 110 ZipEntry ze = new ZipEntry("testEntry"); 111 ze.setTime(System.currentTimeMillis()); 112 zos.putNextEntry(ze); 113 zos.write("Hello World".getBytes()); 114 zos.closeEntry(); 115 zos.close(); 116 zis = new ZipInputStream(new ByteArrayInputStream(bos.toByteArray())); 117 ZipEntry ze2 = zis.getNextEntry(); 118 zis.closeEntry(); 119 assertEquals("Failed to write correct entry", ze.getName(), ze2.getName()); 120 assertEquals("Failed to write correct entry", ze.getCrc(), ze2.getCrc()); 121 try { 122 zos.putNextEntry(ze); 123 fail("Entry with incorrect setting failed to throw exception"); 124 } catch (IOException e) { 125 // expected 126 } 127 } 128 129 /** 130 * java.util.zip.ZipOutputStream#setComment(java.lang.String) 131 */ 132 @DisableResourceLeakageDetection( 133 why = "InflaterOutputStream.close() does not work properly if finish() throws an" 134 + " exception; finish() throws an exception if the output is invalid; this is" 135 + " an issue with the ZipOutputStream created in setUp()", 136 bug = "31797037") test_setCommentLjava_lang_String()137 public void test_setCommentLjava_lang_String() { 138 // There is no way to get the comment back, so no way to determine if 139 // the comment is set correct 140 zos.setComment("test setComment"); 141 142 try { 143 zos.setComment(new String(new byte[0xFFFF + 1])); 144 fail("Comment over 0xFFFF in length should throw exception"); 145 } catch (IllegalArgumentException e) { 146 // Passed 147 } 148 } 149 150 /** 151 * java.util.zip.ZipOutputStream#setLevel(int) 152 */ test_setLevelI()153 public void test_setLevelI() throws IOException { 154 ZipEntry ze = new ZipEntry("test"); 155 zos.putNextEntry(ze); 156 zos.write(data.getBytes()); 157 zos.closeEntry(); 158 long csize = ze.getCompressedSize(); 159 zos.setLevel(9); // Max Compression 160 zos.putNextEntry(ze = new ZipEntry("test2")); 161 zos.write(data.getBytes()); 162 zos.closeEntry(); 163 assertTrue("setLevel failed", csize <= ze.getCompressedSize()); 164 } 165 166 /** 167 * java.util.zip.ZipOutputStream#setMethod(int) 168 */ test_setMethodI()169 public void test_setMethodI() throws IOException { 170 ZipEntry ze = new ZipEntry("test"); 171 zos.setMethod(ZipOutputStream.STORED); 172 CRC32 tempCrc = new CRC32(); 173 tempCrc.update(data.getBytes()); 174 ze.setCrc(tempCrc.getValue()); 175 ze.setSize(new String(data).length()); 176 zos.putNextEntry(ze); 177 zos.write(data.getBytes()); 178 zos.closeEntry(); 179 long csize = ze.getCompressedSize(); 180 zos.setMethod(ZipOutputStream.DEFLATED); 181 zos.putNextEntry(ze = new ZipEntry("test2")); 182 zos.write(data.getBytes()); 183 zos.closeEntry(); 184 assertTrue("setLevel failed", csize >= ze.getCompressedSize()); 185 } 186 187 /** 188 * java.util.zip.ZipOutputStream#write(byte[], int, int) 189 */ 190 @DisableResourceLeakageDetection( 191 why = "InflaterOutputStream.close() does not work properly if finish() throws an" 192 + " exception; finish() throws an exception if the output is invalid.", 193 bug = "31797037") test_write$BII()194 public void test_write$BII() throws IOException { 195 ZipEntry ze = new ZipEntry("test"); 196 zos.putNextEntry(ze); 197 zos.write(data.getBytes()); 198 zos.closeEntry(); 199 zos.close(); 200 zos = null; 201 zis = new ZipInputStream(new ByteArrayInputStream(bos.toByteArray())); 202 zis.getNextEntry(); 203 byte[] b = new byte[data.length()]; 204 int r = 0; 205 int count = 0; 206 while (count != b.length && (r = zis.read(b, count, b.length)) != -1) { 207 count += r; 208 } 209 zis.closeEntry(); 210 assertEquals("Write failed to write correct bytes", new String(b), data); 211 212 File f = File.createTempFile("testZip", "tst"); 213 f.deleteOnExit(); 214 FileOutputStream stream = new FileOutputStream(f); 215 ZipOutputStream zip = new ZipOutputStream(stream); 216 zip.setMethod(ZipEntry.STORED); 217 218 try { 219 zip.putNextEntry(new ZipEntry("Second")); 220 fail("Not set an entry. Should have thrown ZipException."); 221 } catch (ZipException e) { 222 // expected -- We have not set an entry 223 } 224 225 try { 226 // We try to write data without entry 227 zip.write(new byte[2]); 228 fail("Writing data without an entry. Should have thrown IOException"); 229 } catch (IOException e) { 230 // expected 231 } 232 233 try { 234 // Try to write without an entry and with nonsense offset and 235 // length 236 zip.write(new byte[2], 0, 12); 237 fail("Writing data without an entry. Should have thrown IndexOutOfBoundsException"); 238 } catch (IndexOutOfBoundsException e) { 239 // expected 240 } 241 242 // Regression for HARMONY-4405 243 try { 244 zip.write(null, 0, -2); 245 fail(); 246 } catch (NullPointerException expected) { 247 } catch (IndexOutOfBoundsException expected) { 248 } 249 try { 250 zip.write(null, 0, 2); 251 fail(); 252 } catch (NullPointerException expected) { 253 } 254 try { 255 zip.write(new byte[2], 0, -2); 256 fail(); 257 } catch (IndexOutOfBoundsException expected) { 258 } 259 260 // Close stream because ZIP is invalid 261 stream.close(); 262 } 263 264 /** 265 * java.util.zip.ZipOutputStream#write(byte[], int, int) 266 */ 267 @DisableResourceLeakageDetection( 268 why = "InflaterOutputStream.close() does not work properly if finish() throws an" 269 + " exception; finish() throws an exception if the output is invalid; this is" 270 + " an issue with the ZipOutputStream created in setUp()", 271 bug = "31797037") test_write$BII_2()272 public void test_write$BII_2() throws IOException { 273 // Regression for HARMONY-577 274 File f1 = File.createTempFile("testZip1", "tst"); 275 f1.deleteOnExit(); 276 FileOutputStream stream1 = new FileOutputStream(f1); 277 ZipOutputStream zip1 = new ZipOutputStream(stream1); 278 zip1.putNextEntry(new ZipEntry("one")); 279 zip1.setMethod(ZipOutputStream.STORED); 280 zip1.setMethod(ZipEntry.STORED); 281 282 zip1.write(new byte[2]); 283 284 try { 285 zip1.putNextEntry(new ZipEntry("Second")); 286 fail("ZipException expected"); 287 } catch (ZipException e) { 288 // expected - We have not set an entry 289 } 290 291 try { 292 zip1.write(new byte[2]); // try to write data without entry 293 fail("expected IOE there"); 294 } catch (IOException e2) { 295 // expected 296 } 297 298 zip1.close(); 299 } 300 /** 301 * Test standard and info-zip-extended timestamp rounding 302 */ test_timeSerializationRounding()303 public void test_timeSerializationRounding() throws Exception { 304 List<ZipEntry> entries = new ArrayList<>(); 305 ZipEntry zipEntry; 306 307 entries.add(zipEntry = new ZipEntry("test1")); 308 final long someTimestamp = 1479139143200L; 309 zipEntry.setTime(someTimestamp); 310 311 entries.add(zipEntry = new ZipEntry("test2")); 312 zipEntry.setLastModifiedTime(FileTime.fromMillis(someTimestamp)); 313 314 for (ZipEntry entry : entries) { 315 zos.putNextEntry(entry); 316 zos.write(data.getBytes()); 317 zos.closeEntry(); 318 } 319 zos.close(); 320 321 try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(bos.toByteArray()))) { 322 // getTime should be rounded down to a multiple of 2s 323 ZipEntry readEntry = zis.getNextEntry(); 324 assertEquals((someTimestamp / 2000) * 2000, 325 readEntry.getTime()); 326 327 // With extended timestamp getTime&getLastModifiedTime should berounded down to a 328 // multiple of 1s 329 readEntry = zis.getNextEntry(); 330 assertEquals((someTimestamp / 1000) * 1000, 331 readEntry.getLastModifiedTime().toMillis()); 332 assertEquals((someTimestamp / 1000) * 1000, 333 readEntry.getTime()); 334 } 335 } 336 337 /** 338 * Test info-zip extended timestamp support 339 */ 340 @NonCts(bug = 310050493, reason = NonCtsReasons.NON_BREAKING_BEHAVIOR_FIX) test_exttSupport()341 public void test_exttSupport() throws Exception { 342 List<ZipEntry> entries = new ArrayList<>(); 343 344 ZipEntry zipEntry; 345 346 // There's no practical way to access ONLY mtime 347 Field mtimeField = ZipEntry.class.getDeclaredField("mtime"); 348 mtimeField.setAccessible(true); 349 350 // Serialized DOS timestamp resolution is 2s. Serialized extended 351 // timestamp resolution is 1s. If we won't use rounded values then 352 // asserting time equality would be more complicated (resolution of 353 // getTime depends weather we use extended timestamp). 354 // 355 // We have to call setTime on all entries. If it's not set then 356 // ZipOutputStream will call setTime(System.currentTimeMillis()) on it. 357 // I will use this as a excuse to test whether setting particular time 358 // values (~< 1980 ~> 2099) triggers use of the extended last-modified 359 // timestamp. 360 final long timestampWithinDostimeBound = ZipEntry.UPPER_DOSTIME_BOUND; 361 assertEquals(0, timestampWithinDostimeBound % 1000); 362 final long timestampBeyondDostimeBound = ZipEntry.UPPER_DOSTIME_BOUND + 2000; 363 assertEquals(0, timestampBeyondDostimeBound % 1000); 364 365 // This will set both dos timestamp and last-modified timestamp (because < 1980) 366 entries.add(zipEntry = new ZipEntry("test_setTime")); 367 zipEntry.setTime(0); 368 assertNotNull(mtimeField.get(zipEntry)); 369 370 // Explicitly set info-zip last-modified extended timestamp 371 entries.add(zipEntry = new ZipEntry("test_setLastModifiedTime")); 372 zipEntry.setLastModifiedTime(FileTime.fromMillis(1000)); 373 374 // Set creation time and (since we have to call setTime on ZipEntry, otherwise 375 // ZipOutputStream will call setTime(System.currentTimeMillis()) and the getTime() 376 // assert will fail due to low serialization resolution) test that calling 377 // setTime with value <= ZipEntry.UPPER_DOSTIME_BOUND won't set the info-zip 378 // last-modified extended timestamp. 379 entries.add(zipEntry = new ZipEntry("test_setCreationTime")); 380 zipEntry.setCreationTime(FileTime.fromMillis(1000)); 381 zipEntry.setTime(timestampWithinDostimeBound); 382 assertNull(mtimeField.get(zipEntry)); 383 384 // Set last access time and test that calling setTime with value > 385 // ZipEntry.UPPER_DOSTIME_BOUND will set the info-zip last-modified extended 386 // timestamp. ZipEntry.UPPER_DOSTIME_BOUND is lower than actual DOS time upper 387 // bound, so adding 3 years to make sure that it is really out of upper bound. 388 entries.add(zipEntry = new ZipEntry("test_setLastAccessTime")); 389 zipEntry.setLastAccessTime(FileTime.fromMillis(3000)); 390 zipEntry.setTime(timestampBeyondDostimeBound + Duration.ofDays(3 * 365).toMillis()); 391 assertNotNull(mtimeField.get(zipEntry)); 392 393 for (ZipEntry entry : entries) { 394 zos.putNextEntry(entry); 395 zos.write(data.getBytes()); 396 zos.closeEntry(); 397 } 398 zos.close(); 399 400 try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(bos.toByteArray()))) { 401 for (ZipEntry entry : entries) { 402 ZipEntry readEntry = zis.getNextEntry(); 403 assertEquals(entry.getName(), readEntry.getName()); 404 assertEquals(entry.getName(), entry.getTime(), readEntry.getTime()); 405 assertEquals(entry.getName(), entry.getLastModifiedTime(), readEntry.getLastModifiedTime()); 406 assertEquals(entry.getLastAccessTime(), readEntry.getLastAccessTime()); 407 assertEquals(entry.getCreationTime(), readEntry.getCreationTime()); 408 } 409 } 410 } 411 412 413 @Override setUp()414 protected void setUp() throws Exception { 415 super.setUp(); 416 zos = new ZipOutputStream(bos = new ByteArrayOutputStream()); 417 } 418 419 @Override tearDown()420 protected void tearDown() throws Exception { 421 try { 422 // Close the ZipInputStream first as that does not fail. 423 if (zis != null) { 424 zis.close(); 425 } 426 if (zos != null) { 427 // This will throw a ZipException if nothing is written to the ZipOutputStream. 428 zos.close(); 429 } 430 } catch (Exception e) { 431 } 432 super.tearDown(); 433 } 434 } 435