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