| OLD | NEW |
| (Empty) |
| 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_balsa_utils.h" | |
| 6 | |
| 7 #include <memory> | |
| 8 #include <string> | |
| 9 | |
| 10 #include "base/strings/string_number_conversions.h" | |
| 11 #include "base/strings/string_piece.h" | |
| 12 #include "base/strings/string_split.h" | |
| 13 #include "base/strings/string_util.h" | |
| 14 #include "net/base/linked_hash_map.h" | |
| 15 #include "net/quic/core/quic_flags.h" | |
| 16 #include "net/quic/core/spdy_utils.h" | |
| 17 #include "net/spdy/spdy_frame_builder.h" | |
| 18 #include "net/spdy/spdy_framer.h" | |
| 19 #include "net/spdy/spdy_protocol.h" | |
| 20 #include "net/tools/balsa/balsa_headers.h" | |
| 21 #include "url/gurl.h" | |
| 22 | |
| 23 using base::StringPiece; | |
| 24 using base::StringPieceHash; | |
| 25 using std::make_pair; | |
| 26 using std::pair; | |
| 27 using std::string; | |
| 28 | |
| 29 namespace net { | |
| 30 namespace { | |
| 31 | |
| 32 const char kV4Host[] = ":authority"; | |
| 33 | |
| 34 const char kV3Host[] = ":host"; | |
| 35 const char kV3Path[] = ":path"; | |
| 36 const char kV3Scheme[] = ":scheme"; | |
| 37 const char kV3Method[] = ":method"; | |
| 38 const char kV3Status[] = ":status"; | |
| 39 const char kV3Version[] = ":version"; | |
| 40 | |
| 41 void PopulateSpdyHeaderBlock(const BalsaHeaders& headers, | |
| 42 SpdyHeaderBlock* block, | |
| 43 bool allow_empty_values) { | |
| 44 using HeaderValuesMap = | |
| 45 linked_hash_map<StringPiece, std::vector<StringPiece>, StringPieceHash>; | |
| 46 std::deque<string> names; | |
| 47 HeaderValuesMap header_values_map; | |
| 48 // First, gather references to all values for each name. | |
| 49 for (BalsaHeaders::const_header_lines_iterator hi = | |
| 50 headers.header_lines_begin(); | |
| 51 hi != headers.header_lines_end(); ++hi) { | |
| 52 if ((hi->second.length() == 0) && !allow_empty_values) { | |
| 53 DVLOG(1) << "Dropping empty header " << hi->first.as_string() | |
| 54 << " from headers"; | |
| 55 continue; | |
| 56 } | |
| 57 const string name = base::ToLowerASCII(hi->first.as_string()); | |
| 58 names.push_back(name); | |
| 59 header_values_map[name].push_back(hi->second); | |
| 60 } | |
| 61 // Then, write joined representations to the header block. | |
| 62 for (const auto& header : header_values_map) { | |
| 63 if (header.second.size() == 1) { | |
| 64 // Avoid string allocation for the single value case. | |
| 65 (*block)[header.first] = header.second[0]; | |
| 66 } else { | |
| 67 StringPiece separator("\0", 1); | |
| 68 auto it = header.second.begin(); | |
| 69 string value = it->as_string(); | |
| 70 ++it; | |
| 71 for (; it != header.second.end(); ++it) { | |
| 72 separator.AppendToString(&value); | |
| 73 value.append(it->data(), it->size()); | |
| 74 } | |
| 75 (*block)[header.first] = value; | |
| 76 } | |
| 77 } | |
| 78 } | |
| 79 | |
| 80 void PopulateHttp2RequestHeaderBlock(const BalsaHeaders& headers, | |
| 81 const string& scheme, | |
| 82 const string& host_and_port, | |
| 83 const string& path, | |
| 84 SpdyHeaderBlock* block) { | |
| 85 PopulateSpdyHeaderBlock(headers, block, true); | |
| 86 StringPiece host_header = headers.GetHeader("Host"); | |
| 87 if (!host_header.empty()) { | |
| 88 DCHECK(host_and_port.empty() || host_header == host_and_port); | |
| 89 block->insert(make_pair(kV4Host, host_header)); | |
| 90 // PopulateSpdyHeaderBlock already added the "host" header, | |
| 91 // which is invalid for HTTP2. | |
| 92 block->erase("host"); | |
| 93 } else { | |
| 94 block->insert(make_pair(kV4Host, host_and_port)); | |
| 95 } | |
| 96 block->insert(make_pair(kV3Path, path)); | |
| 97 block->insert(make_pair(kV3Scheme, scheme)); | |
| 98 | |
| 99 if (!headers.request_method().empty()) { | |
| 100 block->insert(make_pair(kV3Method, headers.request_method())); | |
| 101 } | |
| 102 } | |
| 103 | |
| 104 void PopulateSpdyResponseHeaderBlock(SpdyMajorVersion version, | |
| 105 const BalsaHeaders& headers, | |
| 106 SpdyHeaderBlock* block) { | |
| 107 if (version <= SPDY3) { | |
| 108 string status = headers.response_code().as_string(); | |
| 109 status.append(" "); | |
| 110 status.append(headers.response_reason_phrase().as_string()); | |
| 111 (*block)[kV3Status] = status; | |
| 112 (*block)[kV3Version] = headers.response_version(); | |
| 113 } else { | |
| 114 (*block)[kV3Status] = headers.response_code(); | |
| 115 } | |
| 116 | |
| 117 PopulateSpdyHeaderBlock(headers, block, true); | |
| 118 } | |
| 119 | |
| 120 bool IsSpecialSpdyHeader(SpdyHeaderBlock::const_iterator header, | |
| 121 BalsaHeaders* headers) { | |
| 122 return header->first.empty() || /* header->second.empty() || */ | |
| 123 header->first[0] == ':'; | |
| 124 } | |
| 125 | |
| 126 // The reason phrase should match regexp [\d\d\d [^\r\n]+]. If not, we will | |
| 127 // fail to parse it. | |
| 128 bool ParseReasonAndStatus(StringPiece status_and_reason, | |
| 129 BalsaHeaders* headers) { | |
| 130 int status; | |
| 131 if (!base::StringToInt(status_and_reason, &status)) { | |
| 132 return false; | |
| 133 } | |
| 134 headers->SetResponseCode(status_and_reason); | |
| 135 headers->SetResponseCode(status_and_reason); | |
| 136 headers->set_parsed_response_code(status); | |
| 137 return true; | |
| 138 } | |
| 139 | |
| 140 // static | |
| 141 void SpdyHeadersToResponseHeaders(const SpdyHeaderBlock& header_block, | |
| 142 BalsaHeaders* request_headers) { | |
| 143 typedef SpdyHeaderBlock::const_iterator BlockIt; | |
| 144 | |
| 145 BlockIt status_it = header_block.find(kV3Status); | |
| 146 BlockIt end_it = header_block.end(); | |
| 147 if (status_it == end_it) { | |
| 148 return; | |
| 149 } | |
| 150 | |
| 151 if (!ParseReasonAndStatus(status_it->second, request_headers)) { | |
| 152 return; | |
| 153 } | |
| 154 | |
| 155 for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) { | |
| 156 if (!IsSpecialSpdyHeader(it, request_headers)) { | |
| 157 if (it->second.empty()) { | |
| 158 request_headers->AppendHeader(it->first, it->second); | |
| 159 } else { | |
| 160 DVLOG(2) << "Splitting value: [" << it->second << "]" | |
| 161 << " for key: " << it->first; | |
| 162 for (string value : | |
| 163 base::SplitString(it->second, base::StringPiece("\0", 1), | |
| 164 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { | |
| 165 DVLOG(2) << "AppendHeader(" << it->first << ", " << value << ")"; | |
| 166 request_headers->AppendHeader(it->first, StringPiece(value)); | |
| 167 } | |
| 168 } | |
| 169 } | |
| 170 } | |
| 171 } | |
| 172 | |
| 173 // static | |
| 174 void SpdyHeadersToRequestHeaders(const SpdyHeaderBlock& header_block, | |
| 175 BalsaHeaders* request_headers) { | |
| 176 typedef SpdyHeaderBlock::const_iterator BlockIt; | |
| 177 | |
| 178 BlockIt authority_it = header_block.find(kV4Host); | |
| 179 BlockIt host_it = header_block.find(kV3Host); | |
| 180 BlockIt method_it = header_block.find(kV3Method); | |
| 181 BlockIt path_it = header_block.find(kV3Path); | |
| 182 BlockIt scheme_it = header_block.find(kV3Scheme); | |
| 183 BlockIt end_it = header_block.end(); | |
| 184 | |
| 185 string method; | |
| 186 if (method_it == end_it) { | |
| 187 method = "GET"; | |
| 188 } else { | |
| 189 method = method_it->second.as_string(); | |
| 190 } | |
| 191 string uri; | |
| 192 if (scheme_it == end_it) { | |
| 193 uri += "https"; | |
| 194 } else { | |
| 195 uri += scheme_it->second.as_string(); | |
| 196 } | |
| 197 uri += "://"; | |
| 198 | |
| 199 if (authority_it != end_it) { | |
| 200 uri += authority_it->second.as_string(); | |
| 201 request_headers->AppendHeader("host", authority_it->second); | |
| 202 } else if (host_it != end_it) { | |
| 203 uri += host_it->second.as_string(); | |
| 204 request_headers->AppendHeader("host", host_it->second); | |
| 205 } | |
| 206 | |
| 207 if (path_it == end_it) { | |
| 208 uri += "/"; | |
| 209 } else { | |
| 210 uri += path_it->second.as_string(); | |
| 211 } | |
| 212 request_headers->SetRequestFirstlineFromStringPieces( | |
| 213 method, uri, net::kHttp2VersionString); | |
| 214 | |
| 215 for (BlockIt it = header_block.begin(); it != header_block.end(); ++it) { | |
| 216 if (!IsSpecialSpdyHeader(it, request_headers)) { | |
| 217 if (it->second.empty()) { | |
| 218 request_headers->AppendHeader(it->first, it->second); | |
| 219 } else { | |
| 220 DVLOG(2) << "Splitting value: [" << it->second << "]" | |
| 221 << " for key: " << it->first; | |
| 222 for (string value : | |
| 223 base::SplitString(it->second, base::StringPiece("\0", 1), | |
| 224 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { | |
| 225 DVLOG(2) << "AppendHeader(" << it->first << ", " << value << ")"; | |
| 226 request_headers->AppendHeader(it->first, StringPiece(value)); | |
| 227 } | |
| 228 } | |
| 229 } | |
| 230 } | |
| 231 } | |
| 232 | |
| 233 // static | |
| 234 void SpdyHeadersToBalsaHeaders(const SpdyHeaderBlock& block, | |
| 235 BalsaHeaders* headers, | |
| 236 bool isResponse) { | |
| 237 if (isResponse) { | |
| 238 SpdyHeadersToResponseHeaders(block, headers); | |
| 239 return; | |
| 240 } | |
| 241 SpdyHeadersToRequestHeaders(block, headers); | |
| 242 } | |
| 243 | |
| 244 } // namespace | |
| 245 | |
| 246 // static | |
| 247 SpdyHeaderBlock SpdyBalsaUtils::RequestHeadersToSpdyHeaders( | |
| 248 const BalsaHeaders& request_headers) { | |
| 249 string scheme; | |
| 250 string host_and_port; | |
| 251 string path; | |
| 252 | |
| 253 string url = request_headers.request_uri().as_string(); | |
| 254 if (url.empty() || url[0] == '/') { | |
| 255 path = url; | |
| 256 } else { | |
| 257 std::unique_ptr<GURL> request_uri(new GURL(url)); | |
| 258 if (request_headers.request_method() == "CONNECT") { | |
| 259 path = url; | |
| 260 } else { | |
| 261 path = request_uri->path(); | |
| 262 if (!request_uri->query().empty()) { | |
| 263 path = path + "?" + request_uri->query(); | |
| 264 } | |
| 265 host_and_port = request_uri->host(); | |
| 266 scheme = request_uri->scheme(); | |
| 267 } | |
| 268 } | |
| 269 | |
| 270 if (scheme.empty()) { | |
| 271 if (request_headers.HasHeader("Scheme")) { | |
| 272 request_headers.GetAllOfHeaderAsString("Scheme", &scheme); | |
| 273 } else { | |
| 274 // Requests must contain a :scheme header, and unless another scheme is | |
| 275 // detected, https is assumed. | |
| 276 scheme = "https"; | |
| 277 } | |
| 278 } | |
| 279 | |
| 280 SpdyHeaderBlock block; | |
| 281 PopulateHttp2RequestHeaderBlock(request_headers, scheme, host_and_port, path, | |
| 282 &block); | |
| 283 | |
| 284 // If a "Scheme" header existed in request_headers, it would have been | |
| 285 // propagated to |block|. | |
| 286 block.erase("scheme"); | |
| 287 return block; | |
| 288 } | |
| 289 | |
| 290 // static | |
| 291 SpdyHeaderBlock SpdyBalsaUtils::ResponseHeadersToSpdyHeaders( | |
| 292 const BalsaHeaders& response_headers) { | |
| 293 SpdyHeaderBlock block; | |
| 294 PopulateSpdyResponseHeaderBlock(HTTP2, response_headers, &block); | |
| 295 return block; | |
| 296 } | |
| 297 | |
| 298 // static | |
| 299 string SpdyBalsaUtils::SerializeResponseHeaders( | |
| 300 const BalsaHeaders& response_headers) { | |
| 301 SpdyHeaderBlock block = ResponseHeadersToSpdyHeaders(response_headers); | |
| 302 | |
| 303 return net::SpdyUtils::SerializeUncompressedHeaders(block); | |
| 304 } | |
| 305 | |
| 306 // static | |
| 307 void SpdyBalsaUtils::SpdyHeadersToResponseHeaders(const SpdyHeaderBlock& block, | |
| 308 BalsaHeaders* headers) { | |
| 309 SpdyHeadersToBalsaHeaders(block, headers, true); | |
| 310 } | |
| 311 | |
| 312 // static | |
| 313 void SpdyBalsaUtils::SpdyHeadersToRequestHeaders(const SpdyHeaderBlock& block, | |
| 314 BalsaHeaders* headers) { | |
| 315 SpdyHeadersToBalsaHeaders(block, headers, false); | |
| 316 } | |
| 317 | |
| 318 } // namespace net | |
| OLD | NEW |