1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/spdy/hpack_encoder.h"
9 #include "base/logging.h"
10 #include "net/spdy/hpack_header_table.h"
11 #include "net/spdy/hpack_huffman_table.h"
12 #include "net/spdy/hpack_output_stream.h"
16 using base::StringPiece;
19 HpackEncoder::HpackEncoder(const HpackHuffmanTable& table)
21 allow_huffman_compression_(true),
22 huffman_table_(table),
24 total_char_counts_(NULL) {}
26 HpackEncoder::~HpackEncoder() {}
28 bool HpackEncoder::EncodeHeaderSet(const std::map<string, string>& header_set,
30 // Separate header set into pseudo-headers and regular headers.
31 Representations pseudo_headers;
32 Representations regular_headers;
33 for (std::map<string, string>::const_iterator it = header_set.begin();
34 it != header_set.end(); ++it) {
35 if (it->first == "cookie") {
36 // Note that there can only be one "cookie" header, because header_set is
38 CookieToCrumbs(*it, ®ular_headers);
39 } else if (it->first[0] == kPseudoHeaderPrefix) {
40 DecomposeRepresentation(*it, &pseudo_headers);
42 DecomposeRepresentation(*it, ®ular_headers);
46 // Encode pseudo-headers.
47 for (Representations::const_iterator it = pseudo_headers.begin();
48 it != pseudo_headers.end(); ++it) {
49 const HpackEntry* entry =
50 header_table_.GetByNameAndValue(it->first, it->second);
54 if (it->first == ":authority") {
55 // :authority is always present and rarely changes, and has moderate
56 // length, therefore it makes a lot of sense to index (insert in the
58 EmitIndexedLiteral(*it);
60 // Most common pseudo-header fields are represented in the static table,
61 // while uncommon ones are small, so do not index them.
62 EmitNonIndexedLiteral(*it);
67 // Encode regular headers that are already in the header table first,
68 // save the rest into another vector. This way we avoid evicting an entry
69 // from the header table before it can be used.
70 Representations literal_headers;
71 for (Representations::const_iterator it = regular_headers.begin();
72 it != regular_headers.end(); ++it) {
73 const HpackEntry* entry =
74 header_table_.GetByNameAndValue(it->first, it->second);
78 literal_headers.push_back(*it);
82 // Encode the remaining header fields, while inserting them in the header
84 for (Representations::const_iterator it = literal_headers.begin();
85 it != literal_headers.end(); ++it) {
86 EmitIndexedLiteral(*it);
89 output_stream_.TakeString(output);
93 bool HpackEncoder::EncodeHeaderSetWithoutCompression(
94 const std::map<string, string>& header_set,
97 allow_huffman_compression_ = false;
98 for (std::map<string, string>::const_iterator it = header_set.begin();
99 it != header_set.end(); ++it) {
100 // Note that cookies are not crumbled in this case.
101 EmitNonIndexedLiteral(*it);
103 allow_huffman_compression_ = true;
104 output_stream_.TakeString(output);
108 void HpackEncoder::EmitIndex(const HpackEntry* entry) {
109 output_stream_.AppendPrefix(kIndexedOpcode);
110 output_stream_.AppendUint32(header_table_.IndexOf(entry));
113 void HpackEncoder::EmitIndexedLiteral(const Representation& representation) {
114 output_stream_.AppendPrefix(kLiteralIncrementalIndexOpcode);
115 EmitLiteral(representation);
116 header_table_.TryAddEntry(representation.first, representation.second);
119 void HpackEncoder::EmitNonIndexedLiteral(
120 const Representation& representation) {
121 output_stream_.AppendPrefix(kLiteralNoIndexOpcode);
122 output_stream_.AppendUint32(0);
123 EmitString(representation.first);
124 EmitString(representation.second);
127 void HpackEncoder::EmitLiteral(const Representation& representation) {
128 const HpackEntry* name_entry = header_table_.GetByName(representation.first);
129 if (name_entry != NULL) {
130 output_stream_.AppendUint32(header_table_.IndexOf(name_entry));
132 output_stream_.AppendUint32(0);
133 EmitString(representation.first);
135 EmitString(representation.second);
138 void HpackEncoder::EmitString(StringPiece str) {
139 size_t encoded_size = (!allow_huffman_compression_ ? str.size()
140 : huffman_table_.EncodedSize(str));
141 if (encoded_size < str.size()) {
142 output_stream_.AppendPrefix(kStringLiteralHuffmanEncoded);
143 output_stream_.AppendUint32(encoded_size);
144 huffman_table_.EncodeString(str, &output_stream_);
146 output_stream_.AppendPrefix(kStringLiteralIdentityEncoded);
147 output_stream_.AppendUint32(str.size());
148 output_stream_.AppendBytes(str);
150 UpdateCharacterCounts(str);
153 void HpackEncoder::SetCharCountsStorage(std::vector<size_t>* char_counts,
154 size_t* total_char_counts) {
155 CHECK_LE(256u, char_counts->size());
156 char_counts_ = char_counts;
157 total_char_counts_ = total_char_counts;
160 void HpackEncoder::UpdateCharacterCounts(base::StringPiece str) {
161 if (char_counts_ == NULL || total_char_counts_ == NULL) {
164 for (StringPiece::const_iterator it = str.begin(); it != str.end(); ++it) {
165 ++(*char_counts_)[static_cast<uint8>(*it)];
167 (*total_char_counts_) += str.size();
171 void HpackEncoder::CookieToCrumbs(const Representation& cookie,
172 Representations* out) {
173 size_t prior_size = out->size();
175 // See Section 8.1.2.5. "Compressing the Cookie Header Field" in the HTTP/2
176 // specification at https://tools.ietf.org/html/draft-ietf-httpbis-http2-14.
177 // Cookie values are split into individually-encoded HPACK representations.
178 for (size_t pos = 0;;) {
179 size_t end = cookie.second.find(";", pos);
181 if (end == StringPiece::npos) {
182 out->push_back(make_pair(
184 cookie.second.substr(pos)));
187 out->push_back(make_pair(
189 cookie.second.substr(pos, end - pos)));
191 // Consume next space if present.
193 if (pos != cookie.second.size() && cookie.second[pos] == ' ') {
197 // Sort crumbs and remove duplicates.
198 std::sort(out->begin() + prior_size, out->end());
199 out->erase(std::unique(out->begin() + prior_size, out->end()),
204 void HpackEncoder::DecomposeRepresentation(const Representation& header_field,
205 Representations* out) {
208 while (end != StringPiece::npos) {
209 end = header_field.second.find('\0', pos);
210 out->push_back(make_pair(header_field.first,
211 header_field.second.substr(pos, end - pos)));