Bound the number of RestartWith* calls to HttpNetworkTransaction.
This is a simple mitigation, but not a complete fix, for
crbug.com/488043 and crbug.com/823387. It is intended to improve
robustness against both those sorts of bugs, and also against servers or
proxies which repeatedly request auth for whatever reason.
Although RestartWith* calls are typically associated with a user prompt,
but in other scenarios (remembered preferences, extensions, multi-leg
authentication), they may be triggered automatically. To avoid looping
forever, bound the number of restarts.
This will be followed by the complete fix for crbug.com/823387, but this
is intended to be small enough to hopefully merge.
Bug: 488043, 823387
Change-Id: Ieb0193ba7aad93efe6e7e1e4cf3355c1fb53f811
Reviewed-on: https://chromium-review.googlesource.com/986677
Reviewed-by: Matt Menke <[email protected]>
Commit-Queue: David Benjamin <[email protected]>
Cr-Commit-Position: refs/heads/master@{#547050}
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index a4b3c98..e7e1cd3 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -2690,6 +2690,77 @@
EXPECT_EQ("127.0.0.2:80", endpoint.ToString());
}
+// Test that, if the server requests auth indefinitely, HttpNetworkTransaction
+// will eventually give up.
+TEST_F(HttpNetworkTransactionTest, BasicAuthForever) {
+ HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.example.org/");
+ request.traffic_annotation =
+ net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS);
+
+ TestNetLog log;
+ session_deps_.net_log = &log;
+ std::unique_ptr<HttpNetworkSession> session(CreateSession(&session_deps_));
+ HttpNetworkTransaction trans(DEFAULT_PRIORITY, session.get());
+
+ MockWrite data_writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.example.org\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+ };
+
+ MockRead data_reads[] = {
+ MockRead("HTTP/1.0 401 Unauthorized\r\n"),
+ // Give a couple authenticate options (only the middle one is actually
+ // supported).
+ MockRead("WWW-Authenticate: Basic invalid\r\n"), // Malformed.
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("WWW-Authenticate: UNSUPPORTED realm=\"FOO\"\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ // Large content-length -- won't matter, as connection will be reset.
+ MockRead("Content-Length: 10000\r\n\r\n"),
+ MockRead(SYNCHRONOUS, ERR_FAILED),
+ };
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite data_writes_restart[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.example.org\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes,
+ arraysize(data_writes));
+ session_deps_.socket_factory->AddSocketDataProvider(&data);
+
+ TestCompletionCallback callback;
+ int rv = callback.GetResult(
+ trans.Start(&request, callback.callback(), NetLogWithSource()));
+
+ std::vector<std::unique_ptr<StaticSocketDataProvider>> data_restarts;
+ for (int i = 0; i < 32; i++) {
+ // Check the previous response was a 401.
+ EXPECT_THAT(rv, IsOk());
+ const HttpResponseInfo* response = trans.GetResponseInfo();
+ ASSERT_TRUE(response);
+ EXPECT_TRUE(CheckBasicServerAuth(response->auth_challenge.get()));
+
+ data_restarts.push_back(std::make_unique<StaticSocketDataProvider>(
+ data_reads, arraysize(data_reads), data_writes_restart,
+ arraysize(data_writes_restart)));
+ session_deps_.socket_factory->AddSocketDataProvider(
+ data_restarts.back().get());
+ rv = callback.GetResult(trans.RestartWithAuth(AuthCredentials(kFoo, kBar),
+ callback.callback()));
+ }
+
+ // After too many tries, the transaction should have given up.
+ EXPECT_THAT(rv, IsError(ERR_TOO_MANY_RETRIES));
+}
+
TEST_F(HttpNetworkTransactionTest, DoNotSendAuth) {
HttpRequestInfo request;
request.method = "GET";