View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.client;
20  
21  import java.nio.ByteBuffer;
22  import java.util.Arrays;
23  import java.util.zip.DataFormatException;
24  import java.util.zip.Inflater;
25  import java.util.zip.ZipException;
26  
27  import org.eclipse.jetty.util.BufferUtil;
28  
29  /**
30   * {@link ContentDecoder} for the "gzip" encoding.
31   */
32  public class GZIPContentDecoder implements ContentDecoder
33  {
34      private final Inflater inflater = new Inflater(true);
35      private final byte[] bytes;
36      private byte[] output;
37      private State state;
38      private int size;
39      private int value;
40      private byte flags;
41  
42      public GZIPContentDecoder()
43      {
44          this(2048);
45      }
46  
47      public GZIPContentDecoder(int bufferSize)
48      {
49          this.bytes = new byte[bufferSize];
50          reset();
51      }
52  
53      /**
54       * {@inheritDoc}
55       * <p>If the decoding did not produce any output, for example because it consumed gzip header
56       * or trailer bytes, it returns a buffer with zero capacity.</p>
57       * <p>This method never returns null.</p>
58       * <p>The given {@code buffer}'s position will be modified to reflect the bytes consumed during
59       * the decoding.</p>
60       * <p>The decoding may be finished without consuming the buffer completely if the buffer contains
61       * gzip bytes plus other bytes (either plain or gzipped).</p>
62       */
63      @Override
64      public ByteBuffer decode(ByteBuffer buffer)
65      {
66          try
67          {
68              while (buffer.hasRemaining())
69              {
70                  byte currByte = buffer.get();
71                  switch (state)
72                  {
73                      case INITIAL:
74                      {
75                          buffer.position(buffer.position() - 1);
76                          state = State.ID;
77                          break;
78                      }
79                      case ID:
80                      {
81                          value += (currByte & 0xFF) << 8 * size;
82                          ++size;
83                          if (size == 2)
84                          {
85                              if (value != 0x8B1F)
86                                  throw new ZipException("Invalid gzip bytes");
87                              state = State.CM;
88                          }
89                          break;
90                      }
91                      case CM:
92                      {
93                          if ((currByte & 0xFF) != 0x08)
94                              throw new ZipException("Invalid gzip compression method");
95                          state = State.FLG;
96                          break;
97                      }
98                      case FLG:
99                      {
100                         flags = currByte;
101                         state = State.MTIME;
102                         size = 0;
103                         value = 0;
104                         break;
105                     }
106                     case MTIME:
107                     {
108                         // Skip the 4 MTIME bytes
109                         ++size;
110                         if (size == 4)
111                             state = State.XFL;
112                         break;
113                     }
114                     case XFL:
115                     {
116                         // Skip XFL
117                         state = State.OS;
118                         break;
119                     }
120                     case OS:
121                     {
122                         // Skip OS
123                         state = State.FLAGS;
124                         break;
125                     }
126                     case FLAGS:
127                     {
128                         buffer.position(buffer.position() - 1);
129                         if ((flags & 0x04) == 0x04)
130                         {
131                             state = State.EXTRA_LENGTH;
132                             size = 0;
133                             value = 0;
134                         }
135                         else if ((flags & 0x08) == 0x08)
136                             state = State.NAME;
137                         else if ((flags & 0x10) == 0x10)
138                             state = State.COMMENT;
139                         else if ((flags & 0x2) == 0x2)
140                         {
141                             state = State.HCRC;
142                             size = 0;
143                             value = 0;
144                         }
145                         else
146                             state = State.DATA;
147                         break;
148                     }
149                     case EXTRA_LENGTH:
150                     {
151                         value += (currByte & 0xFF) << 8 * size;
152                         ++size;
153                         if (size == 2)
154                             state = State.EXTRA;
155                         break;
156                     }
157                     case EXTRA:
158                     {
159                         // Skip EXTRA bytes
160                         --value;
161                         if (value == 0)
162                         {
163                             // Clear the EXTRA flag and loop on the flags
164                             flags &= ~0x04;
165                             state = State.FLAGS;
166                         }
167                         break;
168                     }
169                     case NAME:
170                     {
171                         // Skip NAME bytes
172                         if (currByte == 0)
173                         {
174                             // Clear the NAME flag and loop on the flags
175                             flags &= ~0x08;
176                             state = State.FLAGS;
177                         }
178                         break;
179                     }
180                     case COMMENT:
181                     {
182                         // Skip COMMENT bytes
183                         if (currByte == 0)
184                         {
185                             // Clear the COMMENT flag and loop on the flags
186                             flags &= ~0x10;
187                             state = State.FLAGS;
188                         }
189                         break;
190                     }
191                     case HCRC:
192                     {
193                         // Skip HCRC
194                         ++size;
195                         if (size == 2)
196                         {
197                             // Clear the HCRC flag and loop on the flags
198                             flags &= ~0x02;
199                             state = State.FLAGS;
200                         }
201                         break;
202                     }
203                     case DATA:
204                     {
205                         buffer.position(buffer.position() - 1);
206                         while (true)
207                         {
208                             int decoded = inflate(bytes);
209                             if (decoded == 0)
210                             {
211                                 if (inflater.needsInput())
212                                 {
213                                     if (buffer.hasRemaining())
214                                     {
215                                         byte[] input = new byte[buffer.remaining()];
216                                         buffer.get(input);
217                                         inflater.setInput(input);
218                                     }
219                                     else
220                                     {
221                                         if (output != null)
222                                         {
223                                             ByteBuffer result = ByteBuffer.wrap(output);
224                                             output = null;
225                                             return result;
226                                         }
227                                         break;
228                                     }
229                                 }
230                                 else if (inflater.finished())
231                                 {
232                                     int remaining = inflater.getRemaining();
233                                     buffer.position(buffer.limit() - remaining);
234                                     state = State.CRC;
235                                     size = 0;
236                                     value = 0;
237                                     break;
238                                 }
239                                 else
240                                 {
241                                     throw new ZipException("Invalid inflater state");
242                                 }
243                             }
244                             else
245                             {
246                                 if (output == null)
247                                 {
248                                     // Save the inflated bytes and loop to see if we have finished
249                                     output = new byte[decoded];
250                                     System.arraycopy(bytes, 0, output, 0, decoded);
251                                 }
252                                 else
253                                 {
254                                     // Accumulate inflated bytes and loop to see if we have finished
255                                     byte[] newOutput = new byte[output.length + decoded];
256                                     System.arraycopy(output, 0, newOutput, 0, output.length);
257                                     System.arraycopy(bytes, 0, newOutput, output.length, decoded);
258                                     output = newOutput;
259                                 }
260                             }
261                         }
262                         break;
263                     }
264                     case CRC:
265                     {
266                         value += (currByte & 0xFF) << 8 * size;
267                         ++size;
268                         if (size == 4)
269                         {
270                             // From RFC 1952, compliant decoders need not to verify the CRC
271                             state = State.ISIZE;
272                             size = 0;
273                             value = 0;
274                         }
275                         break;
276                     }
277                     case ISIZE:
278                     {
279                         value += (currByte & 0xFF) << 8 * size;
280                         ++size;
281                         if (size == 4)
282                         {
283                             if (value != inflater.getBytesWritten())
284                                 throw new ZipException("Invalid input size");
285 
286                             ByteBuffer result = output == null ? BufferUtil.EMPTY_BUFFER : ByteBuffer.wrap(output);
287                             reset();
288                             return result;
289                         }
290                         break;
291                     }
292                     default:
293                         throw new ZipException();
294                 }
295             }
296             return BufferUtil.EMPTY_BUFFER;
297         }
298         catch (ZipException x)
299         {
300             throw new RuntimeException(x);
301         }
302     }
303 
304     private int inflate(byte[] bytes) throws ZipException
305     {
306         try
307         {
308             return inflater.inflate(bytes);
309         }
310         catch (DataFormatException x)
311         {
312             throw new ZipException(x.getMessage());
313         }
314     }
315 
316     private void reset()
317     {
318         inflater.reset();
319         Arrays.fill(bytes, (byte)0);
320         output = null;
321         state = State.INITIAL;
322         size = 0;
323         value = 0;
324         flags = 0;
325     }
326 
327     protected boolean isFinished()
328     {
329         return state == State.INITIAL;
330     }
331 
332     /**
333      * Specialized {@link ContentDecoder.Factory} for the "gzip" encoding.
334      */
335     public static class Factory extends ContentDecoder.Factory
336     {
337         private final int bufferSize;
338 
339         public Factory()
340         {
341             this(2048);
342         }
343 
344         public Factory(int bufferSize)
345         {
346             super("gzip");
347             this.bufferSize = bufferSize;
348         }
349 
350         @Override
351         public ContentDecoder newContentDecoder()
352         {
353             return new GZIPContentDecoder(bufferSize);
354         }
355     }
356 
357     private enum State
358     {
359         INITIAL, ID, CM, FLG, MTIME, XFL, OS, FLAGS, EXTRA_LENGTH, EXTRA, NAME, COMMENT, HCRC, DATA, CRC, ISIZE
360     }
361 }