• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "quiche/spdy/core/hpack/hpack_encoder.h"
6 
7 #include <algorithm>
8 #include <limits>
9 #include <utility>
10 
11 #include "quiche/http2/hpack/huffman/hpack_huffman_encoder.h"
12 #include "quiche/common/platform/api/quiche_bug_tracker.h"
13 #include "quiche/common/platform/api/quiche_logging.h"
14 #include "quiche/spdy/core/hpack/hpack_constants.h"
15 #include "quiche/spdy/core/hpack/hpack_header_table.h"
16 #include "quiche/spdy/core/hpack/hpack_output_stream.h"
17 
18 namespace spdy {
19 
20 class HpackEncoder::RepresentationIterator {
21  public:
22   // |pseudo_headers| and |regular_headers| must outlive the iterator.
RepresentationIterator(const Representations & pseudo_headers,const Representations & regular_headers)23   RepresentationIterator(const Representations& pseudo_headers,
24                          const Representations& regular_headers)
25       : pseudo_begin_(pseudo_headers.begin()),
26         pseudo_end_(pseudo_headers.end()),
27         regular_begin_(regular_headers.begin()),
28         regular_end_(regular_headers.end()) {}
29 
30   // |headers| must outlive the iterator.
RepresentationIterator(const Representations & headers)31   explicit RepresentationIterator(const Representations& headers)
32       : pseudo_begin_(headers.begin()),
33         pseudo_end_(headers.end()),
34         regular_begin_(headers.end()),
35         regular_end_(headers.end()) {}
36 
HasNext()37   bool HasNext() {
38     return pseudo_begin_ != pseudo_end_ || regular_begin_ != regular_end_;
39   }
40 
Next()41   const Representation Next() {
42     if (pseudo_begin_ != pseudo_end_) {
43       return *pseudo_begin_++;
44     } else {
45       return *regular_begin_++;
46     }
47   }
48 
49  private:
50   Representations::const_iterator pseudo_begin_;
51   Representations::const_iterator pseudo_end_;
52   Representations::const_iterator regular_begin_;
53   Representations::const_iterator regular_end_;
54 };
55 
56 namespace {
57 
58 // The default header listener.
NoOpListener(absl::string_view,absl::string_view)59 void NoOpListener(absl::string_view /*name*/, absl::string_view /*value*/) {}
60 
61 // The default HPACK indexing policy.
DefaultPolicy(absl::string_view name,absl::string_view)62 bool DefaultPolicy(absl::string_view name, absl::string_view /* value */) {
63   if (name.empty()) {
64     return false;
65   }
66   // :authority is always present and rarely changes, and has moderate
67   // length, therefore it makes a lot of sense to index (insert in the
68   // dynamic table).
69   if (name[0] == kPseudoHeaderPrefix) {
70     return name == ":authority";
71   }
72   return true;
73 }
74 
75 }  // namespace
76 
HpackEncoder()77 HpackEncoder::HpackEncoder()
78     : output_stream_(),
79       min_table_size_setting_received_(std::numeric_limits<size_t>::max()),
80       listener_(NoOpListener),
81       should_index_(DefaultPolicy),
82       enable_compression_(true),
83       should_emit_table_size_(false) {}
84 
85 HpackEncoder::~HpackEncoder() = default;
86 
EncodeHeaderBlock(const Http2HeaderBlock & header_set)87 std::string HpackEncoder::EncodeHeaderBlock(
88     const Http2HeaderBlock& header_set) {
89   // Separate header set into pseudo-headers and regular headers.
90   Representations pseudo_headers;
91   Representations regular_headers;
92   bool found_cookie = false;
93   for (const auto& header : header_set) {
94     if (!found_cookie && header.first == "cookie") {
95       // Note that there can only be one "cookie" header, because header_set is
96       // a map.
97       found_cookie = true;
98       CookieToCrumbs(header, &regular_headers);
99     } else if (!header.first.empty() &&
100                header.first[0] == kPseudoHeaderPrefix) {
101       DecomposeRepresentation(header, &pseudo_headers);
102     } else {
103       DecomposeRepresentation(header, &regular_headers);
104     }
105   }
106 
107   RepresentationIterator iter(pseudo_headers, regular_headers);
108   return EncodeRepresentations(&iter);
109 }
110 
ApplyHeaderTableSizeSetting(size_t size_setting)111 void HpackEncoder::ApplyHeaderTableSizeSetting(size_t size_setting) {
112   if (size_setting == header_table_.settings_size_bound()) {
113     return;
114   }
115   if (size_setting < header_table_.settings_size_bound()) {
116     min_table_size_setting_received_ =
117         std::min(size_setting, min_table_size_setting_received_);
118   }
119   header_table_.SetSettingsHeaderTableSize(size_setting);
120   should_emit_table_size_ = true;
121 }
122 
EncodeRepresentations(RepresentationIterator * iter)123 std::string HpackEncoder::EncodeRepresentations(RepresentationIterator* iter) {
124   MaybeEmitTableSize();
125   while (iter->HasNext()) {
126     const auto header = iter->Next();
127     listener_(header.first, header.second);
128     if (enable_compression_) {
129       size_t index =
130           header_table_.GetByNameAndValue(header.first, header.second);
131       if (index != kHpackEntryNotFound) {
132         EmitIndex(index);
133       } else if (should_index_(header.first, header.second)) {
134         EmitIndexedLiteral(header);
135       } else {
136         EmitNonIndexedLiteral(header, enable_compression_);
137       }
138     } else {
139       EmitNonIndexedLiteral(header, enable_compression_);
140     }
141   }
142 
143   return output_stream_.TakeString();
144 }
145 
EmitIndex(size_t index)146 void HpackEncoder::EmitIndex(size_t index) {
147   QUICHE_DVLOG(2) << "Emitting index " << index;
148   output_stream_.AppendPrefix(kIndexedOpcode);
149   output_stream_.AppendUint32(index);
150 }
151 
EmitIndexedLiteral(const Representation & representation)152 void HpackEncoder::EmitIndexedLiteral(const Representation& representation) {
153   QUICHE_DVLOG(2) << "Emitting indexed literal: (" << representation.first
154                   << ", " << representation.second << ")";
155   output_stream_.AppendPrefix(kLiteralIncrementalIndexOpcode);
156   EmitLiteral(representation);
157   header_table_.TryAddEntry(representation.first, representation.second);
158 }
159 
EmitNonIndexedLiteral(const Representation & representation,bool enable_compression)160 void HpackEncoder::EmitNonIndexedLiteral(const Representation& representation,
161                                          bool enable_compression) {
162   QUICHE_DVLOG(2) << "Emitting nonindexed literal: (" << representation.first
163                   << ", " << representation.second << ")";
164   output_stream_.AppendPrefix(kLiteralNoIndexOpcode);
165   size_t name_index = header_table_.GetByName(representation.first);
166   if (enable_compression && name_index != kHpackEntryNotFound) {
167     output_stream_.AppendUint32(name_index);
168   } else {
169     output_stream_.AppendUint32(0);
170     EmitString(representation.first);
171   }
172   EmitString(representation.second);
173 }
174 
EmitLiteral(const Representation & representation)175 void HpackEncoder::EmitLiteral(const Representation& representation) {
176   size_t name_index = header_table_.GetByName(representation.first);
177   if (name_index != kHpackEntryNotFound) {
178     output_stream_.AppendUint32(name_index);
179   } else {
180     output_stream_.AppendUint32(0);
181     EmitString(representation.first);
182   }
183   EmitString(representation.second);
184 }
185 
EmitString(absl::string_view str)186 void HpackEncoder::EmitString(absl::string_view str) {
187   size_t encoded_size =
188       enable_compression_ ? http2::HuffmanSize(str) : str.size();
189   if (encoded_size < str.size()) {
190     QUICHE_DVLOG(2) << "Emitted Huffman-encoded string of length "
191                     << encoded_size;
192     output_stream_.AppendPrefix(kStringLiteralHuffmanEncoded);
193     output_stream_.AppendUint32(encoded_size);
194     http2::HuffmanEncodeFast(str, encoded_size, output_stream_.MutableString());
195   } else {
196     QUICHE_DVLOG(2) << "Emitted literal string of length " << str.size();
197     output_stream_.AppendPrefix(kStringLiteralIdentityEncoded);
198     output_stream_.AppendUint32(str.size());
199     output_stream_.AppendBytes(str);
200   }
201 }
202 
MaybeEmitTableSize()203 void HpackEncoder::MaybeEmitTableSize() {
204   if (!should_emit_table_size_) {
205     return;
206   }
207   const size_t current_size = CurrentHeaderTableSizeSetting();
208   QUICHE_DVLOG(1) << "MaybeEmitTableSize current_size=" << current_size;
209   QUICHE_DVLOG(1) << "MaybeEmitTableSize min_table_size_setting_received_="
210                   << min_table_size_setting_received_;
211   if (min_table_size_setting_received_ < current_size) {
212     output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
213     output_stream_.AppendUint32(min_table_size_setting_received_);
214   }
215   output_stream_.AppendPrefix(kHeaderTableSizeUpdateOpcode);
216   output_stream_.AppendUint32(current_size);
217   min_table_size_setting_received_ = std::numeric_limits<size_t>::max();
218   should_emit_table_size_ = false;
219 }
220 
221 // static
CookieToCrumbs(const Representation & cookie,Representations * out)222 void HpackEncoder::CookieToCrumbs(const Representation& cookie,
223                                   Representations* out) {
224   // See Section 8.1.2.5. "Compressing the Cookie Header Field" in the HTTP/2
225   // specification at https://tools.ietf.org/html/draft-ietf-httpbis-http2-14.
226   // Cookie values are split into individually-encoded HPACK representations.
227   absl::string_view cookie_value = cookie.second;
228   // Consume leading and trailing whitespace if present.
229   absl::string_view::size_type first = cookie_value.find_first_not_of(" \t");
230   absl::string_view::size_type last = cookie_value.find_last_not_of(" \t");
231   if (first == absl::string_view::npos) {
232     cookie_value = absl::string_view();
233   } else {
234     cookie_value = cookie_value.substr(first, (last - first) + 1);
235   }
236   for (size_t pos = 0;;) {
237     size_t end = cookie_value.find(';', pos);
238 
239     if (end == absl::string_view::npos) {
240       out->push_back(std::make_pair(cookie.first, cookie_value.substr(pos)));
241       break;
242     }
243     out->push_back(
244         std::make_pair(cookie.first, cookie_value.substr(pos, end - pos)));
245 
246     // Consume next space if present.
247     pos = end + 1;
248     if (pos != cookie_value.size() && cookie_value[pos] == ' ') {
249       pos++;
250     }
251   }
252 }
253 
254 // static
DecomposeRepresentation(const Representation & header_field,Representations * out)255 void HpackEncoder::DecomposeRepresentation(const Representation& header_field,
256                                            Representations* out) {
257   size_t pos = 0;
258   size_t end = 0;
259   while (end != absl::string_view::npos) {
260     end = header_field.second.find('\0', pos);
261     out->push_back(std::make_pair(
262         header_field.first,
263         header_field.second.substr(
264             pos, end == absl::string_view::npos ? end : end - pos)));
265     pos = end + 1;
266   }
267 }
268 
269 // Iteratively encodes a Http2HeaderBlock.
270 class HpackEncoder::Encoderator : public ProgressiveEncoder {
271  public:
272   Encoderator(const Http2HeaderBlock& header_set, HpackEncoder* encoder);
273   Encoderator(const Representations& representations, HpackEncoder* encoder);
274 
275   // Encoderator is neither copyable nor movable.
276   Encoderator(const Encoderator&) = delete;
277   Encoderator& operator=(const Encoderator&) = delete;
278 
279   // Returns true iff more remains to encode.
HasNext() const280   bool HasNext() const override { return has_next_; }
281 
282   // Encodes and returns up to max_encoded_bytes of the current header block.
283   std::string Next(size_t max_encoded_bytes) override;
284 
285  private:
286   HpackEncoder* encoder_;
287   std::unique_ptr<RepresentationIterator> header_it_;
288   Representations pseudo_headers_;
289   Representations regular_headers_;
290   bool has_next_;
291 };
292 
Encoderator(const Http2HeaderBlock & header_set,HpackEncoder * encoder)293 HpackEncoder::Encoderator::Encoderator(const Http2HeaderBlock& header_set,
294                                        HpackEncoder* encoder)
295     : encoder_(encoder), has_next_(true) {
296   // Separate header set into pseudo-headers and regular headers.
297   bool found_cookie = false;
298   for (const auto& header : header_set) {
299     if (!found_cookie && header.first == "cookie") {
300       // Note that there can only be one "cookie" header, because header_set
301       // is a map.
302       found_cookie = true;
303       CookieToCrumbs(header, &regular_headers_);
304     } else if (!header.first.empty() &&
305                header.first[0] == kPseudoHeaderPrefix) {
306       DecomposeRepresentation(header, &pseudo_headers_);
307     } else {
308       DecomposeRepresentation(header, &regular_headers_);
309     }
310   }
311   header_it_ = std::make_unique<RepresentationIterator>(pseudo_headers_,
312                                                         regular_headers_);
313 
314   encoder_->MaybeEmitTableSize();
315 }
316 
Encoderator(const Representations & representations,HpackEncoder * encoder)317 HpackEncoder::Encoderator::Encoderator(const Representations& representations,
318                                        HpackEncoder* encoder)
319     : encoder_(encoder), has_next_(true) {
320   for (const auto& header : representations) {
321     if (header.first == "cookie") {
322       CookieToCrumbs(header, &regular_headers_);
323     } else if (!header.first.empty() &&
324                header.first[0] == kPseudoHeaderPrefix) {
325       pseudo_headers_.push_back(header);
326     } else {
327       regular_headers_.push_back(header);
328     }
329   }
330   header_it_ = std::make_unique<RepresentationIterator>(pseudo_headers_,
331                                                         regular_headers_);
332 
333   encoder_->MaybeEmitTableSize();
334 }
335 
Next(size_t max_encoded_bytes)336 std::string HpackEncoder::Encoderator::Next(size_t max_encoded_bytes) {
337   QUICHE_BUG_IF(spdy_bug_61_1, !has_next_)
338       << "Encoderator::Next called with nothing left to encode.";
339   const bool enable_compression = encoder_->enable_compression_;
340 
341   // Encode up to max_encoded_bytes of headers.
342   while (header_it_->HasNext() &&
343          encoder_->output_stream_.size() <= max_encoded_bytes) {
344     const Representation header = header_it_->Next();
345     encoder_->listener_(header.first, header.second);
346     if (enable_compression) {
347       size_t index = encoder_->header_table_.GetByNameAndValue(header.first,
348                                                                header.second);
349       if (index != kHpackEntryNotFound) {
350         encoder_->EmitIndex(index);
351       } else if (encoder_->should_index_(header.first, header.second)) {
352         encoder_->EmitIndexedLiteral(header);
353       } else {
354         encoder_->EmitNonIndexedLiteral(header, enable_compression);
355       }
356     } else {
357       encoder_->EmitNonIndexedLiteral(header, enable_compression);
358     }
359   }
360 
361   has_next_ = encoder_->output_stream_.size() > max_encoded_bytes;
362   return encoder_->output_stream_.BoundedTakeString(max_encoded_bytes);
363 }
364 
EncodeHeaderSet(const Http2HeaderBlock & header_set)365 std::unique_ptr<HpackEncoder::ProgressiveEncoder> HpackEncoder::EncodeHeaderSet(
366     const Http2HeaderBlock& header_set) {
367   return std::make_unique<Encoderator>(header_set, this);
368 }
369 
370 std::unique_ptr<HpackEncoder::ProgressiveEncoder>
EncodeRepresentations(const Representations & representations)371 HpackEncoder::EncodeRepresentations(const Representations& representations) {
372   return std::make_unique<Encoderator>(representations, this);
373 }
374 
375 }  // namespace spdy
376