OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "net/spdy/hpack_encoder.h" | 5 #include "net/spdy/hpack_encoder.h" |
6 | 6 |
7 #include "base/strings/string_util.h" | 7 #include <algorithm> |
| 8 |
| 9 #include "base/logging.h" |
| 10 #include "net/spdy/hpack_header_table.h" |
| 11 #include "net/spdy/hpack_huffman_table.h" |
8 #include "net/spdy/hpack_output_stream.h" | 12 #include "net/spdy/hpack_output_stream.h" |
9 | 13 |
10 namespace net { | 14 namespace net { |
11 | 15 |
12 using base::StringPiece; | 16 using base::StringPiece; |
13 using std::string; | 17 using std::string; |
14 | 18 |
15 HpackEncoder::HpackEncoder() | 19 namespace { |
16 : max_string_literal_size_(kDefaultMaxStringLiteralSize) {} | 20 |
| 21 const uint8 kNoState = 0; |
| 22 // Set on a referenced HpackEntry which is part of the current header set. |
| 23 const uint8 kReferencedImplicitOn = 1; |
| 24 // Set on a referenced HpackEntry which is not part of the current header set. |
| 25 const uint8 kReferencedExplicitOff = 2; |
| 26 // Set on a entries added to the reference set during this encoding. |
| 27 const uint8 kReferencedThisEncoding = 3; |
| 28 |
| 29 } // namespace |
| 30 |
| 31 HpackEncoder::HpackEncoder(const HpackHuffmanTable& table) |
| 32 : output_stream_(), |
| 33 allow_huffman_compression_(true), |
| 34 huffman_table_(table) {} |
17 | 35 |
18 HpackEncoder::~HpackEncoder() {} | 36 HpackEncoder::~HpackEncoder() {} |
19 | 37 |
20 bool HpackEncoder::EncodeHeaderSet(const std::map<string, string>& header_set, | 38 bool HpackEncoder::EncodeHeaderSet(const std::map<string, string>& header_set, |
21 string* output) { | 39 string* output) { |
22 // TOOD(jgraettinger): Do more sophisticated encoding. | 40 // Walk the set of entries to encode, which are not already implied by the |
23 HpackOutputStream output_stream(max_string_literal_size_); | 41 // header table's reference set. They must be explicitly emitted. |
| 42 Representations explicit_set(DetermineEncodingDelta(header_set)); |
| 43 for (Representations::const_iterator it = explicit_set.begin(); |
| 44 it != explicit_set.end(); ++it) { |
| 45 // Try to find an exact match. Note that dynamic entries are preferred |
| 46 // by the header table index. |
| 47 HpackEntry* entry = header_table_.GetByNameAndValue(it->first, it->second); |
| 48 if (entry != NULL && !entry->IsStatic()) { |
| 49 // Already in the dynamic table. Simply toggle on. |
| 50 CHECK_EQ(kNoState, entry->state()); |
| 51 EmitDynamicIndex(entry); |
| 52 continue; |
| 53 } |
| 54 |
| 55 // Walk the set of entries to be evicted by this insertion. |
| 56 HpackHeaderTable::EntryTable::iterator evict_begin, evict_end, evict_it; |
| 57 header_table_.EvictionSet(it->first, it->second, &evict_begin, &evict_end); |
| 58 |
| 59 for (evict_it = evict_begin; evict_it != evict_end; ++evict_it) { |
| 60 HpackEntry* evictee = &(*evict_it); |
| 61 |
| 62 if (evict_it->state() == kReferencedImplicitOn) { |
| 63 // Issue twice to explicitly emit. |
| 64 EmitDynamicIndex(evictee); |
| 65 EmitDynamicIndex(evictee); |
| 66 } else if (evictee->state() == kReferencedExplicitOff) { |
| 67 // Eviction saves us from having to explicitly toggle off. |
| 68 evictee->set_state(kNoState); |
| 69 } else if (evictee->state() == kReferencedThisEncoding) { |
| 70 // Already emitted. No action required. |
| 71 evictee->set_state(kNoState); |
| 72 } |
| 73 } |
| 74 if (entry != NULL) { |
| 75 EmitStaticIndex(entry); |
| 76 } else { |
| 77 EmitIndexedLiteral(*it); |
| 78 } |
| 79 } |
| 80 // Walk the reference set, toggling off as needed and clearing encoding state. |
| 81 for (HpackEntry::OrderedSet::const_iterator it = |
| 82 header_table_.reference_set().begin(); |
| 83 it != header_table_.reference_set().end();) { |
| 84 HpackEntry* entry = *(it++); // Step to prevent invalidation. |
| 85 CHECK_NE(kNoState, entry->state()); |
| 86 |
| 87 if (entry->state() == kReferencedExplicitOff) { |
| 88 // Explicitly toggle off. |
| 89 EmitDynamicIndex(entry); |
| 90 } |
| 91 entry->set_state(kNoState); |
| 92 } |
| 93 output_stream_.TakeString(output); |
| 94 return true; |
| 95 } |
| 96 |
| 97 bool HpackEncoder::EncodeHeaderSetWithoutCompression( |
| 98 const std::map<string, string>& header_set, |
| 99 string* output) { |
| 100 |
| 101 allow_huffman_compression_ = false; |
24 for (std::map<string, string>::const_iterator it = header_set.begin(); | 102 for (std::map<string, string>::const_iterator it = header_set.begin(); |
25 it != header_set.end(); ++it) { | 103 it != header_set.end(); ++it) { |
26 // TODO(jgraettinger): HTTP/2 requires strict lowercasing of headers, | 104 // Note that cookies are not crumbled in this case. |
27 // and the permissiveness here isn't wanted. Back this out in upstream. | 105 EmitNonIndexedLiteral(*it); |
28 if (LowerCaseEqualsASCII(it->first, "cookie")) { | 106 } |
29 std::vector<StringPiece> crumbs; | 107 allow_huffman_compression_ = true; |
30 CookieToCrumbs(it->second, &crumbs); | 108 output_stream_.TakeString(output); |
31 for (size_t i = 0; i != crumbs.size(); i++) { | |
32 if (!output_stream.AppendLiteralHeaderNoIndexingWithName( | |
33 it->first, crumbs[i])) { | |
34 return false; | |
35 } | |
36 } | |
37 } else if (!output_stream.AppendLiteralHeaderNoIndexingWithName( | |
38 it->first, it->second)) { | |
39 return false; | |
40 } | |
41 } | |
42 output_stream.TakeString(output); | |
43 return true; | 109 return true; |
44 } | 110 } |
45 | 111 |
46 void HpackEncoder::CookieToCrumbs(StringPiece cookie, | 112 void HpackEncoder::EmitDynamicIndex(HpackEntry* entry) { |
47 std::vector<StringPiece>* out) { | 113 DCHECK(!entry->IsStatic()); |
48 out->clear(); | 114 output_stream_.AppendPrefix(kIndexedOpcode); |
| 115 output_stream_.AppendUint32(entry->Index()); |
| 116 |
| 117 entry->set_state(kNoState); |
| 118 if (header_table_.Toggle(entry)) { |
| 119 // Was added to the reference set. |
| 120 entry->set_state(kReferencedThisEncoding); |
| 121 } |
| 122 } |
| 123 |
| 124 void HpackEncoder::EmitStaticIndex(HpackEntry* entry) { |
| 125 DCHECK(entry->IsStatic()); |
| 126 output_stream_.AppendPrefix(kIndexedOpcode); |
| 127 output_stream_.AppendUint32(entry->Index()); |
| 128 |
| 129 HpackEntry* new_entry = header_table_.TryAddEntry(entry->name(), |
| 130 entry->value()); |
| 131 if (new_entry) { |
| 132 // This is a static entry: no need to pin. |
| 133 header_table_.Toggle(new_entry); |
| 134 new_entry->set_state(kReferencedThisEncoding); |
| 135 } |
| 136 } |
| 137 |
| 138 void HpackEncoder::EmitIndexedLiteral(const Representation& representation) { |
| 139 output_stream_.AppendPrefix(kLiteralIncrementalIndexOpcode); |
| 140 EmitLiteral(representation); |
| 141 |
| 142 HpackEntry* new_entry = header_table_.TryAddEntry(representation.first, |
| 143 representation.second); |
| 144 if (new_entry) { |
| 145 header_table_.Toggle(new_entry); |
| 146 new_entry->set_state(kReferencedThisEncoding); |
| 147 } |
| 148 } |
| 149 |
| 150 void HpackEncoder::EmitNonIndexedLiteral( |
| 151 const Representation& representation) { |
| 152 output_stream_.AppendPrefix(kLiteralNoIndexOpcode); |
| 153 output_stream_.AppendUint32(0); |
| 154 EmitString(representation.first); |
| 155 EmitString(representation.second); |
| 156 } |
| 157 |
| 158 void HpackEncoder::EmitLiteral(const Representation& representation) { |
| 159 const HpackEntry* name_entry = header_table_.GetByName(representation.first); |
| 160 if (name_entry != NULL) { |
| 161 output_stream_.AppendUint32(name_entry->Index()); |
| 162 } else { |
| 163 output_stream_.AppendUint32(0); |
| 164 EmitString(representation.first); |
| 165 } |
| 166 EmitString(representation.second); |
| 167 } |
| 168 |
| 169 void HpackEncoder::EmitString(StringPiece str) { |
| 170 size_t encoded_size = (!allow_huffman_compression_ ? str.size() |
| 171 : huffman_table_.EncodedSize(str)); |
| 172 if (encoded_size < str.size()) { |
| 173 output_stream_.AppendPrefix(kStringLiteralHuffmanEncoded); |
| 174 output_stream_.AppendUint32(encoded_size); |
| 175 huffman_table_.EncodeString(str, &output_stream_); |
| 176 } else { |
| 177 output_stream_.AppendPrefix(kStringLiteralIdentityEncoded); |
| 178 output_stream_.AppendUint32(str.size()); |
| 179 output_stream_.AppendBytes(str); |
| 180 } |
| 181 } |
| 182 |
| 183 // static |
| 184 HpackEncoder::Representations HpackEncoder::DetermineEncodingDelta( |
| 185 const std::map<string, string>& header_set) { |
| 186 // Flatten & crumble headers into an ordered list of representations. |
| 187 Representations full_set; |
| 188 for (std::map<string, string>::const_iterator it = header_set.begin(); |
| 189 it != header_set.end(); ++it) { |
| 190 if (it->first == "cookie") { |
| 191 // Crumble cookie, and sort the result crumbs. |
| 192 size_t sort_offset = full_set.size(); |
| 193 CookieToCrumbs(*it, &full_set); |
| 194 std::sort(full_set.begin() + sort_offset, full_set.end()); |
| 195 } else { |
| 196 // Note std::map guarantees representations are ordered. |
| 197 full_set.push_back(make_pair( |
| 198 StringPiece(it->first), StringPiece(it->second))); |
| 199 } |
| 200 } |
| 201 // Perform a linear merge of ordered representations with the (also ordered) |
| 202 // reference set. Mark each referenced entry with current membership state, |
| 203 // and gather representations which must be explicitly emitted. |
| 204 Representations::const_iterator r_it = full_set.begin(); |
| 205 HpackEntry::OrderedSet::const_iterator s_it = |
| 206 header_table_.reference_set().begin(); |
| 207 |
| 208 Representations explicit_set; |
| 209 while (r_it != full_set.end() && |
| 210 s_it != header_table_.reference_set().end()) { |
| 211 // Compare on name, then value. |
| 212 int result = r_it->first.compare((*s_it)->name()); |
| 213 if (result == 0) { |
| 214 result = r_it->second.compare((*s_it)->value()); |
| 215 } |
| 216 |
| 217 if (result < 0) { |
| 218 explicit_set.push_back(*r_it); |
| 219 ++r_it; |
| 220 } else if (result > 0) { |
| 221 (*s_it)->set_state(kReferencedExplicitOff); |
| 222 ++s_it; |
| 223 } else { |
| 224 (*s_it)->set_state(kReferencedImplicitOn); |
| 225 ++s_it; |
| 226 ++r_it; |
| 227 } |
| 228 } |
| 229 while (r_it != full_set.end()) { |
| 230 explicit_set.push_back(*r_it); |
| 231 ++r_it; |
| 232 } |
| 233 while (s_it != header_table_.reference_set().end()) { |
| 234 (*s_it)->set_state(kReferencedExplicitOff); |
| 235 ++s_it; |
| 236 } |
| 237 return explicit_set; |
| 238 } |
| 239 |
| 240 // static |
| 241 void HpackEncoder::CookieToCrumbs(const Representation& cookie, |
| 242 Representations* out) { |
| 243 // See Section 8.1.3.4 "Compressing the Cookie Header Field" in the HTTP/2 |
| 244 // specification at http://tools.ietf.org/html/draft-ietf-httpbis-http2-11 |
| 245 // Cookie values are split into individually-encoded HPACK representations. |
49 for (size_t pos = 0;;) { | 246 for (size_t pos = 0;;) { |
50 size_t end = cookie.find(';', pos); | 247 size_t end = cookie.second.find(";", pos); |
51 | 248 |
52 if (end == StringPiece::npos) { | 249 if (end == StringPiece::npos) { |
53 out->push_back(cookie.substr(pos)); | 250 out->push_back(make_pair( |
| 251 cookie.first, |
| 252 cookie.second.substr(pos))); |
54 return; | 253 return; |
55 } | 254 } |
56 out->push_back(cookie.substr(pos, end - pos)); | 255 out->push_back(make_pair( |
| 256 cookie.first, |
| 257 cookie.second.substr(pos, end - pos))); |
57 | 258 |
58 // Consume next space if present. | 259 // Consume next space if present. |
59 pos = end + 1; | 260 pos = end + 1; |
60 if (pos != cookie.size() && cookie[pos] == ' ') { | 261 if (pos != cookie.second.size() && cookie.second[pos] == ' ') { |
61 pos++; | 262 pos++; |
62 } | 263 } |
63 } | 264 } |
64 } | 265 } |
65 | 266 |
66 } // namespace net | 267 } // namespace net |
OLD | NEW |