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_balsa_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/quic/spdy_utils.h"
14 #include "net/spdy/spdy_frame_builder.h"
15 #include "net/spdy/spdy_framer.h"
16 #include "net/spdy/spdy_protocol.h"
17 #include "net/tools/balsa/balsa_headers.h"
20 using base::StringPiece
;
29 const char kV4Host
[] = ":authority";
31 const char kV3Host
[] = ":host";
32 const char kV3Path
[] = ":path";
33 const char kV3Scheme
[] = ":scheme";
34 const char kV3Method
[] = ":method";
35 const char kV3Status
[] = ":status";
36 const char kV3Version
[] = ":version";
38 void PopulateSpdyHeaderBlock(const BalsaHeaders
& headers
,
39 SpdyHeaderBlock
* block
,
40 bool allow_empty_values
) {
41 for (BalsaHeaders::const_header_lines_iterator hi
=
42 headers
.header_lines_begin();
43 hi
!= headers
.header_lines_end(); ++hi
) {
44 if ((hi
->second
.length() == 0) && !allow_empty_values
) {
45 DVLOG(1) << "Dropping empty header " << hi
->first
.as_string()
50 // This unfortunately involves loads of copying, but its the simplest way
51 // to sort the headers and leverage the framer.
52 string name
= hi
->first
.as_string();
53 base::StringToLowerASCII(&name
);
54 SpdyHeaderBlock::iterator it
= block
->find(name
);
55 if (it
!= block
->end()) {
56 it
->second
.reserve(it
->second
.size() + 1 + hi
->second
.size());
57 it
->second
.append("\0", 1);
58 it
->second
.append(hi
->second
.data(), hi
->second
.size());
60 block
->insert(make_pair(name
, hi
->second
.as_string()));
65 void PopulateSpdy3RequestHeaderBlock(const BalsaHeaders
& headers
,
67 const string
& host_and_port
,
69 SpdyHeaderBlock
* block
) {
70 PopulateSpdyHeaderBlock(headers
, block
, true);
71 StringPiece host_header
= headers
.GetHeader("Host");
72 if (!host_header
.empty()) {
73 DCHECK(host_and_port
.empty() || host_header
== host_and_port
);
74 block
->insert(make_pair(kV3Host
, host_header
.as_string()));
76 block
->insert(make_pair(kV3Host
, host_and_port
));
78 block
->insert(make_pair(kV3Path
, path
));
79 block
->insert(make_pair(kV3Scheme
, scheme
));
81 if (!headers
.request_method().empty()) {
82 block
->insert(make_pair(kV3Method
, headers
.request_method().as_string()));
85 if (!headers
.request_version().empty()) {
86 (*block
)[kV3Version
] = headers
.request_version().as_string();
90 void PopulateSpdy4RequestHeaderBlock(const BalsaHeaders
& headers
,
92 const string
& host_and_port
,
94 SpdyHeaderBlock
* block
) {
95 PopulateSpdyHeaderBlock(headers
, block
, true);
96 StringPiece host_header
= headers
.GetHeader("Host");
97 if (!host_header
.empty()) {
98 DCHECK(host_and_port
.empty() || host_header
== host_and_port
);
99 block
->insert(make_pair(kV4Host
, host_header
.as_string()));
100 // PopulateSpdyHeaderBlock already added the "host" header,
101 // which is invalid for SPDY4.
102 block
->erase("host");
104 block
->insert(make_pair(kV4Host
, host_and_port
));
106 block
->insert(make_pair(kV3Path
, path
));
107 block
->insert(make_pair(kV3Scheme
, scheme
));
109 if (!headers
.request_method().empty()) {
110 block
->insert(make_pair(kV3Method
, headers
.request_method().as_string()));
114 void PopulateSpdyResponseHeaderBlock(SpdyMajorVersion version
,
115 const BalsaHeaders
& headers
,
116 SpdyHeaderBlock
* block
) {
117 if (version
<= SPDY3
) {
118 string status
= headers
.response_code().as_string();
120 status
.append(headers
.response_reason_phrase().as_string());
121 (*block
)[kV3Status
] = status
;
122 (*block
)[kV3Version
] = headers
.response_version().as_string();
124 (*block
)[kV3Status
] = headers
.response_code().as_string();
127 PopulateSpdyHeaderBlock(headers
, block
, true);
130 bool IsSpecialSpdyHeader(SpdyHeaderBlock::const_iterator header
,
131 BalsaHeaders
* headers
) {
132 if (header
->first
.empty() || header
->second
.empty()) {
135 const string
& header_name
= header
->first
;
136 return header_name
.c_str()[0] == ':';
139 // The reason phrase should match regexp [\d\d\d [^\r\n]+]. If not, we will
141 bool ParseReasonAndStatus(StringPiece status_and_reason
,
142 BalsaHeaders
* headers
,
143 QuicVersion quic_version
) {
144 if (quic_version
> QUIC_VERSION_24
) {
146 if (!base::StringToInt(status_and_reason
, &status
)) {
149 headers
->SetResponseCode(status_and_reason
);
150 headers
->SetResponseCode(status_and_reason
);
151 headers
->set_parsed_response_code(status
);
155 if (status_and_reason
.size() < 5)
158 if (status_and_reason
[3] != ' ')
161 const StringPiece status_str
= StringPiece(status_and_reason
.data(), 3);
163 if (!base::StringToInt(status_str
, &status
)) {
167 headers
->SetResponseCode(status_str
);
168 headers
->set_parsed_response_code(status
);
170 StringPiece
reason(status_and_reason
.data() + 4,
171 status_and_reason
.length() - 4);
173 headers
->SetResponseReasonPhrase(reason
);
180 SpdyHeaderBlock
SpdyBalsaUtils::RequestHeadersToSpdyHeaders(
181 const BalsaHeaders
& request_headers
,
182 QuicVersion quic_version
) {
184 string host_and_port
;
187 string url
= request_headers
.request_uri().as_string();
188 if (url
.empty() || url
[0] == '/') {
191 GURL
request_uri(url
);
192 if (request_headers
.request_method() == "CONNECT") {
195 path
= request_uri
.path();
196 if (!request_uri
.query().empty()) {
197 path
= path
+ "?" + request_uri
.query();
199 host_and_port
= request_uri
.host();
200 scheme
= request_uri
.scheme();
204 DCHECK(!scheme
.empty());
205 DCHECK(!host_and_port
.empty());
206 DCHECK(!path
.empty());
208 SpdyHeaderBlock block
;
209 if (net::SpdyUtils::GetSpdyVersionForQuicVersion(quic_version
) == SPDY3
) {
210 PopulateSpdy3RequestHeaderBlock(request_headers
, scheme
, host_and_port
,
213 PopulateSpdy4RequestHeaderBlock(request_headers
, scheme
, host_and_port
,
216 if (block
.find("host") != block
.end()) {
217 block
.erase(block
.find("host"));
223 SpdyHeaderBlock
SpdyBalsaUtils::ResponseHeadersToSpdyHeaders(
224 const BalsaHeaders
& response_headers
,
225 QuicVersion quic_version
) {
226 SpdyHeaderBlock block
;
227 PopulateSpdyResponseHeaderBlock(
228 net::SpdyUtils::GetSpdyVersionForQuicVersion(quic_version
),
229 response_headers
, &block
);
234 string
SpdyBalsaUtils::SerializeResponseHeaders(
235 const BalsaHeaders
& response_headers
,
236 QuicVersion quic_version
) {
237 SpdyHeaderBlock block
=
238 ResponseHeadersToSpdyHeaders(response_headers
, quic_version
);
240 return net::SpdyUtils::SerializeUncompressedHeaders(block
, quic_version
);
244 void SpdyBalsaUtils::SpdyHeadersToResponseHeaders(
245 const SpdyHeaderBlock
& header_block
,
246 BalsaHeaders
* request_headers
,
247 QuicVersion quic_version
) {
248 typedef SpdyHeaderBlock::const_iterator BlockIt
;
250 BlockIt status_it
= header_block
.find(kV3Status
);
251 BlockIt version_it
= header_block
.find(kV3Version
);
252 BlockIt end_it
= header_block
.end();
253 if (quic_version
> QUIC_VERSION_24
) {
254 if (status_it
== end_it
) {
258 if (status_it
== end_it
|| version_it
== end_it
) {
263 if (!ParseReasonAndStatus(status_it
->second
, request_headers
, quic_version
)) {
267 if (quic_version
<= QUIC_VERSION_24
) {
268 request_headers
->SetResponseVersion(version_it
->second
);
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
);