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