| /* |
| * Copyright (C) 2014 Square, Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package okio; |
| |
| import java.io.IOException; |
| import java.util.zip.CRC32; |
| import org.junit.Test; |
| |
| import static kotlin.text.Charsets.UTF_8; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| public final class GzipSourceTest { |
| |
| @Test public void gunzip() throws Exception { |
| Buffer gzipped = new Buffer(); |
| gzipped.write(gzipHeader); |
| gzipped.write(deflated); |
| gzipped.write(gzipTrailer); |
| assertGzipped(gzipped); |
| } |
| |
| @Test public void gunzip_withHCRC() throws Exception { |
| CRC32 hcrc = new CRC32(); |
| ByteString gzipHeader = gzipHeaderWithFlags((byte) 0x02); |
| hcrc.update(gzipHeader.toByteArray()); |
| |
| Buffer gzipped = new Buffer(); |
| gzipped.write(gzipHeader); |
| gzipped.writeShort(TestUtil.reverseBytes((short) hcrc.getValue())); // little endian |
| gzipped.write(deflated); |
| gzipped.write(gzipTrailer); |
| assertGzipped(gzipped); |
| } |
| |
| @Test public void gunzip_withExtra() throws Exception { |
| Buffer gzipped = new Buffer(); |
| gzipped.write(gzipHeaderWithFlags((byte) 0x04)); |
| gzipped.writeShort(TestUtil.reverseBytes((short) 7)); // little endian extra length |
| gzipped.write("blubber".getBytes(UTF_8), 0, 7); |
| gzipped.write(deflated); |
| gzipped.write(gzipTrailer); |
| assertGzipped(gzipped); |
| } |
| |
| @Test public void gunzip_withName() throws Exception { |
| Buffer gzipped = new Buffer(); |
| gzipped.write(gzipHeaderWithFlags((byte) 0x08)); |
| gzipped.write("foo.txt".getBytes(UTF_8), 0, 7); |
| gzipped.writeByte(0); // zero-terminated |
| gzipped.write(deflated); |
| gzipped.write(gzipTrailer); |
| assertGzipped(gzipped); |
| } |
| |
| @Test public void gunzip_withComment() throws Exception { |
| Buffer gzipped = new Buffer(); |
| gzipped.write(gzipHeaderWithFlags((byte) 0x10)); |
| gzipped.write("rubbish".getBytes(UTF_8), 0, 7); |
| gzipped.writeByte(0); // zero-terminated |
| gzipped.write(deflated); |
| gzipped.write(gzipTrailer); |
| assertGzipped(gzipped); |
| } |
| |
| /** |
| * For portability, it is a good idea to export the gzipped bytes and try running gzip. Ex. |
| * {@code echo gzipped | base64 --decode | gzip -l -v} |
| */ |
| @Test public void gunzip_withAll() throws Exception { |
| Buffer gzipped = new Buffer(); |
| gzipped.write(gzipHeaderWithFlags((byte) 0x1c)); |
| gzipped.writeShort(TestUtil.reverseBytes((short) 7)); // little endian extra length |
| gzipped.write("blubber".getBytes(UTF_8), 0, 7); |
| gzipped.write("foo.txt".getBytes(UTF_8), 0, 7); |
| gzipped.writeByte(0); // zero-terminated |
| gzipped.write("rubbish".getBytes(UTF_8), 0, 7); |
| gzipped.writeByte(0); // zero-terminated |
| gzipped.write(deflated); |
| gzipped.write(gzipTrailer); |
| assertGzipped(gzipped); |
| } |
| |
| private void assertGzipped(Buffer gzipped) throws IOException { |
| Buffer gunzipped = gunzip(gzipped); |
| assertEquals("It's a UNIX system! I know this!", gunzipped.readUtf8()); |
| } |
| |
| /** |
| * Note that you cannot test this with old versions of gzip, as they interpret flag bit 1 as |
| * CONTINUATION, not HCRC. For example, this is the case with the default gzip on osx. |
| */ |
| @Test public void gunzipWhenHeaderCRCIncorrect() { |
| Buffer gzipped = new Buffer(); |
| gzipped.write(gzipHeaderWithFlags((byte) 0x02)); |
| gzipped.writeShort((short) 0); // wrong HCRC! |
| gzipped.write(deflated); |
| gzipped.write(gzipTrailer); |
| |
| try { |
| gunzip(gzipped); |
| fail(); |
| } catch (IOException e) { |
| assertEquals("FHCRC: actual 0x0000261d != expected 0x00000000", e.getMessage()); |
| } |
| } |
| |
| @Test public void gunzipWhenCRCIncorrect() { |
| Buffer gzipped = new Buffer(); |
| gzipped.write(gzipHeader); |
| gzipped.write(deflated); |
| gzipped.writeInt(TestUtil.reverseBytes(0x1234567)); // wrong CRC |
| gzipped.write(gzipTrailer.toByteArray(), 3, 4); |
| |
| try { |
| gunzip(gzipped); |
| fail(); |
| } catch (IOException e) { |
| assertEquals("CRC: actual 0x37ad8f8d != expected 0x01234567", e.getMessage()); |
| } |
| } |
| |
| @Test public void gunzipWhenLengthIncorrect() { |
| Buffer gzipped = new Buffer(); |
| gzipped.write(gzipHeader); |
| gzipped.write(deflated); |
| gzipped.write(gzipTrailer.toByteArray(), 0, 4); |
| gzipped.writeInt(TestUtil.reverseBytes(0x123456)); // wrong length |
| |
| try { |
| gunzip(gzipped); |
| fail(); |
| } catch (IOException e) { |
| assertEquals("ISIZE: actual 0x00000020 != expected 0x00123456", e.getMessage()); |
| } |
| } |
| |
| @Test public void gunzipExhaustsSource() throws Exception { |
| Buffer gzippedSource = new Buffer() |
| .write(ByteString.decodeHex("1f8b08000000000000004b4c4a0600c241243503000000")); // 'abc' |
| |
| ExhaustableSource exhaustableSource = new ExhaustableSource(gzippedSource); |
| BufferedSource gunzippedSource = Okio.buffer(new GzipSource(exhaustableSource)); |
| |
| assertEquals('a', gunzippedSource.readByte()); |
| assertEquals('b', gunzippedSource.readByte()); |
| assertEquals('c', gunzippedSource.readByte()); |
| assertFalse(exhaustableSource.exhausted); |
| assertEquals(-1, gunzippedSource.read(new Buffer(), 1)); |
| assertTrue(exhaustableSource.exhausted); |
| } |
| |
| @Test public void gunzipThrowsIfSourceIsNotExhausted() throws Exception { |
| Buffer gzippedSource = new Buffer() |
| .write(ByteString.decodeHex("1f8b08000000000000004b4c4a0600c241243503000000")); // 'abc' |
| gzippedSource.writeByte('d'); // This byte shouldn't be here! |
| |
| BufferedSource gunzippedSource = Okio.buffer(new GzipSource(gzippedSource)); |
| |
| assertEquals('a', gunzippedSource.readByte()); |
| assertEquals('b', gunzippedSource.readByte()); |
| assertEquals('c', gunzippedSource.readByte()); |
| try { |
| gunzippedSource.readByte(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| } |
| |
| private ByteString gzipHeaderWithFlags(byte flags) { |
| byte[] result = gzipHeader.toByteArray(); |
| result[3] = flags; |
| return ByteString.of(result); |
| } |
| |
| private final ByteString gzipHeader = ByteString.decodeHex("1f8b0800000000000000"); |
| |
| // Deflated "It's a UNIX system! I know this!" |
| private final ByteString deflated = ByteString.decodeHex( |
| "f32c512f56485408f5f38c5028ae2c2e49cd5554f054c8cecb2f5728c9c82c560400"); |
| |
| private final ByteString gzipTrailer = ByteString.decodeHex("" |
| + "8d8fad37" // Checksum of deflated. |
| + "20000000" // 32 in little endian. |
| ); |
| |
| private Buffer gunzip(Buffer gzipped) throws IOException { |
| Buffer result = new Buffer(); |
| GzipSource source = new GzipSource(gzipped); |
| while (source.read(result, Integer.MAX_VALUE) != -1) { |
| } |
| return result; |
| } |
| |
| /** This source keeps track of whether its read has returned -1. */ |
| static class ExhaustableSource implements Source { |
| private final Source source; |
| private boolean exhausted; |
| |
| ExhaustableSource(Source source) { |
| this.source = source; |
| } |
| |
| @Override public long read(Buffer sink, long byteCount) throws IOException { |
| long result = source.read(sink, byteCount); |
| if (result == -1) exhausted = true; |
| return result; |
| } |
| |
| @Override public Timeout timeout() { |
| return source.timeout(); |
| } |
| |
| @Override public void close() throws IOException { |
| source.close(); |
| } |
| } |
| } |