Implement FTP auth through proxy

BUG=11227

Review URL: https://codereview.chromium.org/12582012

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@192872 0039d316-1c4b-4281-b951-d872f2087c98
parent 1e7565ad
......@@ -42,7 +42,9 @@ bool IsEnclosingPath(const std::string& container, const std::string& path) {
// Debug helper to check that |origin| arguments are properly formed.
void CheckOriginIsValid(const GURL& origin) {
DCHECK(origin.is_valid());
DCHECK(origin.SchemeIs("http") || origin.SchemeIs("https"));
// Note that the scheme may be FTP when we're using a HTTP proxy.
DCHECK(origin.SchemeIs("http") || origin.SchemeIs("https") ||
origin.SchemeIs("ftp"));
DCHECK(origin.GetOrigin() == origin);
}
......
......@@ -30,7 +30,7 @@ URLRequestFtpJob::URLRequestFtpJob(
: URLRequestJob(request, network_delegate),
priority_(DEFAULT_PRIORITY),
pac_request_(NULL),
response_info_(NULL),
http_response_info_(NULL),
read_in_progress_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
ftp_transaction_factory_(ftp_transaction_factory),
......@@ -85,8 +85,8 @@ bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const {
}
void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo* info) {
if (response_info_)
*info = *response_info_;
if (http_response_info_)
*info = *http_response_info_;
}
HostPortPair URLRequestFtpJob::GetSocketAddress() const {
......@@ -194,11 +194,9 @@ void URLRequestFtpJob::StartHttpTransaction() {
DCHECK(!http_transaction_);
// Do not cache FTP responses sent through HTTP proxy.
// Do not send HTTP auth data because this is really FTP.
request_->set_load_flags(request_->load_flags() |
LOAD_DISABLE_CACHE |
LOAD_DO_NOT_SAVE_COOKIES |
LOAD_DO_NOT_SEND_AUTH_DATA |
LOAD_DO_NOT_SEND_COOKIES);
http_request_info_.url = request_->url();
......@@ -235,27 +233,20 @@ void URLRequestFtpJob::OnStartCompleted(int result) {
}
if (result == OK) {
if (http_transaction_)
response_info_ = http_transaction_->GetResponseInfo();
if (http_transaction_) {
http_response_info_ = http_transaction_->GetResponseInfo();
if (http_response_info_->headers->response_code() == 401 ||
http_response_info_->headers->response_code() == 407) {
HandleAuthNeededResponse();
return;
}
}
NotifyHeadersComplete();
} else if (ftp_transaction_ &&
ftp_transaction_->GetResponseInfo()->needs_auth) {
GURL origin = request_->url().GetOrigin();
if (server_auth_ && server_auth_->state == AUTH_STATE_HAVE_AUTH) {
ftp_auth_cache_->Remove(origin, server_auth_->credentials);
} else if (!server_auth_) {
server_auth_ = new AuthData();
}
server_auth_->state = AUTH_STATE_NEED_AUTH;
FtpAuthCache::Entry* cached_auth = ftp_auth_cache_->Lookup(origin);
if (cached_auth) {
// Retry using cached auth data.
SetAuth(cached_auth->credentials);
} else {
// Prompt for a username/password.
NotifyHeadersComplete();
}
HandleAuthNeededResponse();
return;
} else {
NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
}
......@@ -282,17 +273,24 @@ void URLRequestFtpJob::OnReadCompleted(int result) {
}
void URLRequestFtpJob::RestartTransactionWithAuth() {
DCHECK(ftp_transaction_);
DCHECK(server_auth_ && server_auth_->state == AUTH_STATE_HAVE_AUTH);
DCHECK(auth_data_ && auth_data_->state == AUTH_STATE_HAVE_AUTH);
// 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));
int rv = ftp_transaction_->RestartWithAuth(
server_auth_->credentials,
base::Bind(&URLRequestFtpJob::OnStartCompleted,
base::Unretained(this)));
int rv;
if (proxy_info_.is_direct()) {
rv = ftp_transaction_->RestartWithAuth(
auth_data_->credentials,
base::Bind(&URLRequestFtpJob::OnStartCompleted,
base::Unretained(this)));
} else {
rv = http_transaction_->RestartWithAuth(
auth_data_->credentials,
base::Bind(&URLRequestFtpJob::OnStartCompleted,
base::Unretained(this)));
}
if (rv == ERR_IO_PENDING)
return;
......@@ -310,21 +308,18 @@ LoadState URLRequestFtpJob::GetLoadState() const {
}
bool URLRequestFtpJob::NeedsAuth() {
// TODO(phajdan.jr): Implement proxy auth, http://crbug.com/171497 .
if (!ftp_transaction_)
return false;
// Note that we only have to worry about cases where an actual FTP server
// requires auth (and not a proxy), because connecting to FTP via proxy
// effectively means the browser communicates via HTTP, and uses HTTP's
// Proxy-Authenticate protocol when proxy servers require auth.
return server_auth_ && server_auth_->state == AUTH_STATE_NEED_AUTH;
return auth_data_ && auth_data_->state == AUTH_STATE_NEED_AUTH;
}
void URLRequestFtpJob::GetAuthChallengeInfo(
scoped_refptr<AuthChallengeInfo>* result) {
DCHECK((server_auth_ != NULL) &&
(server_auth_->state == AUTH_STATE_NEED_AUTH));
DCHECK(NeedsAuth());
if (http_response_info_) {
*result = http_response_info_->auth_challenge;
return;
}
scoped_refptr<AuthChallengeInfo> auth_info(new AuthChallengeInfo);
auth_info->is_proxy = false;
auth_info->challenger = HostPortPair::FromURL(request_->url());
......@@ -335,20 +330,25 @@ void URLRequestFtpJob::GetAuthChallengeInfo(
}
void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) {
DCHECK(ftp_transaction_);
DCHECK(ftp_transaction_ || http_transaction_);
DCHECK(NeedsAuth());
server_auth_->state = AUTH_STATE_HAVE_AUTH;
server_auth_->credentials = credentials;
ftp_auth_cache_->Add(request_->url().GetOrigin(), server_auth_->credentials);
auth_data_->state = AUTH_STATE_HAVE_AUTH;
auth_data_->credentials = credentials;
if (ftp_transaction_) {
ftp_auth_cache_->Add(request_->url().GetOrigin(),
auth_data_->credentials);
}
RestartTransactionWithAuth();
}
void URLRequestFtpJob::CancelAuth() {
DCHECK(ftp_transaction_);
DCHECK(ftp_transaction_ || http_transaction_);
DCHECK(NeedsAuth());
server_auth_->state = AUTH_STATE_CANCELED;
auth_data_->state = AUTH_STATE_CANCELED;
// Once the auth is cancelled, we proceed with the request as though
// there were no auth. Schedule this for later so that we don't cause
......@@ -392,4 +392,32 @@ bool URLRequestFtpJob::ReadRawData(IOBuffer* buf,
return false;
}
void URLRequestFtpJob::HandleAuthNeededResponse() {
GURL origin = request_->url().GetOrigin();
if (auth_data_) {
if (auth_data_->state == AUTH_STATE_CANCELED) {
NotifyHeadersComplete();
return;
}
if (ftp_transaction_ && auth_data_->state == AUTH_STATE_HAVE_AUTH)
ftp_auth_cache_->Remove(origin, auth_data_->credentials);
} else {
auth_data_ = new AuthData;
}
auth_data_->state = AUTH_STATE_NEED_AUTH;
FtpAuthCache::Entry* cached_auth = NULL;
if (ftp_transaction_ && ftp_transaction_->GetResponseInfo()->needs_auth)
cached_auth = ftp_auth_cache_->Lookup(origin);
if (cached_auth) {
// Retry using cached auth data.
SetAuth(cached_auth->credentials);
} else {
// Prompt for a username/password.
NotifyHeadersComplete();
}
}
} // namespace net
......@@ -80,6 +80,8 @@ class NET_EXPORT_PRIVATE URLRequestFtpJob : public URLRequestJob {
int buf_size,
int *bytes_read) OVERRIDE;
void HandleAuthNeededResponse();
RequestPriority priority_;
ProxyInfo proxy_info_;
......@@ -90,11 +92,11 @@ class NET_EXPORT_PRIVATE URLRequestFtpJob : public URLRequestJob {
HttpRequestInfo http_request_info_;
scoped_ptr<HttpTransaction> http_transaction_;
const HttpResponseInfo* response_info_;
const HttpResponseInfo* http_response_info_;
bool read_in_progress_;
scoped_refptr<AuthData> server_auth_;
scoped_refptr<AuthData> auth_data_;
base::WeakPtrFactory<URLRequestFtpJob> weak_factory_;
......
......@@ -242,7 +242,7 @@ TEST_F(URLRequestFtpJobTest, FtpProxyRequest) {
EXPECT_EQ("test.html", request_delegate.data_received());
}
TEST_F(URLRequestFtpJobTest, FtpProxyRequestNeedAuth) {
TEST_F(URLRequestFtpJobTest, FtpProxyRequestNeedProxyAuthNoCredentials) {
MockWrite writes[] = {
MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
"Host: ftp.example.com\r\n"
......@@ -271,10 +271,201 @@ TEST_F(URLRequestFtpJobTest, FtpProxyRequestNeedAuth) {
EXPECT_TRUE(url_request.status().is_success());
EXPECT_EQ(1, network_delegate()->completed_requests());
EXPECT_EQ(0, network_delegate()->error_count());
EXPECT_FALSE(request_delegate.auth_required_called());
EXPECT_TRUE(request_delegate.auth_required_called());
EXPECT_EQ("test.html", request_delegate.data_received());
}
TEST_F(URLRequestFtpJobTest, FtpProxyRequestNeedProxyAuthWithCredentials) {
MockWrite writes[] = {
MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
"Host: ftp.example.com\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite(ASYNC, 5, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
"Host: ftp.example.com\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic bXl1c2VyOm15cGFzcw==\r\n\r\n"),
};
MockRead reads[] = {
// No credentials.
MockRead(ASYNC, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead(ASYNC, 2, "Proxy-Authenticate: Basic "
"realm=\"MyRealm1\"\r\n"),
MockRead(ASYNC, 3, "Content-Length: 9\r\n\r\n"),
MockRead(ASYNC, 4, "test.html"),
// Second response.
MockRead(ASYNC, 6, "HTTP/1.1 200 OK\r\n"),
MockRead(ASYNC, 7, "Content-Length: 10\r\n\r\n"),
MockRead(ASYNC, 8, "test2.html"),
};
AddSocket(reads, arraysize(reads), writes, arraysize(writes));
TestDelegate request_delegate;
request_delegate.set_credentials(
AuthCredentials(ASCIIToUTF16("myuser"), ASCIIToUTF16("mypass")));
URLRequest url_request(GURL("ftp://ftp.example.com/"),
&request_delegate,
request_context(),
network_delegate());
url_request.Start();
ASSERT_TRUE(url_request.is_pending());
socket_data(0)->RunFor(9);
EXPECT_TRUE(url_request.status().is_success());
EXPECT_EQ(1, network_delegate()->completed_requests());
EXPECT_EQ(0, network_delegate()->error_count());
EXPECT_TRUE(request_delegate.auth_required_called());
EXPECT_EQ("test2.html", request_delegate.data_received());
}
TEST_F(URLRequestFtpJobTest, FtpProxyRequestNeedServerAuthNoCredentials) {
MockWrite writes[] = {
MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
"Host: ftp.example.com\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
MockRead reads[] = {
// No credentials.
MockRead(ASYNC, 1, "HTTP/1.1 401 Unauthorized\r\n"),
MockRead(ASYNC, 2, "WWW-Authenticate: Basic "
"realm=\"MyRealm1\"\r\n"),
MockRead(ASYNC, 3, "Content-Length: 9\r\n\r\n"),
MockRead(ASYNC, 4, "test.html"),
};
AddSocket(reads, arraysize(reads), writes, arraysize(writes));
TestDelegate request_delegate;
URLRequest url_request(GURL("ftp://ftp.example.com/"),
&request_delegate,
request_context(),
network_delegate());
url_request.Start();
ASSERT_TRUE(url_request.is_pending());
socket_data(0)->RunFor(5);
EXPECT_TRUE(url_request.status().is_success());
EXPECT_EQ(1, network_delegate()->completed_requests());
EXPECT_EQ(0, network_delegate()->error_count());
EXPECT_TRUE(request_delegate.auth_required_called());
EXPECT_EQ("test.html", request_delegate.data_received());
}
TEST_F(URLRequestFtpJobTest, FtpProxyRequestNeedServerAuthWithCredentials) {
MockWrite writes[] = {
MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
"Host: ftp.example.com\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite(ASYNC, 5, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
"Host: ftp.example.com\r\n"
"Proxy-Connection: keep-alive\r\n"
"Authorization: Basic bXl1c2VyOm15cGFzcw==\r\n\r\n"),
};
MockRead reads[] = {
// No credentials.
MockRead(ASYNC, 1, "HTTP/1.1 401 Unauthorized\r\n"),
MockRead(ASYNC, 2, "WWW-Authenticate: Basic "
"realm=\"MyRealm1\"\r\n"),
MockRead(ASYNC, 3, "Content-Length: 9\r\n\r\n"),
MockRead(ASYNC, 4, "test.html"),
// Second response.
MockRead(ASYNC, 6, "HTTP/1.1 200 OK\r\n"),
MockRead(ASYNC, 7, "Content-Length: 10\r\n\r\n"),
MockRead(ASYNC, 8, "test2.html"),
};
AddSocket(reads, arraysize(reads), writes, arraysize(writes));
TestDelegate request_delegate;
request_delegate.set_credentials(
AuthCredentials(ASCIIToUTF16("myuser"), ASCIIToUTF16("mypass")));
URLRequest url_request(GURL("ftp://ftp.example.com/"),
&request_delegate,
request_context(),
network_delegate());
url_request.Start();
ASSERT_TRUE(url_request.is_pending());
socket_data(0)->RunFor(9);
EXPECT_TRUE(url_request.status().is_success());
EXPECT_EQ(1, network_delegate()->completed_requests());
EXPECT_EQ(0, network_delegate()->error_count());
EXPECT_TRUE(request_delegate.auth_required_called());
EXPECT_EQ("test2.html", request_delegate.data_received());
}
TEST_F(URLRequestFtpJobTest, FtpProxyRequestNeedProxyAndServerAuth) {
MockWrite writes[] = {
MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
"Host: ftp.example.com\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
MockWrite(ASYNC, 5, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
"Host: ftp.example.com\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic "
"cHJveHl1c2VyOnByb3h5cGFzcw==\r\n\r\n"),
MockWrite(ASYNC, 10, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
"Host: ftp.example.com\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic "
"cHJveHl1c2VyOnByb3h5cGFzcw==\r\n"
"Authorization: Basic bXl1c2VyOm15cGFzcw==\r\n\r\n"),
};
MockRead reads[] = {
// No credentials.
MockRead(ASYNC, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead(ASYNC, 2, "Proxy-Authenticate: Basic "
"realm=\"MyRealm1\"\r\n"),
MockRead(ASYNC, 3, "Content-Length: 9\r\n\r\n"),
MockRead(ASYNC, 4, "test.html"),
// Second response.
MockRead(ASYNC, 6, "HTTP/1.1 401 Unauthorized\r\n"),
MockRead(ASYNC, 7, "WWW-Authenticate: Basic "
"realm=\"MyRealm1\"\r\n"),
MockRead(ASYNC, 8, "Content-Length: 9\r\n\r\n"),
MockRead(ASYNC, 9, "test.html"),
// Third response.
MockRead(ASYNC, 11, "HTTP/1.1 200 OK\r\n"),
MockRead(ASYNC, 12, "Content-Length: 10\r\n\r\n"),
MockRead(ASYNC, 13, "test2.html"),
};
AddSocket(reads, arraysize(reads), writes, arraysize(writes));
GURL url("ftp://ftp.example.com");
// Make sure cached FTP credentials are not used for proxy authentication.
request_context()->ftp_auth_cache()->Add(
url.GetOrigin(),
AuthCredentials(ASCIIToUTF16("userdonotuse"),
ASCIIToUTF16("passworddonotuse")));
TestDelegate request_delegate;
request_delegate.set_credentials(
AuthCredentials(ASCIIToUTF16("proxyuser"), ASCIIToUTF16("proxypass")));
URLRequest url_request(url,
&request_delegate,
request_context(),
network_delegate());
url_request.Start();
ASSERT_TRUE(url_request.is_pending());
socket_data(0)->RunFor(5);
request_delegate.set_credentials(
AuthCredentials(ASCIIToUTF16("myuser"), ASCIIToUTF16("mypass")));
socket_data(0)->RunFor(9);
EXPECT_TRUE(url_request.status().is_success());
EXPECT_EQ(1, network_delegate()->completed_requests());
EXPECT_EQ(0, network_delegate()->error_count());
EXPECT_TRUE(request_delegate.auth_required_called());
EXPECT_EQ("test2.html", request_delegate.data_received());
}
TEST_F(URLRequestFtpJobTest, FtpProxyRequestDoNotSaveCookies) {
MockWrite writes[] = {
MockWrite(ASYNC, 0, "GET ftp://ftp.example.com/ HTTP/1.1\r\n"
......
......@@ -505,6 +505,7 @@ NetworkDelegate::AuthRequiredResponse TestNetworkDelegate::OnAuthRequired(
EXPECT_TRUE(next_states_[req_id] & kStageAuthRequired) <<
event_order_[req_id];
next_states_[req_id] = kStageBeforeSendHeaders |
kStageAuthRequired | // For example, proxy auth followed by server auth.
kStageHeadersReceived | // Request canceled by delegate simulates empty
// response.
kStageResponseStarted | // data: URLs do not trigger sending headers
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment