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.
5 #include "net/tools/quic/spdy_utils.h"
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"
19 using base::StringPiece
;
27 const char kV4Host
[] = ":authority";
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";
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();
43 if ((hi
->second
.length() == 0) && !allow_empty_values
) {
44 DVLOG(1) << "Dropping empty header " << hi
->first
.as_string()
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());
59 block
->insert(make_pair(name
, hi
->second
.as_string()));
64 void PopulateSpdy3RequestHeaderBlock(const BalsaHeaders
& headers
,
66 const string
& host_and_port
,
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()));
75 block
->insert(make_pair(kV3Host
, host_and_port
));
77 block
->insert(make_pair(kV3Path
, path
));
78 block
->insert(make_pair(kV3Scheme
, scheme
));
80 if (!headers
.request_method().empty()) {
81 block
->insert(make_pair(kV3Method
, headers
.request_method().as_string()));
84 if (!headers
.request_version().empty()) {
85 (*block
)[kV3Version
] = headers
.request_version().as_string();
89 void PopulateSpdy4RequestHeaderBlock(const BalsaHeaders
& headers
,
91 const string
& host_and_port
,
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");
103 block
->insert(make_pair(kV4Host
, host_and_port
));
105 block
->insert(make_pair(kV3Path
, path
));
106 block
->insert(make_pair(kV3Scheme
, scheme
));
108 if (!headers
.request_method().empty()) {
109 block
->insert(make_pair(kV3Method
, headers
.request_method().as_string()));
113 void PopulateSpdyResponseHeaderBlock(const BalsaHeaders
& headers
,
114 SpdyHeaderBlock
* block
) {
115 string status
= headers
.response_code().as_string();
117 status
.append(headers
.response_reason_phrase().as_string());
118 (*block
)[kV3Status
] = status
;
119 (*block
)[kV3Version
] =
120 headers
.response_version().as_string();
122 // Empty header values are only allowed because this is spdy3.
123 PopulateSpdyHeaderBlock(headers
, block
, true);
127 SpdyHeaderBlock
SpdyUtils::RequestHeadersToSpdyHeaders(
128 const BalsaHeaders
& request_headers
) {
130 string host_and_port
;
133 string url
= request_headers
.request_uri().as_string();
134 if (url
.empty() || url
[0] == '/') {
137 GURL
request_uri(url
);
138 if (request_headers
.request_method() == "CONNECT") {
141 path
= request_uri
.path();
142 if (!request_uri
.query().empty()) {
143 path
= path
+ "?" + request_uri
.query();
145 host_and_port
= request_uri
.host();
146 scheme
= request_uri
.scheme();
150 DCHECK(!scheme
.empty());
151 DCHECK(!host_and_port
.empty());
152 DCHECK(!path
.empty());
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"));
164 SpdyHeaderBlock
SpdyUtils::RequestHeadersToSpdy4Headers(
165 const BalsaHeaders
& request_headers
) {
167 string host_and_port
;
170 string url
= request_headers
.request_uri().as_string();
171 if (url
.empty() || url
[0] == '/') {
174 GURL
request_uri(url
);
175 if (request_headers
.request_method() == "CONNECT") {
178 path
= request_uri
.path();
179 if (!request_uri
.query().empty()) {
180 path
= path
+ "?" + request_uri
.query();
182 host_and_port
= request_uri
.host();
183 scheme
= request_uri
.scheme();
187 DCHECK(!scheme
.empty());
188 DCHECK(!host_and_port
.empty());
189 DCHECK(!path
.empty());
191 SpdyHeaderBlock block
;
192 PopulateSpdy4RequestHeaderBlock(request_headers
, scheme
, host_and_port
, path
,
194 if (block
.find("host") != block
.end()) {
195 block
.erase(block
.find("host"));
201 string
SpdyUtils::SerializeRequestHeaders(const BalsaHeaders
& request_headers
) {
202 SpdyHeaderBlock block
= RequestHeadersToSpdyHeaders(request_headers
);
203 return SerializeUncompressedHeaders(block
);
207 SpdyHeaderBlock
SpdyUtils::ResponseHeadersToSpdyHeaders(
208 const BalsaHeaders
& response_headers
) {
209 SpdyHeaderBlock block
;
210 PopulateSpdyResponseHeaderBlock(response_headers
, &block
);
215 string
SpdyUtils::SerializeResponseHeaders(
216 const BalsaHeaders
& response_headers
) {
217 SpdyHeaderBlock block
= ResponseHeadersToSpdyHeaders(response_headers
);
219 return SerializeUncompressedHeaders(block
);
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
);
231 bool IsSpecialSpdyHeader(SpdyHeaderBlock::const_iterator header
,
232 BalsaHeaders
* headers
) {
233 if (header
->first
.empty() || header
->second
.empty()) {
236 const string
& header_name
= header
->first
;
237 return header_name
.c_str()[0] == ':';
240 bool SpdyUtils::FillBalsaRequestHeaders(
241 const SpdyHeaderBlock
& header_block
,
242 BalsaHeaders
* request_headers
) {
243 typedef SpdyHeaderBlock::const_iterator BlockIt
;
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
) {
254 string url
= scheme_it
->second
;
256 url
.append(host_it
->second
);
257 url
.append(path_it
->second
);
258 request_headers
->SetRequestUri(url
);
259 request_headers
->SetRequestMethod(method_it
->second
);
261 BlockIt cl_it
= header_block
.find("content-length");
262 if (cl_it
!= header_block
.end()) {
264 if (!base::StringToInt(cl_it
->second
, &content_length
)) {
267 request_headers
->SetContentLength(content_length
);
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
);
279 // The reason phrase should match regexp [\d\d\d [^\r\n]+]. If not, we will
281 bool ParseReasonAndStatus(StringPiece status_and_reason
,
282 BalsaHeaders
* headers
) {
283 if (status_and_reason
.size() < 5)
286 if (status_and_reason
[3] != ' ')
289 const StringPiece status_str
= StringPiece(status_and_reason
.data(), 3);
291 if (!base::StringToInt(status_str
, &status
)) {
295 headers
->SetResponseCode(status_str
);
296 headers
->set_parsed_response_code(status
);
298 StringPiece
reason(status_and_reason
.data() + 4,
299 status_and_reason
.length() - 4);
301 headers
->SetResponseReasonPhrase(reason
);
305 bool SpdyUtils::FillBalsaResponseHeaders(
306 const SpdyHeaderBlock
& header_block
,
307 BalsaHeaders
* request_headers
) {
308 typedef SpdyHeaderBlock::const_iterator BlockIt
;
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
) {
317 if (!ParseReasonAndStatus(status_it
->second
, request_headers
)) {
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
);
330 void SpdyUtils::SpdyHeadersToResponseHeaders(
331 const SpdyHeaderBlock
& block
,
332 BalsaHeaders
* headers
) {
333 FillBalsaResponseHeaders(block
, headers
);