// 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_encoder.h" #include #include #include "base/logging.h" #include "net/spdy/hpack/hpack_constants.h" #include "net/spdy/hpack/hpack_header_table.h" #include "net/spdy/hpack/hpack_huffman_table.h" #include "net/spdy/hpack/hpack_output_stream.h" namespace net { using base::StringPiece; using std::string; HpackEncoder::HpackEncoder(const HpackHuffmanTable& table) : output_stream_(), huffman_table_(table), min_table_size_setting_received_(std::numeric_limits::max()), allow_huffman_compression_(true), should_emit_table_size_(false) {} HpackEncoder::~HpackEncoder() {} bool HpackEncoder::EncodeHeaderSet(const SpdyHeaderBlock& header_set, string* output) { MaybeEmitTableSize(); // Separate header set into pseudo-headers and regular headers. Representations pseudo_headers; Representations regular_headers; bool found_cookie = false; for (const auto& header : header_set) { if (!found_cookie && header.first == "cookie") { // Note that there can only be one "cookie" header, because header_set is // a map. found_cookie = true; CookieToCrumbs(header, ®ular_headers); } else if (!header.first.empty() && header.first[0] == kPseudoHeaderPrefix) { DecomposeRepresentation(header, &pseudo_headers); } else { DecomposeRepresentation(header, ®ular_headers); } } // Encode pseudo-headers. bool found_authority = false; for (const auto& header : pseudo_headers) { const HpackEntry* entry = header_table_.GetByNameAndValue(header.first, header.second); if (entry != NULL) { EmitIndex(entry); } else { // :authority is always present and rarely changes, and has moderate // length, therefore it makes a lot of sense to index (insert in the // header table). if (!found_authority && header.first == ":authority") { // Note that there can only be one ":authority" header, because // |header_set| is a map. found_authority = true; EmitIndexedLiteral(header); } else { // Most common pseudo-header fields are represented in the static table, // while uncommon ones are small, so do not index them. EmitNonIndexedLiteral(header); } } } // Encode regular headers. for (const auto& header : regular_headers) { const HpackEntry* entry = header_table_.GetByNameAndValue(header.first, header.second); if (entry != NULL) { EmitIndex(entry); } else { EmitIndexedLiteral(header); } } output_stream_.TakeString(output); return true; } bool HpackEncoder::EncodeHeaderSetWithoutCompression( const SpdyHeaderBlock& header_set, string* output) { allow_huffman_compression_ = false; MaybeEmitTableSize(); for (const auto& header : header_set) { // Note that cookies are not crumbled in this case. EmitNonIndexedLiteral(header); } allow_huffman_compression_ = true; output_stream_.TakeString(output); return true; } void HpackEncoder::ApplyHeaderTableSizeSetting(size_t size_setting) { if (size_setting == header_table_.settings_size_bound()) { return; } if (size_setting < header_table_.settings_size_bound()) { min_table_size_setting_received_ = std::min(size_setting, min_table_size_setting_received_); } header_table_.SetSettingsHeaderTableSize(size_setting); should_emit_table_size_ = true; } void HpackEncoder::EmitIndex(const HpackEntry* entry) { output_stream_.AppendPrefix(kIndexedOpcode); output_stream_.AppendUint32(header_table_.IndexOf(entry)); } void HpackEncoder::EmitIndexedLiteral(const Representation& representation) { output_stream_.AppendPrefix(kLiteralIncrementalIndexOpcode); EmitLiteral(representation); header_table_.TryAddEntry(representation.first, representation.second); } void HpackEncoder::EmitNonIndexedLiteral(const Representation& representation) { output_stream_.AppendPrefix(kLiteralNoIndexOpcode); output_stream_.AppendUint32(0); EmitString(representation.first); EmitString(representation.second); } void HpackEncoder::EmitLiteral(const Representation& representation) { const HpackEntry* name_entry = header_table_.GetByName(representation.first); if (name_entry != NULL) { output_stream_.AppendUint32(header_table_.IndexOf(name_entry)); } else { output_stream_.AppendUint32(0); EmitString(representation.first); } EmitString(representation.second); } void HpackEncoder::EmitString(StringPiece str) { size_t encoded_size = (!allow_huffman_compression_ ? str.size() : huffman_table_.EncodedSize(str)); if (encoded_size < str.size()) { output_stream_.AppendPrefix(kStringLiteralHuffmanEncoded); output_stream_.AppendUint32(encoded_size); huffman_table_.EncodeString(str, &output_stream_); } else { output_stream_.AppendPrefix(kStringLiteralIdentityEncoded); output_stream_.AppendUint32(str.size()); output_stream_.AppendBytes(str); } } void HpackEncoder::MaybeEmitTableSize() { if (!should_emit_table_size_) { return; } const size_t current_size = CurrentHeaderTableSizeSetting(); if (min_table_size_setting_received_ < current_size) { output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode); output_stream_.AppendUint32(min_table_size_setting_received_); } output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode); output_stream_.AppendUint32(current_size); min_table_size_setting_received_ = std::numeric_limits::max(); should_emit_table_size_ = false; } // static void HpackEncoder::CookieToCrumbs(const Representation& cookie, Representations* out) { // See Section 8.1.2.5. "Compressing the Cookie Header Field" in the HTTP/2 // specification at https://tools.ietf.org/html/draft-ietf-httpbis-http2-14. // Cookie values are split into individually-encoded HPACK representations. StringPiece cookie_value = cookie.second; // Consume leading and trailing whitespace if present. StringPiece::size_type first = cookie_value.find_first_not_of(" \t"); StringPiece::size_type last = cookie_value.find_last_not_of(" \t"); if (first == StringPiece::npos) { cookie_value.clear(); } else { cookie_value = cookie_value.substr(first, (last - first) + 1); } for (size_t pos = 0;;) { size_t end = cookie_value.find(";", pos); if (end == StringPiece::npos) { out->push_back(std::make_pair(cookie.first, cookie_value.substr(pos))); break; } out->push_back( std::make_pair(cookie.first, cookie_value.substr(pos, end - pos))); // Consume next space if present. pos = end + 1; if (pos != cookie_value.size() && cookie_value[pos] == ' ') { pos++; } } } // static void HpackEncoder::DecomposeRepresentation(const Representation& header_field, Representations* out) { size_t pos = 0; size_t end = 0; while (end != StringPiece::npos) { end = header_field.second.find('\0', pos); out->push_back( std::make_pair(header_field.first, header_field.second.substr( pos, end == StringPiece::npos ? end : end - pos))); pos = end + 1; } } } // namespace net