| // 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/tools/quic/spdy_utils.h" | 
 |  | 
 | #include <string> | 
 |  | 
 | #include "base/memory/scoped_ptr.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_piece.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "net/spdy/spdy_frame_builder.h" | 
 | #include "net/spdy/spdy_framer.h" | 
 | #include "net/spdy/spdy_protocol.h" | 
 | #include "net/tools/balsa/balsa_headers.h" | 
 | #include "url/gurl.h" | 
 |  | 
 | using base::StringPiece; | 
 | using std::make_pair; | 
 | using std::pair; | 
 | using std::string; | 
 |  | 
 | namespace net { | 
 | namespace tools { | 
 |  | 
 | const char* const kV4Host = ":authority"; | 
 |  | 
 | const char* const kV3Host = ":host"; | 
 | const char* const kV3Path = ":path"; | 
 | const char* const kV3Scheme = ":scheme"; | 
 | const char* const kV3Status = ":status"; | 
 | const char* const kV3Method = ":method"; | 
 | const char* const kV3Version = ":version"; | 
 |  | 
 | void PopulateSpdyHeaderBlock(const BalsaHeaders& headers, | 
 |                              SpdyHeaderBlock* block, | 
 |                              bool allow_empty_values) { | 
 |   for (BalsaHeaders::const_header_lines_iterator hi = | 
 |        headers.header_lines_begin(); | 
 |        hi != headers.header_lines_end(); | 
 |        ++hi) { | 
 |     if ((hi->second.length() == 0) && !allow_empty_values) { | 
 |       DVLOG(1) << "Dropping empty header " << hi->first.as_string() | 
 |                << " from headers"; | 
 |       continue; | 
 |     } | 
 |  | 
 |     // This unfortunately involves loads of copying, but its the simplest way | 
 |     // to sort the headers and leverage the framer. | 
 |     string name = hi->first.as_string(); | 
 |     base::StringToLowerASCII(&name); | 
 |     SpdyHeaderBlock::iterator it = block->find(name); | 
 |     if (it != block->end()) { | 
 |       it->second.reserve(it->second.size() + 1 + hi->second.size()); | 
 |       it->second.append("\0", 1); | 
 |       it->second.append(hi->second.data(), hi->second.size()); | 
 |     } else { | 
 |       block->insert(make_pair(name, hi->second.as_string())); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void PopulateSpdy3RequestHeaderBlock(const BalsaHeaders& headers, | 
 |                                      const string& scheme, | 
 |                                      const string& host_and_port, | 
 |                                      const string& path, | 
 |                                      SpdyHeaderBlock* block) { | 
 |   PopulateSpdyHeaderBlock(headers, block, true); | 
 |   StringPiece host_header = headers.GetHeader("Host"); | 
 |   if (!host_header.empty()) { | 
 |     DCHECK(host_and_port.empty() || host_header == host_and_port); | 
 |     block->insert(make_pair(kV3Host, host_header.as_string())); | 
 |   } else { | 
 |     block->insert(make_pair(kV3Host, host_and_port)); | 
 |   } | 
 |   block->insert(make_pair(kV3Path, path)); | 
 |   block->insert(make_pair(kV3Scheme, scheme)); | 
 |  | 
 |   if (!headers.request_method().empty()) { | 
 |     block->insert(make_pair(kV3Method, headers.request_method().as_string())); | 
 |   } | 
 |  | 
 |   if (!headers.request_version().empty()) { | 
 |     (*block)[kV3Version] = headers.request_version().as_string(); | 
 |   } | 
 | } | 
 |  | 
 | void PopulateSpdy4RequestHeaderBlock(const BalsaHeaders& headers, | 
 |                                      const string& scheme, | 
 |                                      const string& host_and_port, | 
 |                                      const string& path, | 
 |                                      SpdyHeaderBlock* block) { | 
 |   PopulateSpdyHeaderBlock(headers, block, true); | 
 |   StringPiece host_header = headers.GetHeader("Host"); | 
 |   if (!host_header.empty()) { | 
 |     DCHECK(host_and_port.empty() || host_header == host_and_port); | 
 |     block->insert(make_pair(kV4Host, host_header.as_string())); | 
 |     // PopulateSpdyHeaderBlock already added the "host" header, | 
 |     // which is invalid for SPDY4. | 
 |     block->erase("host"); | 
 |   } else { | 
 |     block->insert(make_pair(kV4Host, host_and_port)); | 
 |   } | 
 |   block->insert(make_pair(kV3Path, path)); | 
 |   block->insert(make_pair(kV3Scheme, scheme)); | 
 |  | 
 |   if (!headers.request_method().empty()) { | 
 |     block->insert(make_pair(kV3Method, headers.request_method().as_string())); | 
 |   } | 
 | } | 
 |  | 
 | void PopulateSpdyResponseHeaderBlock(const BalsaHeaders& headers, | 
 |                                      SpdyHeaderBlock* block) { | 
 |   string status = headers.response_code().as_string(); | 
 |   status.append(" "); | 
 |   status.append(headers.response_reason_phrase().as_string()); | 
 |   (*block)[kV3Status] = status; | 
 |   (*block)[kV3Version] = | 
 |       headers.response_version().as_string(); | 
 |  | 
 |   // Empty header values are only allowed because this is spdy3. | 
 |   PopulateSpdyHeaderBlock(headers, block, true); | 
 | } | 
 |  | 
 | // static | 
 | SpdyHeaderBlock SpdyUtils::RequestHeadersToSpdyHeaders( | 
 |     const BalsaHeaders& request_headers) { | 
 |   string scheme; | 
 |   string host_and_port; | 
 |   string path; | 
 |  | 
 |   string url = request_headers.request_uri().as_string(); | 
 |   if (url.empty() || url[0] == '/') { | 
 |     path = url; | 
 |   } else { | 
 |     GURL request_uri(url); | 
 |     if (request_headers.request_method() == "CONNECT") { | 
 |       path = url; | 
 |     } else { | 
 |       path = request_uri.path(); | 
 |       if (!request_uri.query().empty()) { | 
 |         path = path + "?" + request_uri.query(); | 
 |       } | 
 |       host_and_port = request_uri.host(); | 
 |       scheme = request_uri.scheme(); | 
 |     } | 
 |   } | 
 |  | 
 |   DCHECK(!scheme.empty()); | 
 |   DCHECK(!host_and_port.empty()); | 
 |   DCHECK(!path.empty()); | 
 |  | 
 |   SpdyHeaderBlock block; | 
 |   PopulateSpdy3RequestHeaderBlock( | 
 |       request_headers, scheme, host_and_port, path, &block); | 
 |   if (block.find("host") != block.end()) { | 
 |     block.erase(block.find("host")); | 
 |   } | 
 |   return block; | 
 | } | 
 |  | 
 | // static | 
 | SpdyHeaderBlock SpdyUtils::RequestHeadersToSpdy4Headers( | 
 |     const BalsaHeaders& request_headers) { | 
 |   string scheme; | 
 |   string host_and_port; | 
 |   string path; | 
 |  | 
 |   string url = request_headers.request_uri().as_string(); | 
 |   if (url.empty() || url[0] == '/') { | 
 |     path = url; | 
 |   } else { | 
 |     GURL request_uri(url); | 
 |     if (request_headers.request_method() == "CONNECT") { | 
 |       path = url; | 
 |     } else { | 
 |       path = request_uri.path(); | 
 |       if (!request_uri.query().empty()) { | 
 |         path = path + "?" + request_uri.query(); | 
 |       } | 
 |       host_and_port = request_uri.host(); | 
 |       scheme = request_uri.scheme(); | 
 |     } | 
 |   } | 
 |  | 
 |   DCHECK(!scheme.empty()); | 
 |   DCHECK(!host_and_port.empty()); | 
 |   DCHECK(!path.empty()); | 
 |  | 
 |   SpdyHeaderBlock block; | 
 |   PopulateSpdy4RequestHeaderBlock(request_headers, scheme, host_and_port, path, | 
 |                                   &block); | 
 |   if (block.find("host") != block.end()) { | 
 |     block.erase(block.find("host")); | 
 |   } | 
 |   return block; | 
 | } | 
 |  | 
 | // static | 
 | string SpdyUtils::SerializeRequestHeaders(const BalsaHeaders& request_headers) { | 
 |   SpdyHeaderBlock block = RequestHeadersToSpdyHeaders(request_headers); | 
 |   return SerializeUncompressedHeaders(block); | 
 | } | 
 |  | 
 | // static | 
 | SpdyHeaderBlock SpdyUtils::ResponseHeadersToSpdyHeaders( | 
 |     const BalsaHeaders& response_headers) { | 
 |   SpdyHeaderBlock block; | 
 |   PopulateSpdyResponseHeaderBlock(response_headers, &block); | 
 |   return block; | 
 | } | 
 |  | 
 | // static | 
 | string SpdyUtils::SerializeResponseHeaders( | 
 |     const BalsaHeaders& response_headers) { | 
 |   SpdyHeaderBlock block = ResponseHeadersToSpdyHeaders(response_headers); | 
 |  | 
 |   return SerializeUncompressedHeaders(block); | 
 | } | 
 |  | 
 | // static | 
 | string SpdyUtils::SerializeUncompressedHeaders(const SpdyHeaderBlock& headers) { | 
 |   size_t length = SpdyFramer::GetSerializedLength(SPDY3, &headers); | 
 |   SpdyFrameBuilder builder(length, SPDY3); | 
 |   SpdyFramer::WriteHeaderBlock(&builder, SPDY3, &headers); | 
 |   scoped_ptr<SpdyFrame> block(builder.take()); | 
 |   return string(block->data(), length); | 
 | } | 
 |  | 
 | bool IsSpecialSpdyHeader(SpdyHeaderBlock::const_iterator header, | 
 |                             BalsaHeaders* headers) { | 
 |   if (header->first.empty() || header->second.empty()) { | 
 |     return true; | 
 |   } | 
 |   const string& header_name = header->first; | 
 |   return header_name.c_str()[0] == ':'; | 
 | } | 
 |  | 
 | bool SpdyUtils::FillBalsaRequestHeaders( | 
 |     const SpdyHeaderBlock& header_block, | 
 |     BalsaHeaders* request_headers) { | 
 |   typedef SpdyHeaderBlock::const_iterator BlockIt; | 
 |  | 
 |   BlockIt host_it = header_block.find(kV3Host); | 
 |   BlockIt path_it = header_block.find(kV3Path); | 
 |   BlockIt scheme_it = header_block.find(kV3Scheme); | 
 |   BlockIt method_it = header_block.find(kV3Method); | 
 |   BlockIt end_it = header_block.end(); | 
 |   if (host_it == end_it || path_it == end_it || scheme_it == end_it || | 
 |       method_it == end_it) { | 
 |     return false; | 
 |   } | 
 |   string url = scheme_it->second; | 
 |   url.append("://"); | 
 |   url.append(host_it->second); | 
 |   url.append(path_it->second); | 
 |   request_headers->SetRequestUri(url); | 
 |   request_headers->SetRequestMethod(method_it->second); | 
 |  | 
 |   BlockIt cl_it = header_block.find("content-length"); | 
 |   if (cl_it != header_block.end()) { | 
 |     int content_length; | 
 |     if (!base::StringToInt(cl_it->second, &content_length)) { | 
 |       return false; | 
 |     } | 
 |     request_headers->SetContentLength(content_length); | 
 |   } | 
 |  | 
 |   for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) { | 
 |    if (!IsSpecialSpdyHeader(it, request_headers)) { | 
 |      request_headers->AppendHeader(it->first, it->second); | 
 |    } | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | // The reason phrase should match regexp [\d\d\d [^\r\n]+].  If not, we will | 
 | // fail to parse it. | 
 | bool ParseReasonAndStatus(StringPiece status_and_reason, | 
 |                           BalsaHeaders* headers) { | 
 |   if (status_and_reason.size() < 5) | 
 |     return false; | 
 |  | 
 |   if (status_and_reason[3] != ' ') | 
 |     return false; | 
 |  | 
 |   const StringPiece status_str = StringPiece(status_and_reason.data(), 3); | 
 |   int status; | 
 |   if (!base::StringToInt(status_str, &status)) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   headers->SetResponseCode(status_str); | 
 |   headers->set_parsed_response_code(status); | 
 |  | 
 |   StringPiece reason(status_and_reason.data() + 4, | 
 |                      status_and_reason.length() - 4); | 
 |  | 
 |   headers->SetResponseReasonPhrase(reason); | 
 |   return true; | 
 | } | 
 |  | 
 | bool SpdyUtils::FillBalsaResponseHeaders( | 
 |     const SpdyHeaderBlock& header_block, | 
 |     BalsaHeaders* request_headers) { | 
 |   typedef SpdyHeaderBlock::const_iterator BlockIt; | 
 |  | 
 |   BlockIt status_it = header_block.find(kV3Status); | 
 |   BlockIt version_it = header_block.find(kV3Version); | 
 |   BlockIt end_it = header_block.end(); | 
 |   if (status_it == end_it || version_it == end_it) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (!ParseReasonAndStatus(status_it->second, request_headers)) { | 
 |     return false; | 
 |   } | 
 |   request_headers->SetResponseVersion(version_it->second); | 
 |   for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) { | 
 |    if (!IsSpecialSpdyHeader(it, request_headers)) { | 
 |      request_headers->AppendHeader(it->first, it->second); | 
 |    } | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | }  // namespace tools | 
 | }  // namespace net |