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 }