// Copyright 2016 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/http2/hpack/decoder/hpack_decoder_state.h" #include "base/logging.h" #include "net/http2/hpack/hpack_string.h" #include "net/http2/http2_constants.h" namespace net { namespace { HpackString ExtractHpackString(HpackDecoderStringBuffer* string_buffer) { if (string_buffer->IsBuffered()) { return HpackString(string_buffer->ReleaseString()); } else { auto result = HpackString(string_buffer->str()); string_buffer->Reset(); return result; } } } // namespace HpackDecoderState::HpackDecoderState(HpackDecoderListener* listener) : listener_(listener), final_header_table_size_(Http2SettingsInfo::DefaultHeaderTableSize()), lowest_header_table_size_(final_header_table_size_), require_dynamic_table_size_update_(false), allow_dynamic_table_size_update_(true), saw_dynamic_table_size_update_(false), error_detected_(false) { CHECK(listener); } HpackDecoderState::~HpackDecoderState() {} void HpackDecoderState::set_tables_debug_listener( HpackDecoderTablesDebugListener* debug_listener) { decoder_tables_.set_debug_listener(debug_listener); } void HpackDecoderState::ApplyHeaderTableSizeSetting( uint32_t header_table_size) { DVLOG(2) << "HpackDecoderState::ApplyHeaderTableSizeSetting(" << header_table_size << ")"; DCHECK_LE(lowest_header_table_size_, final_header_table_size_); if (header_table_size < lowest_header_table_size_) { lowest_header_table_size_ = header_table_size; } final_header_table_size_ = header_table_size; DVLOG(2) << "low water mark: " << lowest_header_table_size_; DVLOG(2) << "final limit: " << final_header_table_size_; } // Called to notify this object that we're starting to decode an HPACK block // (e.g. a HEADERS or PUSH_PROMISE frame's header has been decoded). void HpackDecoderState::OnHeaderBlockStart() { DVLOG(2) << "HpackDecoderState::OnHeaderBlockStart"; // This instance can't be reused after an error has been detected, as we must // assume that the encoder and decoder compression states are no longer // synchronized. DCHECK(!error_detected_); DCHECK_LE(lowest_header_table_size_, final_header_table_size_); allow_dynamic_table_size_update_ = true; saw_dynamic_table_size_update_ = false; // If the peer has acknowledged a HEADER_TABLE_SIZE smaller than that which // its HPACK encoder has been using, then the next HPACK block it sends MUST // start with a Dynamic Table Size Update entry that is at least as low as // lowest_header_table_size_. That may be followed by another as great as // final_header_table_size_, if those are different. require_dynamic_table_size_update_ = (lowest_header_table_size_ < decoder_tables_.current_header_table_size() || final_header_table_size_ < decoder_tables_.header_table_size_limit()); DVLOG(2) << "HpackDecoderState::OnHeaderListStart " << "require_dynamic_table_size_update_=" << require_dynamic_table_size_update_; listener_->OnHeaderListStart(); } void HpackDecoderState::OnIndexedHeader(size_t index) { DVLOG(2) << "HpackDecoderState::OnIndexedHeader: " << index; if (error_detected_) { return; } if (require_dynamic_table_size_update_) { ReportError("Missing dynamic table size update."); return; } allow_dynamic_table_size_update_ = false; const HpackStringPair* entry = decoder_tables_.Lookup(index); if (entry != nullptr) { listener_->OnHeader(HpackEntryType::kIndexedHeader, entry->name, entry->value); } else { ReportError("Invalid index."); } } void HpackDecoderState::OnNameIndexAndLiteralValue( HpackEntryType entry_type, size_t name_index, HpackDecoderStringBuffer* value_buffer) { DVLOG(2) << "HpackDecoderState::OnNameIndexAndLiteralValue " << entry_type << ", " << name_index << ", " << value_buffer->str(); if (error_detected_) { return; } if (require_dynamic_table_size_update_) { ReportError("Missing dynamic table size update."); return; } allow_dynamic_table_size_update_ = false; const HpackStringPair* entry = decoder_tables_.Lookup(name_index); if (entry != nullptr) { HpackString value(ExtractHpackString(value_buffer)); listener_->OnHeader(entry_type, entry->name, value); if (entry_type == HpackEntryType::kIndexedLiteralHeader) { decoder_tables_.Insert(entry->name, value); } } else { ReportError("Invalid name index."); } } void HpackDecoderState::OnLiteralNameAndValue( HpackEntryType entry_type, HpackDecoderStringBuffer* name_buffer, HpackDecoderStringBuffer* value_buffer) { DVLOG(2) << "HpackDecoderState::OnLiteralNameAndValue " << entry_type << ", " << name_buffer->str() << ", " << value_buffer->str(); if (error_detected_) { return; } if (require_dynamic_table_size_update_) { ReportError("Missing dynamic table size update."); return; } allow_dynamic_table_size_update_ = false; HpackString name(ExtractHpackString(name_buffer)); HpackString value(ExtractHpackString(value_buffer)); listener_->OnHeader(entry_type, name, value); if (entry_type == HpackEntryType::kIndexedLiteralHeader) { decoder_tables_.Insert(name, value); } } void HpackDecoderState::OnDynamicTableSizeUpdate(size_t size_limit) { DVLOG(2) << "HpackDecoderState::OnDynamicTableSizeUpdate " << size_limit << ", required=" << (require_dynamic_table_size_update_ ? "true" : "false") << ", allowed=" << (allow_dynamic_table_size_update_ ? "true" : "false"); if (error_detected_) { return; } DCHECK_LE(lowest_header_table_size_, final_header_table_size_); if (!allow_dynamic_table_size_update_) { // At most two dynamic table size updates allowed at the start, and not // after a header. ReportError("Dynamic table size update not allowed."); return; } if (require_dynamic_table_size_update_) { // The new size must not be greater than the low water mark. if (size_limit > lowest_header_table_size_) { ReportError("Initial dynamic table size update is above low water mark."); return; } require_dynamic_table_size_update_ = false; } else if (size_limit > final_header_table_size_) { // The new size must not be greater than the final max header table size // that the peer acknowledged. ReportError("Dynamic table size update is above acknowledged setting."); return; } decoder_tables_.DynamicTableSizeUpdate(size_limit); if (saw_dynamic_table_size_update_) { allow_dynamic_table_size_update_ = false; } else { saw_dynamic_table_size_update_ = true; } // We no longer need to keep an eye out for a lower header table size. lowest_header_table_size_ = final_header_table_size_; } void HpackDecoderState::OnHpackDecodeError(Http2StringPiece error_message) { DVLOG(2) << "HpackDecoderState::OnHpackDecodeError " << error_message; if (!error_detected_) { ReportError(error_message); } } void HpackDecoderState::OnHeaderBlockEnd() { DVLOG(2) << "HpackDecoderState::OnHeaderBlockEnd"; if (error_detected_) { return; } if (require_dynamic_table_size_update_) { // Apparently the HPACK block was empty, but we needed it to contain at // least 1 dynamic table size update. ReportError("Missing dynamic table size update."); } else { listener_->OnHeaderListEnd(); } } void HpackDecoderState::ReportError(Http2StringPiece error_message) { DVLOG(2) << "HpackDecoderState::ReportError is new=" << (!error_detected_ ? "true" : "false") << ", error_message: " << error_message; if (!error_detected_) { listener_->OnHeaderErrorDetected(error_message); error_detected_ = true; } } } // namespace net