| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/spdy/hpack/hpack_decoder.h" |
| |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "net/spdy/hpack/hpack_constants.h" |
| #include "net/spdy/hpack/hpack_entry.h" |
| #include "net/spdy/platform/api/spdy_estimate_memory_usage.h" |
| #include "net/spdy/spdy_flags.h" |
| |
| namespace net { |
| |
| using std::string; |
| |
| HpackDecoder::HpackDecoder() |
| : handler_(nullptr), |
| total_header_bytes_(0), |
| total_parsed_bytes_(0), |
| header_block_started_(false), |
| size_updates_seen_(0), |
| size_updates_allowed_(true), |
| incremental_decode_(false) {} |
| |
| HpackDecoder::~HpackDecoder() {} |
| |
| void HpackDecoder::ApplyHeaderTableSizeSetting(size_t size_setting) { |
| header_table_.SetSettingsHeaderTableSize(size_setting); |
| } |
| |
| void HpackDecoder::HandleControlFrameHeadersStart( |
| SpdyHeadersHandlerInterface* handler) { |
| handler_ = handler; |
| total_header_bytes_ = 0; |
| } |
| |
| bool HpackDecoder::HandleControlFrameHeadersData(const char* headers_data, |
| size_t headers_data_length) { |
| if (!header_block_started_) { |
| decoded_block_.clear(); |
| header_block_started_ = true; |
| size_updates_allowed_ = true; |
| size_updates_seen_ = 0; |
| if (handler_ != nullptr) { |
| handler_->OnHeaderBlockStart(); |
| } |
| } |
| size_t new_size = headers_block_buffer_.size() + headers_data_length; |
| if (max_decode_buffer_size_bytes_ > 0 && |
| new_size > max_decode_buffer_size_bytes_) { |
| DVLOG(1) << "max_decode_buffer_size_bytes_ < new_size: " |
| << max_decode_buffer_size_bytes_ << " < " << new_size; |
| return false; |
| } |
| headers_block_buffer_.insert(headers_block_buffer_.end(), headers_data, |
| headers_data + headers_data_length); |
| |
| // Parse as many whole HPACK entries in the buffer as possible, |
| // and then remove the parsed data from the buffer. |
| HpackInputStream input_stream(headers_block_buffer_); |
| while (input_stream.HasMoreData()) { |
| if (!DecodeNextOpcodeWrapper(&input_stream)) { |
| if (input_stream.NeedMoreData()) { |
| break; |
| } |
| DVLOG(1) << "!DecodeNextOpcodeWrapper"; |
| return false; |
| } |
| } |
| uint32_t parsed_bytes = input_stream.ParsedBytes(); |
| DCHECK_GE(headers_block_buffer_.size(), parsed_bytes); |
| headers_block_buffer_.erase(0, parsed_bytes); |
| total_parsed_bytes_ += parsed_bytes; |
| |
| return true; |
| } |
| |
| bool HpackDecoder::HandleControlFrameHeadersComplete(size_t* compressed_len) { |
| if (compressed_len != nullptr) { |
| *compressed_len = total_parsed_bytes_; |
| } |
| |
| // Data in headers_block_buffer_ should have been parsed by |
| // HandleControlFrameHeadersData and removed. |
| if (headers_block_buffer_.size() > 0) { |
| DVLOG(1) << "headers_block_buffer_.size() should be zero, but is " |
| << headers_block_buffer_.size(); |
| return false; |
| } |
| |
| if (handler_ != nullptr) { |
| if (FLAGS_chromium_http2_flag_log_compressed_size) { |
| handler_->OnHeaderBlockEnd(total_header_bytes_, total_parsed_bytes_); |
| } else { |
| handler_->OnHeaderBlockEnd(total_header_bytes_); |
| } |
| } |
| headers_block_buffer_.clear(); |
| total_parsed_bytes_ = 0; |
| header_block_started_ = false; |
| handler_ = nullptr; |
| return true; |
| } |
| |
| const SpdyHeaderBlock& HpackDecoder::decoded_block() const { |
| return decoded_block_; |
| } |
| |
| void HpackDecoder::SetHeaderTableDebugVisitor( |
| std::unique_ptr<HpackHeaderTable::DebugVisitorInterface> visitor) { |
| header_table_.set_debug_visitor(std::move(visitor)); |
| } |
| |
| void HpackDecoder::set_max_decode_buffer_size_bytes( |
| size_t max_decode_buffer_size_bytes) { |
| max_decode_buffer_size_bytes_ = max_decode_buffer_size_bytes; |
| } |
| |
| size_t HpackDecoder::EstimateMemoryUsage() const { |
| return SpdyEstimateMemoryUsage(header_table_) + |
| SpdyEstimateMemoryUsage(headers_block_buffer_) + |
| SpdyEstimateMemoryUsage(decoded_block_) + |
| SpdyEstimateMemoryUsage(key_buffer_) + |
| SpdyEstimateMemoryUsage(value_buffer_); |
| } |
| |
| bool HpackDecoder::HandleHeaderRepresentation(SpdyStringPiece name, |
| SpdyStringPiece value) { |
| size_updates_allowed_ = false; |
| total_header_bytes_ += name.size() + value.size(); |
| |
| if (handler_ == nullptr) { |
| decoded_block_.AppendValueOrAddHeader(name, value); |
| } else { |
| DCHECK(decoded_block_.empty()); |
| handler_->OnHeader(name, value); |
| } |
| return true; |
| } |
| |
| bool HpackDecoder::DecodeNextOpcodeWrapper(HpackInputStream* input_stream) { |
| if (DecodeNextOpcode(input_stream)) { |
| // Decoding next opcode succeeds. Mark total bytes parsed successfully. |
| input_stream->MarkCurrentPosition(); |
| return true; |
| } |
| return false; |
| } |
| |
| bool HpackDecoder::DecodeNextOpcode(HpackInputStream* input_stream) { |
| // Implements 7.1: Indexed Header Field Representation. |
| if (input_stream->MatchPrefixAndConsume(kIndexedOpcode)) { |
| return DecodeNextIndexedHeader(input_stream); |
| } |
| // Implements 7.2.1: Literal Header Field with Incremental Indexing. |
| if (input_stream->MatchPrefixAndConsume(kLiteralIncrementalIndexOpcode)) { |
| return DecodeNextLiteralHeader(input_stream, true); |
| } |
| // Implements 7.2.2: Literal Header Field without Indexing. |
| if (input_stream->MatchPrefixAndConsume(kLiteralNoIndexOpcode)) { |
| return DecodeNextLiteralHeader(input_stream, false); |
| } |
| // Implements 7.2.3: Literal Header Field never Indexed. |
| // TODO(jgraettinger): Preserve the never-indexed bit. |
| if (input_stream->MatchPrefixAndConsume(kLiteralNeverIndexOpcode)) { |
| return DecodeNextLiteralHeader(input_stream, false); |
| } |
| // Implements 7.3: Header Table Size Update. |
| if (input_stream->MatchPrefixAndConsume(kHeaderTableSizeUpdateOpcode)) { |
| // Header table size updates cannot appear mid-block. |
| return DecodeNextHeaderTableSizeUpdate(input_stream); |
| } |
| // Unrecognized opcode. |
| return false; |
| } |
| |
| bool HpackDecoder::DecodeNextHeaderTableSizeUpdate( |
| HpackInputStream* input_stream) { |
| uint32_t size = 0; |
| if (!input_stream->DecodeNextUint32(&size)) { |
| return false; |
| } |
| if (!size_updates_allowed_) { |
| DVLOG(1) << "Size updates not allowed after header entries."; |
| return false; |
| } |
| ++size_updates_seen_; |
| if (size_updates_seen_ > 2) { |
| DVLOG(1) << "Too many size updates at the start of the block."; |
| return false; |
| } |
| if (size > header_table_.settings_size_bound()) { |
| DVLOG(1) << "Size (" << size << ") exceeds SETTINGS limit (" |
| << header_table_.settings_size_bound() << ")"; |
| return false; |
| } |
| header_table_.SetMaxSize(size); |
| return true; |
| } |
| |
| bool HpackDecoder::DecodeNextIndexedHeader(HpackInputStream* input_stream) { |
| uint32_t index = 0; |
| if (!input_stream->DecodeNextUint32(&index)) { |
| return false; |
| } |
| |
| const HpackEntry* entry = header_table_.GetByIndex(index); |
| if (entry == NULL) { |
| DVLOG(1) << "Index " << index << " is not valid."; |
| return false; |
| } |
| |
| return HandleHeaderRepresentation(entry->name(), entry->value()); |
| } |
| |
| bool HpackDecoder::DecodeNextLiteralHeader(HpackInputStream* input_stream, |
| bool should_index) { |
| SpdyStringPiece name; |
| if (!DecodeNextName(input_stream, &name)) { |
| return false; |
| } |
| |
| SpdyStringPiece value; |
| if (!DecodeNextStringLiteral(input_stream, false, &value)) { |
| return false; |
| } |
| |
| if (!HandleHeaderRepresentation(name, value)) { |
| return false; |
| } |
| |
| if (!should_index) { |
| return true; |
| } |
| |
| ignore_result(header_table_.TryAddEntry(name, value)); |
| return true; |
| } |
| |
| bool HpackDecoder::DecodeNextName(HpackInputStream* input_stream, |
| SpdyStringPiece* next_name) { |
| uint32_t index_or_zero = 0; |
| if (!input_stream->DecodeNextUint32(&index_or_zero)) { |
| DVLOG(1) << "Failed to decode the next uint."; |
| return false; |
| } |
| |
| if (index_or_zero == 0) { |
| return DecodeNextStringLiteral(input_stream, true, next_name); |
| } |
| |
| const HpackEntry* entry = header_table_.GetByIndex(index_or_zero); |
| if (entry == NULL) { |
| DVLOG(1) << "index " << index_or_zero << " is not valid."; |
| return false; |
| } |
| if (entry->IsStatic()) { |
| *next_name = entry->name(); |
| } else { |
| // |entry| could be evicted as part of this insertion. Preemptively copy. |
| key_buffer_.assign(entry->name().data(), entry->name().size()); |
| *next_name = key_buffer_; |
| } |
| return true; |
| } |
| |
| bool HpackDecoder::DecodeNextStringLiteral(HpackInputStream* input_stream, |
| bool is_key, |
| SpdyStringPiece* output) { |
| if (input_stream->MatchPrefixAndConsume(kStringLiteralHuffmanEncoded)) { |
| string* buffer = is_key ? &key_buffer_ : &value_buffer_; |
| bool result = input_stream->DecodeNextHuffmanString(buffer); |
| *output = SpdyStringPiece(*buffer); |
| return result; |
| } else if (input_stream->MatchPrefixAndConsume( |
| kStringLiteralIdentityEncoded)) { |
| return input_stream->DecodeNextIdentityString(output); |
| } else { |
| DVLOG(1) << "String literal is neither Huffman nor identity encoded!"; |
| return false; |
| } |
| } |
| |
| } // namespace net |