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/spdy/spdy_http_utils.h"
6
7#include <string>
8
9#include "base/strings/string_number_conversions.h"
10#include "base/strings/string_util.h"
11#include "base/time/time.h"
12#include "net/base/escape.h"
13#include "net/base/load_flags.h"
14#include "net/base/net_util.h"
15#include "net/http/http_request_headers.h"
16#include "net/http/http_request_info.h"
17#include "net/http/http_response_headers.h"
18#include "net/http/http_response_info.h"
19#include "net/http/http_util.h"
20
21namespace net {
22
23namespace {
24
25void AddSpdyHeader(const std::string& name,
26                   const std::string& value,
27                   SpdyHeaderBlock* headers) {
28  if (headers->find(name) == headers->end()) {
29    (*headers)[name] = value;
30  } else {
31    (*headers)[name] += '\0' + value;
32  }
33}
34
35} // namespace
36
37bool SpdyHeadersToHttpResponse(const SpdyHeaderBlock& headers,
38                               SpdyMajorVersion protocol_version,
39                               HttpResponseInfo* response) {
40  std::string status_key = (protocol_version >= SPDY3) ? ":status" : "status";
41  std::string version_key =
42      (protocol_version >= SPDY3) ? ":version" : "version";
43  std::string version;
44  std::string status;
45
46  // The "status" header is required. "version" is required below SPDY4.
47  SpdyHeaderBlock::const_iterator it;
48  it = headers.find(status_key);
49  if (it == headers.end())
50    return false;
51  status = it->second;
52
53  if (protocol_version >= SPDY4) {
54    version = "HTTP/1.1";
55  } else {
56    it = headers.find(version_key);
57    if (it == headers.end())
58      return false;
59    version = it->second;
60  }
61  std::string raw_headers(version);
62  raw_headers.push_back(' ');
63  raw_headers.append(status);
64  raw_headers.push_back('\0');
65  for (it = headers.begin(); it != headers.end(); ++it) {
66    // For each value, if the server sends a NUL-separated
67    // list of values, we separate that back out into
68    // individual headers for each value in the list.
69    // e.g.
70    //    Set-Cookie "foo\0bar"
71    // becomes
72    //    Set-Cookie: foo\0
73    //    Set-Cookie: bar\0
74    std::string value = it->second;
75    size_t start = 0;
76    size_t end = 0;
77    do {
78      end = value.find('\0', start);
79      std::string tval;
80      if (end != value.npos)
81        tval = value.substr(start, (end - start));
82      else
83        tval = value.substr(start);
84      if (protocol_version >= 3 && it->first[0] == ':')
85        raw_headers.append(it->first.substr(1));
86      else
87        raw_headers.append(it->first);
88      raw_headers.push_back(':');
89      raw_headers.append(tval);
90      raw_headers.push_back('\0');
91      start = end + 1;
92    } while (end != value.npos);
93  }
94
95  response->headers = new HttpResponseHeaders(raw_headers);
96  response->was_fetched_via_spdy = true;
97  return true;
98}
99
100void CreateSpdyHeadersFromHttpRequest(const HttpRequestInfo& info,
101                                      const HttpRequestHeaders& request_headers,
102                                      SpdyMajorVersion protocol_version,
103                                      bool direct,
104                                      SpdyHeaderBlock* headers) {
105
106  HttpRequestHeaders::Iterator it(request_headers);
107  while (it.GetNext()) {
108    std::string name = base::StringToLowerASCII(it.name());
109    if (name == "connection" || name == "proxy-connection" ||
110        name == "transfer-encoding" || name == "host") {
111      continue;
112    }
113    AddSpdyHeader(name, it.value(), headers);
114  }
115  static const char kHttpProtocolVersion[] = "HTTP/1.1";
116
117  if (protocol_version < SPDY3) {
118    (*headers)["version"] = kHttpProtocolVersion;
119    (*headers)["method"] = info.method;
120    (*headers)["host"] = GetHostAndOptionalPort(info.url);
121    (*headers)["scheme"] = info.url.scheme();
122    if (direct)
123      (*headers)["url"] = HttpUtil::PathForRequest(info.url);
124    else
125      (*headers)["url"] = HttpUtil::SpecForRequest(info.url);
126  } else {
127    if (protocol_version < SPDY4) {
128      (*headers)[":version"] = kHttpProtocolVersion;
129      (*headers)[":host"] = GetHostAndOptionalPort(info.url);
130    } else {
131      (*headers)[":authority"] = GetHostAndOptionalPort(info.url);
132    }
133    (*headers)[":method"] = info.method;
134    (*headers)[":scheme"] = info.url.scheme();
135    (*headers)[":path"] = HttpUtil::PathForRequest(info.url);
136  }
137}
138
139void CreateSpdyHeadersFromHttpResponse(
140    const HttpResponseHeaders& response_headers,
141    SpdyMajorVersion protocol_version,
142    SpdyHeaderBlock* headers) {
143  std::string status_key = (protocol_version >= SPDY3) ? ":status" : "status";
144  std::string version_key =
145      (protocol_version >= SPDY3) ? ":version" : "version";
146
147  const std::string status_line = response_headers.GetStatusLine();
148  std::string::const_iterator after_version =
149      std::find(status_line.begin(), status_line.end(), ' ');
150  if (protocol_version < SPDY4) {
151    (*headers)[version_key] = std::string(status_line.begin(), after_version);
152  }
153  (*headers)[status_key] = std::string(after_version + 1, status_line.end());
154
155  void* iter = NULL;
156  std::string raw_name, value;
157  while (response_headers.EnumerateHeaderLines(&iter, &raw_name, &value)) {
158    std::string name = base::StringToLowerASCII(raw_name);
159    AddSpdyHeader(name, value, headers);
160  }
161}
162
163
164COMPILE_ASSERT(HIGHEST - LOWEST < 4 &&
165               HIGHEST - MINIMUM_PRIORITY < 5,
166               request_priority_incompatible_with_spdy);
167
168SpdyPriority ConvertRequestPriorityToSpdyPriority(
169    const RequestPriority priority,
170    SpdyMajorVersion protocol_version) {
171  DCHECK_GE(priority, MINIMUM_PRIORITY);
172  DCHECK_LE(priority, MAXIMUM_PRIORITY);
173  if (protocol_version == SPDY2) {
174    // SPDY 2 only has 2 bits of priority, but we have 5 RequestPriorities.
175    // Map IDLE => 3, LOWEST => 2, LOW => 2, MEDIUM => 1, HIGHEST => 0.
176    if (priority > LOWEST) {
177      return static_cast<SpdyPriority>(HIGHEST - priority);
178    } else {
179      return static_cast<SpdyPriority>(HIGHEST - priority - 1);
180    }
181  } else {
182    return static_cast<SpdyPriority>(HIGHEST - priority);
183  }
184}
185
186NET_EXPORT_PRIVATE RequestPriority ConvertSpdyPriorityToRequestPriority(
187    SpdyPriority priority,
188    SpdyMajorVersion protocol_version) {
189  // Handle invalid values gracefully, and pick LOW to map 2 back
190  // to for SPDY/2.
191  SpdyPriority idle_cutoff = (protocol_version == SPDY2) ? 3 : 5;
192  return (priority >= idle_cutoff) ?
193      IDLE : static_cast<RequestPriority>(HIGHEST - priority);
194}
195
196GURL GetUrlFromHeaderBlock(const SpdyHeaderBlock& headers,
197                           SpdyMajorVersion protocol_version,
198                           bool pushed) {
199  // SPDY 2 server push urls are specified in a single "url" header.
200  if (pushed && protocol_version == SPDY2) {
201      std::string url;
202      SpdyHeaderBlock::const_iterator it;
203      it = headers.find("url");
204      if (it != headers.end())
205        url = it->second;
206      return GURL(url);
207  }
208
209  const char* scheme_header = protocol_version >= SPDY3 ? ":scheme" : "scheme";
210  const char* host_header = protocol_version >= SPDY4 ? ":authority" :
211      (protocol_version >= SPDY3 ? ":host" : "host");
212  const char* path_header = protocol_version >= SPDY3 ? ":path" : "url";
213
214  std::string scheme;
215  std::string host_port;
216  std::string path;
217  SpdyHeaderBlock::const_iterator it;
218  it = headers.find(scheme_header);
219  if (it != headers.end())
220    scheme = it->second;
221  it = headers.find(host_header);
222  if (it != headers.end())
223    host_port = it->second;
224  it = headers.find(path_header);
225  if (it != headers.end())
226    path = it->second;
227
228  std::string url = (scheme.empty() || host_port.empty() || path.empty())
229                        ? std::string()
230                        : scheme + "://" + host_port + path;
231  return GURL(url);
232}
233
234}  // namespace net
235