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 DVLOG(1) << "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 base::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 size_t length = SpdyFramer::GetSerializedLength(SPDY3, &headers); 161 SpdyFrameBuilder builder(length, SPDY3); 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