blob: 1352dc3517b2b7d4893b8a42b162e079f726715d [file] [log] [blame]
// 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