Prevent CancelAuth from doing things twice.
Previously, CancelAuth would cause the URLRequestHttpJob to resume at a
point earlier than where the caller was informed of the auth challenge
resulting in doing a number of things twice (Setting cookies, calling
into the network delegate, logging a histogram, setting various
headers received times).
This CL fixes that, causing the URLRequestHttpJob to resume at the step
right after it informed the called of the auth challenge.
Bug: 971836
Change-Id: I8db5f759075799ed338ac0ce8dcc941c9d07e777
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1649078
Reviewed-by: Asanka Herath <[email protected]>
Commit-Queue: Matt Menke <[email protected]>
Cr-Commit-Position: refs/heads/master@{#668126}
diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
index d11742c..f5711c7 100644
--- a/net/url_request/url_request.cc
+++ b/net/url_request/url_request.cc
@@ -862,6 +862,10 @@
if (status.status() != URLRequestStatus::SUCCESS)
set_status(status);
+ // |status_| should not be ERR_IO_PENDING when calling into the
+ // URLRequest::Delegate().
+ DCHECK(!status_.is_io_pending());
+
int net_error = OK;
if (!status_.is_success())
net_error = status_.error();
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 80c73f7..8d6d3f13 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -1236,7 +1236,6 @@
}
void URLRequestHttpJob::CancelAuth() {
- // Proxy gets set first, then WWW.
if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) {
proxy_auth_state_ = AUTH_STATE_CANCELED;
} else {
@@ -1244,26 +1243,18 @@
server_auth_state_ = AUTH_STATE_CANCELED;
}
- // These will be reset in OnStartCompleted.
- response_info_ = nullptr;
- receive_headers_end_ = base::TimeTicks::Now();
- // TODO(davidben,mmenke): We should either reset override_response_headers_
- // here or not call NotifyHeadersReceived a second time on the same response
- // headers. See https://crbug.com/810063.
+ // The above lines should ensure this is the case.
+ DCHECK(!NeedsAuth());
- ResetTimer();
-
- // OK, let the consumer read the error page...
+ // Let the consumer read the HTTP error page. NeedsAuth() should now return
+ // false, so NotifyHeadersComplete() should not request auth from the client
+ // again.
//
- // Because we set the AUTH_STATE_CANCELED flag, NeedsAuth will return false,
- // which will cause the consumer to receive OnResponseStarted instead of
- // OnAuthRequired.
- //
- // We have to do this via InvokeLater to avoid "recursing" the consumer.
- //
+ // Have to do this via PostTask to avoid re-entrantly calling into the
+ // consumer.
base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(&URLRequestHttpJob::OnStartCompleted,
- weak_factory_.GetWeakPtr(), OK));
+ FROM_HERE, base::BindOnce(&URLRequestHttpJob::NotifyFinalHeadersReceived,
+ weak_factory_.GetWeakPtr()));
}
void URLRequestHttpJob::ContinueWithCertificate(
diff --git a/net/url_request/url_request_job.cc b/net/url_request/url_request_job.cc
index 3a7efea..18d56f1e 100644
--- a/net/url_request/url_request_job.cc
+++ b/net/url_request/url_request_job.cc
@@ -433,6 +433,22 @@
}
}
+ NotifyFinalHeadersReceived();
+ // |this| may be destroyed at this point.
+}
+
+void URLRequestJob::NotifyFinalHeadersReceived() {
+ DCHECK(!NeedsAuth() || !GetAuthChallengeInfo());
+
+ if (has_handled_response_)
+ return;
+
+ // While the request's status is normally updated in NotifyHeadersComplete(),
+ // URLRequestHttpJob::CancelAuth() posts a task to invoke this method
+ // directly, which bypasses that logic.
+ if (request_->status().is_io_pending())
+ request_->set_status(URLRequestStatus());
+
has_handled_response_ = true;
if (request_->status().is_success()) {
DCHECK(!source_stream_);
@@ -463,7 +479,6 @@
}
request_->NotifyResponseStarted(URLRequestStatus());
-
// |this| may be destroyed at this point.
}
diff --git a/net/url_request/url_request_job.h b/net/url_request/url_request_job.h
index 5e8e307..df3eda2 100644
--- a/net/url_request/url_request_job.h
+++ b/net/url_request/url_request_job.h
@@ -276,6 +276,10 @@
// Notifies the job that headers have been received.
void NotifyHeadersComplete();
+ // Called when the final set headers have been received (no more redirects to
+ // follow, and no more auth challenges that will be responded to).
+ void NotifyFinalHeadersReceived();
+
// Notifies the request that a start error has occurred.
void NotifyStartError(const URLRequestStatus& status);
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index ecea7cb..3b23b56 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -43,6 +43,7 @@
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/test/bind_test_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_task_runner_handle.h"
@@ -290,6 +291,19 @@
}
#endif
+CookieList GetAllCookies(URLRequestContext* request_context) {
+ CookieList cookie_list;
+ base::RunLoop run_loop;
+ request_context->cookie_store()->GetAllCookiesAsync(
+ base::BindLambdaForTesting([&](const CookieList& cookies,
+ const CookieStatusList& excluded_list) {
+ cookie_list = cookies;
+ run_loop.Quit();
+ }));
+ run_loop.Run();
+ return cookie_list;
+}
+
void TestLoadTimingCacheHitNoNetwork(
const LoadTimingInfo& load_timing_info) {
EXPECT_FALSE(load_timing_info.socket_reused);
@@ -8539,6 +8553,51 @@
}
}
+TEST_F(URLRequestTestHTTP, BasicAuthWithCookiesCancelAuth) {
+ ASSERT_TRUE(http_test_server()->Start());
+
+ GURL url_requiring_auth =
+ http_test_server()->GetURL("/auth-basic?set-cookie-if-challenged");
+
+ // Request a page that will give a 401 containing a Set-Cookie header.
+ // Verify that cookies are set before credentials are provided, and then
+ // cancelling auth does not result in setting the cookies again.
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ TestDelegate d;
+
+ EXPECT_TRUE(GetAllCookies(&context).empty());
+
+ std::unique_ptr<URLRequest> r(context.CreateRequest(
+ url_requiring_auth, DEFAULT_PRIORITY, &d, TRAFFIC_ANNOTATION_FOR_TESTS));
+ r->set_site_for_cookies(url_requiring_auth);
+ r->Start();
+ d.RunUntilAuthRequired();
+
+ // Cookie should have been set.
+ EXPECT_EQ(1, network_delegate.set_cookie_count());
+ CookieList cookies = GetAllCookies(&context);
+ ASSERT_EQ(1u, cookies.size());
+ EXPECT_EQ("got_challenged", cookies[0].Name());
+ EXPECT_EQ("true", cookies[0].Value());
+
+ // Delete cookie.
+ context.cookie_store()->DeleteAllAsync(CookieStore::DeleteCallback());
+
+ // Cancel auth and continue the request.
+ r->CancelAuth();
+ d.RunUntilComplete();
+ ASSERT_TRUE(r->response_headers());
+ EXPECT_EQ(401, r->response_headers()->response_code());
+
+ // Cookie should not have been set again.
+ EXPECT_TRUE(GetAllCookies(&context).empty());
+ EXPECT_EQ(1, network_delegate.set_cookie_count());
+}
+
TEST_F(URLRequestTest, CatchFilteredCookies) {
HttpTestServer test_server;
ASSERT_TRUE(test_server.Start());