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