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  
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 = Arrays.copyOf(bytes, decoded);
250                                 }
251                                 else
252                                 {
253                                     // Accumulate inflated bytes and loop to see if we have finished
254                                     byte[] newOutput = Arrays.copyOf(output, output.length + decoded);
255                                     System.arraycopy(bytes, 0, newOutput, output.length, decoded);
256                                     output = newOutput;
257                                 }
258                             }
259                         }
260                         break;
261                     }
262                     case CRC:
263                     {
264                         value += (currByte & 0xFF) << 8 * size;
265                         ++size;
266                         if (size == 4)
267                         {
268                             // From RFC 1952, compliant decoders need not to verify the CRC
269                             state = State.ISIZE;
270                             size = 0;
271                             value = 0;
272                         }
273                         break;
274                     }
275                     case ISIZE:
276                     {
277                         value += (currByte & 0xFF) << 8 * size;
278                         ++size;
279                         if (size == 4)
280                         {
281                             if (value != inflater.getBytesWritten())
282                                 throw new ZipException("Invalid input size");
283 
284                             ByteBuffer result = output == null ? BufferUtil.EMPTY_BUFFER : ByteBuffer.wrap(output);
285                             reset();
286                             return result;
287                         }
288                         break;
289                     }
290                     default:
291                         throw new ZipException();
292                 }
293             }
294             return BufferUtil.EMPTY_BUFFER;
295         }
296         catch (ZipException x)
297         {
298             throw new RuntimeException(x);
299         }
300     }
301 
302     private int inflate(byte[] bytes) throws ZipException
303     {
304         try
305         {
306             return inflater.inflate(bytes);
307         }
308         catch (DataFormatException x)
309         {
310             throw new ZipException(x.getMessage());
311         }
312     }
313 
314     private void reset()
315     {
316         inflater.reset();
317         Arrays.fill(bytes, (byte)0);
318         output = null;
319         state = State.INITIAL;
320         size = 0;
321         value = 0;
322         flags = 0;
323     }
324 
325     protected boolean isFinished()
326     {
327         return state == State.INITIAL;
328     }
329 
330     /**
331      * Specialized {@link ContentDecoder.Factory} for the "gzip" encoding.
332      */
333     public static class Factory extends ContentDecoder.Factory
334     {
335         private final int bufferSize;
336 
337         public Factory()
338         {
339             this(2048);
340         }
341 
342         public Factory(int bufferSize)
343         {
344             super("gzip");
345             this.bufferSize = bufferSize;
346         }
347 
348         @Override
349         public ContentDecoder newContentDecoder()
350         {
351             return new GZIPContentDecoder(bufferSize);
352         }
353     }
354 
355     private enum State
356     {
357         INITIAL, ID, CM, FLG, MTIME, XFL, OS, FLAGS, EXTRA_LENGTH, EXTRA, NAME, COMMENT, HCRC, DATA, CRC, ISIZE
358     }
359 }