View Javadoc

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