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())
82              {
83                  int l=Math.min(buffer.remaining(),16);
84                  // TODO: not guaranteed the buffer has a backing array !
85                  LOG.debug("decode {}{}",
86                          TypeUtil.toHexString(buffer.array(),buffer.arrayOffset()+buffer.position(),l),
87                          l<buffer.remaining()?"...":"");
88              }
89  
90              byte b = buffer.get();
91              if (b<0)
92              {
93                  // 7.1 indexed if the high bit is set
94                  int index = NBitInteger.decode(buffer,7);
95                  Entry entry=_context.get(index);
96                  if (entry==null)
97                  {
98                      throw new BadMessageException("Unknown index "+index);
99                  }
100                 else if (entry.isStatic())
101                 {
102                     if (LOG.isDebugEnabled())
103                         LOG.debug("decode IdxStatic {}",entry);
104                     // emit field
105                     _builder.emit(entry.getHttpField());
106 
107                     // TODO copy and add to reference set if there is room
108                     // _context.add(entry.getHttpField());
109                 }
110                 else
111                 {
112                     if (LOG.isDebugEnabled())
113                         LOG.debug("decode Idx {}",entry);
114                     // emit
115                     _builder.emit(entry.getHttpField());
116                 }
117             }
118             else
119             {
120                 // look at the first nibble in detail
121                 byte f= (byte)((b&0xF0)>>4);
122                 String name;
123                 HttpHeader header;
124                 String value;
125 
126                 boolean indexed;
127                 int name_index;
128 
129                 switch (f)
130                 {
131                     case 2: // 7.3
132                     case 3: // 7.3
133                         // change table size
134                         int size = NBitInteger.decode(buffer,5);
135                         if (LOG.isDebugEnabled())
136                             LOG.debug("decode resize="+size);
137                         if (size>_localMaxDynamicTableSize)
138                             throw new IllegalArgumentException();
139                         _context.resize(size);
140                         continue;
141 
142                     case 0: // 7.2.2
143                     case 1: // 7.2.3
144                         indexed=false;
145                         name_index=NBitInteger.decode(buffer,4);
146                         break;
147 
148 
149                     case 4: // 7.2.1
150                     case 5: // 7.2.1
151                     case 6: // 7.2.1
152                     case 7: // 7.2.1
153                         indexed=true;
154                         name_index=NBitInteger.decode(buffer,6);
155                         break;
156 
157                     default:
158                         throw new IllegalStateException();
159                 }
160 
161 
162                 boolean huffmanName=false;
163 
164                 // decode the name
165                 if (name_index>0)
166                 {
167                     Entry name_entry=_context.get(name_index);
168                     name=name_entry.getHttpField().getName();
169                     header=name_entry.getHttpField().getHeader();
170                 }
171                 else
172                 {
173                     huffmanName = (buffer.get()&0x80)==0x80;
174                     int length = NBitInteger.decode(buffer,7);
175                     _builder.checkSize(length,huffmanName);
176                     if (huffmanName)
177                         name=Huffman.decode(buffer,length);
178                     else
179                         name=toASCIIString(buffer,length);
180                     for (int i=0;i<name.length();i++)
181                     {
182                         char c=name.charAt(i);
183                         if (c>='A'&&c<='Z')
184                         {
185                             throw new BadMessageException(400,"Uppercase header name");
186                         }
187                     }
188                     header=HttpHeader.CACHE.get(name);
189                 }
190 
191                 // decode the value
192                 boolean huffmanValue = (buffer.get()&0x80)==0x80;
193                 int length = NBitInteger.decode(buffer,7);
194                 _builder.checkSize(length,huffmanValue);
195                 if (huffmanValue)
196                     value=Huffman.decode(buffer,length);
197                 else
198                     value=toASCIIString(buffer,length);
199 
200                 // Make the new field
201                 HttpField field;
202                 if (header==null)
203                 {
204                     // just make a normal field and bypass header name lookup
205                     field = new HttpField(null,name,value);
206                 }
207                 else
208                 {
209                     // might be worthwhile to create a value HttpField if it is indexed
210                     // and/or of a type that may be looked up multiple times.
211                     switch(header)
212                     {
213                         case C_STATUS:
214                             if (indexed)
215                                 field = new HttpField.IntValueHttpField(header,name,value);
216                             else
217                                 field = new HttpField(header,name,value);
218                             break;
219 
220                         case C_AUTHORITY:
221                             field = new AuthorityHttpField(value);
222                             break;
223 
224                         case CONTENT_LENGTH:
225                             if ("0".equals(value))
226                                 field = CONTENT_LENGTH_0;
227                             else
228                                 field = new HttpField.LongValueHttpField(header,name,value);
229                             break;
230 
231                         default:
232                             field = new HttpField(header,name,value);
233                             break;
234                     }
235                 }
236 
237                 if (LOG.isDebugEnabled())
238                 {
239                     LOG.debug("decoded '{}' by {}/{}/{}",
240                             field,
241                             name_index > 0 ? "IdxName" : (huffmanName ? "HuffName" : "LitName"),
242                             huffmanValue ? "HuffVal" : "LitVal",
243                             indexed ? "Idx" : "");
244                 }
245 
246                 // emit the field
247                 _builder.emit(field);
248 
249                 // if indexed
250                 if (indexed)
251                 {
252                     // add to dynamic table
253                     _context.add(field);
254                 }
255 
256             }
257         }
258 
259         return _builder.build();
260     }
261 
262     public static String toASCIIString(ByteBuffer buffer,int length)
263     {
264         StringBuilder builder = new StringBuilder(length);
265         int position=buffer.position();
266         int start=buffer.arrayOffset()+ position;
267         int end=start+length;
268         buffer.position(position+length);
269         byte[] array=buffer.array();
270         for (int i=start;i<end;i++)
271             builder.append((char)(0x7f&array[i]));
272         return builder.toString();
273     }
274 
275     @Override
276     public String toString()
277     {
278         return String.format("HpackDecoder@%x{%s}",hashCode(),_context);
279     }
280 }