/// <summary> /// Encode literal header field according to Section 6.2. /// </summary> /// <param name="output">Output.</param> /// <param name="name">Name.</param> /// <param name="value">Value.</param> /// <param name="indexType">Index type.</param> /// <param name="nameIndex">Name index.</param> private void EncodeLiteral(BinaryWriter output, byte[] name, byte[] value, HPackUtil.IndexType indexType, int nameIndex) { int mask; int prefixBits; switch (indexType) { case HPackUtil.IndexType.INCREMENTAL: mask = 0x40; prefixBits = 6; break; case HPackUtil.IndexType.NONE: mask = 0x00; prefixBits = 4; break; case HPackUtil.IndexType.NEVER: mask = 0x10; prefixBits = 4; break; default: throw new Exception("should not reach here"); } Encoder.EncodeInteger(output, mask, prefixBits, nameIndex == -1 ? 0 : nameIndex); if (nameIndex == -1) { this.EncodeStringLiteral(output, name); } this.EncodeStringLiteral(output, value); }
/// <summary> /// Decode the header block into header fields. /// </summary> /// <param name="input">Input.</param> /// <param name="headerListener">Header listener.</param> public void Decode(BinaryReader input, AddHeaderDelegate addHeaderDelegate) { while(input.BaseStream.Length - input.BaseStream.Position > 0) { switch(this.state) { case State.READ_HEADER_REPRESENTATION: sbyte b = input.ReadSByte(); if (maxDynamicTableSizeChangeRequired && (b & 0xE0) != 0x20) { // Encoder MUST signal maximum dynamic table size change throw new IOException("max dynamic table size change required"); } if (b < 0) { // Indexed Header Field index = b & 0x7F; if (index == 0) { throw new IOException("illegal index value (" + index + ")"); } else if (index == 0x7F) { state = State.READ_INDEXED_HEADER; } else { this.IndexHeader(index, addHeaderDelegate); } } else if ((b & 0x40) == 0x40) { // Literal Header Field with Incremental Indexing indexType = HPackUtil.IndexType.INCREMENTAL; index = b & 0x3F; if (index == 0) { state = State.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX; } else if (index == 0x3F) { state = State.READ_INDEXED_HEADER_NAME; } else { // Index was stored as the prefix this.ReadName(index); state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; } } else if ((b & 0x20) == 0x20) { // Dynamic Table Size Update index = b & 0x1F; if (index == 0x1F) { state = State.READ_MAX_DYNAMIC_TABLE_SIZE; } else { this.SetDynamicTableSize(index); state = State.READ_HEADER_REPRESENTATION; } } else { // Literal Header Field without Indexing / never Indexed indexType = ((b & 0x10) == 0x10) ? HPackUtil.IndexType.NEVER : HPackUtil.IndexType.NONE; index = b & 0x0F; if (index == 0) { state = State.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX; } else if (index == 0x0F) { state = State.READ_INDEXED_HEADER_NAME; } else { // Index was stored as the prefix this.ReadName(index); state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; } } break; case State.READ_MAX_DYNAMIC_TABLE_SIZE: int maxSize = Decoder.DecodeULE128(input); if (maxSize == -1) { return; } // Check for numerical overflow if (maxSize > int.MaxValue - index) { throw DECOMPRESSION_EXCEPTION; } this.SetDynamicTableSize(index + maxSize); state = State.READ_HEADER_REPRESENTATION; break; case State.READ_INDEXED_HEADER: int headerIndex = Decoder.DecodeULE128(input); if (headerIndex == -1) { return; } // Check for numerical overflow if (headerIndex > int.MaxValue - index) { throw DECOMPRESSION_EXCEPTION; } this.IndexHeader(index + headerIndex, addHeaderDelegate); state = State.READ_HEADER_REPRESENTATION; break; case State.READ_INDEXED_HEADER_NAME: // Header Name matches an entry in the Header Table int nameIndex = Decoder.DecodeULE128(input); if (nameIndex == -1) { return; } // Check for numerical overflow if (nameIndex > int.MaxValue - index) { throw DECOMPRESSION_EXCEPTION; } this.ReadName(index + nameIndex); state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; break; case State.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX: b = input.ReadSByte(); huffmanEncoded = (b & 0x80) == 0x80; index = b & 0x7F; if (index == 0x7f) { state = State.READ_LITERAL_HEADER_NAME_LENGTH; } else { nameLength = index; // Disallow empty names -- they cannot be represented in HTTP/1.x if (nameLength == 0) { throw DECOMPRESSION_EXCEPTION; } // Check name length against max header size if (this.ExceedsMaxHeaderSize(nameLength)) { if (indexType == HPackUtil.IndexType.NONE) { // Name is unused so skip bytes name = EMPTY; this.skipLength = nameLength; state = State.SKIP_LITERAL_HEADER_NAME; break; } // Check name length against max dynamic table size if (nameLength + HeaderField.HEADER_ENTRY_OVERHEAD > this.dynamicTable.Capacity) { this.dynamicTable.Clear(); name = EMPTY; this.skipLength = nameLength; state = State.SKIP_LITERAL_HEADER_NAME; break; } } state = State.READ_LITERAL_HEADER_NAME; } break; case State.READ_LITERAL_HEADER_NAME_LENGTH: // Header Name is a Literal String nameLength = Decoder.DecodeULE128(input); if (nameLength == -1) { return; } // Check for numerical overflow if (nameLength > int.MaxValue - index) { throw DECOMPRESSION_EXCEPTION; } nameLength += index; // Check name length against max header size if (this.ExceedsMaxHeaderSize(nameLength)) { if (indexType == HPackUtil.IndexType.NONE) { // Name is unused so skip bytes name = EMPTY; this.skipLength = nameLength; state = State.SKIP_LITERAL_HEADER_NAME; break; } // Check name length against max dynamic table size if (nameLength + HeaderField.HEADER_ENTRY_OVERHEAD > this.dynamicTable.Capacity) { this.dynamicTable.Clear(); name = EMPTY; this.skipLength = nameLength; state = State.SKIP_LITERAL_HEADER_NAME; break; } } state = State.READ_LITERAL_HEADER_NAME; break; case State.READ_LITERAL_HEADER_NAME: // Wait until entire name is readable if (input.BaseStream.Length - input.BaseStream.Position < nameLength) { return; } name = this.ReadStringLiteral(input, nameLength); state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; break; case State.SKIP_LITERAL_HEADER_NAME: this.skipLength -= (int)input.BaseStream.Seek(this.skipLength, SeekOrigin.Current); if (this.skipLength < 0) { this.skipLength = 0; } if (this.skipLength == 0) { state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; } break; case State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX: b = input.ReadSByte(); huffmanEncoded = (b & 0x80) == 0x80; index = b & 0x7F; if (index == 0x7f) { state = State.READ_LITERAL_HEADER_VALUE_LENGTH; } else { this.valueLength = index; // Check new header size against max header size long newHeaderSize1 = (long)nameLength + (long)this.valueLength; if (this.ExceedsMaxHeaderSize(newHeaderSize1)) { // truncation will be reported during endHeaderBlock headerSize = maxHeaderSize + 1; if (indexType == HPackUtil.IndexType.NONE) { // Value is unused so skip bytes state = State.SKIP_LITERAL_HEADER_VALUE; break; } // Check new header size against max dynamic table size if (newHeaderSize1 + HeaderField.HEADER_ENTRY_OVERHEAD > this.dynamicTable.Capacity) { this.dynamicTable.Clear(); state = State.SKIP_LITERAL_HEADER_VALUE; break; } } if (this.valueLength == 0) { this.InsertHeader(addHeaderDelegate, name, EMPTY, indexType); state = State.READ_HEADER_REPRESENTATION; } else { state = State.READ_LITERAL_HEADER_VALUE; } } break; case State.READ_LITERAL_HEADER_VALUE_LENGTH: // Header Value is a Literal String this.valueLength = Decoder.DecodeULE128(input); if (this.valueLength == -1) { return; } // Check for numerical overflow if (this.valueLength > int.MaxValue - index) { throw DECOMPRESSION_EXCEPTION; } this.valueLength += index; // Check new header size against max header size long newHeaderSize2 = (long)nameLength + (long)this.valueLength; if (newHeaderSize2 + headerSize > maxHeaderSize) { // truncation will be reported during endHeaderBlock headerSize = maxHeaderSize + 1; if (indexType == HPackUtil.IndexType.NONE) { // Value is unused so skip bytes state = State.SKIP_LITERAL_HEADER_VALUE; break; } // Check new header size against max dynamic table size if (newHeaderSize2 + HeaderField.HEADER_ENTRY_OVERHEAD > this.dynamicTable.Capacity) { this.dynamicTable.Clear(); state = State.SKIP_LITERAL_HEADER_VALUE; break; } } state = State.READ_LITERAL_HEADER_VALUE; break; case State.READ_LITERAL_HEADER_VALUE: // Wait until entire value is readable if (input.BaseStream.Length - input.BaseStream.Position < this.valueLength) { return; } byte[] value = this.ReadStringLiteral(input, this.valueLength); this.InsertHeader(addHeaderDelegate, name, value, indexType); state = State.READ_HEADER_REPRESENTATION; break; case State.SKIP_LITERAL_HEADER_VALUE: this.valueLength -= (int)input.BaseStream.Seek(this.valueLength, SeekOrigin.Current); if (this.valueLength < 0) { this.valueLength = 0; } if (this.valueLength == 0) { state = State.READ_HEADER_REPRESENTATION; } break; default: throw new Exception("should not reach here"); } } }
private void Reset() { this.headerSize = 0; this.state = State.READ_HEADER_REPRESENTATION; this.indexType = HPackUtil.IndexType.NONE; }
/// <summary> /// Decode the header block into header fields. /// </summary> /// <param name="input">Input.</param> /// <param name="headerListener">Header listener.</param> public void Decode(BinaryReader input, AddHeaderDelegate addHeaderDelegate) { while (input.BaseStream.Length - input.BaseStream.Position > 0) { switch (this.state) { case State.READ_HEADER_REPRESENTATION: sbyte b = input.ReadSByte(); if (maxDynamicTableSizeChangeRequired && (b & 0xE0) != 0x20) { // Encoder MUST signal maximum dynamic table size change throw new IOException("max dynamic table size change required"); } if (b < 0) { // Indexed Header Field index = b & 0x7F; if (index == 0) { throw new IOException("illegal index value (" + index + ")"); } else if (index == 0x7F) { state = State.READ_INDEXED_HEADER; } else { this.IndexHeader(index, addHeaderDelegate); } } else if ((b & 0x40) == 0x40) { // Literal Header Field with Incremental Indexing indexType = HPackUtil.IndexType.INCREMENTAL; index = b & 0x3F; if (index == 0) { state = State.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX; } else if (index == 0x3F) { state = State.READ_INDEXED_HEADER_NAME; } else { // Index was stored as the prefix this.ReadName(index); state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; } } else if ((b & 0x20) == 0x20) { // Dynamic Table Size Update index = b & 0x1F; if (index == 0x1F) { state = State.READ_MAX_DYNAMIC_TABLE_SIZE; } else { this.SetDynamicTableSize(index); state = State.READ_HEADER_REPRESENTATION; } } else { // Literal Header Field without Indexing / never Indexed indexType = ((b & 0x10) == 0x10) ? HPackUtil.IndexType.NEVER : HPackUtil.IndexType.NONE; index = b & 0x0F; if (index == 0) { state = State.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX; } else if (index == 0x0F) { state = State.READ_INDEXED_HEADER_NAME; } else { // Index was stored as the prefix this.ReadName(index); state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; } } break; case State.READ_MAX_DYNAMIC_TABLE_SIZE: int maxSize = Decoder.DecodeULE128(input); if (maxSize == -1) { return; } // Check for numerical overflow if (maxSize > int.MaxValue - index) { throw DECOMPRESSION_EXCEPTION; } this.SetDynamicTableSize(index + maxSize); state = State.READ_HEADER_REPRESENTATION; break; case State.READ_INDEXED_HEADER: int headerIndex = Decoder.DecodeULE128(input); if (headerIndex == -1) { return; } // Check for numerical overflow if (headerIndex > int.MaxValue - index) { throw DECOMPRESSION_EXCEPTION; } this.IndexHeader(index + headerIndex, addHeaderDelegate); state = State.READ_HEADER_REPRESENTATION; break; case State.READ_INDEXED_HEADER_NAME: // Header Name matches an entry in the Header Table int nameIndex = Decoder.DecodeULE128(input); if (nameIndex == -1) { return; } // Check for numerical overflow if (nameIndex > int.MaxValue - index) { throw DECOMPRESSION_EXCEPTION; } this.ReadName(index + nameIndex); state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; break; case State.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX: b = input.ReadSByte(); huffmanEncoded = (b & 0x80) == 0x80; index = b & 0x7F; if (index == 0x7f) { state = State.READ_LITERAL_HEADER_NAME_LENGTH; } else { nameLength = index; // Disallow empty names -- they cannot be represented in HTTP/1.x if (nameLength == 0) { throw DECOMPRESSION_EXCEPTION; } // Check name length against max header size if (this.ExceedsMaxHeaderSize(nameLength)) { if (indexType == HPackUtil.IndexType.NONE) { // Name is unused so skip bytes name = EMPTY; this.skipLength = nameLength; state = State.SKIP_LITERAL_HEADER_NAME; break; } // Check name length against max dynamic table size if (nameLength + HeaderField.HEADER_ENTRY_OVERHEAD > this.dynamicTable.Capacity) { this.dynamicTable.Clear(); name = EMPTY; this.skipLength = nameLength; state = State.SKIP_LITERAL_HEADER_NAME; break; } } state = State.READ_LITERAL_HEADER_NAME; } break; case State.READ_LITERAL_HEADER_NAME_LENGTH: // Header Name is a Literal String nameLength = Decoder.DecodeULE128(input); if (nameLength == -1) { return; } // Check for numerical overflow if (nameLength > int.MaxValue - index) { throw DECOMPRESSION_EXCEPTION; } nameLength += index; // Check name length against max header size if (this.ExceedsMaxHeaderSize(nameLength)) { if (indexType == HPackUtil.IndexType.NONE) { // Name is unused so skip bytes name = EMPTY; this.skipLength = nameLength; state = State.SKIP_LITERAL_HEADER_NAME; break; } // Check name length against max dynamic table size if (nameLength + HeaderField.HEADER_ENTRY_OVERHEAD > this.dynamicTable.Capacity) { this.dynamicTable.Clear(); name = EMPTY; this.skipLength = nameLength; state = State.SKIP_LITERAL_HEADER_NAME; break; } } state = State.READ_LITERAL_HEADER_NAME; break; case State.READ_LITERAL_HEADER_NAME: // Wait until entire name is readable if (input.BaseStream.Length - input.BaseStream.Position < nameLength) { return; } name = this.ReadStringLiteral(input, nameLength); state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; break; case State.SKIP_LITERAL_HEADER_NAME: this.skipLength -= (int)input.BaseStream.Seek(this.skipLength, SeekOrigin.Current); if (this.skipLength < 0) { this.skipLength = 0; } if (this.skipLength == 0) { state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX; } break; case State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX: b = input.ReadSByte(); huffmanEncoded = (b & 0x80) == 0x80; index = b & 0x7F; if (index == 0x7f) { state = State.READ_LITERAL_HEADER_VALUE_LENGTH; } else { this.valueLength = index; // Check new header size against max header size long newHeaderSize1 = (long)nameLength + (long)this.valueLength; if (this.ExceedsMaxHeaderSize(newHeaderSize1)) { // truncation will be reported during endHeaderBlock headerSize = maxHeaderSize + 1; if (indexType == HPackUtil.IndexType.NONE) { // Value is unused so skip bytes state = State.SKIP_LITERAL_HEADER_VALUE; break; } // Check new header size against max dynamic table size if (newHeaderSize1 + HeaderField.HEADER_ENTRY_OVERHEAD > this.dynamicTable.Capacity) { this.dynamicTable.Clear(); state = State.SKIP_LITERAL_HEADER_VALUE; break; } } if (this.valueLength == 0) { this.InsertHeader(addHeaderDelegate, name, EMPTY, indexType); state = State.READ_HEADER_REPRESENTATION; } else { state = State.READ_LITERAL_HEADER_VALUE; } } break; case State.READ_LITERAL_HEADER_VALUE_LENGTH: // Header Value is a Literal String this.valueLength = Decoder.DecodeULE128(input); if (this.valueLength == -1) { return; } // Check for numerical overflow if (this.valueLength > int.MaxValue - index) { throw DECOMPRESSION_EXCEPTION; } this.valueLength += index; // Check new header size against max header size long newHeaderSize2 = (long)nameLength + (long)this.valueLength; if (newHeaderSize2 + headerSize > maxHeaderSize) { // truncation will be reported during endHeaderBlock headerSize = maxHeaderSize + 1; if (indexType == HPackUtil.IndexType.NONE) { // Value is unused so skip bytes state = State.SKIP_LITERAL_HEADER_VALUE; break; } // Check new header size against max dynamic table size if (newHeaderSize2 + HeaderField.HEADER_ENTRY_OVERHEAD > this.dynamicTable.Capacity) { this.dynamicTable.Clear(); state = State.SKIP_LITERAL_HEADER_VALUE; break; } } state = State.READ_LITERAL_HEADER_VALUE; break; case State.READ_LITERAL_HEADER_VALUE: // Wait until entire value is readable if (input.BaseStream.Length - input.BaseStream.Position < this.valueLength) { return; } byte[] value = this.ReadStringLiteral(input, this.valueLength); this.InsertHeader(addHeaderDelegate, name, value, indexType); state = State.READ_HEADER_REPRESENTATION; break; case State.SKIP_LITERAL_HEADER_VALUE: this.valueLength -= (int)input.BaseStream.Seek(this.valueLength, SeekOrigin.Current); if (this.valueLength < 0) { this.valueLength = 0; } if (this.valueLength == 0) { state = State.READ_HEADER_REPRESENTATION; } break; default: throw new Exception("should not reach here"); } } }
private void InsertHeader(AddHeaderDelegate addHeaderDelegate, byte[] name, byte[] value, HPackUtil.IndexType indexType) { this.AddHeader(addHeaderDelegate, name, value, indexType == HPackUtil.IndexType.NEVER); switch (indexType) { case HPackUtil.IndexType.NONE: case HPackUtil.IndexType.NEVER: break; case HPackUtil.IndexType.INCREMENTAL: this.dynamicTable.Add(new HeaderField(name, value)); break; default: throw new Exception("should not reach here"); } }