blob: 27021a33687bdad7255e681678f5e25f98de4f63 [file] [log] [blame]
// 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<AuthChallengeInfo>* result) {
DCHECK((server_auth_ != NULL) &&
(server_auth_->state == AUTH_STATE_NEED_AUTH));
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(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