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  
20  package org.eclipse.jetty.http2.hpack;
21  
22  import java.nio.ByteBuffer;
23  
24  import org.eclipse.jetty.http.BadMessageException;
25  import org.eclipse.jetty.http.HttpField;
26  import org.eclipse.jetty.http.HttpHeader;
27  import org.eclipse.jetty.http.HttpStatus;
28  import org.eclipse.jetty.http.MetaData;
29  import org.eclipse.jetty.http2.hpack.HpackContext.Entry;
30  import org.eclipse.jetty.util.TypeUtil;
31  import org.eclipse.jetty.util.log.Log;
32  import org.eclipse.jetty.util.log.Logger;
33  
34  /**
35   * Hpack Decoder
36   * <p>This is not thread safe and may only be called by 1 thread at a time.</p>
37   */
38  public class HpackDecoder
39  {
40      public static final Logger LOG = Log.getLogger(HpackDecoder.class);
41      public final static HttpField.LongValueHttpField CONTENT_LENGTH_0 =
42              new HttpField.LongValueHttpField(HttpHeader.CONTENT_LENGTH,0L);
43  
44      private final HpackContext _context;
45      private final MetaDataBuilder _builder;
46      private int _localMaxDynamicTableSize;
47  
48      /**
49       * @param localMaxDynamicTableSize  The maximum allowed size of the local dynamic header field table.
50       * @param maxHeaderSize The maximum allowed size of a headers block, expressed as total of all name and value characters.
51       */
52      public HpackDecoder(int localMaxDynamicTableSize, int maxHeaderSize)
53      {
54          _context=new HpackContext(localMaxDynamicTableSize);
55          _localMaxDynamicTableSize=localMaxDynamicTableSize;
56          _builder = new MetaDataBuilder(maxHeaderSize);
57      }
58  
59      public HpackContext getHpackContext()
60      {
61          return _context;
62      }
63  
64      public void setLocalMaxDynamicTableSize(int localMaxdynamciTableSize)
65      {
66          _localMaxDynamicTableSize=localMaxdynamciTableSize;
67      }
68  
69      public MetaData decode(ByteBuffer buffer)
70      {
71          if (LOG.isDebugEnabled())
72              LOG.debug(String.format("CtxTbl[%x] decoding %d octets",_context.hashCode(),buffer.remaining()));
73  
74          // If the buffer is big, don't even think about decoding it
75          if (buffer.remaining()>_builder.getMaxSize())
76              throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413,"Header frame size "+buffer.remaining()+">"+_builder.getMaxSize());
77  
78  
79          while(buffer.hasRemaining())
80          {
81              if (LOG.isDebugEnabled() && buffer.hasArray())
82              {
83                  int l=Math.min(buffer.remaining(),32);
84                  LOG.debug("decode {}{}",
85                          TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+buffer.position(),l),
86                          l<buffer.remaining()?"...":"");
87              }
88  
89              byte b = buffer.get();
90              if (b<0)
91              {
92                  // 7.1 indexed if the high bit is set
93                  int index = NBitInteger.decode(buffer,7);
94                  Entry entry=_context.get(index);
95                  if (entry==null)
96                  {
97                      throw new BadMessageException("Unknown index "+index);
98                  }
99                  else if (entry.isStatic())
100                 {
101                     if (LOG.isDebugEnabled())
102                         LOG.debug("decode IdxStatic {}",entry);
103                     // emit field
104                     _builder.emit(entry.getHttpField());
105 
106                     // TODO copy and add to reference set if there is room
107                     // _context.add(entry.getHttpField());
108                 }
109                 else
110                 {
111                     if (LOG.isDebugEnabled())
112                         LOG.debug("decode Idx {}",entry);
113                     // emit
114                     _builder.emit(entry.getHttpField());
115                 }
116             }
117             else
118             {
119                 // look at the first nibble in detail
120                 byte f= (byte)((b&0xF0)>>4);
121                 String name;
122                 HttpHeader header;
123                 String value;
124 
125                 boolean indexed;
126                 int name_index;
127 
128                 switch (f)
129                 {
130                     case 2: // 7.3
131                     case 3: // 7.3
132                         // change table size
133                         int size = NBitInteger.decode(buffer,5);
134                         if (LOG.isDebugEnabled())
135                             LOG.debug("decode resize="+size);
136                         if (size>_localMaxDynamicTableSize)
137                             throw new IllegalArgumentException();
138                         _context.resize(size);
139                         continue;
140 
141                     case 0: // 7.2.2
142                     case 1: // 7.2.3
143                         indexed=false;
144                         name_index=NBitInteger.decode(buffer,4);
145                         break;
146 
147 
148                     case 4: // 7.2.1
149                     case 5: // 7.2.1
150                     case 6: // 7.2.1
151                     case 7: // 7.2.1
152                         indexed=true;
153                         name_index=NBitInteger.decode(buffer,6);
154                         break;
155 
156                     default:
157                         throw new IllegalStateException();
158                 }
159 
160 
161                 boolean huffmanName=false;
162 
163                 // decode the name
164                 if (name_index>0)
165                 {
166                     Entry name_entry=_context.get(name_index);
167                     name=name_entry.getHttpField().getName();
168                     header=name_entry.getHttpField().getHeader();
169                 }
170                 else
171                 {
172                     huffmanName = (buffer.get()&0x80)==0x80;
173                     int length = NBitInteger.decode(buffer,7);
174                     _builder.checkSize(length,huffmanName);
175                     if (huffmanName)
176                         name=Huffman.decode(buffer,length);
177                     else
178                         name=toASCIIString(buffer,length);
179                     for (int i=0;i<name.length();i++)
180                     {
181                         char c=name.charAt(i);
182                         if (c>='A'&&c<='Z')
183                         {
184                             throw new BadMessageException(400,"Uppercase header name");
185                         }
186                     }
187                     header=HttpHeader.CACHE.get(name);
188                 }
189 
190                 // decode the value
191                 boolean huffmanValue = (buffer.get()&0x80)==0x80;
192                 int length = NBitInteger.decode(buffer,7);
193                 _builder.checkSize(length,huffmanValue);
194                 if (huffmanValue)
195                     value=Huffman.decode(buffer,length);
196                 else
197                     value=toASCIIString(buffer,length);
198 
199                 // Make the new field
200                 HttpField field;
201                 if (header==null)
202                 {
203                     // just make a normal field and bypass header name lookup
204                     field = new HttpField(null,name,value);
205                 }
206                 else
207                 {
208                     // might be worthwhile to create a value HttpField if it is indexed
209                     // and/or of a type that may be looked up multiple times.
210                     switch(header)
211                     {
212                         case C_STATUS:
213                             if (indexed)
214                                 field = new HttpField.IntValueHttpField(header,name,value);
215                             else
216                                 field = new HttpField(header,name,value);
217                             break;
218 
219                         case C_AUTHORITY:
220                             field = new AuthorityHttpField(value);
221                             break;
222 
223                         case CONTENT_LENGTH:
224                             if ("0".equals(value))
225                                 field = CONTENT_LENGTH_0;
226                             else
227                                 field = new HttpField.LongValueHttpField(header,name,value);
228                             break;
229 
230                         default:
231                             field = new HttpField(header,name,value);
232                             break;
233                     }
234                 }
235 
236                 if (LOG.isDebugEnabled())
237                 {
238                     LOG.debug("decoded '{}' by {}/{}/{}",
239                             field,
240                             name_index > 0 ? "IdxName" : (huffmanName ? "HuffName" : "LitName"),
241                             huffmanValue ? "HuffVal" : "LitVal",
242                             indexed ? "Idx" : "");
243                 }
244 
245                 // emit the field
246                 _builder.emit(field);
247 
248                 // if indexed
249                 if (indexed)
250                 {
251                     // add to dynamic table
252                     _context.add(field);
253                 }
254 
255             }
256         }
257 
258         return _builder.build();
259     }
260 
261     public static String toASCIIString(ByteBuffer buffer,int length)
262     {
263         StringBuilder builder = new StringBuilder(length);
264         int position=buffer.position();
265         int start=buffer.arrayOffset()+ position;
266         int end=start+length;
267         buffer.position(position+length);
268         byte[] array=buffer.array();
269         for (int i=start;i<end;i++)
270             builder.append((char)(0x7f&array[i]));
271         return builder.toString();
272     }
273 
274     @Override
275     public String toString()
276     {
277         return String.format("HpackDecoder@%x{%s}",hashCode(),_context);
278     }
279 }