diff options
author | ericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-27 04:00:22 +0000 |
---|---|---|
committer | ericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-27 04:00:22 +0000 |
commit | 0757e770ac9ce685ee0db0179271f1a3dba47cb0 (patch) | |
tree | 54e1a153fd38a9f950187929076a1d5d6966357e /net | |
parent | 3a2d366b664bb0c13f4427f3ed6a3b6af6e77451 (diff) | |
download | chromium_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.h | 3 | ||||
-rw-r--r-- | net/http/http_cache.cc | 7 | ||||
-rw-r--r-- | net/http/http_network_transaction.cc | 53 | ||||
-rw-r--r-- | net/http/http_network_transaction.h | 20 | ||||
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 78 | ||||
-rw-r--r-- | net/http/http_transaction.h | 8 | ||||
-rw-r--r-- | net/http/http_transaction_unittest.h | 4 | ||||
-rw-r--r-- | net/http/http_util.cc | 25 | ||||
-rw-r--r-- | net/http/http_util.h | 8 | ||||
-rw-r--r-- | net/http/http_util_unittest.cc | 23 | ||||
-rw-r--r-- | net/tools/testserver/testserver.py | 5 | ||||
-rw-r--r-- | net/url_request/url_request.cc | 22 | ||||
-rw-r--r-- | net/url_request/url_request_http_job.cc | 51 | ||||
-rw-r--r-- | net/url_request/url_request_http_job.h | 4 | ||||
-rw-r--r-- | net/url_request/url_request_unittest.cc | 62 |
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. |