| // 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/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/time/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 { |
| |
| namespace { |
| |
| void AddSpdyHeader(const std::string& name, |
| const std::string& value, |
| SpdyHeaderBlock* headers) { |
| if (headers->find(name) == headers->end()) { |
| (*headers)[name] = value; |
| } else { |
| std::string joint_value = (*headers)[name].as_string(); |
| joint_value.append(1, '\0'); |
| joint_value.append(value); |
| (*headers)[name] = joint_value; |
| } |
| } |
| |
| } // namespace |
| |
| bool SpdyHeadersToHttpResponse(const SpdyHeaderBlock& headers, |
| SpdyMajorVersion protocol_version, |
| HttpResponseInfo* response) { |
| std::string status_key = (protocol_version >= SPDY3) ? ":status" : "status"; |
| std::string version_key = |
| (protocol_version >= SPDY3) ? ":version" : "version"; |
| std::string version; |
| std::string status; |
| |
| // The "status" header is required. "version" is required below HTTP/2. |
| SpdyHeaderBlock::const_iterator it; |
| it = headers.find(status_key); |
| if (it == headers.end()) |
| return false; |
| status = it->second.as_string(); |
| |
| if (protocol_version >= HTTP2) { |
| version = "HTTP/1.1"; |
| } else { |
| it = headers.find(version_key); |
| if (it == headers.end()) |
| return false; |
| version = it->second.as_string(); |
| } |
| 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.as_string(); |
| 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.as_string().substr(1)); |
| else |
| raw_headers.append(it->first.as_string()); |
| 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, |
| SpdyMajorVersion protocol_version, |
| bool direct, |
| SpdyHeaderBlock* headers) { |
| static const char kHttpProtocolVersion[] = "HTTP/1.1"; |
| switch (protocol_version) { |
| case SPDY2: |
| // TODO(bnc): Remove this code now that SPDY/2 is deprecated. |
| (*headers)["version"] = kHttpProtocolVersion; |
| (*headers)["method"] = info.method; |
| (*headers)["host"] = GetHostAndOptionalPort(info.url); |
| if (info.method == "CONNECT") { |
| (*headers)["url"] = GetHostAndPort(info.url); |
| } else { |
| (*headers)["scheme"] = info.url.scheme(); |
| (*headers)["url"] = direct ? info.url.PathForRequest() |
| : HttpUtil::SpecForRequest(info.url); |
| } |
| break; |
| case SPDY3: |
| (*headers)[":version"] = kHttpProtocolVersion; |
| (*headers)[":method"] = info.method; |
| (*headers)[":host"] = GetHostAndOptionalPort(info.url); |
| if (info.method == "CONNECT") { |
| (*headers)[":path"] = GetHostAndPort(info.url); |
| } else { |
| (*headers)[":scheme"] = info.url.scheme(); |
| (*headers)[":path"] = info.url.PathForRequest(); |
| } |
| break; |
| case HTTP2: |
| (*headers)[":method"] = info.method; |
| if (info.method == "CONNECT") { |
| (*headers)[":authority"] = GetHostAndPort(info.url); |
| } else { |
| (*headers)[":authority"] = GetHostAndOptionalPort(info.url); |
| (*headers)[":scheme"] = info.url.scheme(); |
| (*headers)[":path"] = info.url.PathForRequest(); |
| } |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| HttpRequestHeaders::Iterator it(request_headers); |
| while (it.GetNext()) { |
| std::string name = base::ToLowerASCII(it.name()); |
| if (name.empty() || name[0] == ':' || name == "connection" || |
| name == "proxy-connection" || name == "transfer-encoding" || |
| name == "host") { |
| continue; |
| } |
| AddSpdyHeader(name, it.value(), headers); |
| } |
| } |
| |
| void CreateSpdyHeadersFromHttpResponse( |
| const HttpResponseHeaders& response_headers, |
| SpdyMajorVersion protocol_version, |
| SpdyHeaderBlock* headers) { |
| std::string status_key = (protocol_version >= SPDY3) ? ":status" : "status"; |
| std::string version_key = |
| (protocol_version >= SPDY3) ? ":version" : "version"; |
| |
| const std::string status_line = response_headers.GetStatusLine(); |
| std::string::const_iterator after_version = |
| std::find(status_line.begin(), status_line.end(), ' '); |
| if (protocol_version < HTTP2) { |
| (*headers)[version_key] = std::string(status_line.begin(), after_version); |
| } |
| (*headers)[status_key] = std::string(after_version + 1, status_line.end()); |
| |
| void* iter = NULL; |
| std::string raw_name, value; |
| while (response_headers.EnumerateHeaderLines(&iter, &raw_name, &value)) { |
| std::string name = base::ToLowerASCII(raw_name); |
| AddSpdyHeader(name, value, headers); |
| } |
| } |
| |
| static_assert(HIGHEST - LOWEST < 4 && HIGHEST - MINIMUM_PRIORITY < 5, |
| "request priority incompatible with spdy"); |
| |
| SpdyPriority ConvertRequestPriorityToSpdyPriority( |
| const RequestPriority priority, |
| SpdyMajorVersion protocol_version) { |
| DCHECK_GE(priority, MINIMUM_PRIORITY); |
| DCHECK_LE(priority, MAXIMUM_PRIORITY); |
| return static_cast<SpdyPriority>(MAXIMUM_PRIORITY - priority); |
| } |
| |
| NET_EXPORT_PRIVATE RequestPriority ConvertSpdyPriorityToRequestPriority( |
| SpdyPriority priority, |
| SpdyMajorVersion protocol_version) { |
| // Handle invalid values gracefully. |
| // Note that SpdyPriority is not an enum, hence the magic constants. |
| return (priority >= 5) ? |
| IDLE : static_cast<RequestPriority>(4 - priority); |
| } |
| |
| GURL GetUrlFromHeaderBlock(const SpdyHeaderBlock& headers, |
| SpdyMajorVersion protocol_version, |
| bool pushed) { |
| DCHECK_LE(SPDY3, protocol_version); |
| SpdyHeaderBlock::const_iterator it = headers.find(":scheme"); |
| if (it == headers.end()) |
| return GURL(); |
| std::string url = it->second.as_string(); |
| url.append("://"); |
| |
| it = headers.find(protocol_version >= HTTP2 ? ":authority" : ":host"); |
| if (it == headers.end()) |
| return GURL(); |
| url.append(it->second.as_string()); |
| |
| it = headers.find(":path"); |
| if (it == headers.end()) |
| return GURL(); |
| url.append(it->second.as_string()); |
| return GURL(url); |
| } |
| |
| } // namespace net |