• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2012 The Chromium Authors
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/spdy/spdy_http_utils.h"
6 
7 #include <string>
8 #include <vector>
9 
10 #include "base/check_op.h"
11 #include "base/feature_list.h"
12 #include "base/strings/strcat.h"
13 #include "base/strings/string_piece.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "base/types/expected.h"
17 #include "base/types/expected_macros.h"
18 #include "net/base/features.h"
19 #include "net/base/url_util.h"
20 #include "net/http/http_request_headers.h"
21 #include "net/http/http_request_info.h"
22 #include "net/http/http_response_headers.h"
23 #include "net/http/http_response_info.h"
24 #include "net/http/http_util.h"
25 #include "net/quic/quic_http_utils.h"
26 #include "net/third_party/quiche/src/quiche/quic/core/quic_stream_priority.h"
27 #include "net/third_party/quiche/src/quiche/spdy/core/http2_header_block.h"
28 
29 namespace net {
30 
31 const char* const kHttp2PriorityHeader = "priority";
32 
33 namespace {
34 
35 // The number of bytes to reserve for the raw headers string to avoid having to
36 // do reallocations most of the time. Equal to the 99th percentile of header
37 // sizes in ricea@'s cache on 3 Aug 2023.
38 constexpr size_t kExpectedRawHeaderSize = 4035;
39 
40 // Add header `name` with `value` to `headers`. `name` must not already exist in
41 // `headers`.
AddUniqueSpdyHeader(base::StringPiece name,base::StringPiece value,spdy::Http2HeaderBlock * headers)42 void AddUniqueSpdyHeader(base::StringPiece name,
43                          base::StringPiece value,
44                          spdy::Http2HeaderBlock* headers) {
45   auto insert_result = headers->insert({name, value});
46   CHECK_EQ(insert_result, spdy::Http2HeaderBlock::InsertResult::kInserted);
47 }
48 
49 // Convert `headers` to an HttpResponseHeaders object based on the features
50 // enabled at runtime.
51 base::expected<scoped_refptr<HttpResponseHeaders>, int>
SpdyHeadersToHttpResponseHeadersUsingFeatures(const spdy::Http2HeaderBlock & headers)52 SpdyHeadersToHttpResponseHeadersUsingFeatures(
53     const spdy::Http2HeaderBlock& headers) {
54   if (base::FeatureList::IsEnabled(
55           features::kSpdyHeadersToHttpResponseUseBuilder)) {
56     return SpdyHeadersToHttpResponseHeadersUsingBuilder(headers);
57   } else {
58     return SpdyHeadersToHttpResponseHeadersUsingRawString(headers);
59   }
60 }
61 
62 }  // namespace
63 
SpdyHeadersToHttpResponse(const spdy::Http2HeaderBlock & headers,HttpResponseInfo * response)64 int SpdyHeadersToHttpResponse(const spdy::Http2HeaderBlock& headers,
65                               HttpResponseInfo* response) {
66   ASSIGN_OR_RETURN(response->headers,
67                    SpdyHeadersToHttpResponseHeadersUsingFeatures(headers));
68   response->was_fetched_via_spdy = true;
69   return OK;
70 }
71 
72 NET_EXPORT_PRIVATE base::expected<scoped_refptr<HttpResponseHeaders>, int>
SpdyHeadersToHttpResponseHeadersUsingRawString(const spdy::Http2HeaderBlock & headers)73 SpdyHeadersToHttpResponseHeadersUsingRawString(
74     const spdy::Http2HeaderBlock& headers) {
75   // The ":status" header is required.
76   spdy::Http2HeaderBlock::const_iterator it =
77       headers.find(spdy::kHttp2StatusHeader);
78   if (it == headers.end())
79     return base::unexpected(ERR_INCOMPLETE_HTTP2_HEADERS);
80 
81   const auto status = it->second;
82 
83   std::string raw_headers =
84       base::StrCat({"HTTP/1.1 ", status, base::StringPiece("\0", 1)});
85   raw_headers.reserve(kExpectedRawHeaderSize);
86   for (const auto& [name, value] : headers) {
87     DCHECK_GT(name.size(), 0u);
88     if (name[0] == ':') {
89       // https://tools.ietf.org/html/rfc7540#section-8.1.2.4
90       // Skip pseudo headers.
91       continue;
92     }
93     // For each value, if the server sends a NUL-separated
94     // list of values, we separate that back out into
95     // individual headers for each value in the list.
96     // e.g.
97     //    Set-Cookie "foo\0bar"
98     // becomes
99     //    Set-Cookie: foo\0
100     //    Set-Cookie: bar\0
101     size_t start = 0;
102     size_t end = 0;
103     do {
104       end = value.find('\0', start);
105       base::StringPiece tval;
106       if (end != value.npos) {
107         tval = value.substr(start, (end - start));
108       } else {
109         tval = value.substr(start);
110       }
111       base::StrAppend(&raw_headers,
112                       {name, ":", tval, base::StringPiece("\0", 1)});
113       start = end + 1;
114     } while (end != value.npos);
115   }
116 
117   auto response_headers =
118       base::MakeRefCounted<HttpResponseHeaders>(raw_headers);
119 
120   // When there are multiple location headers the response is a potential
121   // response smuggling attack.
122   if (HttpUtil::HeadersContainMultipleCopiesOfField(*response_headers,
123                                                     "location")) {
124     return base::unexpected(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION);
125   }
126 
127   return response_headers;
128 }
129 
130 NET_EXPORT_PRIVATE base::expected<scoped_refptr<HttpResponseHeaders>, int>
SpdyHeadersToHttpResponseHeadersUsingBuilder(const spdy::Http2HeaderBlock & headers)131 SpdyHeadersToHttpResponseHeadersUsingBuilder(
132     const spdy::Http2HeaderBlock& headers) {
133   // The ":status" header is required.
134   // TODO(ricea): The ":status" header should always come first. Skip this hash
135   // lookup after we no longer need to be compatible with the old
136   // implementation.
137   spdy::Http2HeaderBlock::const_iterator it =
138       headers.find(spdy::kHttp2StatusHeader);
139   if (it == headers.end()) {
140     return base::unexpected(ERR_INCOMPLETE_HTTP2_HEADERS);
141   }
142 
143   const auto status = it->second;
144 
145   HttpResponseHeaders::Builder builder({1, 1}, status);
146 
147   for (const auto& [name, value] : headers) {
148     DCHECK_GT(name.size(), 0u);
149     if (name[0] == ':') {
150       // https://tools.ietf.org/html/rfc7540#section-8.1.2.4
151       // Skip pseudo headers.
152       continue;
153     }
154     // For each value, if the server sends a NUL-separated
155     // list of values, we separate that back out into
156     // individual headers for each value in the list.
157     // e.g.
158     //    Set-Cookie "foo\0bar"
159     // becomes
160     //    Set-Cookie: foo\0
161     //    Set-Cookie: bar\0
162     size_t start = 0;
163     size_t end = 0;
164     absl::optional<base::StringPiece> location_value;
165     do {
166       end = value.find('\0', start);
167       base::StringPiece tval;
168       if (end != value.npos) {
169         tval = value.substr(start, (end - start));
170 
171         // TODO(ricea): Make this comparison case-sensitive when we are no
172         // longer maintaining compatibility with the old version of the
173         // function.
174         if (base::EqualsCaseInsensitiveASCII(name, "location") &&
175             !location_value.has_value()) {
176           location_value = HttpUtil::TrimLWS(tval);
177         }
178       } else {
179         tval = value.substr(start);
180       }
181       if (location_value.has_value() && start > 0) {
182         DCHECK(base::EqualsCaseInsensitiveASCII(name, "location"));
183         base::StringPiece trimmed_value = HttpUtil::TrimLWS(tval);
184         if (trimmed_value != location_value.value()) {
185           return base::unexpected(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION);
186         }
187       }
188       builder.AddHeader(name, tval);
189       start = end + 1;
190     } while (end != value.npos);
191   }
192 
193   return builder.Build();
194 }
195 
CreateSpdyHeadersFromHttpRequest(const HttpRequestInfo & info,absl::optional<RequestPriority> priority,const HttpRequestHeaders & request_headers,spdy::Http2HeaderBlock * headers)196 void CreateSpdyHeadersFromHttpRequest(const HttpRequestInfo& info,
197                                       absl::optional<RequestPriority> priority,
198                                       const HttpRequestHeaders& request_headers,
199                                       spdy::Http2HeaderBlock* headers) {
200   headers->insert({spdy::kHttp2MethodHeader, info.method});
201   if (info.method == "CONNECT") {
202     headers->insert({spdy::kHttp2AuthorityHeader, GetHostAndPort(info.url)});
203   } else {
204     headers->insert(
205         {spdy::kHttp2AuthorityHeader, GetHostAndOptionalPort(info.url)});
206     headers->insert({spdy::kHttp2SchemeHeader, info.url.scheme()});
207     headers->insert({spdy::kHttp2PathHeader, info.url.PathForRequest()});
208   }
209 
210   HttpRequestHeaders::Iterator it(request_headers);
211   while (it.GetNext()) {
212     std::string name = base::ToLowerASCII(it.name());
213     if (name.empty() || name[0] == ':' || name == "connection" ||
214         name == "proxy-connection" || name == "transfer-encoding" ||
215         name == "host") {
216       continue;
217     }
218     AddUniqueSpdyHeader(name, it.value(), headers);
219   }
220 
221   // Add the priority header if there is not already one set. This uses the
222   // quic helpers but the header values for HTTP extensible priorities are
223   // independent of quic.
224   if (priority &&
225       base::FeatureList::IsEnabled(net::features::kPriorityHeader) &&
226       headers->find(kHttp2PriorityHeader) == headers->end()) {
227     uint8_t urgency = ConvertRequestPriorityToQuicPriority(priority.value());
228     bool incremental = info.priority_incremental;
229     quic::HttpStreamPriority quic_priority{urgency, incremental};
230     AddUniqueSpdyHeader(kHttp2PriorityHeader,
231                         quic::SerializePriorityFieldValue(quic_priority),
232                         headers);
233   }
234 }
235 
CreateSpdyHeadersFromHttpRequestForWebSocket(const GURL & url,const HttpRequestHeaders & request_headers,spdy::Http2HeaderBlock * headers)236 void CreateSpdyHeadersFromHttpRequestForWebSocket(
237     const GURL& url,
238     const HttpRequestHeaders& request_headers,
239     spdy::Http2HeaderBlock* headers) {
240   headers->insert({spdy::kHttp2MethodHeader, "CONNECT"});
241   headers->insert({spdy::kHttp2AuthorityHeader, GetHostAndOptionalPort(url)});
242   headers->insert({spdy::kHttp2SchemeHeader, "https"});
243   headers->insert({spdy::kHttp2PathHeader, url.PathForRequest()});
244   headers->insert({spdy::kHttp2ProtocolHeader, "websocket"});
245 
246   HttpRequestHeaders::Iterator it(request_headers);
247   while (it.GetNext()) {
248     std::string name = base::ToLowerASCII(it.name());
249     if (name.empty() || name[0] == ':' || name == "upgrade" ||
250         name == "connection" || name == "proxy-connection" ||
251         name == "transfer-encoding" || name == "host") {
252       continue;
253     }
254     AddUniqueSpdyHeader(name, it.value(), headers);
255   }
256 }
257 
258 static_assert(HIGHEST - LOWEST < 4 && HIGHEST - MINIMUM_PRIORITY < 6,
259               "request priority incompatible with spdy");
260 
ConvertRequestPriorityToSpdyPriority(const RequestPriority priority)261 spdy::SpdyPriority ConvertRequestPriorityToSpdyPriority(
262     const RequestPriority priority) {
263   DCHECK_GE(priority, MINIMUM_PRIORITY);
264   DCHECK_LE(priority, MAXIMUM_PRIORITY);
265   return static_cast<spdy::SpdyPriority>(MAXIMUM_PRIORITY - priority +
266                                          spdy::kV3HighestPriority);
267 }
268 
269 NET_EXPORT_PRIVATE RequestPriority
ConvertSpdyPriorityToRequestPriority(spdy::SpdyPriority priority)270 ConvertSpdyPriorityToRequestPriority(spdy::SpdyPriority priority) {
271   // Handle invalid values gracefully.
272   return ((priority - spdy::kV3HighestPriority) >
273           (MAXIMUM_PRIORITY - MINIMUM_PRIORITY))
274              ? IDLE
275              : static_cast<RequestPriority>(
276                    MAXIMUM_PRIORITY - (priority - spdy::kV3HighestPriority));
277 }
278 
ConvertHeaderBlockToHttpRequestHeaders(const spdy::Http2HeaderBlock & spdy_headers,HttpRequestHeaders * http_headers)279 NET_EXPORT_PRIVATE void ConvertHeaderBlockToHttpRequestHeaders(
280     const spdy::Http2HeaderBlock& spdy_headers,
281     HttpRequestHeaders* http_headers) {
282   for (const auto& it : spdy_headers) {
283     base::StringPiece key = it.first;
284     if (key[0] == ':') {
285       key.remove_prefix(1);
286     }
287     std::vector<base::StringPiece> values = base::SplitStringPiece(
288         it.second, "\0", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
289     for (const auto& value : values) {
290       http_headers->SetHeader(key, value);
291     }
292   }
293 }
294 
295 }  // namespace net
296