// 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 #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.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/url_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 (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 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); } // Get status code only. std::string::const_iterator after_status = std::find(after_version + 1, status_line.end(), ' '); (*headers)[status_key] = std::string(after_version + 1, after_status); size_t iter = 0; 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(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(4 - priority); } NET_EXPORT_PRIVATE void ConvertHeaderBlockToHttpRequestHeaders( const SpdyHeaderBlock& spdy_headers, HttpRequestHeaders* http_headers) { for (const auto& it : spdy_headers) { base::StringPiece key = it.first; if (key[0] == ':') { key.remove_prefix(1); } std::vector values = base::SplitStringPiece( it.second, "\0", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); for (const auto& value : values) { http_headers->SetHeader(key, value); } } } GURL GetUrlFromHeaderBlock(const SpdyHeaderBlock& headers, SpdyMajorVersion 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