• 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 #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