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