| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/spdy/spdy_http_utils.h" |
| |
| #include <string> |
| |
| #include "base/string_number_conversions.h" |
| #include "base/string_util.h" |
| #include "base/time.h" |
| #include "net/base/escape.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/net_util.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_request_info.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_response_info.h" |
| #include "net/http/http_util.h" |
| |
| namespace net { |
| |
| bool SpdyHeadersToHttpResponse(const SpdyHeaderBlock& headers, |
| int protocol_version, |
| HttpResponseInfo* response) { |
| std::string status_key = (protocol_version >= 3) ? ":status" : "status"; |
| std::string version_key = (protocol_version >= 3) ? ":version" : "version"; |
| std::string version; |
| std::string status; |
| |
| // The "status" and "version" headers are required. |
| SpdyHeaderBlock::const_iterator it; |
| it = headers.find(status_key); |
| if (it == headers.end()) |
| return false; |
| status = it->second; |
| |
| it = headers.find(version_key); |
| if (it == headers.end()) |
| return false; |
| version = it->second; |
| |
| response->response_time = base::Time::Now(); |
| |
| std::string raw_headers(version); |
| raw_headers.push_back(' '); |
| raw_headers.append(status); |
| raw_headers.push_back('\0'); |
| for (it = headers.begin(); it != headers.end(); ++it) { |
| // For each value, if the server sends a NUL-separated |
| // list of values, we separate that back out into |
| // individual headers for each value in the list. |
| // e.g. |
| // Set-Cookie "foo\0bar" |
| // becomes |
| // Set-Cookie: foo\0 |
| // Set-Cookie: bar\0 |
| std::string value = it->second; |
| size_t start = 0; |
| size_t end = 0; |
| do { |
| end = value.find('\0', start); |
| std::string tval; |
| if (end != value.npos) |
| tval = value.substr(start, (end - start)); |
| else |
| tval = value.substr(start); |
| if (protocol_version >= 3 && it->first[0] == ':') |
| raw_headers.append(it->first.substr(1)); |
| else |
| raw_headers.append(it->first); |
| raw_headers.push_back(':'); |
| raw_headers.append(tval); |
| raw_headers.push_back('\0'); |
| start = end + 1; |
| } while (end != value.npos); |
| } |
| |
| response->headers = new HttpResponseHeaders(raw_headers); |
| response->was_fetched_via_spdy = true; |
| return true; |
| } |
| |
| void CreateSpdyHeadersFromHttpRequest(const HttpRequestInfo& info, |
| const HttpRequestHeaders& request_headers, |
| SpdyHeaderBlock* headers, |
| int protocol_version, |
| bool direct) { |
| |
| HttpRequestHeaders::Iterator it(request_headers); |
| while (it.GetNext()) { |
| std::string name = StringToLowerASCII(it.name()); |
| if (name == "connection" || name == "proxy-connection" || |
| name == "transfer-encoding") { |
| continue; |
| } |
| if (headers->find(name) == headers->end()) { |
| (*headers)[name] = it.value(); |
| } else { |
| std::string new_value = (*headers)[name]; |
| new_value.append(1, '\0'); // +=() doesn't append 0's |
| new_value += it.value(); |
| (*headers)[name] = new_value; |
| } |
| } |
| static const char kHttpProtocolVersion[] = "HTTP/1.1"; |
| |
| if (protocol_version < 3) { |
| (*headers)["version"] = kHttpProtocolVersion; |
| (*headers)["method"] = info.method; |
| (*headers)["host"] = GetHostAndOptionalPort(info.url); |
| (*headers)["scheme"] = info.url.scheme(); |
| if (direct) |
| (*headers)["url"] = HttpUtil::PathForRequest(info.url); |
| else |
| (*headers)["url"] = HttpUtil::SpecForRequest(info.url); |
| } else { |
| (*headers)[":version"] = kHttpProtocolVersion; |
| (*headers)[":method"] = info.method; |
| (*headers)[":host"] = GetHostAndOptionalPort(info.url); |
| (*headers)[":scheme"] = info.url.scheme(); |
| (*headers)[":path"] = HttpUtil::PathForRequest(info.url); |
| headers->erase("host"); // this is kinda insane, spdy 3 spec. |
| } |
| |
| } |
| |
| COMPILE_ASSERT(HIGHEST - LOWEST < 4 && |
| HIGHEST - MINIMUM_PRIORITY < 5, |
| request_priority_incompatible_with_spdy); |
| |
| SpdyPriority ConvertRequestPriorityToSpdyPriority( |
| const RequestPriority priority, |
| int protocol_version) { |
| DCHECK_GE(priority, MINIMUM_PRIORITY); |
| DCHECK_LT(priority, NUM_PRIORITIES); |
| if (protocol_version == 2) { |
| // SPDY 2 only has 2 bits of priority, but we have 5 RequestPriorities. |
| // Map IDLE => 3, LOWEST => 2, LOW => 2, MEDIUM => 1, HIGHEST => 0. |
| if (priority > LOWEST) { |
| return static_cast<SpdyPriority>(HIGHEST - priority); |
| } else { |
| return static_cast<SpdyPriority>(HIGHEST - priority - 1); |
| } |
| } else { |
| return static_cast<SpdyPriority>(HIGHEST - priority); |
| } |
| } |
| |
| GURL GetUrlFromHeaderBlock(const SpdyHeaderBlock& headers, |
| int protocol_version, |
| bool pushed) { |
| // SPDY 2 server push urls are specified in a single "url" header. |
| if (pushed && protocol_version == 2) { |
| std::string url; |
| SpdyHeaderBlock::const_iterator it; |
| it = headers.find("url"); |
| if (it != headers.end()) |
| url = it->second; |
| return GURL(url); |
| } |
| |
| const char* scheme_header = protocol_version >= 3 ? ":scheme" : "scheme"; |
| const char* host_header = protocol_version >= 3 ? ":host" : "host"; |
| const char* path_header = protocol_version >= 3 ? ":path" : "url"; |
| |
| std::string scheme; |
| std::string host_port; |
| std::string path; |
| SpdyHeaderBlock::const_iterator it; |
| it = headers.find(scheme_header); |
| if (it != headers.end()) |
| scheme = it->second; |
| it = headers.find(host_header); |
| if (it != headers.end()) |
| host_port = it->second; |
| it = headers.find(path_header); |
| if (it != headers.end()) |
| path = it->second; |
| |
| std::string url = (scheme.empty() || host_port.empty() || path.empty()) |
| ? "" : scheme + "://" + host_port + path; |
| return GURL(url); |
| } |
| |
| } // namespace net |