summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-03-27 04:00:22 +0000
committerericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-03-27 04:00:22 +0000
commit0757e770ac9ce685ee0db0179271f1a3dba47cb0 (patch)
tree54e1a153fd38a9f950187929076a1d5d6966357e /net
parent3a2d366b664bb0c13f4427f3ed6a3b6af6e77451 (diff)
downloadchromium_src-0757e770ac9ce685ee0db0179271f1a3dba47cb0.zip
chromium_src-0757e770ac9ce685ee0db0179271f1a3dba47cb0.tar.gz
chromium_src-0757e770ac9ce685ee0db0179271f1a3dba47cb0.tar.bz2
Respect cookies set in a 401 responses when restarting the http transaction.
There are two parts to this change: (1) rebuild the request cookies before each transaction restart for authentication (2) notify the URLRequestHttpJob of header completion before *each* transaction restart for authentication By "each transaction" I mean the automatic restarts that don't require user input, such as: - replying to the first step of NTLM - selecting identity embedded in URL - selecting identity in auth-cache Needing to notify URLRequestHttpJob for these intermediate restarts is a consequence of cookie store management being done outside of HttpNetworkTransaction. After updating the cookie store, URLRequestHttpJob now tests |HttpTransaction::IsReadyToRestartForAuth()| to check whether the notification was informational or an identity is actually needed. R=wtc BUG=6450 Review URL: http://codereview.chromium.org/51004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@12635 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/http/http_auth.h3
-rw-r--r--net/http/http_cache.cc7
-rw-r--r--net/http/http_network_transaction.cc53
-rw-r--r--net/http/http_network_transaction.h20
-rw-r--r--net/http/http_network_transaction_unittest.cc78
-rw-r--r--net/http/http_transaction.h8
-rw-r--r--net/http/http_transaction_unittest.h4
-rw-r--r--net/http/http_util.cc25
-rw-r--r--net/http/http_util.h8
-rw-r--r--net/http/http_util_unittest.cc23
-rw-r--r--net/tools/testserver/testserver.py5
-rw-r--r--net/url_request/url_request.cc22
-rw-r--r--net/url_request/url_request_http_job.cc51
-rw-r--r--net/url_request/url_request_http_job.h4
-rw-r--r--net/url_request/url_request_unittest.cc62
15 files changed, 296 insertions, 77 deletions
diff --git a/net/http/http_auth.h b/net/http/http_auth.h
index 8f594e2..5557694 100644
--- a/net/http/http_auth.h
+++ b/net/http/http_auth.h
@@ -21,6 +21,9 @@ class HttpAuth {
// Http authentication can be done the the proxy server, origin server,
// or both. This enum tracks who the target is.
enum Target {
+ AUTH_NONE = -1,
+ // We depend on the valid targets (!= AUTH_NONE) being usable as indexes
+ // in an array, so start from 0.
AUTH_PROXY = 0,
AUTH_SERVER = 1,
};
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc
index 38a8f23..46bb64c 100644
--- a/net/http/http_cache.cc
+++ b/net/http/http_cache.cc
@@ -166,6 +166,7 @@ class HttpCache::Transaction
virtual int RestartWithAuth(const std::wstring& username,
const std::wstring& password,
CompletionCallback* callback);
+ virtual bool IsReadyToRestartForAuth();
virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback*);
virtual const HttpResponseInfo* GetResponseInfo() const;
virtual LoadState GetLoadState() const;
@@ -402,6 +403,12 @@ int HttpCache::Transaction::RestartWithAuth(
return rv;
}
+bool HttpCache::Transaction::IsReadyToRestartForAuth() {
+ if (!network_trans_.get())
+ return false;
+ return network_trans_->IsReadyToRestartForAuth();
+}
+
int HttpCache::Transaction::Read(IOBuffer* buf, int buf_len,
CompletionCallback* callback) {
DCHECK(buf);
diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc
index fc43066..77a5e19 100644
--- a/net/http/http_network_transaction.cc
+++ b/net/http/http_network_transaction.cc
@@ -35,7 +35,8 @@ namespace net {
HttpNetworkTransaction::HttpNetworkTransaction(HttpNetworkSession* session,
ClientSocketFactory* csf)
- : ALLOW_THIS_IN_INITIALIZER_LIST(
+ : pending_auth_target_(HttpAuth::AUTH_NONE),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
io_callback_(this, &HttpNetworkTransaction::OnIOComplete)),
user_callback_(NULL),
session_(session),
@@ -92,20 +93,24 @@ int HttpNetworkTransaction::RestartWithAuth(
const std::wstring& username,
const std::wstring& password,
CompletionCallback* callback) {
+ HttpAuth::Target target = pending_auth_target_;
+ if (target == HttpAuth::AUTH_NONE) {
+ NOTREACHED();
+ return ERR_UNEXPECTED;
+ }
- DCHECK(NeedAuth(HttpAuth::AUTH_PROXY) ||
- NeedAuth(HttpAuth::AUTH_SERVER));
+ pending_auth_target_ = HttpAuth::AUTH_NONE;
- // Figure out whether this username password is for proxy or server.
- // Proxy gets set first, then server.
- HttpAuth::Target target = NeedAuth(HttpAuth::AUTH_PROXY) ?
- HttpAuth::AUTH_PROXY : HttpAuth::AUTH_SERVER;
+ DCHECK(auth_identity_[target].invalid ||
+ (username.empty() && password.empty()));
- // Update the username/password.
- auth_identity_[target].source = HttpAuth::IDENT_SRC_EXTERNAL;
- auth_identity_[target].invalid = false;
- auth_identity_[target].username = username;
- auth_identity_[target].password = password;
+ if (auth_identity_[target].invalid) {
+ // Update the username/password.
+ auth_identity_[target].source = HttpAuth::IDENT_SRC_EXTERNAL;
+ auth_identity_[target].invalid = false;
+ auth_identity_[target].username = username;
+ auth_identity_[target].password = password;
+ }
PrepareForAuthRestart(target);
@@ -1097,8 +1102,6 @@ int HttpNetworkTransaction::DidReadResponseHeaders() {
}
int rv = HandleAuthChallenge();
- if (rv == WILL_RESTART_TRANSACTION)
- return OK;
if (rv != OK)
return rv;
@@ -1182,6 +1185,7 @@ int HttpNetworkTransaction::HandleIOError(int error) {
}
void HttpNetworkTransaction::ResetStateForRestart() {
+ pending_auth_target_ = HttpAuth::AUTH_NONE;
header_buf_.reset();
header_buf_capacity_ = 0;
header_buf_len_ = 0;
@@ -1473,11 +1477,10 @@ int HttpNetworkTransaction::HandleAuthChallenge() {
return OK;
}
- bool has_identity_to_try;
if (auth_handler_[target]->NeedsIdentity()) {
// Pick a new auth identity to try, by looking to the URL and auth cache.
// If an identity to try is found, it is saved to auth_identity_[target].
- has_identity_to_try = SelectNextAuthIdentityToTry(target);
+ SelectNextAuthIdentityToTry(target);
} else {
// Proceed with a null identity.
//
@@ -1487,19 +1490,17 @@ int HttpNetworkTransaction::HandleAuthChallenge() {
auth_identity_[target].invalid = false;
auth_identity_[target].username.clear();
auth_identity_[target].password.clear();
- has_identity_to_try = true;
}
- DCHECK(has_identity_to_try == !auth_identity_[target].invalid);
- if (has_identity_to_try) {
- DCHECK(user_callback_);
- PrepareForAuthRestart(target);
- return WILL_RESTART_TRANSACTION;
- }
+ // Make a note that we are waiting for auth. This variable is inspected
+ // when the client calls RestartWithAuth() to pick up where we left off.
+ pending_auth_target_ = target;
- // We have exhausted all identity possibilities, all we can do now is
- // pass the challenge information back to the client.
- PopulateAuthChallenge(target);
+ if (auth_identity_[target].invalid) {
+ // We have exhausted all identity possibilities, all we can do now is
+ // pass the challenge information back to the client.
+ PopulateAuthChallenge(target);
+ }
return OK;
}
diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h
index 088a090..939a900 100644
--- a/net/http/http_network_transaction.h
+++ b/net/http/http_network_transaction.h
@@ -41,6 +41,11 @@ class HttpNetworkTransaction : public HttpTransaction {
virtual int RestartWithAuth(const std::wstring& username,
const std::wstring& password,
CompletionCallback* callback);
+ virtual bool IsReadyToRestartForAuth() {
+ return pending_auth_target_ != HttpAuth::AUTH_NONE &&
+ HaveAuth(pending_auth_target_);
+ }
+
virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback);
virtual const HttpResponseInfo* GetResponseInfo() const;
virtual LoadState GetLoadState() const;
@@ -152,10 +157,8 @@ class HttpNetworkTransaction : public HttpTransaction {
void AddAuthorizationHeader(HttpAuth::Target target);
// Handles HTTP status code 401 or 407.
- // HandleAuthChallenge() returns a network error code, or OK, or
- // WILL_RESTART_TRANSACTION. The latter indicates that the state machine has
- // been updated to restart the transaction with a new auth attempt.
- enum { WILL_RESTART_TRANSACTION = 1 };
+ // HandleAuthChallenge() returns a network error code, or OK on success.
+ // May update |pending_auth_target_| or |response_.auth_challenge|.
int HandleAuthChallenge();
// Populates response_.auth_challenge with the challenge information, so that
@@ -177,10 +180,6 @@ class HttpNetworkTransaction : public HttpTransaction {
// auth_handler_[target] with the cache entry's data and returns true.
bool SelectPreemptiveAuth(HttpAuth::Target target);
- bool NeedAuth(HttpAuth::Target target) const {
- return auth_handler_[target].get() && auth_identity_[target].invalid;
- }
-
bool HaveAuth(HttpAuth::Target target) const {
return auth_handler_[target].get() && !auth_identity_[target].invalid;
}
@@ -206,6 +205,11 @@ class HttpNetworkTransaction : public HttpTransaction {
// a number of places (url, cache, prompt).
HttpAuth::Identity auth_identity_[2];
+ // Whether this transaction is waiting for proxy auth, server auth, or is
+ // not waiting for any auth at all. |pending_auth_target_| is read and
+ // cleared by RestartWithAuth().
+ HttpAuth::Target pending_auth_target_;
+
CompletionCallbackImpl<HttpNetworkTransaction> io_callback_;
CompletionCallback* user_callback_;
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index a9fa974..4e8209c 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -1651,7 +1651,7 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth1) {
};
MockWrite data_writes2[] = {
- // After automatically restarting with a null identity, this is the
+ // After restarting with a null identity, this is the
// request we should be issuing -- the final header line contains a Type
// 1 message.
MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
@@ -1715,6 +1715,14 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth1) {
rv = callback1.WaitForResult();
EXPECT_EQ(net::OK, rv);
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+ TestCompletionCallback callback2;
+ rv = trans->RestartWithAuth(std::wstring(), std::wstring(), &callback2);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
const net::HttpResponseInfo* response = trans->GetResponseInfo();
EXPECT_FALSE(response == NULL);
@@ -1726,12 +1734,12 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth1) {
EXPECT_EQ(L"", response->auth_challenge->realm);
EXPECT_EQ(L"ntlm", response->auth_challenge->scheme);
- TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
- rv = trans->RestartWithAuth(L"testing-ntlm", L"testing-ntlm", &callback2);
+ rv = trans->RestartWithAuth(L"testing-ntlm", L"testing-ntlm", &callback3);
EXPECT_EQ(net::ERR_IO_PENDING, rv);
- rv = callback2.WaitForResult();
+ rv = callback3.WaitForResult();
EXPECT_EQ(net::OK, rv);
response = trans->GetResponseInfo();
@@ -1773,7 +1781,7 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth2) {
};
MockWrite data_writes2[] = {
- // After automatically restarting with a null identity, this is the
+ // After restarting with a null identity, this is the
// request we should be issuing -- the final header line contains a Type
// 1 message.
MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
@@ -1824,7 +1832,7 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth2) {
};
MockWrite data_writes3[] = {
- // After automatically restarting with a null identity, this is the
+ // After restarting with a null identity, this is the
// request we should be issuing -- the final header line contains a Type
// 1 message.
MockWrite("GET /kids/login.aspx HTTP/1.1\r\n"
@@ -1892,6 +1900,14 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth2) {
rv = callback1.WaitForResult();
EXPECT_EQ(net::OK, rv);
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+ TestCompletionCallback callback2;
+ rv = trans->RestartWithAuth(std::wstring(), std::wstring(), &callback2);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
const net::HttpResponseInfo* response = trans->GetResponseInfo();
EXPECT_FALSE(response == NULL);
@@ -1903,14 +1919,22 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth2) {
EXPECT_EQ(L"", response->auth_challenge->realm);
EXPECT_EQ(L"ntlm", response->auth_challenge->scheme);
- TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
// Enter the wrong password.
- rv = trans->RestartWithAuth(L"testing-ntlm", L"wrongpassword", &callback2);
+ rv = trans->RestartWithAuth(L"testing-ntlm", L"wrongpassword", &callback3);
EXPECT_EQ(net::ERR_IO_PENDING, rv);
- rv = callback2.WaitForResult();
+ rv = callback3.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+ TestCompletionCallback callback4;
+ rv = trans->RestartWithAuth(std::wstring(), std::wstring(), &callback4);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ rv = callback4.WaitForResult();
EXPECT_EQ(net::OK, rv);
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
response = trans->GetResponseInfo();
EXPECT_FALSE(response == NULL);
@@ -1923,13 +1947,13 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth2) {
EXPECT_EQ(L"", response->auth_challenge->realm);
EXPECT_EQ(L"ntlm", response->auth_challenge->scheme);
- TestCompletionCallback callback3;
+ TestCompletionCallback callback5;
// Now enter the right password.
- rv = trans->RestartWithAuth(L"testing-ntlm", L"testing-ntlm", &callback3);
+ rv = trans->RestartWithAuth(L"testing-ntlm", L"testing-ntlm", &callback5);
EXPECT_EQ(net::ERR_IO_PENDING, rv);
- rv = callback3.WaitForResult();
+ rv = callback5.WaitForResult();
EXPECT_EQ(net::OK, rv);
response = trans->GetResponseInfo();
@@ -2187,6 +2211,14 @@ TEST_F(HttpNetworkTransactionTest, AuthIdentityInUrl) {
rv = callback1.WaitForResult();
EXPECT_EQ(net::OK, rv);
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+ TestCompletionCallback callback2;
+ rv = trans->RestartWithAuth(std::wstring(), std::wstring(), &callback2);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
const net::HttpResponseInfo* response = trans->GetResponseInfo();
EXPECT_FALSE(response == NULL);
@@ -2490,6 +2522,14 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthCacheAndPreauth) {
rv = callback1.WaitForResult();
EXPECT_EQ(net::OK, rv);
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+ TestCompletionCallback callback2;
+ rv = trans->RestartWithAuth(std::wstring(), std::wstring(), &callback2);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
const net::HttpResponseInfo* response = trans->GetResponseInfo();
EXPECT_FALSE(response == NULL);
EXPECT_TRUE(response->auth_challenge.get() == NULL);
@@ -2577,6 +2617,14 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthCacheAndPreauth) {
rv = callback1.WaitForResult();
EXPECT_EQ(net::OK, rv);
+ EXPECT_TRUE(trans->IsReadyToRestartForAuth());
+ TestCompletionCallback callback2;
+ rv = trans->RestartWithAuth(std::wstring(), std::wstring(), &callback2);
+ EXPECT_EQ(net::ERR_IO_PENDING, rv);
+ rv = callback2.WaitForResult();
+ EXPECT_EQ(net::OK, rv);
+ EXPECT_FALSE(trans->IsReadyToRestartForAuth());
+
const net::HttpResponseInfo* response = trans->GetResponseInfo();
EXPECT_FALSE(response == NULL);
@@ -2589,12 +2637,12 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthCacheAndPreauth) {
EXPECT_EQ(L"MyRealm1", response->auth_challenge->realm);
EXPECT_EQ(L"basic", response->auth_challenge->scheme);
- TestCompletionCallback callback2;
+ TestCompletionCallback callback3;
- rv = trans->RestartWithAuth(L"foo3", L"bar3", &callback2);
+ rv = trans->RestartWithAuth(L"foo3", L"bar3", &callback3);
EXPECT_EQ(net::ERR_IO_PENDING, rv);
- rv = callback2.WaitForResult();
+ rv = callback3.WaitForResult();
EXPECT_EQ(net::OK, rv);
response = trans->GetResponseInfo();
diff --git a/net/http/http_transaction.h b/net/http/http_transaction.h
index 2c857ed..cc7dd92 100644
--- a/net/http/http_transaction.h
+++ b/net/http/http_transaction.h
@@ -55,6 +55,14 @@ class HttpTransaction {
const std::wstring& password,
CompletionCallback* callback) = 0;
+ // Returns true if auth is ready to be continued. Callers should check
+ // this value anytime Start() completes: if it is true, the transaction
+ // can be resumed with RestartWithAuth(L"", L"", callback) to resume
+ // the automatic auth exchange. This notification gives the caller a
+ // chance to process the response headers from all of the intermediate
+ // restarts needed for authentication.
+ virtual bool IsReadyToRestartForAuth() = 0;
+
// Once response info is available for the transaction, response data may be
// read by calling this method.
//
diff --git a/net/http/http_transaction_unittest.h b/net/http/http_transaction_unittest.h
index e530423..d0a51b0 100644
--- a/net/http/http_transaction_unittest.h
+++ b/net/http/http_transaction_unittest.h
@@ -240,6 +240,10 @@ class MockNetworkTransaction : public net::HttpTransaction {
return net::ERR_FAILED;
}
+ virtual bool IsReadyToRestartForAuth() {
+ return false;
+ }
+
virtual int Read(net::IOBuffer* buf, int buf_len,
net::CompletionCallback* callback) {
int data_len = static_cast<int>(data_.size());
diff --git a/net/http/http_util.cc b/net/http/http_util.cc
index 7094b03..500222a 100644
--- a/net/http/http_util.cc
+++ b/net/http/http_util.cc
@@ -218,6 +218,31 @@ bool HttpUtil::HasHeader(const std::string& headers, const char* name) {
}
// static
+std::string HttpUtil::StripHeaders(const std::string& headers,
+ const char* const headers_to_remove[],
+ size_t headers_to_remove_len) {
+ std::string stripped_headers;
+ net::HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
+
+ while (it.GetNext()) {
+ bool should_remove = false;
+ for (size_t i = 0; i < headers_to_remove_len; ++i) {
+ if (LowerCaseEqualsASCII(it.name_begin(), it.name_end(),
+ headers_to_remove[i])) {
+ should_remove = true;
+ break;
+ }
+ }
+ if (!should_remove) {
+ // Assume that name and values are on the same line.
+ stripped_headers.append(it.name_begin(), it.values_end());
+ stripped_headers.append("\r\n");
+ }
+ }
+ return stripped_headers;
+}
+
+// static
bool HttpUtil::IsNonCoalescingHeader(string::const_iterator name_begin,
string::const_iterator name_end) {
// NOTE: "set-cookie2" headers do not support expires attributes, so we don't
diff --git a/net/http/http_util.h b/net/http/http_util.h
index 8bf055f..ade6241 100644
--- a/net/http/http_util.h
+++ b/net/http/http_util.h
@@ -47,6 +47,14 @@ class HttpUtil {
// TODO(darin): kill this
static bool HasHeader(const std::string& headers, const char* name);
+ // Strips all header lines from |headers| whose name matches
+ // |headers_to_remove|. |headers_to_remove| is a list of null-terminated
+ // lower-case header names, with array length |headers_to_remove_len|.
+ // Returns the stripped header lines list, separated by "\r\n".
+ static std::string StripHeaders(const std::string& headers,
+ const char* const headers_to_remove[],
+ size_t headers_to_remove_len);
+
// Multiple occurances of some headers cannot be coalesced into a comma-
// separated list since their values are (or contain) unquoted HTTP-date
// values, which may contain a comma (see RFC 2616 section 3.3.1).
diff --git a/net/http/http_util_unittest.cc b/net/http/http_util_unittest.cc
index 098468f..87ae3cc 100644
--- a/net/http/http_util_unittest.cc
+++ b/net/http/http_util_unittest.cc
@@ -34,6 +34,29 @@ TEST(HttpUtilTest, HasHeader) {
}
}
+TEST(HttpUtilTest, StripHeaders) {
+ static const char* headers =
+ "Origin: origin\r\n"
+ "Content-Type: text/plain\r\n"
+ "Cookies: foo1\r\n"
+ "Custom: baz\r\n"
+ "COOKIES: foo2\r\n"
+ "Server: Apache\r\n"
+ "OrIGin: origin2\r\n";
+
+ static const char* header_names[] = {
+ "origin", "content-type", "cookies"
+ };
+
+ static const char* expected_stripped_headers =
+ "Custom: baz\r\n"
+ "Server: Apache\r\n";
+
+ EXPECT_EQ(expected_stripped_headers,
+ HttpUtil::StripHeaders(headers, header_names,
+ arraysize(header_names)));
+}
+
TEST(HttpUtilTest, HeadersIterator) {
std::string headers = "foo: 1\t\r\nbar: hello world\r\nbaz: 3 \r\n";
diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py
index 407694a..5784059 100644
--- a/net/tools/testserver/testserver.py
+++ b/net/tools/testserver/testserver.py
@@ -691,6 +691,8 @@ class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
username = userpass = password = b64str = ""
+ set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
+
auth = self.headers.getheader('authorization')
try:
if not auth:
@@ -705,6 +707,8 @@ class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.send_response(401)
self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
self.send_header('Content-type', 'text/html')
+ if set_cookie_if_challenged:
+ self.send_header('Set-Cookie', 'got_challenged=true')
self.end_headers()
self.wfile.write('<html><head>')
self.wfile.write('<title>Denied: %s</title>' % e)
@@ -734,6 +738,7 @@ class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.wfile.write('<title>%s/%s</title>' % (username, password))
self.wfile.write('</head><body>')
self.wfile.write('auth=%s<p>' % auth)
+ self.wfile.write('You sent:<br>%s<p>' % self.headers)
self.wfile.write('</body></html>')
return True
diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
index 335f974..d6c34f1 100644
--- a/net/url_request/url_request.cc
+++ b/net/url_request/url_request.cc
@@ -352,26 +352,8 @@ std::string URLRequest::StripPostSpecificHeaders(const std::string& headers) {
"content-length",
"origin"
};
-
- std::string stripped_headers;
- net::HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
-
- while (it.GetNext()) {
- bool is_post_specific = false;
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kPostHeaders); ++i) {
- if (LowerCaseEqualsASCII(it.name_begin(), it.name_end(),
- kPostHeaders[i])) {
- is_post_specific = true;
- break;
- }
- }
- if (!is_post_specific) {
- // Assume that name and values are on the same line.
- stripped_headers.append(it.name_begin(), it.values_end());
- stripped_headers.append("\r\n");
- }
- }
- return stripped_headers;
+ return net::HttpUtil::StripHeaders(
+ headers, kPostHeaders, arraysize(kPostHeaders));
}
int URLRequest::Redirect(const GURL& location, int http_status_code) {
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 4180bef..42b8182 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -21,6 +21,7 @@
#include "net/http/http_response_info.h"
#include "net/http/http_transaction.h"
#include "net/http/http_transaction_factory.h"
+#include "net/http/http_util.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_error_job.h"
@@ -299,10 +300,27 @@ void URLRequestHttpJob::SetAuth(const std::wstring& username,
server_auth_state_ = net::AUTH_STATE_HAVE_AUTH;
}
+ RestartTransactionWithAuth(username, password);
+}
+
+void URLRequestHttpJob::RestartTransactionWithAuth(
+ const std::wstring& username,
+ const std::wstring& password) {
+
// These will be reset in OnStartCompleted.
response_info_ = NULL;
response_cookies_.clear();
+ // Update the cookies, since the cookie store may have been updated from the
+ // headers in the 401/407. Since cookies were already appended to
+ // extra_headers by AddExtraHeaders(), we need to strip them out.
+ static const char* const cookie_name[] = { "cookie" };
+ request_info_.extra_headers = net::HttpUtil::StripHeaders(
+ request_info_.extra_headers, cookie_name, arraysize(cookie_name));
+ // TODO(eroman): this ordering is inconsistent with non-restarted request,
+ // where cookies header appears second from the bottom.
+ request_info_.extra_headers += AssembleRequestCookies();
+
// No matter what, we want to report our status as IO pending since we will
// be notifying our consumer asynchronously via OnStartCompleted.
SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
@@ -474,6 +492,15 @@ void URLRequestHttpJob::NotifyHeadersComplete() {
}
}
+ // The HTTP transaction may be restarted several times for the purposes
+ // of sending authorization information. Each time it restarts, we get
+ // notified of the headers completion so that we can update the cookie store.
+ if (transaction_->IsReadyToRestartForAuth()) {
+ DCHECK(!response_info_->auth_challenge.get());
+ RestartTransactionWithAuth(std::wstring(), std::wstring());
+ return;
+ }
+
URLRequestJob::NotifyHeadersComplete();
}
@@ -544,24 +571,32 @@ void URLRequestHttpJob::AddExtraHeaders() {
URLRequestContext* context = request_->context();
if (context) {
+ request_info_.extra_headers += AssembleRequestCookies();
+ if (!context->accept_language().empty())
+ request_info_.extra_headers += "Accept-Language: " +
+ context->accept_language() + "\r\n";
+ if (!context->accept_charset().empty())
+ request_info_.extra_headers += "Accept-Charset: " +
+ context->accept_charset() + "\r\n";
+ }
+}
+
+std::string URLRequestHttpJob::AssembleRequestCookies() {
+ URLRequestContext* context = request_->context();
+ if (context) {
// Add in the cookie header. TODO might we need more than one header?
if (context->cookie_store() &&
context->cookie_policy()->CanGetCookies(request_->url(),
- request_->policy_url())) {
+ request_->policy_url())) {
net::CookieMonster::CookieOptions options;
options.set_include_httponly();
std::string cookies = request_->context()->cookie_store()->
GetCookiesWithOptions(request_->url(), options);
if (!cookies.empty())
- request_info_.extra_headers += "Cookie: " + cookies + "\r\n";
+ return "Cookie: " + cookies + "\r\n";
}
- if (!context->accept_language().empty())
- request_info_.extra_headers += "Accept-Language: " +
- context->accept_language() + "\r\n";
- if (!context->accept_charset().empty())
- request_info_.extra_headers += "Accept-Charset: " +
- context->accept_charset() + "\r\n";
}
+ return std::string();
}
void URLRequestHttpJob::FetchResponseCookies() {
diff --git a/net/url_request/url_request_http_job.h b/net/url_request/url_request_http_job.h
index b165d2e..dbbdfab 100644
--- a/net/url_request/url_request_http_job.h
+++ b/net/url_request/url_request_http_job.h
@@ -63,11 +63,15 @@ class URLRequestHttpJob : public URLRequestJob {
void DestroyTransaction();
void StartTransaction();
void AddExtraHeaders();
+ std::string AssembleRequestCookies();
void FetchResponseCookies();
void OnStartCompleted(int result);
void OnReadCompleted(int result);
+ void RestartTransactionWithAuth(const std::wstring& username,
+ const std::wstring& password);
+
net::HttpRequestInfo request_info_;
scoped_ptr<net::HttpTransaction> transaction_;
const net::HttpResponseInfo* response_info_;
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index e134208..fb4a650 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -21,6 +21,7 @@
#include "base/process_util.h"
#include "base/string_piece.h"
#include "base/string_util.h"
+#include "net/base/cookie_monster.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/net_module.h"
@@ -46,9 +47,12 @@ class URLRequestHttpCacheContext : public URLRequestContext {
http_transaction_factory_ =
new net::HttpCache(net::HttpNetworkLayer::CreateFactory(proxy_service_),
disk_cache::CreateInMemoryCacheBackend(0));
+ // In-memory cookie store.
+ cookie_store_ = new net::CookieMonster();
}
virtual ~URLRequestHttpCacheContext() {
+ delete cookie_store_;
delete http_transaction_factory_;
delete proxy_service_;
}
@@ -931,6 +935,64 @@ TEST_F(URLRequestTest, BasicAuth) {
}
}
+// Check that Set-Cookie headers in 401 responses are respected.
+// http://crbug.com/6450
+TEST_F(URLRequestTest, BasicAuthWithCookies) {
+ scoped_refptr<HTTPTestServer> server =
+ HTTPTestServer::CreateServer(L"", NULL);
+ ASSERT_TRUE(NULL != server.get());
+
+ GURL url_requiring_auth =
+ server->TestServerPage("auth-basic?set-cookie-if-challenged");
+
+ // Request a page that will give a 401 containing a Set-Cookie header.
+ // Verify that when the transaction is restarted, it includes the new cookie.
+ {
+ scoped_refptr<URLRequestContext> context = new URLRequestHttpCacheContext();
+ TestDelegate d;
+ d.set_username(L"user");
+ d.set_password(L"secret");
+
+ URLRequest r(url_requiring_auth, &d);
+ r.set_context(context);
+ r.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user/secret") != std::string::npos);
+
+ // Make sure we sent the cookie in the restarted transaction.
+ EXPECT_TRUE(d.data_received().find("Cookie: got_challenged=true")
+ != std::string::npos);
+ }
+
+ // Same test as above, except this time the restart is initiated earlier
+ // (without user intervention since identity is embedded in the URL).
+ {
+ scoped_refptr<URLRequestContext> context = new URLRequestHttpCacheContext();
+ TestDelegate d;
+
+ GURL::Replacements replacements;
+ std::string username("user2");
+ std::string password("secret");
+ replacements.SetUsernameStr(username);
+ replacements.SetPasswordStr(password);
+ GURL url_with_identity = url_requiring_auth.ReplaceComponents(replacements);
+
+ URLRequest r(url_with_identity, &d);
+ r.set_context(context);
+ r.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user2/secret") != std::string::npos);
+
+ // Make sure we sent the cookie in the restarted transaction.
+ EXPECT_TRUE(d.data_received().find("Cookie: got_challenged=true")
+ != std::string::npos);
+ }
+}
+
// In this test, we do a POST which the server will 302 redirect.
// The subsequent transaction should use GET, and should not send the
// Content-Type header.