diff options
Diffstat (limited to 'chromium/net/url_request/url_request_ftp_job.cc')
-rw-r--r-- | chromium/net/url_request/url_request_ftp_job.cc | 407 |
1 files changed, 407 insertions, 0 deletions
diff --git a/chromium/net/url_request/url_request_ftp_job.cc b/chromium/net/url_request/url_request_ftp_job.cc new file mode 100644 index 00000000000..eda245ca761 --- /dev/null +++ b/chromium/net/url_request/url_request_ftp_job.cc @@ -0,0 +1,407 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/url_request/url_request_ftp_job.h" + +#include "base/compiler_specific.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "net/base/auth.h" +#include "net/base/host_port_pair.h" +#include "net/base/load_flags.h" +#include "net/base/net_errors.h" +#include "net/base/net_util.h" +#include "net/ftp/ftp_auth_cache.h" +#include "net/ftp/ftp_response_info.h" +#include "net/ftp/ftp_transaction_factory.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_transaction_factory.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" + +namespace net { + +URLRequestFtpJob::URLRequestFtpJob( + URLRequest* request, + NetworkDelegate* network_delegate, + FtpTransactionFactory* ftp_transaction_factory, + FtpAuthCache* ftp_auth_cache) + : URLRequestJob(request, network_delegate), + priority_(DEFAULT_PRIORITY), + proxy_service_(request_->context()->proxy_service()), + pac_request_(NULL), + http_response_info_(NULL), + read_in_progress_(false), + weak_factory_(this), + ftp_transaction_factory_(ftp_transaction_factory), + ftp_auth_cache_(ftp_auth_cache) { + DCHECK(proxy_service_); + DCHECK(ftp_transaction_factory); + DCHECK(ftp_auth_cache); +} + +URLRequestFtpJob::~URLRequestFtpJob() { + if (pac_request_) + proxy_service_->CancelPacRequest(pac_request_); +} + +bool URLRequestFtpJob::IsSafeRedirect(const GURL& location) { + // Disallow all redirects. + return false; +} + +bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const { + if (proxy_info_.is_direct()) { + if (ftp_transaction_->GetResponseInfo()->is_directory_listing) { + *mime_type = "text/vnd.chromium.ftp-dir"; + return true; + } + } else { + // No special handling of MIME type is needed. As opposed to direct FTP + // transaction, we do not get a raw directory listing to parse. + return http_transaction_->GetResponseInfo()-> + headers->GetMimeType(mime_type); + } + return false; +} + +void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo* info) { + if (http_response_info_) + *info = *http_response_info_; +} + +HostPortPair URLRequestFtpJob::GetSocketAddress() const { + if (proxy_info_.is_direct()) { + if (!ftp_transaction_) + return HostPortPair(); + return ftp_transaction_->GetResponseInfo()->socket_address; + } else { + if (!http_transaction_) + return HostPortPair(); + return http_transaction_->GetResponseInfo()->socket_address; + } +} + +void URLRequestFtpJob::SetPriority(RequestPriority priority) { + priority_ = priority; + if (http_transaction_) + http_transaction_->SetPriority(priority); +} + +void URLRequestFtpJob::Start() { + DCHECK(!pac_request_); + DCHECK(!ftp_transaction_); + DCHECK(!http_transaction_); + + int rv = OK; + if (request_->load_flags() & LOAD_BYPASS_PROXY) { + proxy_info_.UseDirect(); + } else { + DCHECK_EQ(request_->context()->proxy_service(), proxy_service_); + rv = proxy_service_->ResolveProxy( + request_->url(), + &proxy_info_, + base::Bind(&URLRequestFtpJob::OnResolveProxyComplete, + base::Unretained(this)), + &pac_request_, + request_->net_log()); + + if (rv == ERR_IO_PENDING) + return; + } + OnResolveProxyComplete(rv); +} + +void URLRequestFtpJob::Kill() { + if (ftp_transaction_) + ftp_transaction_.reset(); + if (http_transaction_) + http_transaction_.reset(); + URLRequestJob::Kill(); + weak_factory_.InvalidateWeakPtrs(); +} + +void URLRequestFtpJob::OnResolveProxyComplete(int result) { + pac_request_ = NULL; + + if (result != OK) { + OnStartCompletedAsync(result); + return; + } + + // Remove unsupported proxies from the list. + proxy_info_.RemoveProxiesWithoutScheme( + ProxyServer::SCHEME_DIRECT | + ProxyServer::SCHEME_HTTP | + ProxyServer::SCHEME_HTTPS); + + // TODO(phajdan.jr): Implement proxy fallback, http://crbug.com/171495 . + if (proxy_info_.is_direct()) + StartFtpTransaction(); + else if (proxy_info_.is_http() || proxy_info_.is_https()) + StartHttpTransaction(); + else + OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES); +} + +void URLRequestFtpJob::StartFtpTransaction() { + // Create a transaction. + DCHECK(!ftp_transaction_); + + ftp_request_info_.url = request_->url(); + ftp_transaction_.reset(ftp_transaction_factory_->CreateTransaction()); + + // 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; + if (ftp_transaction_) { + rv = ftp_transaction_->Start( + &ftp_request_info_, + base::Bind(&URLRequestFtpJob::OnStartCompleted, + base::Unretained(this)), + request_->net_log()); + if (rv == ERR_IO_PENDING) + return; + } else { + rv = ERR_FAILED; + } + // The transaction started synchronously, but we need to notify the + // URLRequest delegate via the message loop. + OnStartCompletedAsync(rv); +} + +void URLRequestFtpJob::StartHttpTransaction() { + // Create a transaction. + DCHECK(!http_transaction_); + + // Do not cache FTP responses sent through HTTP proxy. + request_->set_load_flags(request_->load_flags() | + LOAD_DISABLE_CACHE | + LOAD_DO_NOT_SAVE_COOKIES | + LOAD_DO_NOT_SEND_COOKIES); + + http_request_info_.url = request_->url(); + http_request_info_.method = request_->method(); + http_request_info_.load_flags = request_->load_flags(); + http_request_info_.request_id = request_->identifier(); + + int rv = request_->context()->http_transaction_factory()->CreateTransaction( + priority_, &http_transaction_, NULL); + if (rv == OK) { + rv = http_transaction_->Start( + &http_request_info_, + base::Bind(&URLRequestFtpJob::OnStartCompleted, + base::Unretained(this)), + request_->net_log()); + if (rv == ERR_IO_PENDING) + return; + } + // The transaction started synchronously, but we need to notify the + // URLRequest delegate via the message loop. + OnStartCompletedAsync(rv); +} + +void URLRequestFtpJob::OnStartCompleted(int result) { + // Clear the IO_PENDING status + SetStatus(URLRequestStatus()); + + // Note that ftp_transaction_ may be NULL due to a creation failure. + if (ftp_transaction_) { + // FTP obviously doesn't have HTTP Content-Length header. We have to pass + // the content size information manually. + set_expected_content_size( + ftp_transaction_->GetResponseInfo()->expected_content_size); + } + + if (result == OK) { + 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) { + HandleAuthNeededResponse(); + return; + } else { + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); + } +} + +void URLRequestFtpJob::OnStartCompletedAsync(int result) { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&URLRequestFtpJob::OnStartCompleted, + weak_factory_.GetWeakPtr(), result)); +} + +void URLRequestFtpJob::OnReadCompleted(int result) { + read_in_progress_ = false; + if (result == 0) { + NotifyDone(URLRequestStatus()); + } else if (result < 0) { + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); + } else { + // Clear the IO_PENDING status + SetStatus(URLRequestStatus()); + } + NotifyReadComplete(result); +} + +void URLRequestFtpJob::RestartTransactionWithAuth() { + DCHECK(auth_data_.get() && 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; + 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; + + OnStartCompletedAsync(rv); +} + +LoadState URLRequestFtpJob::GetLoadState() const { + if (proxy_info_.is_direct()) { + return ftp_transaction_ ? + ftp_transaction_->GetLoadState() : LOAD_STATE_IDLE; + } else { + return http_transaction_ ? + http_transaction_->GetLoadState() : LOAD_STATE_IDLE; + } +} + +bool URLRequestFtpJob::NeedsAuth() { + return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH; +} + +void URLRequestFtpJob::GetAuthChallengeInfo( + scoped_refptr<AuthChallengeInfo>* result) { + 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()); + // scheme and realm are kept empty. + DCHECK(auth_info->scheme.empty()); + DCHECK(auth_info->realm.empty()); + result->swap(auth_info); +} + +void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) { + DCHECK(ftp_transaction_ || http_transaction_); + DCHECK(NeedsAuth()); + + 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_ || http_transaction_); + DCHECK(NeedsAuth()); + + 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 + // any recursing into the caller as a result of this call. + OnStartCompletedAsync(OK); +} + +UploadProgress URLRequestFtpJob::GetUploadProgress() const { + return UploadProgress(); +} + +bool URLRequestFtpJob::ReadRawData(IOBuffer* buf, + int buf_size, + int *bytes_read) { + DCHECK_NE(buf_size, 0); + DCHECK(bytes_read); + DCHECK(!read_in_progress_); + + int rv; + if (proxy_info_.is_direct()) { + rv = ftp_transaction_->Read(buf, buf_size, + base::Bind(&URLRequestFtpJob::OnReadCompleted, + base::Unretained(this))); + } else { + rv = http_transaction_->Read(buf, buf_size, + base::Bind(&URLRequestFtpJob::OnReadCompleted, + base::Unretained(this))); + } + + if (rv >= 0) { + *bytes_read = rv; + return true; + } + + if (rv == ERR_IO_PENDING) { + read_in_progress_ = true; + SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); + } else { + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); + } + return false; +} + +void URLRequestFtpJob::HandleAuthNeededResponse() { + GURL origin = request_->url().GetOrigin(); + + if (auth_data_.get()) { + 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 |