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 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "net/spdy/spdy_http_utils.h"
11
12 #include <stdint.h>
13
14 #include <limits>
15
16 #include "base/test/gmock_expected_support.h"
17 #include "base/test/scoped_feature_list.h"
18 #include "net/base/features.h"
19 #include "net/base/ip_endpoint.h"
20 #include "net/http/http_request_info.h"
21 #include "net/http/http_response_headers.h"
22 #include "net/http/http_response_headers_test_util.h"
23 #include "net/http/http_response_info.h"
24 #include "net/third_party/quiche/src/quiche/common/http/http_header_block.h"
25 #include "net/third_party/quiche/src/quiche/http2/core/spdy_framer.h"
26 #include "net/third_party/quiche/src/quiche/http2/core/spdy_protocol.h"
27 #include "net/third_party/quiche/src/quiche/http2/test_tools/spdy_test_utils.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29
30 namespace net {
31
32 namespace {
33
34 using ::testing::Values;
35
36 // Check that the headers are ordered correctly, with pseudo-headers
37 // preceding HTTP headers per
38 // https://datatracker.ietf.org/doc/html/rfc9114#section-4.3
CheckOrdering(const quiche::HttpHeaderBlock & headers)39 void CheckOrdering(const quiche::HttpHeaderBlock& headers) {
40 bool seen_http_header = false;
41
42 for (auto& header : headers) {
43 const bool is_pseudo = header.first.starts_with(':');
44 if (is_pseudo) {
45 ASSERT_FALSE(seen_http_header) << "Header order is incorrect:\n"
46 << headers.DebugString();
47 } else {
48 seen_http_header = true;
49 }
50 }
51 }
52
TEST(SpdyHttpUtilsTest,ConvertRequestPriorityToSpdy3Priority)53 TEST(SpdyHttpUtilsTest, ConvertRequestPriorityToSpdy3Priority) {
54 EXPECT_EQ(0, ConvertRequestPriorityToSpdyPriority(HIGHEST));
55 EXPECT_EQ(1, ConvertRequestPriorityToSpdyPriority(MEDIUM));
56 EXPECT_EQ(2, ConvertRequestPriorityToSpdyPriority(LOW));
57 EXPECT_EQ(3, ConvertRequestPriorityToSpdyPriority(LOWEST));
58 EXPECT_EQ(4, ConvertRequestPriorityToSpdyPriority(IDLE));
59 EXPECT_EQ(5, ConvertRequestPriorityToSpdyPriority(THROTTLED));
60 }
61
TEST(SpdyHttpUtilsTest,ConvertSpdy3PriorityToRequestPriority)62 TEST(SpdyHttpUtilsTest, ConvertSpdy3PriorityToRequestPriority) {
63 EXPECT_EQ(HIGHEST, ConvertSpdyPriorityToRequestPriority(0));
64 EXPECT_EQ(MEDIUM, ConvertSpdyPriorityToRequestPriority(1));
65 EXPECT_EQ(LOW, ConvertSpdyPriorityToRequestPriority(2));
66 EXPECT_EQ(LOWEST, ConvertSpdyPriorityToRequestPriority(3));
67 EXPECT_EQ(IDLE, ConvertSpdyPriorityToRequestPriority(4));
68 EXPECT_EQ(THROTTLED, ConvertSpdyPriorityToRequestPriority(5));
69 // These are invalid values, but we should still handle them
70 // gracefully.
71 for (int i = 6; i < std::numeric_limits<uint8_t>::max(); ++i) {
72 EXPECT_EQ(IDLE, ConvertSpdyPriorityToRequestPriority(i));
73 }
74 }
75
TEST(SpdyHttpUtilsTest,CreateSpdyHeadersFromHttpRequestHTTP2)76 TEST(SpdyHttpUtilsTest, CreateSpdyHeadersFromHttpRequestHTTP2) {
77 GURL url("https://www.google.com/index.html");
78 HttpRequestInfo request;
79 request.method = "GET";
80 request.url = url;
81 request.priority_incremental = true;
82 request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, "Chrome/1.1");
83 quiche::HttpHeaderBlock headers;
84 CreateSpdyHeadersFromHttpRequest(request, RequestPriority::HIGHEST,
85 request.extra_headers, &headers);
86 CheckOrdering(headers);
87 EXPECT_EQ("GET", headers[":method"]);
88 EXPECT_EQ("https", headers[":scheme"]);
89 EXPECT_EQ("www.google.com", headers[":authority"]);
90 EXPECT_EQ("/index.html", headers[":path"]);
91 EXPECT_EQ("u=0, i", headers[net::kHttp2PriorityHeader]);
92 EXPECT_EQ(headers.end(), headers.find(":version"));
93 EXPECT_EQ("Chrome/1.1", headers["user-agent"]);
94 }
95
TEST(SpdyHttpUtilsTest,CreateSpdyHeadersFromHttpRequestForExtendedConnect)96 TEST(SpdyHttpUtilsTest, CreateSpdyHeadersFromHttpRequestForExtendedConnect) {
97 GURL url("https://www.google.com/index.html");
98 HttpRequestInfo request;
99 request.method = "CONNECT";
100 request.url = url;
101 request.priority_incremental = true;
102 request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, "Chrome/1.1");
103 quiche::HttpHeaderBlock headers;
104 CreateSpdyHeadersFromHttpRequestForExtendedConnect(
105 request, RequestPriority::HIGHEST, "connect-ftp", request.extra_headers,
106 &headers);
107 CheckOrdering(headers);
108 EXPECT_EQ("CONNECT", headers[":method"]);
109 EXPECT_EQ("https", headers[":scheme"]);
110 EXPECT_EQ("www.google.com", headers[":authority"]);
111 EXPECT_EQ("connect-ftp", headers[":protocol"]);
112 EXPECT_EQ("/index.html", headers[":path"]);
113 EXPECT_EQ("u=0, i", headers[net::kHttp2PriorityHeader]);
114 EXPECT_EQ("Chrome/1.1", headers["user-agent"]);
115 }
116
TEST(SpdyHttpUtilsTest,CreateSpdyHeadersWithDefaultPriority)117 TEST(SpdyHttpUtilsTest, CreateSpdyHeadersWithDefaultPriority) {
118 GURL url("https://www.google.com/index.html");
119 HttpRequestInfo request;
120 request.method = "GET";
121 request.url = url;
122 request.priority_incremental = false;
123 request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, "Chrome/1.1");
124 quiche::HttpHeaderBlock headers;
125 CreateSpdyHeadersFromHttpRequest(request, RequestPriority::DEFAULT_PRIORITY,
126 request.extra_headers, &headers);
127 CheckOrdering(headers);
128 EXPECT_EQ("GET", headers[":method"]);
129 EXPECT_EQ("https", headers[":scheme"]);
130 EXPECT_EQ("www.google.com", headers[":authority"]);
131 EXPECT_EQ("/index.html", headers[":path"]);
132 EXPECT_FALSE(headers.contains(net::kHttp2PriorityHeader));
133 EXPECT_FALSE(headers.contains(":version"));
134 EXPECT_EQ("Chrome/1.1", headers["user-agent"]);
135 }
136
TEST(SpdyHttpUtilsTest,CreateSpdyHeadersWithExistingPriority)137 TEST(SpdyHttpUtilsTest, CreateSpdyHeadersWithExistingPriority) {
138 GURL url("https://www.google.com/index.html");
139 HttpRequestInfo request;
140 request.method = "GET";
141 request.url = url;
142 request.priority_incremental = true;
143 request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, "Chrome/1.1");
144 request.extra_headers.SetHeader(net::kHttp2PriorityHeader,
145 "explicit-priority");
146 quiche::HttpHeaderBlock headers;
147 CreateSpdyHeadersFromHttpRequest(request, RequestPriority::HIGHEST,
148 request.extra_headers, &headers);
149 CheckOrdering(headers);
150 EXPECT_EQ("GET", headers[":method"]);
151 EXPECT_EQ("https", headers[":scheme"]);
152 EXPECT_EQ("www.google.com", headers[":authority"]);
153 EXPECT_EQ("/index.html", headers[":path"]);
154 EXPECT_EQ("explicit-priority", headers[net::kHttp2PriorityHeader]);
155 EXPECT_EQ(headers.end(), headers.find(":version"));
156 EXPECT_EQ("Chrome/1.1", headers["user-agent"]);
157 }
158
TEST(SpdyHttpUtilsTest,CreateSpdyHeadersFromHttpRequestConnectHTTP2)159 TEST(SpdyHttpUtilsTest, CreateSpdyHeadersFromHttpRequestConnectHTTP2) {
160 GURL url("https://www.google.com/index.html");
161 HttpRequestInfo request;
162 request.method = "CONNECT";
163 request.url = url;
164 request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, "Chrome/1.1");
165 quiche::HttpHeaderBlock headers;
166 CreateSpdyHeadersFromHttpRequest(request, RequestPriority::DEFAULT_PRIORITY,
167 request.extra_headers, &headers);
168 CheckOrdering(headers);
169 EXPECT_EQ("CONNECT", headers[":method"]);
170 EXPECT_TRUE(headers.end() == headers.find(":scheme"));
171 EXPECT_EQ("www.google.com:443", headers[":authority"]);
172 EXPECT_EQ(headers.end(), headers.find(":path"));
173 EXPECT_EQ(headers.end(), headers.find(":scheme"));
174 EXPECT_TRUE(headers.end() == headers.find(":version"));
175 EXPECT_EQ("Chrome/1.1", headers["user-agent"]);
176 }
177
178 constexpr auto ToSimpleString = test::HttpResponseHeadersToSimpleString;
179
180 enum class SpdyHeadersToHttpResponseHeadersFeatureConfig {
181 kUseRawString,
182 kUseBuilder
183 };
184
PrintToString(SpdyHeadersToHttpResponseHeadersFeatureConfig config)185 std::string PrintToString(
186 SpdyHeadersToHttpResponseHeadersFeatureConfig config) {
187 switch (config) {
188 case SpdyHeadersToHttpResponseHeadersFeatureConfig::kUseRawString:
189 return "RawString";
190
191 case SpdyHeadersToHttpResponseHeadersFeatureConfig::kUseBuilder:
192 return "UseBuilder";
193 }
194 }
195
196 class SpdyHeadersToHttpResponseTest
197 : public ::testing::TestWithParam<
198 SpdyHeadersToHttpResponseHeadersFeatureConfig> {
199 public:
SpdyHeadersToHttpResponseTest()200 SpdyHeadersToHttpResponseTest() {
201 switch (GetParam()) {
202 case SpdyHeadersToHttpResponseHeadersFeatureConfig::kUseRawString:
203 feature_list_.InitWithFeatures(
204 {}, {features::kSpdyHeadersToHttpResponseUseBuilder});
205 break;
206
207 case SpdyHeadersToHttpResponseHeadersFeatureConfig::kUseBuilder:
208 feature_list_.InitWithFeatures(
209 {features::kSpdyHeadersToHttpResponseUseBuilder}, {});
210 break;
211 }
212 }
213
214 private:
215 base::test::ScopedFeatureList feature_list_;
216 };
217
218 // This test behaves the same regardless of which features are enabled.
TEST_P(SpdyHeadersToHttpResponseTest,SpdyHeadersToHttpResponse)219 TEST_P(SpdyHeadersToHttpResponseTest, SpdyHeadersToHttpResponse) {
220 constexpr char kExpectedSimpleString[] =
221 "HTTP/1.1 200\n"
222 "content-type: text/html\n"
223 "cache-control: no-cache, no-store\n"
224 "set-cookie: test_cookie=1234567890; Max-Age=3600; Secure; HttpOnly\n"
225 "set-cookie: session_id=abcdefghijklmnopqrstuvwxyz; Path=/; HttpOnly\n";
226 quiche::HttpHeaderBlock input;
227 input[spdy::kHttp2StatusHeader] = "200";
228 input["content-type"] = "text/html";
229 input["cache-control"] = "no-cache, no-store";
230 input.AppendValueOrAddHeader(
231 "set-cookie", "test_cookie=1234567890; Max-Age=3600; Secure; HttpOnly");
232 input.AppendValueOrAddHeader(
233 "set-cookie", "session_id=abcdefghijklmnopqrstuvwxyz; Path=/; HttpOnly");
234
235 net::HttpResponseInfo output;
236 output.remote_endpoint = {{127, 0, 0, 1}, 80};
237
238 EXPECT_EQ(OK, SpdyHeadersToHttpResponse(input, &output));
239
240 // This should be set.
241 EXPECT_TRUE(output.was_fetched_via_spdy);
242
243 // This should be untouched.
244 EXPECT_EQ(output.remote_endpoint, IPEndPoint({127, 0, 0, 1}, 80));
245
246 EXPECT_EQ(kExpectedSimpleString, ToSimpleString(output.headers));
247 }
248
249 INSTANTIATE_TEST_SUITE_P(
250 SpdyHttpUtils,
251 SpdyHeadersToHttpResponseTest,
252 Values(SpdyHeadersToHttpResponseHeadersFeatureConfig::kUseRawString,
253 SpdyHeadersToHttpResponseHeadersFeatureConfig::kUseBuilder),
254 ::testing::PrintToStringParamName());
255
256 // TODO(ricea): Once SpdyHeadersToHttpResponseHeadersUsingRawString has been
257 // removed, remove the parameterization and make these into
258 // SpdyHeadersToHttpResponse tests.
259
260 using SpdyHeadersToHttpResponseHeadersFunctionPtrType =
261 base::expected<scoped_refptr<HttpResponseHeaders>, int> (*)(
262 const quiche::HttpHeaderBlock&);
263
264 class SpdyHeadersToHttpResponseHeadersTest
265 : public testing::TestWithParam<
266 SpdyHeadersToHttpResponseHeadersFunctionPtrType> {
267 public:
PerformConversion(const quiche::HttpHeaderBlock & headers)268 base::expected<scoped_refptr<HttpResponseHeaders>, int> PerformConversion(
269 const quiche::HttpHeaderBlock& headers) {
270 return GetParam()(headers);
271 }
272 };
273
TEST_P(SpdyHeadersToHttpResponseHeadersTest,NoStatus)274 TEST_P(SpdyHeadersToHttpResponseHeadersTest, NoStatus) {
275 quiche::HttpHeaderBlock headers;
276 EXPECT_THAT(PerformConversion(headers),
277 base::test::ErrorIs(ERR_INCOMPLETE_HTTP2_HEADERS));
278 }
279
TEST_P(SpdyHeadersToHttpResponseHeadersTest,EmptyStatus)280 TEST_P(SpdyHeadersToHttpResponseHeadersTest, EmptyStatus) {
281 constexpr char kRawHeaders[] = "HTTP/1.1 200\n";
282 quiche::HttpHeaderBlock headers;
283 headers[":status"] = "";
284 ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
285 EXPECT_EQ(kRawHeaders, ToSimpleString(output));
286 }
287
TEST_P(SpdyHeadersToHttpResponseHeadersTest,Plain200)288 TEST_P(SpdyHeadersToHttpResponseHeadersTest, Plain200) {
289 // ":status" does not appear as a header in the output.
290 constexpr char kRawHeaders[] = "HTTP/1.1 200\n";
291 quiche::HttpHeaderBlock headers;
292 headers[spdy::kHttp2StatusHeader] = "200";
293 ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
294 EXPECT_EQ(kRawHeaders, ToSimpleString(output));
295 }
296
TEST_P(SpdyHeadersToHttpResponseHeadersTest,MultipleLocation)297 TEST_P(SpdyHeadersToHttpResponseHeadersTest, MultipleLocation) {
298 quiche::HttpHeaderBlock headers;
299 headers[spdy::kHttp2StatusHeader] = "304";
300 headers["Location"] = "https://example.com/1";
301 headers.AppendValueOrAddHeader("location", "https://example.com/2");
302 EXPECT_THAT(PerformConversion(headers),
303 base::test::ErrorIs(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION));
304 }
305
TEST_P(SpdyHeadersToHttpResponseHeadersTest,SpacesAmongValues)306 TEST_P(SpdyHeadersToHttpResponseHeadersTest, SpacesAmongValues) {
307 constexpr char kRawHeaders[] =
308 "HTTP/1.1 200\n"
309 "spaces: foo , bar\n";
310 quiche::HttpHeaderBlock headers;
311 headers[spdy::kHttp2StatusHeader] = "200";
312 headers["spaces"] = "foo , bar";
313 ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
314 EXPECT_EQ(kRawHeaders, ToSimpleString(output));
315 }
316
TEST_P(SpdyHeadersToHttpResponseHeadersTest,RepeatedHeader)317 TEST_P(SpdyHeadersToHttpResponseHeadersTest, RepeatedHeader) {
318 constexpr char kRawHeaders[] =
319 "HTTP/1.1 200\n"
320 "name: value1\n"
321 "name: value2\n";
322 quiche::HttpHeaderBlock headers;
323 headers[spdy::kHttp2StatusHeader] = "200";
324 headers.AppendValueOrAddHeader("name", "value1");
325 headers.AppendValueOrAddHeader("name", "value2");
326 ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
327 EXPECT_EQ(kRawHeaders, ToSimpleString(output));
328 }
329
TEST_P(SpdyHeadersToHttpResponseHeadersTest,EmptyValue)330 TEST_P(SpdyHeadersToHttpResponseHeadersTest, EmptyValue) {
331 constexpr char kRawHeaders[] =
332 "HTTP/1.1 200\n"
333 "empty: \n";
334 quiche::HttpHeaderBlock headers;
335 headers[spdy::kHttp2StatusHeader] = "200";
336 headers.AppendValueOrAddHeader("empty", "");
337 ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
338 EXPECT_EQ(kRawHeaders, ToSimpleString(output));
339 }
340
TEST_P(SpdyHeadersToHttpResponseHeadersTest,PseudoHeadersAreDropped)341 TEST_P(SpdyHeadersToHttpResponseHeadersTest, PseudoHeadersAreDropped) {
342 constexpr char kRawHeaders[] =
343 "HTTP/1.1 200\n"
344 "Content-Length: 5\n";
345 quiche::HttpHeaderBlock headers;
346 headers[spdy::kHttp2StatusHeader] = "200";
347 headers[spdy::kHttp2MethodHeader] = "GET";
348 headers["Content-Length"] = "5";
349 headers[":fake"] = "ignored";
350 ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
351 EXPECT_EQ(kRawHeaders, ToSimpleString(output));
352 }
353
TEST_P(SpdyHeadersToHttpResponseHeadersTest,DoubleEmptyLocationHeader)354 TEST_P(SpdyHeadersToHttpResponseHeadersTest, DoubleEmptyLocationHeader) {
355 constexpr char kRawHeaders[] =
356 "HTTP/1.1 200\n"
357 "location: \n"
358 "location: \n";
359 quiche::HttpHeaderBlock headers;
360 headers[spdy::kHttp2StatusHeader] = "200";
361 headers.AppendValueOrAddHeader("location", "");
362 headers.AppendValueOrAddHeader("location", "");
363 ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
364 EXPECT_EQ(kRawHeaders, ToSimpleString(output));
365 }
366
TEST_P(SpdyHeadersToHttpResponseHeadersTest,DifferentLocationHeaderTriggersError)367 TEST_P(SpdyHeadersToHttpResponseHeadersTest,
368 DifferentLocationHeaderTriggersError) {
369 quiche::HttpHeaderBlock headers;
370 headers[spdy::kHttp2StatusHeader] = "200";
371 headers.AppendValueOrAddHeader("location", "https://same/");
372 headers.AppendValueOrAddHeader("location", "https://same/");
373 headers.AppendValueOrAddHeader("location", "https://different/");
374 EXPECT_THAT(PerformConversion(headers),
375 base::test::ErrorIs(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION));
376 }
377
378 // TODO(ricea): Ensure that QUICHE will never send us header values with leading
379 // or trailing whitespace and remove this test.
TEST_P(SpdyHeadersToHttpResponseHeadersTest,LocationEquivalenceIgnoresSurroundingSpace)380 TEST_P(SpdyHeadersToHttpResponseHeadersTest,
381 LocationEquivalenceIgnoresSurroundingSpace) {
382 constexpr char kRawHeaders[] =
383 "HTTP/1.1 200\n"
384 "location: https://same/\n"
385 "location: https://same/\n";
386 quiche::HttpHeaderBlock headers;
387 headers[spdy::kHttp2StatusHeader] = "200";
388 headers.AppendValueOrAddHeader("location", " https://same/");
389 headers.AppendValueOrAddHeader("location", "https://same/ ");
390 ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
391 EXPECT_EQ(kRawHeaders, ToSimpleString(output));
392 }
393
394 INSTANTIATE_TEST_SUITE_P(
395 SpdyHttpUtils,
396 SpdyHeadersToHttpResponseHeadersTest,
397 Values(SpdyHeadersToHttpResponseHeadersUsingRawString,
398 SpdyHeadersToHttpResponseHeadersUsingBuilder),
399 [](const testing::TestParamInfo<
__anond71fb3520202(const testing::TestParamInfo< SpdyHeadersToHttpResponseHeadersTest::ParamType>& info) 400 SpdyHeadersToHttpResponseHeadersTest::ParamType>& info) {
401 return info.param == SpdyHeadersToHttpResponseHeadersUsingRawString
402 ? "SpdyHeadersToHttpResponseHeadersUsingRawString"
403 : "SpdyHeadersToHttpResponseHeadersUsingBuilder";
404 });
405
406 } // namespace
407
408 } // namespace net
409