View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.http.client.entity;
28  
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.PushbackInputStream;
32  import java.util.zip.DataFormatException;
33  import java.util.zip.Inflater;
34  import java.util.zip.InflaterInputStream;
35  
36  /** Deflate input stream.    This class includes logic needed for various Rfc's in order
37  * to reasonably implement the "deflate" compression style.
38  */
39  public class DeflateInputStream extends InputStream
40  {
41      private InputStream sourceStream;
42  
43      public DeflateInputStream(final InputStream wrapped)
44          throws IOException
45      {
46          /*
47              * A zlib stream will have a header.
48              *
49              * CMF | FLG [| DICTID ] | ...compressed data | ADLER32 |
50              *
51              * * CMF is one byte.
52              *
53              * * FLG is one byte.
54              *
55              * * DICTID is four bytes, and only present if FLG.FDICT is set.
56              *
57              * Sniff the content. Does it look like a zlib stream, with a CMF, etc? c.f. RFC1950,
58              * section 2.2. http://tools.ietf.org/html/rfc1950#page-4
59              *
60              * We need to see if it looks like a proper zlib stream, or whether it is just a deflate
61              * stream. RFC2616 calls zlib streams deflate. Confusing, isn't it? That's why some servers
62              * implement deflate Content-Encoding using deflate streams, rather than zlib streams.
63              *
64              * We could start looking at the bytes, but to be honest, someone else has already read
65              * the RFCs and implemented that for us. So we'll just use the JDK libraries and exception
66              * handling to do this. If that proves slow, then we could potentially change this to check
67              * the first byte - does it look like a CMF? What about the second byte - does it look like
68              * a FLG, etc.
69              */
70  
71          /* We read a small buffer to sniff the content. */
72          final byte[] peeked = new byte[6];
73  
74          final PushbackInputStream pushback = new PushbackInputStream(wrapped, peeked.length);
75  
76          final int headerLength = pushback.read(peeked);
77  
78          if (headerLength == -1) {
79              throw new IOException("Unable to read the response");
80          }
81  
82          /* We try to read the first uncompressed byte. */
83          final byte[] dummy = new byte[1];
84  
85          final Inflater inf = new Inflater();
86  
87          try {
88              int n;
89              while ((n = inf.inflate(dummy)) == 0) {
90                  if (inf.finished()) {
91  
92                      /* Not expecting this, so fail loudly. */
93                      throw new IOException("Unable to read the response");
94                  }
95  
96                  if (inf.needsDictionary()) {
97  
98                      /* Need dictionary - then it must be zlib stream with DICTID part? */
99                      break;
100                 }
101 
102                 if (inf.needsInput()) {
103                     inf.setInput(peeked);
104                 }
105             }
106 
107             if (n == -1) {
108                 throw new IOException("Unable to read the response");
109             }
110 
111             /*
112                 * We read something without a problem, so it's a valid zlib stream. Just need to reset
113                 * and return an unused InputStream now.
114                 */
115             pushback.unread(peeked, 0, headerLength);
116             sourceStream = new DeflateStream(pushback, new Inflater());
117         } catch (final DataFormatException e) {
118 
119             /* Presume that it's an RFC1951 deflate stream rather than RFC1950 zlib stream and try
120                 * again. */
121             pushback.unread(peeked, 0, headerLength);
122             sourceStream = new DeflateStream(pushback, new Inflater(true));
123         } finally {
124             inf.end();
125         }
126 
127     }
128 
129     /** Read a byte.
130     */
131     @Override
132     public int read()
133         throws IOException
134     {
135         return sourceStream.read();
136     }
137 
138     /** Read lots of bytes.
139     */
140     @Override
141     public int read(final byte[] b)
142         throws IOException
143     {
144         return sourceStream.read(b);
145     }
146 
147     /** Read lots of specific bytes.
148     */
149     @Override
150     public int read(final byte[] b, final int off, final int len)
151         throws IOException
152     {
153         return sourceStream.read(b,off,len);
154     }
155 
156     /** Skip
157     */
158     @Override
159     public long skip(final long n)
160         throws IOException
161     {
162         return sourceStream.skip(n);
163     }
164 
165     /** Get available.
166     */
167     @Override
168     public int available()
169         throws IOException
170     {
171         return sourceStream.available();
172     }
173 
174     /** Mark.
175     */
176     @Override
177     public void mark(final int readLimit)
178     {
179         sourceStream.mark(readLimit);
180     }
181 
182     /** Reset.
183     */
184     @Override
185     public void reset()
186         throws IOException
187     {
188         sourceStream.reset();
189     }
190 
191     /** Check if mark is supported.
192     */
193     @Override
194     public boolean markSupported()
195     {
196         return sourceStream.markSupported();
197     }
198 
199     /** Close.
200     */
201     @Override
202     public void close()
203         throws IOException
204     {
205         sourceStream.close();
206     }
207 
208     static class DeflateStream extends InflaterInputStream {
209 
210         private boolean closed = false;
211 
212         public DeflateStream(final InputStream in, final Inflater inflater) {
213             super(in, inflater);
214         }
215 
216         @Override
217         public void close() throws IOException {
218             if (closed) {
219                 return;
220             }
221             closed = true;
222             inf.end();
223             super.close();
224         }
225 
226     }
227 
228 }
229