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