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_utils.h" | |
6 | |
7 #include <string> | |
8 | |
9 #include "base/memory/scoped_ptr.h" | |
10 #include "base/strings/string_number_conversions.h" | |
11 #include "base/strings/string_piece.h" | |
12 #include "base/strings/string_util.h" | |
13 #include "net/spdy/spdy_frame_builder.h" | |
14 #include "net/spdy/spdy_framer.h" | |
15 #include "net/spdy/spdy_protocol.h" | |
16 #include "net/tools/balsa/balsa_headers.h" | |
17 #include "url/gurl.h" | |
18 | |
19 using base::StringPiece; | |
20 using std::make_pair; | |
21 using std::pair; | |
22 using std::string; | |
23 | |
24 namespace net { | |
25 namespace tools { | |
26 | |
27 const char kV4Host[] = ":authority"; | |
28 | |
29 const char kV3Host[] = ":host"; | |
30 const char kV3Path[] = ":path"; | |
31 const char kV3Scheme[] = ":scheme"; | |
32 const char kV3Status[] = ":status"; | |
33 const char kV3Method[] = ":method"; | |
34 const char kV3Version[] = ":version"; | |
35 | |
36 void PopulateSpdyHeaderBlock(const BalsaHeaders& headers, | |
37 SpdyHeaderBlock* block, | |
38 bool allow_empty_values) { | |
39 for (BalsaHeaders::const_header_lines_iterator hi = | |
40 headers.header_lines_begin(); | |
41 hi != headers.header_lines_end(); | |
42 ++hi) { | |
43 if ((hi->second.length() == 0) && !allow_empty_values) { | |
44 DVLOG(1) << "Dropping empty header " << hi->first.as_string() | |
45 << " from headers"; | |
46 continue; | |
47 } | |
48 | |
49 // This unfortunately involves loads of copying, but its the simplest way | |
50 // to sort the headers and leverage the framer. | |
51 string name = hi->first.as_string(); | |
52 base::StringToLowerASCII(&name); | |
53 SpdyHeaderBlock::iterator it = block->find(name); | |
54 if (it != block->end()) { | |
55 it->second.reserve(it->second.size() + 1 + hi->second.size()); | |
56 it->second.append("\0", 1); | |
57 it->second.append(hi->second.data(), hi->second.size()); | |
58 } else { | |
59 block->insert(make_pair(name, hi->second.as_string())); | |
60 } | |
61 } | |
62 } | |
63 | |
64 void PopulateSpdy3RequestHeaderBlock(const BalsaHeaders& headers, | |
65 const string& scheme, | |
66 const string& host_and_port, | |
67 const string& path, | |
68 SpdyHeaderBlock* block) { | |
69 PopulateSpdyHeaderBlock(headers, block, true); | |
70 StringPiece host_header = headers.GetHeader("Host"); | |
71 if (!host_header.empty()) { | |
72 DCHECK(host_and_port.empty() || host_header == host_and_port); | |
73 block->insert(make_pair(kV3Host, host_header.as_string())); | |
74 } else { | |
75 block->insert(make_pair(kV3Host, host_and_port)); | |
76 } | |
77 block->insert(make_pair(kV3Path, path)); | |
78 block->insert(make_pair(kV3Scheme, scheme)); | |
79 | |
80 if (!headers.request_method().empty()) { | |
81 block->insert(make_pair(kV3Method, headers.request_method().as_string())); | |
82 } | |
83 | |
84 if (!headers.request_version().empty()) { | |
85 (*block)[kV3Version] = headers.request_version().as_string(); | |
86 } | |
87 } | |
88 | |
89 void PopulateSpdy4RequestHeaderBlock(const BalsaHeaders& headers, | |
90 const string& scheme, | |
91 const string& host_and_port, | |
92 const string& path, | |
93 SpdyHeaderBlock* block) { | |
94 PopulateSpdyHeaderBlock(headers, block, true); | |
95 StringPiece host_header = headers.GetHeader("Host"); | |
96 if (!host_header.empty()) { | |
97 DCHECK(host_and_port.empty() || host_header == host_and_port); | |
98 block->insert(make_pair(kV4Host, host_header.as_string())); | |
99 // PopulateSpdyHeaderBlock already added the "host" header, | |
100 // which is invalid for SPDY4. | |
101 block->erase("host"); | |
102 } else { | |
103 block->insert(make_pair(kV4Host, host_and_port)); | |
104 } | |
105 block->insert(make_pair(kV3Path, path)); | |
106 block->insert(make_pair(kV3Scheme, scheme)); | |
107 | |
108 if (!headers.request_method().empty()) { | |
109 block->insert(make_pair(kV3Method, headers.request_method().as_string())); | |
110 } | |
111 } | |
112 | |
113 void PopulateSpdyResponseHeaderBlock(const BalsaHeaders& headers, | |
114 SpdyHeaderBlock* block) { | |
115 string status = headers.response_code().as_string(); | |
116 status.append(" "); | |
117 status.append(headers.response_reason_phrase().as_string()); | |
118 (*block)[kV3Status] = status; | |
119 (*block)[kV3Version] = | |
120 headers.response_version().as_string(); | |
121 | |
122 // Empty header values are only allowed because this is spdy3. | |
123 PopulateSpdyHeaderBlock(headers, block, true); | |
124 } | |
125 | |
126 // static | |
127 SpdyHeaderBlock SpdyUtils::RequestHeadersToSpdyHeaders( | |
128 const BalsaHeaders& request_headers) { | |
129 string scheme; | |
130 string host_and_port; | |
131 string path; | |
132 | |
133 string url = request_headers.request_uri().as_string(); | |
134 if (url.empty() || url[0] == '/') { | |
135 path = url; | |
136 } else { | |
137 GURL request_uri(url); | |
138 if (request_headers.request_method() == "CONNECT") { | |
139 path = url; | |
140 } else { | |
141 path = request_uri.path(); | |
142 if (!request_uri.query().empty()) { | |
143 path = path + "?" + request_uri.query(); | |
144 } | |
145 host_and_port = request_uri.host(); | |
146 scheme = request_uri.scheme(); | |
147 } | |
148 } | |
149 | |
150 DCHECK(!scheme.empty()); | |
151 DCHECK(!host_and_port.empty()); | |
152 DCHECK(!path.empty()); | |
153 | |
154 SpdyHeaderBlock block; | |
155 PopulateSpdy3RequestHeaderBlock( | |
156 request_headers, scheme, host_and_port, path, &block); | |
157 if (block.find("host") != block.end()) { | |
158 block.erase(block.find("host")); | |
159 } | |
160 return block; | |
161 } | |
162 | |
163 // static | |
164 SpdyHeaderBlock SpdyUtils::RequestHeadersToSpdy4Headers( | |
165 const BalsaHeaders& request_headers) { | |
166 string scheme; | |
167 string host_and_port; | |
168 string path; | |
169 | |
170 string url = request_headers.request_uri().as_string(); | |
171 if (url.empty() || url[0] == '/') { | |
172 path = url; | |
173 } else { | |
174 GURL request_uri(url); | |
175 if (request_headers.request_method() == "CONNECT") { | |
176 path = url; | |
177 } else { | |
178 path = request_uri.path(); | |
179 if (!request_uri.query().empty()) { | |
180 path = path + "?" + request_uri.query(); | |
181 } | |
182 host_and_port = request_uri.host(); | |
183 scheme = request_uri.scheme(); | |
184 } | |
185 } | |
186 | |
187 DCHECK(!scheme.empty()); | |
188 DCHECK(!host_and_port.empty()); | |
189 DCHECK(!path.empty()); | |
190 | |
191 SpdyHeaderBlock block; | |
192 PopulateSpdy4RequestHeaderBlock(request_headers, scheme, host_and_port, path, | |
193 &block); | |
194 if (block.find("host") != block.end()) { | |
195 block.erase(block.find("host")); | |
196 } | |
197 return block; | |
198 } | |
199 | |
200 // static | |
201 string SpdyUtils::SerializeRequestHeaders(const BalsaHeaders& request_headers) { | |
202 SpdyHeaderBlock block = RequestHeadersToSpdyHeaders(request_headers); | |
203 return SerializeUncompressedHeaders(block); | |
204 } | |
205 | |
206 // static | |
207 SpdyHeaderBlock SpdyUtils::ResponseHeadersToSpdyHeaders( | |
208 const BalsaHeaders& response_headers) { | |
209 SpdyHeaderBlock block; | |
210 PopulateSpdyResponseHeaderBlock(response_headers, &block); | |
211 return block; | |
212 } | |
213 | |
214 // static | |
215 string SpdyUtils::SerializeResponseHeaders( | |
216 const BalsaHeaders& response_headers) { | |
217 SpdyHeaderBlock block = ResponseHeadersToSpdyHeaders(response_headers); | |
218 | |
219 return SerializeUncompressedHeaders(block); | |
220 } | |
221 | |
222 // static | |
223 string SpdyUtils::SerializeUncompressedHeaders(const SpdyHeaderBlock& headers) { | |
224 size_t length = SpdyFramer::GetSerializedLength(SPDY3, &headers); | |
225 SpdyFrameBuilder builder(length, SPDY3); | |
226 SpdyFramer::WriteHeaderBlock(&builder, SPDY3, &headers); | |
227 scoped_ptr<SpdyFrame> block(builder.take()); | |
228 return string(block->data(), length); | |
229 } | |
230 | |
231 bool IsSpecialSpdyHeader(SpdyHeaderBlock::const_iterator header, | |
232 BalsaHeaders* headers) { | |
233 if (header->first.empty() || header->second.empty()) { | |
234 return true; | |
235 } | |
236 const string& header_name = header->first; | |
237 return header_name.c_str()[0] == ':'; | |
238 } | |
239 | |
240 bool SpdyUtils::FillBalsaRequestHeaders( | |
241 const SpdyHeaderBlock& header_block, | |
242 BalsaHeaders* request_headers) { | |
243 typedef SpdyHeaderBlock::const_iterator BlockIt; | |
244 | |
245 BlockIt host_it = header_block.find(kV3Host); | |
246 BlockIt path_it = header_block.find(kV3Path); | |
247 BlockIt scheme_it = header_block.find(kV3Scheme); | |
248 BlockIt method_it = header_block.find(kV3Method); | |
249 BlockIt end_it = header_block.end(); | |
250 if (host_it == end_it || path_it == end_it || scheme_it == end_it || | |
251 method_it == end_it) { | |
252 return false; | |
253 } | |
254 string url = scheme_it->second; | |
255 url.append("://"); | |
256 url.append(host_it->second); | |
257 url.append(path_it->second); | |
258 request_headers->SetRequestUri(url); | |
259 request_headers->SetRequestMethod(method_it->second); | |
260 | |
261 BlockIt cl_it = header_block.find("content-length"); | |
262 if (cl_it != header_block.end()) { | |
263 int content_length; | |
264 if (!base::StringToInt(cl_it->second, &content_length)) { | |
265 return false; | |
266 } | |
267 request_headers->SetContentLength(content_length); | |
268 } | |
269 | |
270 for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) { | |
271 if (!IsSpecialSpdyHeader(it, request_headers)) { | |
272 request_headers->AppendHeader(it->first, it->second); | |
273 } | |
274 } | |
275 | |
276 return true; | |
277 } | |
278 | |
279 // The reason phrase should match regexp [\d\d\d [^\r\n]+]. If not, we will | |
280 // fail to parse it. | |
281 bool ParseReasonAndStatus(StringPiece status_and_reason, | |
282 BalsaHeaders* headers) { | |
283 if (status_and_reason.size() < 5) | |
284 return false; | |
285 | |
286 if (status_and_reason[3] != ' ') | |
287 return false; | |
288 | |
289 const StringPiece status_str = StringPiece(status_and_reason.data(), 3); | |
290 int status; | |
291 if (!base::StringToInt(status_str, &status)) { | |
292 return false; | |
293 } | |
294 | |
295 headers->SetResponseCode(status_str); | |
296 headers->set_parsed_response_code(status); | |
297 | |
298 StringPiece reason(status_and_reason.data() + 4, | |
299 status_and_reason.length() - 4); | |
300 | |
301 headers->SetResponseReasonPhrase(reason); | |
302 return true; | |
303 } | |
304 | |
305 bool SpdyUtils::FillBalsaResponseHeaders( | |
306 const SpdyHeaderBlock& header_block, | |
307 BalsaHeaders* request_headers) { | |
308 typedef SpdyHeaderBlock::const_iterator BlockIt; | |
309 | |
310 BlockIt status_it = header_block.find(kV3Status); | |
311 BlockIt version_it = header_block.find(kV3Version); | |
312 BlockIt end_it = header_block.end(); | |
313 if (status_it == end_it || version_it == end_it) { | |
314 return false; | |
315 } | |
316 | |
317 if (!ParseReasonAndStatus(status_it->second, request_headers)) { | |
318 return false; | |
319 } | |
320 request_headers->SetResponseVersion(version_it->second); | |
321 for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) { | |
322 if (!IsSpecialSpdyHeader(it, request_headers)) { | |
323 request_headers->AppendHeader(it->first, it->second); | |
324 } | |
325 } | |
326 return true; | |
327 } | |
328 | |
329 } // namespace tools | |
330 } // namespace net | |
OLD | NEW |