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