Home | History | Annotate | Download | only in quic
      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/tools/quic/spdy_utils.h"
      6 
      7 #include <string>
      8 
      9 #include "base/memory/scoped_ptr.h"
     10 #include "base/strings/string_number_conversions.h"
     11 #include "base/strings/string_piece.h"
     12 #include "base/strings/string_util.h"
     13 #include "net/spdy/spdy_frame_builder.h"
     14 #include "net/spdy/spdy_framer.h"
     15 #include "net/spdy/spdy_protocol.h"
     16 #include "net/tools/balsa/balsa_headers.h"
     17 #include "url/gurl.h"
     18 
     19 using base::StringPiece;
     20 using std::pair;
     21 using std::string;
     22 
     23 namespace net {
     24 namespace tools {
     25 
     26 const char* const kV3Host = ":host";
     27 const char* const kV3Path = ":path";
     28 const char* const kV3Scheme = ":scheme";
     29 const char* const kV3Status = ":status";
     30 const char* const kV3Method = ":method";
     31 const char* const kV3Version = ":version";
     32 
     33 void PopulateSpdyHeaderBlock(const BalsaHeaders& headers,
     34                              SpdyHeaderBlock* block,
     35                              bool allow_empty_values) {
     36   for (BalsaHeaders::const_header_lines_iterator hi =
     37        headers.header_lines_begin();
     38        hi != headers.header_lines_end();
     39        ++hi) {
     40     if ((hi->second.length() == 0) && !allow_empty_values) {
     41       DLOG(INFO) << "Dropping empty header " << hi->first.as_string()
     42                  << " from headers";
     43       continue;
     44     }
     45 
     46     // This unfortunately involves loads of copying, but its the simplest way
     47     // to sort the headers and leverage the framer.
     48     string name = hi->first.as_string();
     49     StringToLowerASCII(&name);
     50     SpdyHeaderBlock::iterator it = block->find(name);
     51     if (it != block->end()) {
     52       it->second.reserve(it->second.size() + 1 + hi->second.size());
     53       it->second.append("\0", 1);
     54       it->second.append(hi->second.data(), hi->second.size());
     55     } else {
     56       block->insert(make_pair(name, hi->second.as_string()));
     57     }
     58   }
     59 }
     60 
     61 void PopulateSpdy3RequestHeaderBlock(const BalsaHeaders& headers,
     62                                      const string& scheme,
     63                                      const string& host_and_port,
     64                                      const string& path,
     65                                      SpdyHeaderBlock* block) {
     66   PopulateSpdyHeaderBlock(headers, block, true);
     67   StringPiece host_header = headers.GetHeader("Host");
     68   if (!host_header.empty()) {
     69     DCHECK(host_and_port.empty() || host_header == host_and_port);
     70     block->insert(make_pair(kV3Host, host_header.as_string()));
     71   } else {
     72     block->insert(make_pair(kV3Host, host_and_port));
     73   }
     74   block->insert(make_pair(kV3Path, path));
     75   block->insert(make_pair(kV3Scheme, scheme));
     76 
     77   if (!headers.request_method().empty()) {
     78     block->insert(make_pair(kV3Method, headers.request_method().as_string()));
     79   }
     80 
     81   if (!headers.request_version().empty()) {
     82     (*block)[kV3Version] = headers.request_version().as_string();
     83   }
     84 }
     85 
     86 void PopulateSpdyResponseHeaderBlock(const BalsaHeaders& headers,
     87                                      SpdyHeaderBlock* block) {
     88   string status = headers.response_code().as_string();
     89   status.append(" ");
     90   status.append(headers.response_reason_phrase().as_string());
     91   (*block)[kV3Status] = status;
     92   (*block)[kV3Version] =
     93       headers.response_version().as_string();
     94 
     95   // Empty header values are only allowed because this is spdy3.
     96   PopulateSpdyHeaderBlock(headers, block, true);
     97 }
     98 
     99 // static
    100 SpdyHeaderBlock SpdyUtils::RequestHeadersToSpdyHeaders(
    101     const BalsaHeaders& request_headers) {
    102   string scheme;
    103   string host_and_port;
    104   string path;
    105 
    106   string url = request_headers.request_uri().as_string();
    107   if (url.empty() || url[0] == '/') {
    108     path = url;
    109   } else {
    110     GURL request_uri(url);
    111     if (request_headers.request_method() == "CONNECT") {
    112       path = url;
    113     } else {
    114       path = request_uri.path();
    115       if (!request_uri.query().empty()) {
    116         path = path + "?" + request_uri.query();
    117       }
    118       host_and_port = request_uri.host();
    119       scheme = request_uri.scheme();
    120     }
    121   }
    122 
    123   DCHECK(!scheme.empty());
    124   DCHECK(!host_and_port.empty());
    125   DCHECK(!path.empty());
    126 
    127   SpdyHeaderBlock block;
    128   PopulateSpdy3RequestHeaderBlock(
    129       request_headers, scheme, host_and_port, path, &block);
    130   if (block.find("host") != block.end()) {
    131     block.erase(block.find("host"));
    132   }
    133   return block;
    134 }
    135 
    136 // static
    137 string SpdyUtils::SerializeRequestHeaders(const BalsaHeaders& request_headers) {
    138   SpdyHeaderBlock block = RequestHeadersToSpdyHeaders(request_headers);
    139   return SerializeUncompressedHeaders(block);
    140 }
    141 
    142 // static
    143 SpdyHeaderBlock SpdyUtils::ResponseHeadersToSpdyHeaders(
    144     const BalsaHeaders& response_headers) {
    145   SpdyHeaderBlock block;
    146   PopulateSpdyResponseHeaderBlock(response_headers, &block);
    147   return block;
    148 }
    149 
    150 // static
    151 string SpdyUtils::SerializeResponseHeaders(
    152     const BalsaHeaders& response_headers) {
    153   SpdyHeaderBlock block = ResponseHeadersToSpdyHeaders(response_headers);
    154 
    155   return SerializeUncompressedHeaders(block);
    156 }
    157 
    158 // static
    159 string SpdyUtils::SerializeUncompressedHeaders(const SpdyHeaderBlock& headers) {
    160   int length = SpdyFramer::GetSerializedLength(SPDY3, &headers);
    161   SpdyFrameBuilder builder(length);
    162   SpdyFramer::WriteHeaderBlock(&builder, SPDY3, &headers);
    163   scoped_ptr<SpdyFrame> block(builder.take());
    164   return string(block->data(), length);
    165 }
    166 
    167 bool IsSpecialSpdyHeader(SpdyHeaderBlock::const_iterator header,
    168                             BalsaHeaders* headers) {
    169   if (header->first.empty() || header->second.empty()) {
    170     return true;
    171   }
    172   const string& header_name = header->first;
    173   return header_name.c_str()[0] == ':';
    174 }
    175 
    176 bool SpdyUtils::FillBalsaRequestHeaders(
    177     const SpdyHeaderBlock& header_block,
    178     BalsaHeaders* request_headers) {
    179   typedef SpdyHeaderBlock::const_iterator BlockIt;
    180 
    181   BlockIt host_it = header_block.find(kV3Host);
    182   BlockIt path_it = header_block.find(kV3Path);
    183   BlockIt scheme_it = header_block.find(kV3Scheme);
    184   BlockIt method_it = header_block.find(kV3Method);
    185   BlockIt end_it = header_block.end();
    186   if (host_it == end_it || path_it == end_it || scheme_it == end_it ||
    187       method_it == end_it) {
    188     return false;
    189   }
    190   string url = scheme_it->second;
    191   url.append("://");
    192   url.append(host_it->second);
    193   url.append(path_it->second);
    194   request_headers->SetRequestUri(url);
    195   request_headers->SetRequestMethod(method_it->second);
    196 
    197   BlockIt cl_it = header_block.find("content-length");
    198   if (cl_it != header_block.end()) {
    199     int content_length;
    200     if (!base::StringToInt(cl_it->second, &content_length)) {
    201       return false;
    202     }
    203     request_headers->SetContentLength(content_length);
    204   }
    205 
    206   for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) {
    207    if (!IsSpecialSpdyHeader(it, request_headers)) {
    208      request_headers->AppendHeader(it->first, it->second);
    209    }
    210   }
    211 
    212   return true;
    213 }
    214 
    215 // The reason phrase should match regexp [\d\d\d [^\r\n]+].  If not, we will
    216 // fail to parse it.
    217 bool ParseReasonAndStatus(StringPiece status_and_reason,
    218                           BalsaHeaders* headers) {
    219   if (status_and_reason.size() < 5)
    220     return false;
    221 
    222   if (status_and_reason[3] != ' ')
    223     return false;
    224 
    225   const StringPiece status_str = StringPiece(status_and_reason.data(), 3);
    226   int status;
    227   if (!base::StringToInt(status_str, &status)) {
    228     return false;
    229   }
    230 
    231   headers->SetResponseCode(status_str);
    232   headers->set_parsed_response_code(status);
    233 
    234   StringPiece reason(status_and_reason.data() + 4,
    235                      status_and_reason.length() - 4);
    236 
    237   headers->SetResponseReasonPhrase(reason);
    238   return true;
    239 }
    240 
    241 bool SpdyUtils::FillBalsaResponseHeaders(
    242     const SpdyHeaderBlock& header_block,
    243     BalsaHeaders* request_headers) {
    244   typedef SpdyHeaderBlock::const_iterator BlockIt;
    245 
    246   BlockIt status_it = header_block.find(kV3Status);
    247   BlockIt version_it = header_block.find(kV3Version);
    248   BlockIt end_it = header_block.end();
    249   if (status_it == end_it || version_it == end_it) {
    250     return false;
    251   }
    252 
    253   if (!ParseReasonAndStatus(status_it->second, request_headers)) {
    254     return false;
    255   }
    256   request_headers->SetResponseVersion(version_it->second);
    257   for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) {
    258    if (!IsSpecialSpdyHeader(it, request_headers)) {
    259      request_headers->AppendHeader(it->first, it->second);
    260    }
    261   }
    262   return true;
    263 }
    264 
    265 }  // namespace tools
    266 }  // namespace net
    267