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 }