// 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.h" #include "base/utf_string_conversions.h" #include "net/base/auth.h" #include "net/base/host_port_pair.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/ftp/ftp_response_info.h" #include "net/ftp/ftp_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), read_in_progress_(false), ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), ftp_transaction_factory_(ftp_transaction_factory), ftp_auth_cache_(ftp_auth_cache) { DCHECK(ftp_transaction_factory); DCHECK(ftp_auth_cache); } // static URLRequestJob* URLRequestFtpJob::Factory(URLRequest* request, NetworkDelegate* network_delegate, const std::string& scheme) { DCHECK_EQ(scheme, "ftp"); int port = request->url().IntPort(); if (request->url().has_port() && !IsPortAllowedByFtp(port) && !IsPortAllowedByOverride(port)) { return new URLRequestErrorJob(request, network_delegate, ERR_UNSAFE_PORT); } return new URLRequestFtpJob(request, network_delegate, request->context()->ftp_transaction_factory(), request->context()->ftp_auth_cache()); } bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const { if (transaction_->GetResponseInfo()->is_directory_listing) { *mime_type = "text/vnd.chromium.ftp-dir"; return true; } return false; } HostPortPair URLRequestFtpJob::GetSocketAddress() const { if (!transaction_.get()) { return HostPortPair(); } return transaction_->GetResponseInfo()->socket_address; } URLRequestFtpJob::~URLRequestFtpJob() { } void URLRequestFtpJob::StartTransaction() { // Create a transaction. DCHECK(!transaction_.get()); 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 (transaction_.get()) { rv = transaction_->Start( &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. MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&URLRequestFtpJob::OnStartCompleted, weak_factory_.GetWeakPtr(), rv)); } void URLRequestFtpJob::OnStartCompleted(int result) { // Clear the IO_PENDING status SetStatus(URLRequestStatus()); // Note that transaction_ may be NULL due to a creation failure. if (transaction_.get()) { // FTP obviously doesn't have HTTP Content-Length header. We have to pass // the content size information manually. set_expected_content_size( transaction_->GetResponseInfo()->expected_content_size); } if (result == OK) { NotifyHeadersComplete(); } else if (transaction_.get() && 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(); } } else { NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, 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(server_auth_ && server_auth_->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 = transaction_->RestartWithAuth( server_auth_->credentials, base::Bind(&URLRequestFtpJob::OnStartCompleted, base::Unretained(this))); if (rv == ERR_IO_PENDING) return; MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&URLRequestFtpJob::OnStartCompleted, weak_factory_.GetWeakPtr(), rv)); } void URLRequestFtpJob::Start() { DCHECK(!transaction_.get()); request_info_.url = request_->url(); StartTransaction(); } void URLRequestFtpJob::Kill() { if (!transaction_.get()) return; transaction_.reset(); URLRequestJob::Kill(); weak_factory_.InvalidateWeakPtrs(); } LoadState URLRequestFtpJob::GetLoadState() const { return transaction_.get() ? transaction_->GetLoadState() : LOAD_STATE_IDLE; } bool URLRequestFtpJob::NeedsAuth() { // 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; } void URLRequestFtpJob::GetAuthChallengeInfo( scoped_refptr* result) { DCHECK((server_auth_ != NULL) && (server_auth_->state == AUTH_STATE_NEED_AUTH)); scoped_refptr 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(NeedsAuth()); server_auth_->state = AUTH_STATE_HAVE_AUTH; server_auth_->credentials = credentials; ftp_auth_cache_->Add(request_->url().GetOrigin(), server_auth_->credentials); RestartTransactionWithAuth(); } void URLRequestFtpJob::CancelAuth() { DCHECK(NeedsAuth()); server_auth_->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. MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&URLRequestFtpJob::OnStartCompleted, weak_factory_.GetWeakPtr(), 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 = 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; } } // namespace net