| // 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_decoder.h" |
| |
| #include "base/basictypes.h" |
| #include "base/logging.h" |
| #include "net/spdy/hpack_constants.h" |
| #include "net/spdy/hpack_output_stream.h" |
| |
| namespace net { |
| |
| using base::StringPiece; |
| using std::string; |
| |
| namespace { |
| |
| const uint8 kNoState = 0; |
| // Set on entries added to the reference set during this decoding. |
| const uint8 kReferencedThisEncoding = 1; |
| |
| const char kCookieKey[] = "cookie"; |
| |
| } // namespace |
| |
| HpackDecoder::HpackDecoder(const HpackHuffmanTable& table) |
| : max_string_literal_size_(kDefaultMaxStringLiteralSize), |
| huffman_table_(table) {} |
| |
| HpackDecoder::~HpackDecoder() {} |
| |
| bool HpackDecoder::HandleControlFrameHeadersData(SpdyStreamId id, |
| const char* headers_data, |
| size_t headers_data_length) { |
| decoded_block_.clear(); |
| |
| size_t new_size = headers_block_buffer_.size() + headers_data_length; |
| if (new_size > kMaxDecodeBufferSize) { |
| return false; |
| } |
| headers_block_buffer_.insert(headers_block_buffer_.end(), |
| headers_data, |
| headers_data + headers_data_length); |
| return true; |
| } |
| |
| bool HpackDecoder::HandleControlFrameHeadersComplete(SpdyStreamId id) { |
| HpackInputStream input_stream(max_string_literal_size_, |
| headers_block_buffer_); |
| while (input_stream.HasMoreData()) { |
| if (!DecodeNextOpcode(&input_stream)) { |
| headers_block_buffer_.clear(); |
| return false; |
| } |
| } |
| headers_block_buffer_.clear(); |
| |
| // Emit everything in the reference set that hasn't already been emitted. |
| // Also clear entry state for the next decoded headers block. |
| // TODO(jgraettinger): We may need to revisit the order in which headers |
| // are emitted (b/14051713). |
| for (HpackHeaderTable::OrderedEntrySet::const_iterator it = |
| header_table_.reference_set().begin(); |
| it != header_table_.reference_set().end(); ++it) { |
| HpackEntry* entry = *it; |
| |
| if (entry->state() == kNoState) { |
| HandleHeaderRepresentation(entry->name(), entry->value()); |
| } else { |
| entry->set_state(kNoState); |
| } |
| } |
| // Emit the Cookie header, if any crumbles were encountered. |
| if (!cookie_value_.empty()) { |
| decoded_block_[kCookieKey] = cookie_value_; |
| cookie_value_.clear(); |
| } |
| return true; |
| } |
| |
| void HpackDecoder::HandleHeaderRepresentation(StringPiece name, |
| StringPiece value) { |
| typedef std::pair<std::map<string, string>::iterator, bool> InsertResult; |
| |
| if (name == kCookieKey) { |
| if (cookie_value_.empty()) { |
| cookie_value_.assign(value.data(), value.size()); |
| } else { |
| cookie_value_ += "; "; |
| cookie_value_.insert(cookie_value_.end(), value.begin(), value.end()); |
| } |
| } else { |
| InsertResult result = decoded_block_.insert( |
| std::make_pair(name.as_string(), value.as_string())); |
| if (!result.second) { |
| result.first->second.push_back('\0'); |
| result.first->second.insert(result.first->second.end(), |
| value.begin(), |
| value.end()); |
| } |
| } |
| } |
| |
| bool HpackDecoder::DecodeNextOpcode(HpackInputStream* input_stream) { |
| // Implements 4.2: Indexed Header Field Representation. |
| if (input_stream->MatchPrefixAndConsume(kIndexedOpcode)) { |
| return DecodeNextIndexedHeader(input_stream); |
| } |
| // Implements 4.3.1: Literal Header Field without Indexing. |
| if (input_stream->MatchPrefixAndConsume(kLiteralNoIndexOpcode)) { |
| return DecodeNextLiteralHeader(input_stream, false); |
| } |
| // Implements 4.3.2: Literal Header Field with Incremental Indexing. |
| if (input_stream->MatchPrefixAndConsume(kLiteralIncrementalIndexOpcode)) { |
| return DecodeNextLiteralHeader(input_stream, true); |
| } |
| // Implements 4.3.3: Literal Header Field never Indexed. |
| // TODO(jgraettinger): Preserve the never-indexed bit. |
| if (input_stream->MatchPrefixAndConsume(kLiteralNeverIndexOpcode)) { |
| return DecodeNextLiteralHeader(input_stream, false); |
| } |
| // Implements 4.4: Encoding context update. |
| if (input_stream->MatchPrefixAndConsume(kEncodingContextOpcode)) { |
| return DecodeNextContextUpdate(input_stream); |
| } |
| // Unrecognized opcode. |
| return false; |
| } |
| |
| bool HpackDecoder::DecodeNextContextUpdate(HpackInputStream* input_stream) { |
| if (input_stream->MatchPrefixAndConsume(kEncodingContextEmptyReferenceSet)) { |
| header_table_.ClearReferenceSet(); |
| return true; |
| } |
| if (input_stream->MatchPrefixAndConsume(kEncodingContextNewMaximumSize)) { |
| uint32 size = 0; |
| if (!input_stream->DecodeNextUint32(&size)) { |
| return false; |
| } |
| if (size > header_table_.settings_size_bound()) { |
| return false; |
| } |
| header_table_.SetMaxSize(size); |
| return true; |
| } |
| // Unrecognized encoding context update. |
| return false; |
| } |
| |
| bool HpackDecoder::DecodeNextIndexedHeader(HpackInputStream* input_stream) { |
| uint32 index = 0; |
| if (!input_stream->DecodeNextUint32(&index)) |
| return false; |
| |
| HpackEntry* entry = header_table_.GetByIndex(index); |
| if (entry == NULL) |
| return false; |
| |
| if (entry->IsStatic()) { |
| HandleHeaderRepresentation(entry->name(), entry->value()); |
| |
| HpackEntry* new_entry = header_table_.TryAddEntry( |
| entry->name(), entry->value()); |
| if (new_entry) { |
| header_table_.Toggle(new_entry); |
| new_entry->set_state(kReferencedThisEncoding); |
| } |
| } else { |
| entry->set_state(kNoState); |
| if (header_table_.Toggle(entry)) { |
| HandleHeaderRepresentation(entry->name(), entry->value()); |
| entry->set_state(kReferencedThisEncoding); |
| } |
| } |
| return true; |
| } |
| |
| 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; |
| |
| HandleHeaderRepresentation(name, value); |
| |
| if (!should_index) |
| return true; |
| |
| HpackEntry* new_entry = header_table_.TryAddEntry(name, value); |
| if (new_entry) { |
| header_table_.Toggle(new_entry); |
| new_entry->set_state(kReferencedThisEncoding); |
| } |
| return true; |
| } |
| |
| bool HpackDecoder::DecodeNextName( |
| HpackInputStream* input_stream, StringPiece* next_name) { |
| uint32 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; |
| } else if (entry->IsStatic()) { |
| *next_name = entry->name(); |
| } else { |
| // |entry| could be evicted as part of this insertion. Preemptively copy. |
| key_buffer_.assign(entry->name()); |
| *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(huffman_table_, buffer); |
| *output = StringPiece(*buffer); |
| return result; |
| } else if (input_stream->MatchPrefixAndConsume( |
| kStringLiteralIdentityEncoded)) { |
| return input_stream->DecodeNextIdentityString(output); |
| } else { |
| return false; |
| } |
| } |
| |
| } // namespace net |