Home | History | Annotate | Download | only in zip
      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.ByteArrayInputStream;
     20 import java.io.ByteArrayOutputStream;
     21 import java.io.EOFException;
     22 import java.io.File;
     23 import java.io.FileInputStream;
     24 import java.io.FileOutputStream;
     25 import java.io.IOException;
     26 import java.io.InputStream;
     27 import java.nio.charset.StandardCharsets;
     28 import java.util.Arrays;
     29 import java.util.Random;
     30 import java.util.zip.GZIPInputStream;
     31 import java.util.zip.GZIPOutputStream;
     32 import libcore.io.IoUtils;
     33 import libcore.io.Streams;
     34 import libcore.junit.junit3.TestCaseWithRules;
     35 import libcore.junit.util.ResourceLeakageDetector;
     36 import org.junit.Rule;
     37 import org.junit.rules.TestRule;
     38 
     39 import java.util.zip.ZipEntry;
     40 import java.util.zip.ZipInputStream;
     41 import java.util.zip.ZipOutputStream;
     42 
     43 
     44 public final class GZIPInputStreamTest extends TestCaseWithRules {
     45     @Rule
     46     public TestRule resourceLeakageDetectorRule = ResourceLeakageDetector.getRule();
     47 
     48     private static final byte[] HELLO_WORLD_GZIPPED = new byte[] {
     49         31, -117, 8, 0, 0, 0, 0, 0, 0, 0,  // 10 byte header
     50         -13, 72, -51, -55, -55, 87, 8, -49, 47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0  // data
     51     };
     52 
     53     /**
     54      * This is the same as the above, except that the 4th header byte is 2 (FHCRC flag)
     55      * and the 2 bytes after the header make up the CRC.
     56      *
     57      * Constructed manually because none of the commonly used tools appear to emit header CRCs.
     58      */
     59     private static final byte[] HELLO_WORLD_GZIPPED_WITH_HEADER_CRC = new byte[] {
     60         31, -117, 8, 2, 0, 0, 0, 0, 0, 0,  // 10 byte header
     61         29, 38, // 2 byte CRC.
     62         -13, 72, -51, -55, -55, 87, 8, -49, 47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0  // data
     63     };
     64 
     65     /*(
     66      * This is the same as {@code HELLO_WORLD_GZIPPED} except that the 4th header byte is 4
     67      * (FEXTRA flag) and that the 8 bytes after the header make up the extra.
     68      *
     69      * Constructed manually because none of the commonly used tools appear to emit header CRCs.
     70      */
     71     private static final byte[] HELLO_WORLD_GZIPPED_WITH_EXTRA = new byte[] {
     72         31, -117, 8, 4, 0, 0, 0, 0, 0, 0,  // 10 byte header
     73         6, 0, 4, 2, 4, 2, 4, 2,  // 2 byte extra length + 6 byte extra.
     74         -13, 72, -51, -55, -55, 87, 8, -49, 47, -54, 73, 1, 0, 86, -79, 23, 74, 11, 0, 0, 0  // data
     75     };
     76 
     77     public void testShortMessage() throws IOException {
     78         assertEquals("Hello World", new String(gunzip(HELLO_WORLD_GZIPPED), "UTF-8"));
     79     }
     80 
     81     public void testShortMessageWithCrc() throws IOException {
     82         assertEquals("Hello World", new String(gunzip(HELLO_WORLD_GZIPPED_WITH_HEADER_CRC), "UTF-8"));
     83     }
     84 
     85     public void testShortMessageWithHeaderExtra() throws IOException {
     86         assertEquals("Hello World", new String(gunzip(HELLO_WORLD_GZIPPED_WITH_EXTRA), "UTF-8"));
     87     }
     88 
     89     public void testLongMessage() throws IOException {
     90         byte[] data = new byte[1024 * 1024];
     91         new Random().nextBytes(data);
     92         assertTrue(Arrays.equals(data, gunzip(GZIPOutputStreamTest.gzip(data))));
     93     }
     94 
     95     /** http://b/3042574 GzipInputStream.skip() causing CRC failures */
     96     public void testSkip() throws IOException {
     97         byte[] data = new byte[1024 * 1024];
     98         byte[] gzipped = GZIPOutputStreamTest.gzip(data);
     99         GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(gzipped));
    100         long totalSkipped = 0;
    101 
    102         long count;
    103         do {
    104             count = in.skip(Long.MAX_VALUE);
    105             totalSkipped += count;
    106         } while (count > 0);
    107 
    108         assertEquals(data.length, totalSkipped);
    109         in.close();
    110     }
    111 
    112     // https://code.google.com/p/android/issues/detail?id=63873
    113     public void testMultipleMembers() throws Exception {
    114         final int length = HELLO_WORLD_GZIPPED.length;
    115         byte[] data = new byte[length * 2];
    116         System.arraycopy(HELLO_WORLD_GZIPPED, 0, data, 0, length);
    117         System.arraycopy(HELLO_WORLD_GZIPPED, 0, data, length, length);
    118 
    119         assertEquals("Hello WorldHello World", new String(gunzip(data), "UTF-8"));
    120     }
    121 
    122     // https://code.google.com/p/android/issues/detail?id=63873
    123     public void testTrailingNonGzipData() throws Exception {
    124         final int length = HELLO_WORLD_GZIPPED.length;
    125         // 50 bytes of 0s at the end of the first message.
    126         byte[] data = new byte[length  + 50];
    127         System.arraycopy(HELLO_WORLD_GZIPPED, 0, data, 0, length);
    128         assertEquals("Hello World", new String(gunzip(data), "UTF-8"));
    129     }
    130 
    131     // https://code.google.com/p/android/issues/detail?id=63873
    132     //
    133     // Differences from the RI: Tests show the RI ignores *some* types of partial
    134     // data but not others and this test case fails as a result. Our implementation
    135     // will throw if it sees the gzip magic sequence at the end of a member
    136     // but malformed / invalid data after.
    137     public void testTrailingHeaderAndPartialMember() throws Exception {
    138         final int length = HELLO_WORLD_GZIPPED.length;
    139         // Copy just the header from HELLO_WORLD_GZIPPED so that our input
    140         // stream becomes one complete member + a header member.
    141         byte[] data = new byte[length  + 10];
    142         System.arraycopy(HELLO_WORLD_GZIPPED, 0, data, 0, length);
    143         System.arraycopy(HELLO_WORLD_GZIPPED, 0, data, length, 10);
    144 
    145         // The trailing data is ignored since the amount of data available is
    146         // less than the size of a header + trailer (which is the absolute minimum required).
    147         byte[] unzipped = gunzip(data);
    148         assertEquals("Hello World", new String(unzipped, StandardCharsets.UTF_8));
    149 
    150         // Must fail here because the data contains a full header and partial data.
    151         data = new byte[length*2 - 4];
    152         System.arraycopy(HELLO_WORLD_GZIPPED, 0, data, 0, length);
    153         System.arraycopy(HELLO_WORLD_GZIPPED, 0, data, length, length - 4);
    154 
    155         try {
    156             gunzip(data);
    157             fail();
    158         } catch (EOFException expected) {
    159         }
    160     }
    161 
    162     // https://code.google.com/p/android/issues/detail?id=66409
    163     public void testMultipleMembersWithCustomBufferSize() throws Exception {
    164         final int[] memberSizes = new int[] { 1000, 2000 };
    165 
    166         // We don't care what the exact contents of this file is, as long
    167         // as the file has multiple members, and that the (compressed) size of
    168         // the second member is larger than the size of the input buffer.
    169         //
    170         // There's no way to achieve this for a GZIPOutputStream so we generate
    171         // pseudo-random sequence of bytes and assert that they don't compress
    172         // well.
    173         final Random r = new Random(10);
    174         byte[] bytes = new byte[3000];
    175         r.nextBytes(bytes);
    176 
    177         File f = File.createTempFile("GZIPInputStreamTest", ".gzip");
    178         int offset = 0;
    179         for (int size : memberSizes) {
    180             GZIPOutputStream gzos = null;
    181             try {
    182                 FileOutputStream fos = new FileOutputStream(f, true /* append */);
    183                 gzos = new GZIPOutputStream(fos, size + 1);
    184                 gzos.write(bytes, offset, size);
    185                 offset += size;
    186                 gzos.finish();
    187             } finally {
    188                 IoUtils.closeQuietly(gzos);
    189             }
    190         }
    191 
    192         assertTrue(f.length() > 2048);
    193 
    194         FileInputStream fis = new FileInputStream(f);
    195         GZIPInputStream gzip = null;
    196         try {
    197             gzip = new GZIPInputStream(fis, memberSizes[0]);
    198             byte[] unzipped = Streams.readFully(gzip);
    199             assertTrue(Arrays.equals(bytes, unzipped));
    200         } finally {
    201             IoUtils.closeQuietly(gzip);
    202         }
    203     }
    204 
    205     /**
    206      * Test a openJdk8 fix for case where GZIPInputStream.readTrailer may accidently
    207      * close the input stream if trailing bytes looks "close" enough. Wrapping GZIP in
    208      * a ZipOutputStream will do that. */
    209     public void testNoCloseInReadTrailerDueToRead() throws IOException {
    210         final int numBytes = 128;
    211         byte[] data = new byte[numBytes];
    212         final boolean[] closedHolder = new boolean[]{false};
    213 
    214         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    215         try (ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
    216             zipOutputStream.putNextEntry(new ZipEntry("entry1"));
    217             try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(zipOutputStream)) {
    218                 gzipOutputStream.write(data);
    219             }
    220         }
    221 
    222         final byte[] compressedData = byteArrayOutputStream.toByteArray();
    223         InputStream byteArrayInputStream =
    224             new ByteArrayInputStream(compressedData) {
    225                 @Override
    226                 public void close() throws IOException {
    227                     closedHolder[0] = true;
    228                 }
    229             };
    230 
    231         try (ZipInputStream zipInputStream = new ZipInputStream(byteArrayInputStream)) {
    232             zipInputStream.getNextEntry();
    233             try (InputStream in = new GZIPInputStream(zipInputStream)) {
    234                 assertEquals(numBytes, in.skip(numBytes+1));
    235                 assertFalse(closedHolder[0]);
    236             }
    237             assertTrue(closedHolder[0]);
    238         }
    239     }
    240 
    241     public static byte[] gunzip(byte[] bytes) throws IOException {
    242         ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
    243         try (InputStream in = new GZIPInputStream(bis)) {
    244             ByteArrayOutputStream out = new ByteArrayOutputStream();
    245             byte[] buffer = new byte[1024];
    246             int count;
    247             while ((count = in.read(buffer)) != -1) {
    248                 out.write(buffer, 0, count);
    249             }
    250 
    251             return out.toByteArray();
    252         }
    253     }
    254 }
    255