blob: 69b81e3f4758a5392de0d13bf7e8572dd175bb51 [file] [log] [blame]
/*
* 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();
}
}
}