michael@0: /*
michael@0: * ====================================================================
michael@0: *
michael@0: * Licensed to the Apache Software Foundation (ASF) under one or more
michael@0: * contributor license agreements. See the NOTICE file distributed with
michael@0: * this work for additional information regarding copyright ownership.
michael@0: * The ASF licenses this file to You under the Apache License, Version 2.0
michael@0: * (the "License"); you may not use this file except in compliance with
michael@0: * the License. You may obtain a copy of the License at
michael@0: *
michael@0: * http://www.apache.org/licenses/LICENSE-2.0
michael@0: *
michael@0: * Unless required by applicable law or agreed to in writing, software
michael@0: * distributed under the License is distributed on an "AS IS" BASIS,
michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
michael@0: * See the License for the specific language governing permissions and
michael@0: * limitations under the License.
michael@0: * ====================================================================
michael@0: *
michael@0: * This software consists of voluntary contributions made by many
michael@0: * individuals on behalf of the Apache Software Foundation. For more
michael@0: * information on the Apache Software Foundation, please see
michael@0: * .
michael@0: *
michael@0: */
michael@0: package ch.boye.httpclientandroidlib.client.entity;
michael@0:
michael@0: import java.io.IOException;
michael@0: import java.io.InputStream;
michael@0: import java.io.PushbackInputStream;
michael@0: import java.util.zip.DataFormatException;
michael@0: import java.util.zip.Inflater;
michael@0: import java.util.zip.InflaterInputStream;
michael@0:
michael@0: import ch.boye.httpclientandroidlib.Header;
michael@0: import ch.boye.httpclientandroidlib.HttpEntity;
michael@0: import ch.boye.httpclientandroidlib.entity.HttpEntityWrapper;
michael@0:
michael@0: /**
michael@0: * {@link HttpEntityWrapper} responsible for handling deflate Content Coded responses. In RFC2616
michael@0: * terms, deflate
means a zlib
stream as defined in RFC1950. Some server
michael@0: * implementations have misinterpreted RFC2616 to mean that a deflate
stream as
michael@0: * defined in RFC1951 should be used (or maybe they did that since that's how IE behaves?). It's
michael@0: * confusing that deflate
in HTTP 1.1 means zlib
streams rather than
michael@0: * deflate
streams. We handle both types in here, since that's what is seen on the
michael@0: * internet. Moral - prefer gzip
!
michael@0: *
michael@0: * @see GzipDecompressingEntity
michael@0: *
michael@0: * @since 4.1
michael@0: */
michael@0: public class DeflateDecompressingEntity extends DecompressingEntity {
michael@0:
michael@0: /**
michael@0: * Creates a new {@link DeflateDecompressingEntity} which will wrap the specified
michael@0: * {@link HttpEntity}.
michael@0: *
michael@0: * @param entity
michael@0: * a non-null {@link HttpEntity} to be wrapped
michael@0: */
michael@0: public DeflateDecompressingEntity(final HttpEntity entity) {
michael@0: super(entity);
michael@0: }
michael@0:
michael@0: /**
michael@0: * Returns the non-null InputStream that should be returned to by all requests to
michael@0: * {@link #getContent()}.
michael@0: *
michael@0: * @return a non-null InputStream
michael@0: * @throws IOException if there was a problem
michael@0: */
michael@0: @Override
michael@0: InputStream getDecompressingInputStream(final InputStream wrapped) throws IOException {
michael@0: /*
michael@0: * A zlib stream will have a header.
michael@0: *
michael@0: * CMF | FLG [| DICTID ] | ...compressed data | ADLER32 |
michael@0: *
michael@0: * * CMF is one byte.
michael@0: *
michael@0: * * FLG is one byte.
michael@0: *
michael@0: * * DICTID is four bytes, and only present if FLG.FDICT is set.
michael@0: *
michael@0: * Sniff the content. Does it look like a zlib stream, with a CMF, etc? c.f. RFC1950,
michael@0: * section 2.2. http://tools.ietf.org/html/rfc1950#page-4
michael@0: *
michael@0: * We need to see if it looks like a proper zlib stream, or whether it is just a deflate
michael@0: * stream. RFC2616 calls zlib streams deflate. Confusing, isn't it? That's why some servers
michael@0: * implement deflate Content-Encoding using deflate streams, rather than zlib streams.
michael@0: *
michael@0: * We could start looking at the bytes, but to be honest, someone else has already read
michael@0: * the RFCs and implemented that for us. So we'll just use the JDK libraries and exception
michael@0: * handling to do this. If that proves slow, then we could potentially change this to check
michael@0: * the first byte - does it look like a CMF? What about the second byte - does it look like
michael@0: * a FLG, etc.
michael@0: */
michael@0:
michael@0: /* We read a small buffer to sniff the content. */
michael@0: byte[] peeked = new byte[6];
michael@0:
michael@0: PushbackInputStream pushback = new PushbackInputStream(wrapped, peeked.length);
michael@0:
michael@0: int headerLength = pushback.read(peeked);
michael@0:
michael@0: if (headerLength == -1) {
michael@0: throw new IOException("Unable to read the response");
michael@0: }
michael@0:
michael@0: /* We try to read the first uncompressed byte. */
michael@0: byte[] dummy = new byte[1];
michael@0:
michael@0: Inflater inf = new Inflater();
michael@0:
michael@0: try {
michael@0: int n;
michael@0: while ((n = inf.inflate(dummy)) == 0) {
michael@0: if (inf.finished()) {
michael@0:
michael@0: /* Not expecting this, so fail loudly. */
michael@0: throw new IOException("Unable to read the response");
michael@0: }
michael@0:
michael@0: if (inf.needsDictionary()) {
michael@0:
michael@0: /* Need dictionary - then it must be zlib stream with DICTID part? */
michael@0: break;
michael@0: }
michael@0:
michael@0: if (inf.needsInput()) {
michael@0: inf.setInput(peeked);
michael@0: }
michael@0: }
michael@0:
michael@0: if (n == -1) {
michael@0: throw new IOException("Unable to read the response");
michael@0: }
michael@0:
michael@0: /*
michael@0: * We read something without a problem, so it's a valid zlib stream. Just need to reset
michael@0: * and return an unused InputStream now.
michael@0: */
michael@0: pushback.unread(peeked, 0, headerLength);
michael@0: return new InflaterInputStream(pushback);
michael@0: } catch (DataFormatException e) {
michael@0:
michael@0: /* Presume that it's an RFC1951 deflate stream rather than RFC1950 zlib stream and try
michael@0: * again. */
michael@0: pushback.unread(peeked, 0, headerLength);
michael@0: return new InflaterInputStream(pushback, new Inflater(true));
michael@0: }
michael@0: }
michael@0:
michael@0: /**
michael@0: * {@inheritDoc}
michael@0: */
michael@0: @Override
michael@0: public Header getContentEncoding() {
michael@0:
michael@0: /* This HttpEntityWrapper has dealt with the Content-Encoding. */
michael@0: return null;
michael@0: }
michael@0:
michael@0: /**
michael@0: * {@inheritDoc}
michael@0: */
michael@0: @Override
michael@0: public long getContentLength() {
michael@0:
michael@0: /* Length of inflated content is unknown. */
michael@0: return -1;
michael@0: }
michael@0:
michael@0: }