| // 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_output_stream.h" |
| |
| namespace net { |
| |
| using base::StringPiece; |
| using std::string; |
| |
| namespace { |
| |
| const char kCookieKey[] = "cookie"; |
| |
| } // namespace |
| |
| HpackDecoder::HpackDecoder() |
| : handler_(nullptr), |
| total_header_bytes_(0), |
| header_block_started_(false), |
| total_parsed_bytes_(0) {} |
| |
| 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(); |
| 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_) { |
| return false; |
| } |
| headers_block_buffer_.insert(headers_block_buffer_.end(), headers_data, |
| headers_data + headers_data_length); |
| |
| // Parse as many data in buffer as possible. And remove the parsed data |
| // from buffer. |
| HpackInputStream input_stream(headers_block_buffer_); |
| |
| // If this is the start of the header block, process table size updates. |
| if (!header_block_started_) { |
| if (!DecodeAtMostTwoHeaderTableSizeUpdates(&input_stream)) { |
| return false; |
| } |
| input_stream.MarkCurrentPosition(); |
| } |
| while (input_stream.HasMoreData()) { |
| if (!DecodeNextOpcodeWrapper(&input_stream)) { |
| if (input_stream.NeedMoreData()) { |
| break; |
| } |
| 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; |
| header_block_started_ = true; |
| 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) { |
| return false; |
| } |
| |
| if (handler_ != nullptr) { |
| 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() { |
| 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; |
| } |
| |
| bool HpackDecoder::HandleHeaderRepresentation(StringPiece name, |
| StringPiece value) { |
| total_header_bytes_ += name.size() + value.size(); |
| |
| if (handler_ == nullptr) { |
| auto it = decoded_block_.find(name); |
| if (it == decoded_block_.end()) { |
| // This is a new key. |
| decoded_block_[name] = value; |
| } else { |
| // The key already exists, append |value| with appropriate delimiter. |
| string new_value = it->second.as_string(); |
| new_value.append((name == kCookieKey) ? "; " : string(1, '\0')); |
| value.AppendToString(&new_value); |
| decoded_block_.ReplaceOrAppendHeader(name, new_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 false; |
| } |
| // Unrecognized opcode. |
| return false; |
| } |
| |
| // Process 0, 1, or 2 Table Size Updates. |
| bool HpackDecoder::DecodeAtMostTwoHeaderTableSizeUpdates( |
| HpackInputStream* input_stream) { |
| // Implements 7.3: Header Table Size Update. |
| if (input_stream->HasMoreData() && |
| input_stream->MatchPrefixAndConsume(kHeaderTableSizeUpdateOpcode)) { |
| // One table size update, decode it. |
| if (DecodeNextHeaderTableSizeUpdate(input_stream)) { |
| // Check for a second table size update. |
| if (input_stream->HasMoreData() && |
| input_stream->MatchPrefixAndConsume(kHeaderTableSizeUpdateOpcode)) { |
| // Second update found, return the result of decode. |
| return DecodeNextHeaderTableSizeUpdate(input_stream); |
| } |
| } else { |
| // Decoding the first table size update failed. |
| return false; |
| } |
| } |
| // No table size updates in this block. |
| return true; |
| } |
| |
| bool HpackDecoder::DecodeNextHeaderTableSizeUpdate( |
| HpackInputStream* input_stream) { |
| uint32_t size = 0; |
| if (!input_stream->DecodeNextUint32(&size)) { |
| return false; |
| } |
| if (size > 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) { |
| return false; |
| } |
| |
| return HandleHeaderRepresentation(entry->name(), entry->value()); |
| } |
| |
| bool HpackDecoder::DecodeNextLiteralHeader(HpackInputStream* input_stream, |
| bool should_index) { |
| StringPiece name; |
| if (!DecodeNextName(input_stream, &name)) { |
| return false; |
| } |
| |
| StringPiece 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, |
| StringPiece* next_name) { |
| uint32_t index_or_zero = 0; |
| if (!input_stream->DecodeNextUint32(&index_or_zero)) { |
| 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) { |
| 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, |
| StringPiece* output) { |
| if (input_stream->MatchPrefixAndConsume(kStringLiteralHuffmanEncoded)) { |
| string* buffer = is_key ? &key_buffer_ : &value_buffer_; |
| bool result = input_stream->DecodeNextHuffmanString(buffer); |
| *output = StringPiece(*buffer); |
| return result; |
| } |
| if (input_stream->MatchPrefixAndConsume(kStringLiteralIdentityEncoded)) { |
| return input_stream->DecodeNextIdentityString(output); |
| } |
| return false; |
| } |
| |
| } // namespace net |