View Javadoc

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