• 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 <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 
TEST(SpdyHttpUtilsTest,ConvertRequestPriorityToSpdy3Priority)50 TEST(SpdyHttpUtilsTest, ConvertRequestPriorityToSpdy3Priority) {
51   EXPECT_EQ(0, ConvertRequestPriorityToSpdyPriority(HIGHEST));
52   EXPECT_EQ(1, ConvertRequestPriorityToSpdyPriority(MEDIUM));
53   EXPECT_EQ(2, ConvertRequestPriorityToSpdyPriority(LOW));
54   EXPECT_EQ(3, ConvertRequestPriorityToSpdyPriority(LOWEST));
55   EXPECT_EQ(4, ConvertRequestPriorityToSpdyPriority(IDLE));
56   EXPECT_EQ(5, ConvertRequestPriorityToSpdyPriority(THROTTLED));
57 }
58 
TEST(SpdyHttpUtilsTest,ConvertSpdy3PriorityToRequestPriority)59 TEST(SpdyHttpUtilsTest, ConvertSpdy3PriorityToRequestPriority) {
60   EXPECT_EQ(HIGHEST, ConvertSpdyPriorityToRequestPriority(0));
61   EXPECT_EQ(MEDIUM, ConvertSpdyPriorityToRequestPriority(1));
62   EXPECT_EQ(LOW, ConvertSpdyPriorityToRequestPriority(2));
63   EXPECT_EQ(LOWEST, ConvertSpdyPriorityToRequestPriority(3));
64   EXPECT_EQ(IDLE, ConvertSpdyPriorityToRequestPriority(4));
65   EXPECT_EQ(THROTTLED, ConvertSpdyPriorityToRequestPriority(5));
66   // These are invalid values, but we should still handle them
67   // gracefully.
68   for (int i = 6; i < std::numeric_limits<uint8_t>::max(); ++i) {
69     EXPECT_EQ(IDLE, ConvertSpdyPriorityToRequestPriority(i));
70   }
71 }
72 
TEST_P(SpdyHttpUtilsTestParam,CreateSpdyHeadersFromHttpRequestHTTP2)73 TEST_P(SpdyHttpUtilsTestParam, CreateSpdyHeadersFromHttpRequestHTTP2) {
74   GURL url("https://www.google.com/index.html");
75   HttpRequestInfo request;
76   request.method = "GET";
77   request.url = url;
78   request.priority_incremental = true;
79   request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, "Chrome/1.1");
80   spdy::Http2HeaderBlock headers;
81   CreateSpdyHeadersFromHttpRequest(request, RequestPriority::HIGHEST,
82                                    request.extra_headers, &headers);
83   EXPECT_EQ("GET", headers[":method"]);
84   EXPECT_EQ("https", headers[":scheme"]);
85   EXPECT_EQ("www.google.com", headers[":authority"]);
86   EXPECT_EQ("/index.html", headers[":path"]);
87   if (base::FeatureList::IsEnabled(net::features::kPriorityHeader)) {
88     EXPECT_EQ("u=0, i", headers[net::kHttp2PriorityHeader]);
89   } else {
90     EXPECT_EQ(headers.end(), headers.find(net::kHttp2PriorityHeader));
91   }
92   EXPECT_EQ(headers.end(), headers.find(":version"));
93   EXPECT_EQ("Chrome/1.1", headers["user-agent"]);
94 }
95 
TEST_P(SpdyHttpUtilsTestParam,CreateSpdyHeadersWithExistingPriority)96 TEST_P(SpdyHttpUtilsTestParam, CreateSpdyHeadersWithExistingPriority) {
97   GURL url("https://www.google.com/index.html");
98   HttpRequestInfo request;
99   request.method = "GET";
100   request.url = url;
101   request.priority_incremental = true;
102   request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, "Chrome/1.1");
103   request.extra_headers.SetHeader(net::kHttp2PriorityHeader,
104                                   "explicit-priority");
105   spdy::Http2HeaderBlock headers;
106   CreateSpdyHeadersFromHttpRequest(request, RequestPriority::HIGHEST,
107                                    request.extra_headers, &headers);
108   EXPECT_EQ("GET", headers[":method"]);
109   EXPECT_EQ("https", headers[":scheme"]);
110   EXPECT_EQ("www.google.com", headers[":authority"]);
111   EXPECT_EQ("/index.html", headers[":path"]);
112   EXPECT_EQ("explicit-priority", headers[net::kHttp2PriorityHeader]);
113   EXPECT_EQ(headers.end(), headers.find(":version"));
114   EXPECT_EQ("Chrome/1.1", headers["user-agent"]);
115 }
116 
TEST(SpdyHttpUtilsTest,CreateSpdyHeadersFromHttpRequestConnectHTTP2)117 TEST(SpdyHttpUtilsTest, CreateSpdyHeadersFromHttpRequestConnectHTTP2) {
118   GURL url("https://www.google.com/index.html");
119   HttpRequestInfo request;
120   request.method = "CONNECT";
121   request.url = url;
122   request.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, "Chrome/1.1");
123   spdy::Http2HeaderBlock headers;
124   CreateSpdyHeadersFromHttpRequest(request, RequestPriority::DEFAULT_PRIORITY,
125                                    request.extra_headers, &headers);
126   EXPECT_EQ("CONNECT", headers[":method"]);
127   EXPECT_TRUE(headers.end() == headers.find(":scheme"));
128   EXPECT_EQ("www.google.com:443", headers[":authority"]);
129   EXPECT_EQ(headers.end(), headers.find(":path"));
130   EXPECT_EQ(headers.end(), headers.find(":scheme"));
131   EXPECT_TRUE(headers.end() == headers.find(":version"));
132   EXPECT_EQ("Chrome/1.1", headers["user-agent"]);
133 }
134 
135 constexpr auto ToSimpleString = test::HttpResponseHeadersToSimpleString;
136 
137 enum class SpdyHeadersToHttpResponseHeadersFeatureConfig {
138   kUseRawString,
139   kUseBuilder
140 };
141 
PrintToString(SpdyHeadersToHttpResponseHeadersFeatureConfig config)142 std::string PrintToString(
143     SpdyHeadersToHttpResponseHeadersFeatureConfig config) {
144   switch (config) {
145     case SpdyHeadersToHttpResponseHeadersFeatureConfig::kUseRawString:
146       return "RawString";
147 
148     case SpdyHeadersToHttpResponseHeadersFeatureConfig::kUseBuilder:
149       return "UseBuilder";
150   }
151 }
152 
153 class SpdyHeadersToHttpResponseTest
154     : public ::testing::TestWithParam<
155           SpdyHeadersToHttpResponseHeadersFeatureConfig> {
156  public:
SpdyHeadersToHttpResponseTest()157   SpdyHeadersToHttpResponseTest() {
158     switch (GetParam()) {
159       case SpdyHeadersToHttpResponseHeadersFeatureConfig::kUseRawString:
160         feature_list_.InitWithFeatures(
161             {}, {features::kSpdyHeadersToHttpResponseUseBuilder});
162         break;
163 
164       case SpdyHeadersToHttpResponseHeadersFeatureConfig::kUseBuilder:
165         feature_list_.InitWithFeatures(
166             {features::kSpdyHeadersToHttpResponseUseBuilder}, {});
167         break;
168     }
169   }
170 
171  private:
172   base::test::ScopedFeatureList feature_list_;
173 };
174 
175 // This test behaves the same regardless of which features are enabled.
TEST_P(SpdyHeadersToHttpResponseTest,SpdyHeadersToHttpResponse)176 TEST_P(SpdyHeadersToHttpResponseTest, SpdyHeadersToHttpResponse) {
177   constexpr char kExpectedSimpleString[] =
178       "HTTP/1.1 200\n"
179       "content-type: text/html\n"
180       "cache-control: no-cache, no-store\n"
181       "set-cookie: test_cookie=1234567890; Max-Age=3600; Secure; HttpOnly\n"
182       "set-cookie: session_id=abcdefghijklmnopqrstuvwxyz; Path=/; HttpOnly\n";
183   spdy::Http2HeaderBlock input;
184   input[spdy::kHttp2StatusHeader] = "200";
185   input["content-type"] = "text/html";
186   input["cache-control"] = "no-cache, no-store";
187   input.AppendValueOrAddHeader(
188       "set-cookie", "test_cookie=1234567890; Max-Age=3600; Secure; HttpOnly");
189   input.AppendValueOrAddHeader(
190       "set-cookie", "session_id=abcdefghijklmnopqrstuvwxyz; Path=/; HttpOnly");
191 
192   net::HttpResponseInfo output;
193   output.remote_endpoint = {{127, 0, 0, 1}, 80};
194 
195   EXPECT_EQ(OK, SpdyHeadersToHttpResponse(input, &output));
196 
197   // This should be set.
198   EXPECT_TRUE(output.was_fetched_via_spdy);
199 
200   // This should be untouched.
201   EXPECT_EQ(output.remote_endpoint, IPEndPoint({127, 0, 0, 1}, 80));
202 
203   EXPECT_EQ(kExpectedSimpleString, ToSimpleString(output.headers));
204 }
205 
206 INSTANTIATE_TEST_SUITE_P(
207     SpdyHttpUtils,
208     SpdyHeadersToHttpResponseTest,
209     Values(SpdyHeadersToHttpResponseHeadersFeatureConfig::kUseRawString,
210            SpdyHeadersToHttpResponseHeadersFeatureConfig::kUseBuilder),
211     ::testing::PrintToStringParamName());
212 
213 // TODO(ricea): Once SpdyHeadersToHttpResponseHeadersUsingRawString has been
214 // removed, remove the parameterization and make these into
215 // SpdyHeadersToHttpResponse tests.
216 
217 using SpdyHeadersToHttpResponseHeadersFunctionPtrType =
218     base::expected<scoped_refptr<HttpResponseHeaders>, int> (*)(
219         const spdy::Http2HeaderBlock&);
220 
221 class SpdyHeadersToHttpResponseHeadersTest
222     : public testing::TestWithParam<
223           SpdyHeadersToHttpResponseHeadersFunctionPtrType> {
224  public:
PerformConversion(const spdy::Http2HeaderBlock & headers)225   base::expected<scoped_refptr<HttpResponseHeaders>, int> PerformConversion(
226       const spdy::Http2HeaderBlock& headers) {
227     return GetParam()(headers);
228   }
229 };
230 
TEST_P(SpdyHeadersToHttpResponseHeadersTest,NoStatus)231 TEST_P(SpdyHeadersToHttpResponseHeadersTest, NoStatus) {
232   spdy::Http2HeaderBlock headers;
233   EXPECT_THAT(PerformConversion(headers),
234               base::test::ErrorIs(ERR_INCOMPLETE_HTTP2_HEADERS));
235 }
236 
TEST_P(SpdyHeadersToHttpResponseHeadersTest,EmptyStatus)237 TEST_P(SpdyHeadersToHttpResponseHeadersTest, EmptyStatus) {
238   constexpr char kRawHeaders[] = "HTTP/1.1 200\n";
239   spdy::Http2HeaderBlock headers;
240   headers[":status"] = "";
241   ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
242   EXPECT_EQ(kRawHeaders, ToSimpleString(output));
243 }
244 
TEST_P(SpdyHeadersToHttpResponseHeadersTest,Plain200)245 TEST_P(SpdyHeadersToHttpResponseHeadersTest, Plain200) {
246   // ":status" does not appear as a header in the output.
247   constexpr char kRawHeaders[] = "HTTP/1.1 200\n";
248   spdy::Http2HeaderBlock headers;
249   headers[spdy::kHttp2StatusHeader] = "200";
250   ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
251   EXPECT_EQ(kRawHeaders, ToSimpleString(output));
252 }
253 
TEST_P(SpdyHeadersToHttpResponseHeadersTest,MultipleLocation)254 TEST_P(SpdyHeadersToHttpResponseHeadersTest, MultipleLocation) {
255   spdy::Http2HeaderBlock headers;
256   headers[spdy::kHttp2StatusHeader] = "304";
257   headers["Location"] = "https://example.com/1";
258   headers.AppendValueOrAddHeader("location", "https://example.com/2");
259   EXPECT_THAT(PerformConversion(headers),
260               base::test::ErrorIs(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION));
261 }
262 
TEST_P(SpdyHeadersToHttpResponseHeadersTest,SpacesAmongValues)263 TEST_P(SpdyHeadersToHttpResponseHeadersTest, SpacesAmongValues) {
264   constexpr char kRawHeaders[] =
265       "HTTP/1.1 200\n"
266       "spaces: foo  ,   bar\n";
267   spdy::Http2HeaderBlock headers;
268   headers[spdy::kHttp2StatusHeader] = "200";
269   headers["spaces"] = "foo  ,   bar";
270   ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
271   EXPECT_EQ(kRawHeaders, ToSimpleString(output));
272 }
273 
TEST_P(SpdyHeadersToHttpResponseHeadersTest,RepeatedHeader)274 TEST_P(SpdyHeadersToHttpResponseHeadersTest, RepeatedHeader) {
275   constexpr char kRawHeaders[] =
276       "HTTP/1.1 200\n"
277       "name: value1\n"
278       "name: value2\n";
279   spdy::Http2HeaderBlock headers;
280   headers[spdy::kHttp2StatusHeader] = "200";
281   headers.AppendValueOrAddHeader("name", "value1");
282   headers.AppendValueOrAddHeader("name", "value2");
283   ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
284   EXPECT_EQ(kRawHeaders, ToSimpleString(output));
285 }
286 
TEST_P(SpdyHeadersToHttpResponseHeadersTest,EmptyValue)287 TEST_P(SpdyHeadersToHttpResponseHeadersTest, EmptyValue) {
288   constexpr char kRawHeaders[] =
289       "HTTP/1.1 200\n"
290       "empty: \n";
291   spdy::Http2HeaderBlock headers;
292   headers[spdy::kHttp2StatusHeader] = "200";
293   headers.AppendValueOrAddHeader("empty", "");
294   ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
295   EXPECT_EQ(kRawHeaders, ToSimpleString(output));
296 }
297 
TEST_P(SpdyHeadersToHttpResponseHeadersTest,PseudoHeadersAreDropped)298 TEST_P(SpdyHeadersToHttpResponseHeadersTest, PseudoHeadersAreDropped) {
299   constexpr char kRawHeaders[] =
300       "HTTP/1.1 200\n"
301       "Content-Length: 5\n";
302   spdy::Http2HeaderBlock headers;
303   headers[spdy::kHttp2StatusHeader] = "200";
304   headers[spdy::kHttp2MethodHeader] = "GET";
305   headers["Content-Length"] = "5";
306   headers[":fake"] = "ignored";
307   ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
308   EXPECT_EQ(kRawHeaders, ToSimpleString(output));
309 }
310 
TEST_P(SpdyHeadersToHttpResponseHeadersTest,DoubleEmptyLocationHeader)311 TEST_P(SpdyHeadersToHttpResponseHeadersTest, DoubleEmptyLocationHeader) {
312   constexpr char kRawHeaders[] =
313       "HTTP/1.1 200\n"
314       "location: \n"
315       "location: \n";
316   spdy::Http2HeaderBlock headers;
317   headers[spdy::kHttp2StatusHeader] = "200";
318   headers.AppendValueOrAddHeader("location", "");
319   headers.AppendValueOrAddHeader("location", "");
320   ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
321   EXPECT_EQ(kRawHeaders, ToSimpleString(output));
322 }
323 
TEST_P(SpdyHeadersToHttpResponseHeadersTest,DifferentLocationHeaderTriggersError)324 TEST_P(SpdyHeadersToHttpResponseHeadersTest,
325        DifferentLocationHeaderTriggersError) {
326   spdy::Http2HeaderBlock headers;
327   headers[spdy::kHttp2StatusHeader] = "200";
328   headers.AppendValueOrAddHeader("location", "https://same/");
329   headers.AppendValueOrAddHeader("location", "https://same/");
330   headers.AppendValueOrAddHeader("location", "https://different/");
331   EXPECT_THAT(PerformConversion(headers),
332               base::test::ErrorIs(ERR_RESPONSE_HEADERS_MULTIPLE_LOCATION));
333 }
334 
335 // TODO(ricea): Ensure that QUICHE will never send us header values with leading
336 // or trailing whitespace and remove this test.
TEST_P(SpdyHeadersToHttpResponseHeadersTest,LocationEquivalenceIgnoresSurroundingSpace)337 TEST_P(SpdyHeadersToHttpResponseHeadersTest,
338        LocationEquivalenceIgnoresSurroundingSpace) {
339   constexpr char kRawHeaders[] =
340       "HTTP/1.1 200\n"
341       "location: https://same/\n"
342       "location: https://same/\n";
343   spdy::Http2HeaderBlock headers;
344   headers[spdy::kHttp2StatusHeader] = "200";
345   headers.AppendValueOrAddHeader("location", " https://same/");
346   headers.AppendValueOrAddHeader("location", "https://same/ ");
347   ASSERT_OK_AND_ASSIGN(const auto output, PerformConversion(headers));
348   EXPECT_EQ(kRawHeaders, ToSimpleString(output));
349 }
350 
351 INSTANTIATE_TEST_SUITE_P(
352     SpdyHttpUtils,
353     SpdyHeadersToHttpResponseHeadersTest,
354     Values(SpdyHeadersToHttpResponseHeadersUsingRawString,
355            SpdyHeadersToHttpResponseHeadersUsingBuilder),
356     [](const testing::TestParamInfo<
__anonadee05060202(const testing::TestParamInfo< SpdyHeadersToHttpResponseHeadersTest::ParamType>& info) 357         SpdyHeadersToHttpResponseHeadersTest::ParamType>& info) {
358       return info.param == SpdyHeadersToHttpResponseHeadersUsingRawString
359                  ? "SpdyHeadersToHttpResponseHeadersUsingRawString"
360                  : "SpdyHeadersToHttpResponseHeadersUsingBuilder";
361     });
362 
363 }  // namespace
364 
365 }  // namespace net
366