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";