Home | History | Annotate | Download | only in url_request
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "net/url_request/url_request_ftp_job.h"
      6 
      7 #include "base/compiler_specific.h"
      8 #include "base/message_loop.h"
      9 #include "base/utf_string_conversions.h"
     10 #include "net/base/auth.h"
     11 #include "net/base/host_port_pair.h"
     12 #include "net/base/net_errors.h"
     13 #include "net/base/net_util.h"
     14 #include "net/ftp/ftp_response_info.h"
     15 #include "net/ftp/ftp_transaction_factory.h"
     16 #include "net/url_request/url_request.h"
     17 #include "net/url_request/url_request_context.h"
     18 #include "net/url_request/url_request_error_job.h"
     19 
     20 namespace net {
     21 
     22 URLRequestFtpJob::URLRequestFtpJob(URLRequest* request)
     23     : URLRequestJob(request),
     24       ALLOW_THIS_IN_INITIALIZER_LIST(
     25           start_callback_(this, &URLRequestFtpJob::OnStartCompleted)),
     26       ALLOW_THIS_IN_INITIALIZER_LIST(
     27           read_callback_(this, &URLRequestFtpJob::OnReadCompleted)),
     28       read_in_progress_(false),
     29       context_(request->context()),
     30       ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
     31 }
     32 
     33 // static
     34 URLRequestJob* URLRequestFtpJob::Factory(URLRequest* request,
     35                                          const std::string& scheme) {
     36   DCHECK_EQ(scheme, "ftp");
     37 
     38   int port = request->url().IntPort();
     39   if (request->url().has_port() &&
     40     !IsPortAllowedByFtp(port) && !IsPortAllowedByOverride(port))
     41     return new URLRequestErrorJob(request, ERR_UNSAFE_PORT);
     42 
     43   DCHECK(request->context());
     44   DCHECK(request->context()->ftp_transaction_factory());
     45   return new URLRequestFtpJob(request);
     46 }
     47 
     48 bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const {
     49   if (transaction_->GetResponseInfo()->is_directory_listing) {
     50     *mime_type = "text/vnd.chromium.ftp-dir";
     51     return true;
     52   }
     53   return false;
     54 }
     55 
     56 HostPortPair URLRequestFtpJob::GetSocketAddress() const {
     57   if (!transaction_.get()) {
     58     return HostPortPair();
     59   }
     60   return transaction_->GetResponseInfo()->socket_address;
     61 }
     62 
     63 URLRequestFtpJob::~URLRequestFtpJob() {
     64 }
     65 
     66 void URLRequestFtpJob::StartTransaction() {
     67   // Create a transaction.
     68   DCHECK(!transaction_.get());
     69   DCHECK(request_->context());
     70   DCHECK(request_->context()->ftp_transaction_factory());
     71 
     72   transaction_.reset(
     73       request_->context()->ftp_transaction_factory()->CreateTransaction());
     74 
     75   // No matter what, we want to report our status as IO pending since we will
     76   // be notifying our consumer asynchronously via OnStartCompleted.
     77   SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
     78   int rv;
     79   if (transaction_.get()) {
     80     rv = transaction_->Start(
     81         &request_info_, &start_callback_, request_->net_log());
     82     if (rv == ERR_IO_PENDING)
     83       return;
     84   } else {
     85     rv = ERR_FAILED;
     86   }
     87   // The transaction started synchronously, but we need to notify the
     88   // URLRequest delegate via the message loop.
     89   MessageLoop::current()->PostTask(
     90       FROM_HERE,
     91       method_factory_.NewRunnableMethod(
     92           &URLRequestFtpJob::OnStartCompleted, rv));
     93 }
     94 
     95 void URLRequestFtpJob::OnStartCompleted(int result) {
     96   // Clear the IO_PENDING status
     97   SetStatus(URLRequestStatus());
     98 
     99   // FTP obviously doesn't have HTTP Content-Length header. We have to pass
    100   // the content size information manually.
    101   set_expected_content_size(
    102       transaction_->GetResponseInfo()->expected_content_size);
    103 
    104   if (result == OK) {
    105     NotifyHeadersComplete();
    106   } else if (transaction_->GetResponseInfo()->needs_auth) {
    107     GURL origin = request_->url().GetOrigin();
    108     if (server_auth_ && server_auth_->state == AUTH_STATE_HAVE_AUTH) {
    109       request_->context()->ftp_auth_cache()->Remove(origin,
    110                                                     server_auth_->username,
    111                                                     server_auth_->password);
    112     } else if (!server_auth_) {
    113       server_auth_ = new AuthData();
    114     }
    115     server_auth_->state = AUTH_STATE_NEED_AUTH;
    116 
    117     FtpAuthCache::Entry* cached_auth =
    118         request_->context()->ftp_auth_cache()->Lookup(origin);
    119 
    120     if (cached_auth) {
    121       // Retry using cached auth data.
    122       SetAuth(cached_auth->username, cached_auth->password);
    123     } else {
    124       // Prompt for a username/password.
    125       NotifyHeadersComplete();
    126     }
    127   } else {
    128     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
    129   }
    130 }
    131 
    132 void URLRequestFtpJob::OnReadCompleted(int result) {
    133   read_in_progress_ = false;
    134   if (result == 0) {
    135     NotifyDone(URLRequestStatus());
    136   } else if (result < 0) {
    137     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
    138   } else {
    139     // Clear the IO_PENDING status
    140     SetStatus(URLRequestStatus());
    141   }
    142   NotifyReadComplete(result);
    143 }
    144 
    145 void URLRequestFtpJob::RestartTransactionWithAuth() {
    146   DCHECK(server_auth_ && server_auth_->state == AUTH_STATE_HAVE_AUTH);
    147 
    148   // No matter what, we want to report our status as IO pending since we will
    149   // be notifying our consumer asynchronously via OnStartCompleted.
    150   SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
    151 
    152   int rv = transaction_->RestartWithAuth(server_auth_->username,
    153                                          server_auth_->password,
    154                                          &start_callback_);
    155   if (rv == ERR_IO_PENDING)
    156     return;
    157 
    158   MessageLoop::current()->PostTask(
    159       FROM_HERE,
    160       method_factory_.NewRunnableMethod(
    161           &URLRequestFtpJob::OnStartCompleted, rv));
    162 }
    163 
    164 void URLRequestFtpJob::Start() {
    165   DCHECK(!transaction_.get());
    166   request_info_.url = request_->url();
    167   StartTransaction();
    168 }
    169 
    170 void URLRequestFtpJob::Kill() {
    171   if (!transaction_.get())
    172     return;
    173   transaction_.reset();
    174   URLRequestJob::Kill();
    175   method_factory_.RevokeAll();
    176 }
    177 
    178 LoadState URLRequestFtpJob::GetLoadState() const {
    179   return transaction_.get() ?
    180       transaction_->GetLoadState() : LOAD_STATE_IDLE;
    181 }
    182 
    183 bool URLRequestFtpJob::NeedsAuth() {
    184   // Note that we only have to worry about cases where an actual FTP server
    185   // requires auth (and not a proxy), because connecting to FTP via proxy
    186   // effectively means the browser communicates via HTTP, and uses HTTP's
    187   // Proxy-Authenticate protocol when proxy servers require auth.
    188   return server_auth_ && server_auth_->state == AUTH_STATE_NEED_AUTH;
    189 }
    190 
    191 void URLRequestFtpJob::GetAuthChallengeInfo(
    192     scoped_refptr<AuthChallengeInfo>* result) {
    193   DCHECK((server_auth_ != NULL) &&
    194          (server_auth_->state == AUTH_STATE_NEED_AUTH));
    195   scoped_refptr<AuthChallengeInfo> auth_info(new AuthChallengeInfo);
    196   auth_info->is_proxy = false;
    197   auth_info->host_and_port = ASCIIToWide(
    198       GetHostAndPort(request_->url()));
    199   auth_info->scheme = L"";
    200   auth_info->realm = L"";
    201   result->swap(auth_info);
    202 }
    203 
    204 void URLRequestFtpJob::SetAuth(const string16& username,
    205                                const string16& password) {
    206   DCHECK(NeedsAuth());
    207   server_auth_->state = AUTH_STATE_HAVE_AUTH;
    208   server_auth_->username = username;
    209   server_auth_->password = password;
    210 
    211   request_->context()->ftp_auth_cache()->Add(request_->url().GetOrigin(),
    212                                              username, password);
    213 
    214   RestartTransactionWithAuth();
    215 }
    216 
    217 void URLRequestFtpJob::CancelAuth() {
    218   DCHECK(NeedsAuth());
    219   server_auth_->state = AUTH_STATE_CANCELED;
    220 
    221   // Once the auth is cancelled, we proceed with the request as though
    222   // there were no auth.  Schedule this for later so that we don't cause
    223   // any recursing into the caller as a result of this call.
    224   MessageLoop::current()->PostTask(
    225       FROM_HERE,
    226       method_factory_.NewRunnableMethod(
    227           &URLRequestFtpJob::OnStartCompleted, OK));
    228 }
    229 
    230 uint64 URLRequestFtpJob::GetUploadProgress() const {
    231   return 0;
    232 }
    233 
    234 bool URLRequestFtpJob::ReadRawData(IOBuffer* buf,
    235                                    int buf_size,
    236                                    int *bytes_read) {
    237   DCHECK_NE(buf_size, 0);
    238   DCHECK(bytes_read);
    239   DCHECK(!read_in_progress_);
    240 
    241   int rv = transaction_->Read(buf, buf_size, &read_callback_);
    242   if (rv >= 0) {
    243     *bytes_read = rv;
    244     return true;
    245   }
    246 
    247   if (rv == ERR_IO_PENDING) {
    248     read_in_progress_ = true;
    249     SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
    250   } else {
    251     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
    252   }
    253   return false;
    254 }
    255 
    256 }  // namespace net
    257