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