blob: 7258128e74708f5097bd15badeee5c35075303fc [file] [log] [blame]
// Copyright (c) 2018 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 "quiche/quic/core/qpack/qpack_progressive_decoder.h"
#include <algorithm>
#include <limits>
#include <utility>
#include "absl/strings/string_view.h"
#include "quiche/quic/core/qpack/qpack_index_conversions.h"
#include "quiche/quic/core/qpack/qpack_instructions.h"
#include "quiche/quic/core/qpack/qpack_required_insert_count.h"
#include "quiche/quic/platform/api/quic_flag_utils.h"
#include "quiche/quic/platform/api/quic_flags.h"
#include "quiche/quic/platform/api/quic_logging.h"
namespace quic {
namespace {
// The value argument passed to OnHeaderDecoded() is from an entry in the static
// table.
constexpr bool kValueFromStaticTable = true;
} // anonymous namespace
QpackProgressiveDecoder::QpackProgressiveDecoder(
QuicStreamId stream_id, BlockedStreamLimitEnforcer* enforcer,
DecodingCompletedVisitor* visitor, QpackDecoderHeaderTable* header_table,
HeadersHandlerInterface* handler)
: stream_id_(stream_id),
prefix_decoder_(std::make_unique<QpackInstructionDecoder>(
QpackPrefixLanguage(), this)),
instruction_decoder_(QpackRequestStreamLanguage(), this),
enforcer_(enforcer),
visitor_(visitor),
header_table_(header_table),
handler_(handler),
required_insert_count_(0),
base_(0),
required_insert_count_so_far_(0),
prefix_decoded_(false),
blocked_(false),
decoding_(true),
error_detected_(false),
cancelled_(false) {}
QpackProgressiveDecoder::~QpackProgressiveDecoder() {
if (blocked_ && !cancelled_) {
header_table_->UnregisterObserver(required_insert_count_, this);
}
}
void QpackProgressiveDecoder::Decode(absl::string_view data) {
QUICHE_DCHECK(decoding_);
if (data.empty() || error_detected_) {
return;
}
// Decode prefix byte by byte until the first (and only) instruction is
// decoded.
while (!prefix_decoded_) {
QUICHE_DCHECK(!blocked_);
if (!prefix_decoder_->Decode(data.substr(0, 1))) {
return;
}
// |prefix_decoder_->Decode()| must return false if an error is detected.
QUICHE_DCHECK(!error_detected_);
data = data.substr(1);
if (data.empty()) {
return;
}
}
if (blocked_) {
buffer_.append(data.data(), data.size());
} else {
QUICHE_DCHECK(buffer_.empty());
instruction_decoder_.Decode(data);
}
}
void QpackProgressiveDecoder::EndHeaderBlock() {
QUICHE_DCHECK(decoding_);
decoding_ = false;
if (!blocked_) {
FinishDecoding();
}
}
bool QpackProgressiveDecoder::OnInstructionDecoded(
const QpackInstruction* instruction) {
if (instruction == QpackPrefixInstruction()) {
return DoPrefixInstruction();
}
QUICHE_DCHECK(prefix_decoded_);
QUICHE_DCHECK_LE(required_insert_count_,
header_table_->inserted_entry_count());
if (instruction == QpackIndexedHeaderFieldInstruction()) {
return DoIndexedHeaderFieldInstruction();
}
if (instruction == QpackIndexedHeaderFieldPostBaseInstruction()) {
return DoIndexedHeaderFieldPostBaseInstruction();
}
if (instruction == QpackLiteralHeaderFieldNameReferenceInstruction()) {
return DoLiteralHeaderFieldNameReferenceInstruction();
}
if (instruction == QpackLiteralHeaderFieldPostBaseInstruction()) {
return DoLiteralHeaderFieldPostBaseInstruction();
}
QUICHE_DCHECK_EQ(instruction, QpackLiteralHeaderFieldInstruction());
return DoLiteralHeaderFieldInstruction();
}
void QpackProgressiveDecoder::OnInstructionDecodingError(
QpackInstructionDecoder::ErrorCode /* error_code */,
absl::string_view error_message) {
// Ignore |error_code| and always use QUIC_QPACK_DECOMPRESSION_FAILED to avoid
// having to define a new QuicErrorCode for every instruction decoder error.
OnError(QUIC_QPACK_DECOMPRESSION_FAILED, error_message);
}
void QpackProgressiveDecoder::OnInsertCountReachedThreshold() {
QUICHE_DCHECK(blocked_);
// Clear |blocked_| before calling instruction_decoder_.Decode() below,
// because that might destroy |this| and ~QpackProgressiveDecoder() needs to
// know not to call UnregisterObserver().
blocked_ = false;
enforcer_->OnStreamUnblocked(stream_id_);
if (!buffer_.empty()) {
std::string buffer(std::move(buffer_));
buffer_.clear();
if (!instruction_decoder_.Decode(buffer)) {
// |this| might be destroyed.
return;
}
}
if (!decoding_) {
FinishDecoding();
}
}
void QpackProgressiveDecoder::Cancel() { cancelled_ = true; }
bool QpackProgressiveDecoder::DoIndexedHeaderFieldInstruction() {
if (!instruction_decoder_.s_bit()) {
uint64_t absolute_index;
if (!QpackRequestStreamRelativeIndexToAbsoluteIndex(
instruction_decoder_.varint(), base_, &absolute_index)) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Invalid relative index.");
return false;
}
if (absolute_index >= required_insert_count_) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED,
"Absolute Index must be smaller than Required Insert Count.");
return false;
}
QUICHE_DCHECK_LT(absolute_index, std::numeric_limits<uint64_t>::max());
required_insert_count_so_far_ =
std::max(required_insert_count_so_far_, absolute_index + 1);
auto entry =
header_table_->LookupEntry(/* is_static = */ false, absolute_index);
if (!entry) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED,
"Dynamic table entry already evicted.");
return false;
}
header_table_->set_dynamic_table_entry_referenced();
return OnHeaderDecoded(!kValueFromStaticTable, entry->name(),
entry->value());
}
auto entry = header_table_->LookupEntry(/* is_static = */ true,
instruction_decoder_.varint());
if (!entry) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Static table entry not found.");
return false;
}
return OnHeaderDecoded(kValueFromStaticTable, entry->name(), entry->value());
}
bool QpackProgressiveDecoder::DoIndexedHeaderFieldPostBaseInstruction() {
uint64_t absolute_index;
if (!QpackPostBaseIndexToAbsoluteIndex(instruction_decoder_.varint(), base_,
&absolute_index)) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Invalid post-base index.");
return false;
}
if (absolute_index >= required_insert_count_) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED,
"Absolute Index must be smaller than Required Insert Count.");
return false;
}
QUICHE_DCHECK_LT(absolute_index, std::numeric_limits<uint64_t>::max());
required_insert_count_so_far_ =
std::max(required_insert_count_so_far_, absolute_index + 1);
auto entry =
header_table_->LookupEntry(/* is_static = */ false, absolute_index);
if (!entry) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED,
"Dynamic table entry already evicted.");
return false;
}
header_table_->set_dynamic_table_entry_referenced();
return OnHeaderDecoded(!kValueFromStaticTable, entry->name(), entry->value());
}
bool QpackProgressiveDecoder::DoLiteralHeaderFieldNameReferenceInstruction() {
if (!instruction_decoder_.s_bit()) {
uint64_t absolute_index;
if (!QpackRequestStreamRelativeIndexToAbsoluteIndex(
instruction_decoder_.varint(), base_, &absolute_index)) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Invalid relative index.");
return false;
}
if (absolute_index >= required_insert_count_) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED,
"Absolute Index must be smaller than Required Insert Count.");
return false;
}
QUICHE_DCHECK_LT(absolute_index, std::numeric_limits<uint64_t>::max());
required_insert_count_so_far_ =
std::max(required_insert_count_so_far_, absolute_index + 1);
auto entry =
header_table_->LookupEntry(/* is_static = */ false, absolute_index);
if (!entry) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED,
"Dynamic table entry already evicted.");
return false;
}
header_table_->set_dynamic_table_entry_referenced();
return OnHeaderDecoded(!kValueFromStaticTable, entry->name(),
instruction_decoder_.value());
}
auto entry = header_table_->LookupEntry(/* is_static = */ true,
instruction_decoder_.varint());
if (!entry) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Static table entry not found.");
return false;
}
return OnHeaderDecoded(kValueFromStaticTable, entry->name(),
instruction_decoder_.value());
}
bool QpackProgressiveDecoder::DoLiteralHeaderFieldPostBaseInstruction() {
uint64_t absolute_index;
if (!QpackPostBaseIndexToAbsoluteIndex(instruction_decoder_.varint(), base_,
&absolute_index)) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Invalid post-base index.");
return false;
}
if (absolute_index >= required_insert_count_) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED,
"Absolute Index must be smaller than Required Insert Count.");
return false;
}
QUICHE_DCHECK_LT(absolute_index, std::numeric_limits<uint64_t>::max());
required_insert_count_so_far_ =
std::max(required_insert_count_so_far_, absolute_index + 1);
auto entry =
header_table_->LookupEntry(/* is_static = */ false, absolute_index);
if (!entry) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED,
"Dynamic table entry already evicted.");
return false;
}
header_table_->set_dynamic_table_entry_referenced();
return OnHeaderDecoded(!kValueFromStaticTable, entry->name(),
instruction_decoder_.value());
}
bool QpackProgressiveDecoder::DoLiteralHeaderFieldInstruction() {
return OnHeaderDecoded(!kValueFromStaticTable, instruction_decoder_.name(),
instruction_decoder_.value());
}
bool QpackProgressiveDecoder::DoPrefixInstruction() {
QUICHE_DCHECK(!prefix_decoded_);
if (!QpackDecodeRequiredInsertCount(
prefix_decoder_->varint(), header_table_->max_entries(),
header_table_->inserted_entry_count(), &required_insert_count_)) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED,
"Error decoding Required Insert Count.");
return false;
}
const bool sign = prefix_decoder_->s_bit();
const uint64_t delta_base = prefix_decoder_->varint2();
if (!DeltaBaseToBase(sign, delta_base, &base_)) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Error calculating Base.");
return false;
}
prefix_decoded_ = true;
if (required_insert_count_ > header_table_->inserted_entry_count()) {
if (!enforcer_->OnStreamBlocked(stream_id_)) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED,
"Limit on number of blocked streams exceeded.");
return false;
}
blocked_ = true;
header_table_->RegisterObserver(required_insert_count_, this);
}
return true;
}
bool QpackProgressiveDecoder::OnHeaderDecoded(bool value_from_static_table,
absl::string_view name,
absl::string_view value) {
// Skip test for static table entries as they are all known to be valid.
if (!value_from_static_table) {
// According to Section 10.3 of
// https://quicwg.org/base-drafts/draft-ietf-quic-http.html,
// "[...] HTTP/3 can transport field values that are not valid. While most
// values that can be encoded will not alter field parsing, carriage return
// (CR, ASCII 0x0d), line feed (LF, ASCII 0x0a), and the zero character
// (NUL, ASCII 0x00) might be exploited by an attacker if they are
// translated verbatim. Any request or response that contains a character
// not permitted in a field value MUST be treated as malformed [...]"
for (const auto c : value) {
if (c == '\0' || c == '\n' || c == '\r') {
OnError(QUIC_INVALID_CHARACTER_IN_FIELD_VALUE,
"Invalid character in field value.");
return false;
}
}
}
handler_->OnHeaderDecoded(name, value);
return true;
}
void QpackProgressiveDecoder::FinishDecoding() {
QUICHE_DCHECK(buffer_.empty());
QUICHE_DCHECK(!blocked_);
QUICHE_DCHECK(!decoding_);
if (error_detected_) {
return;
}
if (!instruction_decoder_.AtInstructionBoundary()) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Incomplete header block.");
return;
}
if (!prefix_decoded_) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED, "Incomplete header data prefix.");
return;
}
if (required_insert_count_ != required_insert_count_so_far_) {
OnError(QUIC_QPACK_DECOMPRESSION_FAILED,
"Required Insert Count too large.");
return;
}
visitor_->OnDecodingCompleted(stream_id_, required_insert_count_);
handler_->OnDecodingCompleted();
}
void QpackProgressiveDecoder::OnError(QuicErrorCode error_code,
absl::string_view error_message) {
QUICHE_DCHECK(!error_detected_);
error_detected_ = true;
// Might destroy |this|.
handler_->OnDecodingErrorDetected(error_code, error_message);
}
bool QpackProgressiveDecoder::DeltaBaseToBase(bool sign, uint64_t delta_base,
uint64_t* base) {
if (sign) {
if (delta_base == std::numeric_limits<uint64_t>::max() ||
required_insert_count_ < delta_base + 1) {
return false;
}
*base = required_insert_count_ - delta_base - 1;
return true;
}
if (delta_base >
std::numeric_limits<uint64_t>::max() - required_insert_count_) {
return false;
}
*base = required_insert_count_ + delta_base;
return true;
}
} // namespace quic