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