OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 "net/tools/quic/spdy_balsa_utils.h" | |
6 | |
7 #include <memory> | |
8 #include <string> | |
9 | |
10 #include "base/strings/string_number_conversions.h" | |
11 #include "base/strings/string_piece.h" | |
12 #include "base/strings/string_split.h" | |
13 #include "base/strings/string_util.h" | |
14 #include "net/base/linked_hash_map.h" | |
15 #include "net/quic/core/quic_flags.h" | |
16 #include "net/quic/core/spdy_utils.h" | |
17 #include "net/spdy/spdy_frame_builder.h" | |
18 #include "net/spdy/spdy_framer.h" | |
19 #include "net/spdy/spdy_protocol.h" | |
20 #include "net/tools/balsa/balsa_headers.h" | |
21 #include "url/gurl.h" | |
22 | |
23 using base::StringPiece; | |
24 using base::StringPieceHash; | |
25 using std::make_pair; | |
26 using std::pair; | |
27 using std::string; | |
28 | |
29 namespace net { | |
30 namespace { | |
31 | |
32 const char kV4Host[] = ":authority"; | |
33 | |
34 const char kV3Host[] = ":host"; | |
35 const char kV3Path[] = ":path"; | |
36 const char kV3Scheme[] = ":scheme"; | |
37 const char kV3Method[] = ":method"; | |
38 const char kV3Status[] = ":status"; | |
39 const char kV3Version[] = ":version"; | |
40 | |
41 void PopulateSpdyHeaderBlock(const BalsaHeaders& headers, | |
42 SpdyHeaderBlock* block, | |
43 bool allow_empty_values) { | |
44 using HeaderValuesMap = | |
45 linked_hash_map<StringPiece, std::vector<StringPiece>, StringPieceHash>; | |
46 std::deque<string> names; | |
47 HeaderValuesMap header_values_map; | |
48 // First, gather references to all values for each name. | |
49 for (BalsaHeaders::const_header_lines_iterator hi = | |
50 headers.header_lines_begin(); | |
51 hi != headers.header_lines_end(); ++hi) { | |
52 if ((hi->second.length() == 0) && !allow_empty_values) { | |
53 DVLOG(1) << "Dropping empty header " << hi->first.as_string() | |
54 << " from headers"; | |
55 continue; | |
56 } | |
57 const string name = base::ToLowerASCII(hi->first.as_string()); | |
58 names.push_back(name); | |
59 header_values_map[name].push_back(hi->second); | |
60 } | |
61 // Then, write joined representations to the header block. | |
62 for (const auto& header : header_values_map) { | |
63 if (header.second.size() == 1) { | |
64 // Avoid string allocation for the single value case. | |
65 (*block)[header.first] = header.second[0]; | |
66 } else { | |
67 StringPiece separator("\0", 1); | |
68 auto it = header.second.begin(); | |
69 string value = it->as_string(); | |
70 ++it; | |
71 for (; it != header.second.end(); ++it) { | |
72 separator.AppendToString(&value); | |
73 value.append(it->data(), it->size()); | |
74 } | |
75 (*block)[header.first] = value; | |
76 } | |
77 } | |
78 } | |
79 | |
80 void PopulateHttp2RequestHeaderBlock(const BalsaHeaders& headers, | |
81 const string& scheme, | |
82 const string& host_and_port, | |
83 const string& path, | |
84 SpdyHeaderBlock* block) { | |
85 PopulateSpdyHeaderBlock(headers, block, true); | |
86 StringPiece host_header = headers.GetHeader("Host"); | |
87 if (!host_header.empty()) { | |
88 DCHECK(host_and_port.empty() || host_header == host_and_port); | |
89 block->insert(make_pair(kV4Host, host_header)); | |
90 // PopulateSpdyHeaderBlock already added the "host" header, | |
91 // which is invalid for HTTP2. | |
92 block->erase("host"); | |
93 } else { | |
94 block->insert(make_pair(kV4Host, host_and_port)); | |
95 } | |
96 block->insert(make_pair(kV3Path, path)); | |
97 block->insert(make_pair(kV3Scheme, scheme)); | |
98 | |
99 if (!headers.request_method().empty()) { | |
100 block->insert(make_pair(kV3Method, headers.request_method())); | |
101 } | |
102 } | |
103 | |
104 void PopulateSpdyResponseHeaderBlock(SpdyMajorVersion version, | |
105 const BalsaHeaders& headers, | |
106 SpdyHeaderBlock* block) { | |
107 if (version <= SPDY3) { | |
108 string status = headers.response_code().as_string(); | |
109 status.append(" "); | |
110 status.append(headers.response_reason_phrase().as_string()); | |
111 (*block)[kV3Status] = status; | |
112 (*block)[kV3Version] = headers.response_version(); | |
113 } else { | |
114 (*block)[kV3Status] = headers.response_code(); | |
115 } | |
116 | |
117 PopulateSpdyHeaderBlock(headers, block, true); | |
118 } | |
119 | |
120 bool IsSpecialSpdyHeader(SpdyHeaderBlock::const_iterator header, | |
121 BalsaHeaders* headers) { | |
122 return header->first.empty() || /* header->second.empty() || */ | |
123 header->first[0] == ':'; | |
124 } | |
125 | |
126 // The reason phrase should match regexp [\d\d\d [^\r\n]+]. If not, we will | |
127 // fail to parse it. | |
128 bool ParseReasonAndStatus(StringPiece status_and_reason, | |
129 BalsaHeaders* headers) { | |
130 int status; | |
131 if (!base::StringToInt(status_and_reason, &status)) { | |
132 return false; | |
133 } | |
134 headers->SetResponseCode(status_and_reason); | |
135 headers->SetResponseCode(status_and_reason); | |
136 headers->set_parsed_response_code(status); | |
137 return true; | |
138 } | |
139 | |
140 // static | |
141 void SpdyHeadersToResponseHeaders(const SpdyHeaderBlock& header_block, | |
142 BalsaHeaders* request_headers) { | |
143 typedef SpdyHeaderBlock::const_iterator BlockIt; | |
144 | |
145 BlockIt status_it = header_block.find(kV3Status); | |
146 BlockIt end_it = header_block.end(); | |
147 if (status_it == end_it) { | |
148 return; | |
149 } | |
150 | |
151 if (!ParseReasonAndStatus(status_it->second, request_headers)) { | |
152 return; | |
153 } | |
154 | |
155 for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) { | |
156 if (!IsSpecialSpdyHeader(it, request_headers)) { | |
157 if (it->second.empty()) { | |
158 request_headers->AppendHeader(it->first, it->second); | |
159 } else { | |
160 DVLOG(2) << "Splitting value: [" << it->second << "]" | |
161 << " for key: " << it->first; | |
162 for (string value : | |
163 base::SplitString(it->second, base::StringPiece("\0", 1), | |
164 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { | |
165 DVLOG(2) << "AppendHeader(" << it->first << ", " << value << ")"; | |
166 request_headers->AppendHeader(it->first, StringPiece(value)); | |
167 } | |
168 } | |
169 } | |
170 } | |
171 } | |
172 | |
173 // static | |
174 void SpdyHeadersToRequestHeaders(const SpdyHeaderBlock& header_block, | |
175 BalsaHeaders* request_headers) { | |
176 typedef SpdyHeaderBlock::const_iterator BlockIt; | |
177 | |
178 BlockIt authority_it = header_block.find(kV4Host); | |
179 BlockIt host_it = header_block.find(kV3Host); | |
180 BlockIt method_it = header_block.find(kV3Method); | |
181 BlockIt path_it = header_block.find(kV3Path); | |
182 BlockIt scheme_it = header_block.find(kV3Scheme); | |
183 BlockIt end_it = header_block.end(); | |
184 | |
185 string method; | |
186 if (method_it == end_it) { | |
187 method = "GET"; | |
188 } else { | |
189 method = method_it->second.as_string(); | |
190 } | |
191 string uri; | |
192 if (scheme_it == end_it) { | |
193 uri += "https"; | |
194 } else { | |
195 uri += scheme_it->second.as_string(); | |
196 } | |
197 uri += "://"; | |
198 | |
199 if (authority_it != end_it) { | |
200 uri += authority_it->second.as_string(); | |
201 request_headers->AppendHeader("host", authority_it->second); | |
202 } else if (host_it != end_it) { | |
203 uri += host_it->second.as_string(); | |
204 request_headers->AppendHeader("host", host_it->second); | |
205 } | |
206 | |
207 if (path_it == end_it) { | |
208 uri += "/"; | |
209 } else { | |
210 uri += path_it->second.as_string(); | |
211 } | |
212 request_headers->SetRequestFirstlineFromStringPieces( | |
213 method, uri, net::kHttp2VersionString); | |
214 | |
215 for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) { | |
216 if (!IsSpecialSpdyHeader(it, request_headers)) { | |
217 if (it->second.empty()) { | |
218 request_headers->AppendHeader(it->first, it->second); | |
219 } else { | |
220 DVLOG(2) << "Splitting value: [" << it->second << "]" | |
221 << " for key: " << it->first; | |
222 for (string value : | |
223 base::SplitString(it->second, base::StringPiece("\0", 1), | |
224 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { | |
225 DVLOG(2) << "AppendHeader(" << it->first << ", " << value << ")"; | |
226 request_headers->AppendHeader(it->first, StringPiece(value)); | |
227 } | |
228 } | |
229 } | |
230 } | |
231 } | |
232 | |
233 // static | |
234 void SpdyHeadersToBalsaHeaders(const SpdyHeaderBlock& block, | |
235 BalsaHeaders* headers, | |
236 bool isResponse) { | |
237 if (isResponse) { | |
238 SpdyHeadersToResponseHeaders(block, headers); | |
239 return; | |
240 } | |
241 SpdyHeadersToRequestHeaders(block, headers); | |
242 } | |
243 | |
244 } // namespace | |
245 | |
246 // static | |
247 SpdyHeaderBlock SpdyBalsaUtils::RequestHeadersToSpdyHeaders( | |
248 const BalsaHeaders& request_headers) { | |
249 string scheme; | |
250 string host_and_port; | |
251 string path; | |
252 | |
253 string url = request_headers.request_uri().as_string(); | |
254 if (url.empty() || url[0] == '/') { | |
255 path = url; | |
256 } else { | |
257 std::unique_ptr<GURL> request_uri(new GURL(url)); | |
258 if (request_headers.request_method() == "CONNECT") { | |
259 path = url; | |
260 } else { | |
261 path = request_uri->path(); | |
262 if (!request_uri->query().empty()) { | |
263 path = path + "?" + request_uri->query(); | |
264 } | |
265 host_and_port = request_uri->host(); | |
266 scheme = request_uri->scheme(); | |
267 } | |
268 } | |
269 | |
270 if (scheme.empty()) { | |
271 if (request_headers.HasHeader("Scheme")) { | |
272 request_headers.GetAllOfHeaderAsString("Scheme", &scheme); | |
273 } else { | |
274 // Requests must contain a :scheme header, and unless another scheme is | |
275 // detected, https is assumed. | |
276 scheme = "https"; | |
277 } | |
278 } | |
279 | |
280 SpdyHeaderBlock block; | |
281 PopulateHttp2RequestHeaderBlock(request_headers, scheme, host_and_port, path, | |
282 &block); | |
283 | |
284 // If a "Scheme" header existed in request_headers, it would have been | |
285 // propagated to |block|. | |
286 block.erase("scheme"); | |
287 return block; | |
288 } | |
289 | |
290 // static | |
291 SpdyHeaderBlock SpdyBalsaUtils::ResponseHeadersToSpdyHeaders( | |
292 const BalsaHeaders& response_headers) { | |
293 SpdyHeaderBlock block; | |
294 PopulateSpdyResponseHeaderBlock(HTTP2, response_headers, &block); | |
295 return block; | |
296 } | |
297 | |
298 // static | |
299 string SpdyBalsaUtils::SerializeResponseHeaders( | |
300 const BalsaHeaders& response_headers) { | |
301 SpdyHeaderBlock block = ResponseHeadersToSpdyHeaders(response_headers); | |
302 | |
303 return net::SpdyUtils::SerializeUncompressedHeaders(block); | |
304 } | |
305 | |
306 // static | |
307 void SpdyBalsaUtils::SpdyHeadersToResponseHeaders(const SpdyHeaderBlock& block, | |
308 BalsaHeaders* headers) { | |
309 SpdyHeadersToBalsaHeaders(block, headers, true); | |
310 } | |
311 | |
312 // static | |
313 void SpdyBalsaUtils::SpdyHeadersToRequestHeaders(const SpdyHeaderBlock& block, | |
314 BalsaHeaders* headers) { | |
315 SpdyHeadersToBalsaHeaders(block, headers, false); | |
316 } | |
317 | |
318 } // namespace net | |
OLD | NEW |