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