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/hpack_encoder.h"
9 #include "base/logging.h"
10 #include "net/spdy/hpack/hpack_header_table.h"
11 #include "net/spdy/hpack/hpack_huffman_table.h"
12 #include "net/spdy/hpack/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 SpdyHeaderBlock
& header_set
,
30 // Separate header set into pseudo-headers and regular headers.
31 Representations pseudo_headers
;
32 Representations regular_headers
;
33 bool found_cookie
= false;
34 for (const auto& header
: header_set
) {
35 if (!found_cookie
&& header
.first
== "cookie") {
36 // Note that there can only be one "cookie" header, because header_set is
39 CookieToCrumbs(header
, ®ular_headers
);
40 } else if (header
.first
[0] == kPseudoHeaderPrefix
) {
41 DecomposeRepresentation(header
, &pseudo_headers
);
43 DecomposeRepresentation(header
, ®ular_headers
);
47 // Encode pseudo-headers.
48 bool found_authority
= false;
49 for (const auto& header
: pseudo_headers
) {
50 const HpackEntry
* entry
=
51 header_table_
.GetByNameAndValue(header
.first
, header
.second
);
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 if (!found_authority
&& header
.first
== ":authority") {
59 // Note that there can only be one ":authority" header, because
60 // |header_set| is a map.
61 found_authority
= true;
62 EmitIndexedLiteral(header
);
64 // Most common pseudo-header fields are represented in the static table,
65 // while uncommon ones are small, so do not index them.
66 EmitNonIndexedLiteral(header
);
71 // Encode regular headers.
72 for (const auto& header
: regular_headers
) {
73 const HpackEntry
* entry
=
74 header_table_
.GetByNameAndValue(header
.first
, header
.second
);
78 EmitIndexedLiteral(header
);
82 output_stream_
.TakeString(output
);
86 bool HpackEncoder::EncodeHeaderSetWithoutCompression(
87 const SpdyHeaderBlock
& header_set
,
89 allow_huffman_compression_
= false;
90 for (const auto& header
: header_set
) {
91 // Note that cookies are not crumbled in this case.
92 EmitNonIndexedLiteral(header
);
94 allow_huffman_compression_
= true;
95 output_stream_
.TakeString(output
);
99 void HpackEncoder::EmitIndex(const HpackEntry
* entry
) {
100 output_stream_
.AppendPrefix(kIndexedOpcode
);
101 output_stream_
.AppendUint32(header_table_
.IndexOf(entry
));
104 void HpackEncoder::EmitIndexedLiteral(const Representation
& representation
) {
105 output_stream_
.AppendPrefix(kLiteralIncrementalIndexOpcode
);
106 EmitLiteral(representation
);
107 header_table_
.TryAddEntry(representation
.first
, representation
.second
);
110 void HpackEncoder::EmitNonIndexedLiteral(const Representation
& representation
) {
111 output_stream_
.AppendPrefix(kLiteralNoIndexOpcode
);
112 output_stream_
.AppendUint32(0);
113 EmitString(representation
.first
);
114 EmitString(representation
.second
);
117 void HpackEncoder::EmitLiteral(const Representation
& representation
) {
118 const HpackEntry
* name_entry
= header_table_
.GetByName(representation
.first
);
119 if (name_entry
!= NULL
) {
120 output_stream_
.AppendUint32(header_table_
.IndexOf(name_entry
));
122 output_stream_
.AppendUint32(0);
123 EmitString(representation
.first
);
125 EmitString(representation
.second
);
128 void HpackEncoder::EmitString(StringPiece str
) {
129 size_t encoded_size
=
130 (!allow_huffman_compression_
? str
.size()
131 : huffman_table_
.EncodedSize(str
));
132 if (encoded_size
< str
.size()) {
133 output_stream_
.AppendPrefix(kStringLiteralHuffmanEncoded
);
134 output_stream_
.AppendUint32(encoded_size
);
135 huffman_table_
.EncodeString(str
, &output_stream_
);
137 output_stream_
.AppendPrefix(kStringLiteralIdentityEncoded
);
138 output_stream_
.AppendUint32(str
.size());
139 output_stream_
.AppendBytes(str
);
141 UpdateCharacterCounts(str
);
144 void HpackEncoder::SetCharCountsStorage(std::vector
<size_t>* char_counts
,
145 size_t* total_char_counts
) {
146 CHECK_LE(256u, char_counts
->size());
147 char_counts_
= char_counts
;
148 total_char_counts_
= total_char_counts
;
151 void HpackEncoder::UpdateCharacterCounts(base::StringPiece str
) {
152 if (char_counts_
== NULL
|| total_char_counts_
== NULL
) {
155 for (StringPiece::const_iterator it
= str
.begin(); it
!= str
.end(); ++it
) {
156 ++(*char_counts_
)[static_cast<uint8
>(*it
)];
158 (*total_char_counts_
) += str
.size();
162 void HpackEncoder::CookieToCrumbs(const Representation
& cookie
,
163 Representations
* out
) {
164 // See Section 8.1.2.5. "Compressing the Cookie Header Field" in the HTTP/2
165 // specification at https://tools.ietf.org/html/draft-ietf-httpbis-http2-14.
166 // Cookie values are split into individually-encoded HPACK representations.
167 StringPiece cookie_value
= cookie
.second
;
168 // Consume leading and trailing whitespace if present.
169 StringPiece::size_type first
= cookie_value
.find_first_not_of(" \t");
170 StringPiece::size_type last
= cookie_value
.find_last_not_of(" \t");
171 if (first
== StringPiece::npos
) {
172 cookie_value
.clear();
174 cookie_value
= cookie_value
.substr(first
, (last
- first
) + 1);
176 for (size_t pos
= 0;;) {
177 size_t end
= cookie_value
.find(";", pos
);
179 if (end
== StringPiece::npos
) {
180 out
->push_back(std::make_pair(cookie
.first
, cookie_value
.substr(pos
)));
184 std::make_pair(cookie
.first
, cookie_value
.substr(pos
, end
- pos
)));
186 // Consume next space if present.
188 if (pos
!= cookie_value
.size() && cookie_value
[pos
] == ' ') {
195 void HpackEncoder::DecomposeRepresentation(const Representation
& header_field
,
196 Representations
* out
) {
199 while (end
!= StringPiece::npos
) {
200 end
= header_field
.second
.find('\0', pos
);
201 out
->push_back(std::make_pair(header_field
.first
,
202 header_field
.second
.substr(pos
, end
- pos
)));