summaryrefslogtreecommitdiffstats
path: root/net/http
diff options
context:
space:
mode:
authorwtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-02-18 21:00:32 +0000
committerwtc@chromium.org <wtc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-02-18 21:00:32 +0000
commit2d2697f9ae8f588c104c017209c1e6158ab53c25 (patch)
treea9470736768a765a341455d987440650ad6d3dc0 /net/http
parent2072f00ba9832d7a3185917a70153415e0866dc1 (diff)
downloadchromium_src-2d2697f9ae8f588c104c017209c1e6158ab53c25.zip
chromium_src-2d2697f9ae8f588c104c017209c1e6158ab53c25.tar.gz
chromium_src-2d2697f9ae8f588c104c017209c1e6158ab53c25.tar.bz2
Perform HTTP authentication over a keep-alive connection.
This is required for NTLM authentication. R=eroman BUG=6567,6824 Review URL: http://codereview.chromium.org/21433 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@9964 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/http')
-rw-r--r--net/http/http_network_transaction.cc116
-rw-r--r--net/http/http_network_transaction.h14
-rw-r--r--net/http/http_network_transaction_unittest.cc346
3 files changed, 464 insertions, 12 deletions
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index f7aeff6..71666fc 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -127,9 +127,36 @@ void HttpNetworkTransaction::PrepareForAuthRestart(HttpAuth::Target target) {
auth_identity_[target].username, auth_identity_[target].password,
AuthPath(target));
- next_state_ = STATE_INIT_CONNECTION;
- connection_.set_socket(NULL);
- connection_.Reset();
+ bool keep_alive = false;
+ if (response_.headers->IsKeepAlive()) {
+ // If there is a response body of known length, we need to drain it first.
+ if (content_length_ > 0 || chunked_decoder_.get()) {
+ next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART;
+ read_buf_ = new IOBuffer(kDrainBodyBufferSize); // A bit bucket
+ read_buf_len_ = kDrainBodyBufferSize;
+ return;
+ }
+ if (content_length_ == 0) // No response body to drain.
+ keep_alive = true;
+ // content_length_ is -1 and we're not using chunked encoding. We don't
+ // know the length of the response body, so we can't reuse this connection
+ // even though the server says it's keep-alive.
+ }
+
+ // We don't need to drain the response body, so we act as if we had drained
+ // the response body.
+ DidDrainBodyForAuthRestart(keep_alive);
+}
+
+void HttpNetworkTransaction::DidDrainBodyForAuthRestart(bool keep_alive) {
+ if (keep_alive) {
+ next_state_ = STATE_WRITE_HEADERS;
+ reused_socket_ = true;
+ } else {
+ next_state_ = STATE_INIT_CONNECTION;
+ connection_.set_socket(NULL);
+ connection_.Reset();
+ }
// Reset the other member variables.
ResetStateForRestart();
@@ -379,6 +406,17 @@ int HttpNetworkTransaction::DoLoop(int result) {
rv = DoReadBodyComplete(rv);
TRACE_EVENT_END("http.read_body", request_, request_->url.spec());
break;
+ case STATE_DRAIN_BODY_FOR_AUTH_RESTART:
+ DCHECK(rv == OK);
+ TRACE_EVENT_BEGIN("http.drain_body_for_auth_restart",
+ request_, request_->url.spec());
+ rv = DoDrainBodyForAuthRestart();
+ break;
+ case STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE:
+ rv = DoDrainBodyForAuthRestartComplete(rv);
+ TRACE_EVENT_END("http.drain_body_for_auth_restart",
+ request_, request_->url.spec());
+ break;
default:
NOTREACHED() << "bad state";
rv = ERR_FAILED;
@@ -778,7 +816,7 @@ int HttpNetworkTransaction::DoReadBodyComplete(int result) {
}
}
- // Clean up the HttpConnection if we are done.
+ // Clean up connection_ if we are done.
if (done) {
LogTransactionMetrics();
if (!keep_alive)
@@ -794,6 +832,62 @@ int HttpNetworkTransaction::DoReadBodyComplete(int result) {
return result;
}
+int HttpNetworkTransaction::DoDrainBodyForAuthRestart() {
+ // This method differs from DoReadBody only in the next_state_. So we just
+ // call DoReadBody and override the next_state_. Perhaps there is a more
+ // elegant way for these two methods to share code.
+ int rv = DoReadBody();
+ DCHECK(next_state_ == STATE_READ_BODY_COMPLETE);
+ next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE;
+ return rv;
+}
+
+// TODO(wtc): The first two thirds of this method and the DoReadBodyComplete
+// method are almost the same. Figure out a good way for these two methods
+// to share code.
+int HttpNetworkTransaction::DoDrainBodyForAuthRestartComplete(int result) {
+ bool unfiltered_eof = (result == 0);
+
+ // Filter incoming data if appropriate. FilterBuf may return an error.
+ if (result > 0 && chunked_decoder_.get()) {
+ result = chunked_decoder_->FilterBuf(read_buf_->data(), result);
+ if (result == 0 && !chunked_decoder_->reached_eof()) {
+ // Don't signal completion of the Read call yet or else it'll look like
+ // we received end-of-file. Wait for more data.
+ next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART;
+ return OK;
+ }
+ }
+
+ bool done = false, keep_alive = false;
+ if (result < 0) {
+ // Error while reading the socket.
+ done = true;
+ } else {
+ content_read_ += result;
+ if (unfiltered_eof ||
+ (content_length_ != -1 && content_read_ >= content_length_) ||
+ (chunked_decoder_.get() && chunked_decoder_->reached_eof())) {
+ done = true;
+ keep_alive = response_.headers->IsKeepAlive();
+ // We can't reuse the connection if we read more than the advertised
+ // content length.
+ if (unfiltered_eof ||
+ (content_length_ != -1 && content_read_ > content_length_))
+ keep_alive = false;
+ }
+ }
+
+ if (done) {
+ DidDrainBodyForAuthRestart(keep_alive);
+ } else {
+ // Keep draining.
+ next_state_ = STATE_DRAIN_BODY_FOR_AUTH_RESTART;
+ }
+
+ return OK;
+}
+
void HttpNetworkTransaction::LogTransactionMetrics() const {
base::TimeDelta duration = base::Time::Now() - response_.request_time;
if (60 < duration.InMinutes())
@@ -869,14 +963,6 @@ int HttpNetworkTransaction::DidReadResponseHeaders() {
response_.headers = headers;
response_.vary_data.Init(*request_, *response_.headers);
- int rv = HandleAuthChallenge();
- if (rv == WILL_RESTART_TRANSACTION) {
- DCHECK(next_state_ == STATE_INIT_CONNECTION);
- return OK;
- }
- if (rv != OK)
- return rv;
-
// Figure how to determine EOF:
// For certain responses, we know the content length is always 0.
@@ -901,6 +987,12 @@ int HttpNetworkTransaction::DidReadResponseHeaders() {
}
}
+ int rv = HandleAuthChallenge();
+ if (rv == WILL_RESTART_TRANSACTION)
+ return OK;
+ if (rv != OK)
+ return rv;
+
if (using_ssl_ && !establishing_tunnel_) {
SSLClientSocket* ssl_socket =
reinterpret_cast<SSLClientSocket*>(connection_.socket());
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
index 9a4c619..4ebc89a 100644
--- a/net/http/http_network_transaction.h
+++ b/net/http/http_network_transaction.h
@@ -75,6 +75,8 @@ class HttpNetworkTransaction : public HttpTransaction {
int DoReadHeadersComplete(int result);
int DoReadBody();
int DoReadBodyComplete(int result);
+ int DoDrainBodyForAuthRestart();
+ int DoDrainBodyForAuthRestartComplete(int result);
// Record histogram of latency (first byte sent till last byte received) as
// well as effective bandwidth used.
@@ -126,6 +128,11 @@ class HttpNetworkTransaction : public HttpTransaction {
// Sets up the state machine to restart the transaction with auth.
void PrepareForAuthRestart(HttpAuth::Target target);
+ // Called when we don't need to drain the response body or have drained it.
+ // Resets |connection_| unless |keep_alive| is true, then calls
+ // ResetStateForRestart. Sets |next_state_| appropriately.
+ void DidDrainBodyForAuthRestart(bool keep_alive);
+
// Resets the members of the transaction so it can be restarted.
void ResetStateForRestart();
@@ -245,6 +252,11 @@ class HttpNetworkTransaction : public HttpTransaction {
// Note: |kMaxHeaderBufSize| should be a multiple of |kHeaderBufInitialSize|.
enum { kMaxHeaderBufSize = 32768 }; // 32 kilobytes.
+ // The size in bytes of the buffer we use to drain the response body that
+ // we want to throw away. The response body is typically a small error
+ // page just a few hundred bytes long.
+ enum { kDrainBodyBufferSize = 1024 };
+
// The position where status line starts; -1 if not found yet.
int header_buf_http_offset_;
@@ -281,6 +293,8 @@ class HttpNetworkTransaction : public HttpTransaction {
STATE_READ_HEADERS_COMPLETE,
STATE_READ_BODY,
STATE_READ_BODY_COMPLETE,
+ STATE_DRAIN_BODY_FOR_AUTH_RESTART,
+ STATE_DRAIN_BODY_FOR_AUTH_RESTART_COMPLETE,
STATE_NONE
};
State next_state_;
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index 1e9f6aad..52e299f 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -711,6 +711,352 @@ TEST_F(HttpNetworkTransactionTest, BasicAuth) {
EXPECT_EQ(100, response->headers->GetContentLength());
}
+// Test the request-challenge-retry sequence for basic auth, over a keep-alive
+// connection.
+TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAlive) {
+ scoped_ptr<net::ProxyService> proxy_service(CreateNullProxyService());
+ scoped_ptr<net::HttpTransaction> trans(new net::HttpNetworkTransaction(
+ CreateSession(proxy_service.get()), &mock_socket_factory));
+
+ net::HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 14\r\n\r\n"),
+ MockRead("Unauthorized\r\n"),
+
+ // Lastly, the server responds with the actual content.
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(false, net::OK),
+ };
+
+ MockSocket data1;
+ data1.reads = data_reads1;
+ data1.writes = data_writes1;
+ mock_sockets[0] = &data1;
+ mock_sockets[1] = NULL;
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, &callback1);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+
+ // The password prompt info should have been set in response->auth_challenge.
+ EXPECT_FALSE(response->auth_challenge.get() == NULL);
+
+ // TODO(eroman): this should really include the effective port (80)
+ EXPECT_EQ(L"www.google.com", response->auth_challenge->host);
+ EXPECT_EQ(L"MyRealm1", response->auth_challenge->realm);
+ EXPECT_EQ(L"basic", response->auth_challenge->scheme);
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(L"foo", L"bar", &callback2);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+}
+
+// Test the request-challenge-retry sequence for basic auth, over a keep-alive
+// connection and with no response body to drain.
+TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveNoBody) {
+ scoped_ptr<net::ProxyService> proxy_service(CreateNullProxyService());
+ scoped_ptr<net::HttpTransaction> trans(new net::HttpNetworkTransaction(
+ CreateSession(proxy_service.get()), &mock_socket_factory));
+
+ net::HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // Respond with 5 kb of response body.
+ std::string large_body_string("Unauthorized");
+ large_body_string.append(5 * 1024, ' ');
+ large_body_string.append("\r\n");
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 0\r\n\r\n"),
+
+ // Lastly, the server responds with the actual content.
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(false, net::OK),
+ };
+
+ MockSocket data1;
+ data1.reads = data_reads1;
+ data1.writes = data_writes1;
+ mock_sockets[0] = &data1;
+ mock_sockets[1] = NULL;
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, &callback1);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+
+ // The password prompt info should have been set in response->auth_challenge.
+ EXPECT_FALSE(response->auth_challenge.get() == NULL);
+
+ // TODO(eroman): this should really include the effective port (80)
+ EXPECT_EQ(L"www.google.com", response->auth_challenge->host);
+ EXPECT_EQ(L"MyRealm1", response->auth_challenge->realm);
+ EXPECT_EQ(L"basic", response->auth_challenge->scheme);
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(L"foo", L"bar", &callback2);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+}
+
+// Test the request-challenge-retry sequence for basic auth, over a keep-alive
+// connection and with a large response body to drain.
+TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveLargeBody) {
+ scoped_ptr<net::ProxyService> proxy_service(CreateNullProxyService());
+ scoped_ptr<net::HttpTransaction> trans(new net::HttpNetworkTransaction(
+ CreateSession(proxy_service.get()), &mock_socket_factory));
+
+ net::HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("http://www.google.com/");
+ request.load_flags = 0;
+
+ MockWrite data_writes1[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: keep-alive\r\n"
+ "Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
+ };
+
+ // Respond with 5 kb of response body.
+ std::string large_body_string("Unauthorized");
+ large_body_string.append(5 * 1024, ' ');
+ large_body_string.append("\r\n");
+
+ MockRead data_reads1[] = {
+ MockRead("HTTP/1.1 401 Unauthorized\r\n"),
+ MockRead("WWW-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ // 5134 = 12 + 5 * 1024 + 2
+ MockRead("Content-Length: 5134\r\n\r\n"),
+ MockRead(true, large_body_string.data(), large_body_string.size()),
+
+ // Lastly, the server responds with the actual content.
+ MockRead("HTTP/1.1 200 OK\r\n"),
+ MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"),
+ MockRead("Content-Length: 100\r\n\r\n"),
+ MockRead(false, net::OK),
+ };
+
+ MockSocket data1;
+ data1.reads = data_reads1;
+ data1.writes = data_writes1;
+ mock_sockets[0] = &data1;
+ mock_sockets[1] = NULL;
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, &callback1);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+
+ // The password prompt info should have been set in response->auth_challenge.
+ EXPECT_FALSE(response->auth_challenge.get() == NULL);
+
+ // TODO(eroman): this should really include the effective port (80)
+ EXPECT_EQ(L"www.google.com", response->auth_challenge->host);
+ EXPECT_EQ(L"MyRealm1", response->auth_challenge->realm);
+ EXPECT_EQ(L"basic", response->auth_challenge->scheme);
+
+ TestCompletionCallback callback2;
+
+ rv = trans->RestartWithAuth(L"foo", L"bar", &callback2);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+ EXPECT_TRUE(response->auth_challenge.get() == NULL);
+ EXPECT_EQ(100, response->headers->GetContentLength());
+}
+
+// Test the request-challenge-retry sequence for basic auth, over a keep-alive
+// proxy connection, when setting up an SSL tunnel.
+TEST_F(HttpNetworkTransactionTest, BasicAuthProxyKeepAlive) {
+ // Configure against proxy server "myproxy:70".
+ scoped_ptr<net::ProxyService> proxy_service(
+ CreateFixedProxyService("myproxy:70"));
+
+ scoped_refptr<net::HttpNetworkSession> session(
+ CreateSession(proxy_service.get()));
+
+ scoped_ptr<net::HttpTransaction> trans(new net::HttpNetworkTransaction(
+ session.get(), &mock_socket_factory));
+
+ net::HttpRequestInfo request;
+ request.method = "GET";
+ request.url = GURL("https://www.google.com/");
+ request.load_flags = 0;
+
+ // Since we have proxy, should try to establish tunnel.
+ MockWrite data_writes1[] = {
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n\r\n"),
+
+ // After calling trans->RestartWithAuth(), this is the request we should
+ // be issuing -- the final header line contains the credentials.
+ MockWrite("CONNECT www.google.com:443 HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Proxy-Authorization: Basic Zm9vOmJheg==\r\n\r\n"),
+ };
+
+ // The proxy responds to the connect with a 407, using a persistent
+ // connection.
+ MockRead data_reads1[] = {
+ // No credentials.
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ MockRead("0123456789"),
+
+ // Wrong credentials (wrong password).
+ MockRead("HTTP/1.1 407 Proxy Authentication Required\r\n"),
+ MockRead("Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
+ MockRead("Content-Length: 10\r\n\r\n"),
+ // No response body because the test stops reading here.
+ MockRead(false, net::ERR_UNEXPECTED), // Should not be reached.
+ };
+
+ MockSocket data1;
+ data1.writes = data_writes1;
+ data1.reads = data_reads1;
+ mock_sockets[0] = &data1;
+ mock_sockets[1] = NULL;
+
+ TestCompletionCallback callback1;
+
+ int rv = trans->Start(&request, &callback1);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback1.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ const net::HttpResponseInfo* response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(407, response->headers->response_code());
+ EXPECT_EQ(10, response->headers->GetContentLength());
+ EXPECT_TRUE(net::HttpVersion(1, 1) == response->headers->GetHttpVersion());
+
+ // The password prompt info should have been set in response->auth_challenge.
+ EXPECT_FALSE(response->auth_challenge.get() == NULL);
+
+ // TODO(eroman): this should really include the effective port (80)
+ EXPECT_EQ(L"myproxy:70", response->auth_challenge->host);
+ EXPECT_EQ(L"MyRealm1", response->auth_challenge->realm);
+ EXPECT_EQ(L"basic", response->auth_challenge->scheme);
+
+ TestCompletionCallback callback2;
+
+ // Wrong password (should be "bar").
+ rv = trans->RestartWithAuth(L"foo", L"baz", &callback2);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ response = trans->GetResponseInfo();
+ EXPECT_FALSE(response == NULL);
+
+ EXPECT_TRUE(response->headers->IsKeepAlive());
+ EXPECT_EQ(407, response->headers->response_code());
+ EXPECT_EQ(10, response->headers->GetContentLength());
+ EXPECT_TRUE(net::HttpVersion(1, 1) == response->headers->GetHttpVersion());
+
+ // The password prompt info should have been set in response->auth_challenge.
+ EXPECT_FALSE(response->auth_challenge.get() == NULL);
+
+ // TODO(eroman): this should really include the effective port (80)
+ EXPECT_EQ(L"myproxy:70", response->auth_challenge->host);
+ EXPECT_EQ(L"MyRealm1", response->auth_challenge->realm);
+ EXPECT_EQ(L"basic", response->auth_challenge->scheme);
+}
+
// Test the flow when both the proxy server AND origin server require
// authentication. Again, this uses basic auth for both since that is
// the simplest to mock.