// 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