Home | History | Annotate | Download | only in url_request
      1 // Copyright (c) 2012 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/message_loop.h"
      9 #include "base/strings/utf_string_conversions.h"
     10 #include "net/base/auth.h"
     11 #include "net/base/host_port_pair.h"
     12 #include "net/base/load_flags.h"
     13 #include "net/base/net_errors.h"
     14 #include "net/base/net_util.h"
     15 #include "net/ftp/ftp_auth_cache.h"
     16 #include "net/ftp/ftp_response_info.h"
     17 #include "net/ftp/ftp_transaction_factory.h"
     18 #include "net/http/http_response_headers.h"
     19 #include "net/http/http_transaction_factory.h"
     20 #include "net/url_request/url_request.h"
     21 #include "net/url_request/url_request_context.h"
     22 #include "net/url_request/url_request_error_job.h"
     23 
     24 namespace net {
     25 
     26 URLRequestFtpJob::URLRequestFtpJob(
     27     URLRequest* request,
     28     NetworkDelegate* network_delegate,
     29     FtpTransactionFactory* ftp_transaction_factory,
     30     FtpAuthCache* ftp_auth_cache)
     31     : URLRequestJob(request, network_delegate),
     32       priority_(DEFAULT_PRIORITY),
     33       proxy_service_(request_->context()->proxy_service()),
     34       pac_request_(NULL),
     35       http_response_info_(NULL),
     36       read_in_progress_(false),
     37       ftp_transaction_factory_(ftp_transaction_factory),
     38       ftp_auth_cache_(ftp_auth_cache),
     39       weak_factory_(this) {
     40   DCHECK(proxy_service_);
     41   DCHECK(ftp_transaction_factory);
     42   DCHECK(ftp_auth_cache);
     43 }
     44 
     45 URLRequestFtpJob::~URLRequestFtpJob() {
     46   if (pac_request_)
     47     proxy_service_->CancelPacRequest(pac_request_);
     48 }
     49 
     50 bool URLRequestFtpJob::IsSafeRedirect(const GURL& location) {
     51   // Disallow all redirects.
     52   return false;
     53 }
     54 
     55 bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const {
     56   if (proxy_info_.is_direct()) {
     57     if (ftp_transaction_->GetResponseInfo()->is_directory_listing) {
     58       *mime_type = "text/vnd.chromium.ftp-dir";
     59       return true;
     60     }
     61   } else {
     62     // No special handling of MIME type is needed. As opposed to direct FTP
     63     // transaction, we do not get a raw directory listing to parse.
     64     return http_transaction_->GetResponseInfo()->
     65         headers->GetMimeType(mime_type);
     66   }
     67   return false;
     68 }
     69 
     70 void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo* info) {
     71   if (http_response_info_)
     72     *info = *http_response_info_;
     73 }
     74 
     75 HostPortPair URLRequestFtpJob::GetSocketAddress() const {
     76   if (proxy_info_.is_direct()) {
     77     if (!ftp_transaction_)
     78       return HostPortPair();
     79     return ftp_transaction_->GetResponseInfo()->socket_address;
     80   } else {
     81     if (!http_transaction_)
     82       return HostPortPair();
     83     return http_transaction_->GetResponseInfo()->socket_address;
     84   }
     85 }
     86 
     87 void URLRequestFtpJob::SetPriority(RequestPriority priority) {
     88   priority_ = priority;
     89   if (http_transaction_)
     90     http_transaction_->SetPriority(priority);
     91 }
     92 
     93 void URLRequestFtpJob::Start() {
     94   DCHECK(!pac_request_);
     95   DCHECK(!ftp_transaction_);
     96   DCHECK(!http_transaction_);
     97 
     98   int rv = OK;
     99   if (request_->load_flags() & LOAD_BYPASS_PROXY) {
    100     proxy_info_.UseDirect();
    101   } else {
    102     DCHECK_EQ(request_->context()->proxy_service(), proxy_service_);
    103     rv = proxy_service_->ResolveProxy(
    104         request_->url(),
    105         request_->load_flags(),
    106         &proxy_info_,
    107         base::Bind(&URLRequestFtpJob::OnResolveProxyComplete,
    108                    base::Unretained(this)),
    109         &pac_request_,
    110         NULL,
    111         request_->net_log());
    112 
    113     if (rv == ERR_IO_PENDING)
    114       return;
    115   }
    116   OnResolveProxyComplete(rv);
    117 }
    118 
    119 void URLRequestFtpJob::Kill() {
    120   if (ftp_transaction_)
    121     ftp_transaction_.reset();
    122   if (http_transaction_)
    123     http_transaction_.reset();
    124   URLRequestJob::Kill();
    125   weak_factory_.InvalidateWeakPtrs();
    126 }
    127 
    128 void URLRequestFtpJob::OnResolveProxyComplete(int result) {
    129   pac_request_ = NULL;
    130 
    131   if (result != OK) {
    132     OnStartCompletedAsync(result);
    133     return;
    134   }
    135 
    136   // Remove unsupported proxies from the list.
    137   proxy_info_.RemoveProxiesWithoutScheme(
    138       ProxyServer::SCHEME_DIRECT |
    139       ProxyServer::SCHEME_HTTP |
    140       ProxyServer::SCHEME_HTTPS);
    141 
    142   // TODO(phajdan.jr): Implement proxy fallback, http://crbug.com/171495 .
    143   if (proxy_info_.is_direct())
    144     StartFtpTransaction();
    145   else if (proxy_info_.is_http() || proxy_info_.is_https())
    146     StartHttpTransaction();
    147   else
    148     OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES);
    149 }
    150 
    151 void URLRequestFtpJob::StartFtpTransaction() {
    152   // Create a transaction.
    153   DCHECK(!ftp_transaction_);
    154 
    155   ftp_request_info_.url = request_->url();
    156   ftp_transaction_.reset(ftp_transaction_factory_->CreateTransaction());
    157 
    158   // No matter what, we want to report our status as IO pending since we will
    159   // be notifying our consumer asynchronously via OnStartCompleted.
    160   SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
    161   int rv;
    162   if (ftp_transaction_) {
    163     rv = ftp_transaction_->Start(
    164         &ftp_request_info_,
    165         base::Bind(&URLRequestFtpJob::OnStartCompleted,
    166                    base::Unretained(this)),
    167         request_->net_log());
    168     if (rv == ERR_IO_PENDING)
    169       return;
    170   } else {
    171     rv = ERR_FAILED;
    172   }
    173   // The transaction started synchronously, but we need to notify the
    174   // URLRequest delegate via the message loop.
    175   OnStartCompletedAsync(rv);
    176 }
    177 
    178 void URLRequestFtpJob::StartHttpTransaction() {
    179   // Create a transaction.
    180   DCHECK(!http_transaction_);
    181 
    182   // Do not cache FTP responses sent through HTTP proxy.
    183   request_->SetLoadFlags(request_->load_flags() |
    184                          LOAD_DISABLE_CACHE |
    185                          LOAD_DO_NOT_SAVE_COOKIES |
    186                          LOAD_DO_NOT_SEND_COOKIES);
    187 
    188   http_request_info_.url = request_->url();
    189   http_request_info_.method = request_->method();
    190   http_request_info_.load_flags = request_->load_flags();
    191 
    192   int rv = request_->context()->http_transaction_factory()->CreateTransaction(
    193       priority_, &http_transaction_);
    194   if (rv == OK) {
    195     rv = http_transaction_->Start(
    196         &http_request_info_,
    197         base::Bind(&URLRequestFtpJob::OnStartCompleted,
    198                   base::Unretained(this)),
    199         request_->net_log());
    200     if (rv == ERR_IO_PENDING)
    201       return;
    202   }
    203   // The transaction started synchronously, but we need to notify the
    204   // URLRequest delegate via the message loop.
    205   OnStartCompletedAsync(rv);
    206 }
    207 
    208 void URLRequestFtpJob::OnStartCompleted(int result) {
    209   // Clear the IO_PENDING status
    210   SetStatus(URLRequestStatus());
    211 
    212   // Note that ftp_transaction_ may be NULL due to a creation failure.
    213   if (ftp_transaction_) {
    214     // FTP obviously doesn't have HTTP Content-Length header. We have to pass
    215     // the content size information manually.
    216     set_expected_content_size(
    217         ftp_transaction_->GetResponseInfo()->expected_content_size);
    218   }
    219 
    220   if (result == OK) {
    221     if (http_transaction_) {
    222       http_response_info_ = http_transaction_->GetResponseInfo();
    223       SetProxyServer(http_response_info_->proxy_server);
    224 
    225       if (http_response_info_->headers->response_code() == 401 ||
    226           http_response_info_->headers->response_code() == 407) {
    227         HandleAuthNeededResponse();
    228         return;
    229       }
    230     }
    231     NotifyHeadersComplete();
    232   } else if (ftp_transaction_ &&
    233              ftp_transaction_->GetResponseInfo()->needs_auth) {
    234     HandleAuthNeededResponse();
    235     return;
    236   } else {
    237     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
    238   }
    239 }
    240 
    241 void URLRequestFtpJob::OnStartCompletedAsync(int result) {
    242   base::MessageLoop::current()->PostTask(
    243       FROM_HERE,
    244       base::Bind(&URLRequestFtpJob::OnStartCompleted,
    245                  weak_factory_.GetWeakPtr(), result));
    246 }
    247 
    248 void URLRequestFtpJob::OnReadCompleted(int result) {
    249   read_in_progress_ = false;
    250   if (result == 0) {
    251     NotifyDone(URLRequestStatus());
    252   } else if (result < 0) {
    253     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
    254   } else {
    255     // Clear the IO_PENDING status
    256     SetStatus(URLRequestStatus());
    257   }
    258   NotifyReadComplete(result);
    259 }
    260 
    261 void URLRequestFtpJob::RestartTransactionWithAuth() {
    262   DCHECK(auth_data_.get() && auth_data_->state == AUTH_STATE_HAVE_AUTH);
    263 
    264   // No matter what, we want to report our status as IO pending since we will
    265   // be notifying our consumer asynchronously via OnStartCompleted.
    266   SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
    267 
    268   int rv;
    269   if (proxy_info_.is_direct()) {
    270     rv = ftp_transaction_->RestartWithAuth(
    271         auth_data_->credentials,
    272         base::Bind(&URLRequestFtpJob::OnStartCompleted,
    273                    base::Unretained(this)));
    274   } else {
    275     rv = http_transaction_->RestartWithAuth(
    276         auth_data_->credentials,
    277         base::Bind(&URLRequestFtpJob::OnStartCompleted,
    278                    base::Unretained(this)));
    279   }
    280   if (rv == ERR_IO_PENDING)
    281     return;
    282 
    283   OnStartCompletedAsync(rv);
    284 }
    285 
    286 LoadState URLRequestFtpJob::GetLoadState() const {
    287   if (proxy_info_.is_direct()) {
    288     return ftp_transaction_ ?
    289         ftp_transaction_->GetLoadState() : LOAD_STATE_IDLE;
    290   } else {
    291     return http_transaction_ ?
    292         http_transaction_->GetLoadState() : LOAD_STATE_IDLE;
    293   }
    294 }
    295 
    296 bool URLRequestFtpJob::NeedsAuth() {
    297   return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH;
    298 }
    299 
    300 void URLRequestFtpJob::GetAuthChallengeInfo(
    301     scoped_refptr<AuthChallengeInfo>* result) {
    302   DCHECK(NeedsAuth());
    303 
    304   if (http_response_info_) {
    305     *result = http_response_info_->auth_challenge;
    306     return;
    307   }
    308 
    309   scoped_refptr<AuthChallengeInfo> auth_info(new AuthChallengeInfo);
    310   auth_info->is_proxy = false;
    311   auth_info->challenger = HostPortPair::FromURL(request_->url());
    312   // scheme and realm are kept empty.
    313   DCHECK(auth_info->scheme.empty());
    314   DCHECK(auth_info->realm.empty());
    315   result->swap(auth_info);
    316 }
    317 
    318 void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) {
    319   DCHECK(ftp_transaction_ || http_transaction_);
    320   DCHECK(NeedsAuth());
    321 
    322   auth_data_->state = AUTH_STATE_HAVE_AUTH;
    323   auth_data_->credentials = credentials;
    324 
    325   if (ftp_transaction_) {
    326     ftp_auth_cache_->Add(request_->url().GetOrigin(),
    327                          auth_data_->credentials);
    328   }
    329 
    330   RestartTransactionWithAuth();
    331 }
    332 
    333 void URLRequestFtpJob::CancelAuth() {
    334   DCHECK(ftp_transaction_ || http_transaction_);
    335   DCHECK(NeedsAuth());
    336 
    337   auth_data_->state = AUTH_STATE_CANCELED;
    338 
    339   // Once the auth is cancelled, we proceed with the request as though
    340   // there were no auth.  Schedule this for later so that we don't cause
    341   // any recursing into the caller as a result of this call.
    342   OnStartCompletedAsync(OK);
    343 }
    344 
    345 UploadProgress URLRequestFtpJob::GetUploadProgress() const {
    346   return UploadProgress();
    347 }
    348 
    349 bool URLRequestFtpJob::ReadRawData(IOBuffer* buf,
    350                                    int buf_size,
    351                                    int *bytes_read) {
    352   DCHECK_NE(buf_size, 0);
    353   DCHECK(bytes_read);
    354   DCHECK(!read_in_progress_);
    355 
    356   int rv;
    357   if (proxy_info_.is_direct()) {
    358     rv = ftp_transaction_->Read(buf, buf_size,
    359                                 base::Bind(&URLRequestFtpJob::OnReadCompleted,
    360                                            base::Unretained(this)));
    361   } else {
    362     rv = http_transaction_->Read(buf, buf_size,
    363                                  base::Bind(&URLRequestFtpJob::OnReadCompleted,
    364                                             base::Unretained(this)));
    365   }
    366 
    367   if (rv >= 0) {
    368     *bytes_read = rv;
    369     return true;
    370   }
    371 
    372   if (rv == ERR_IO_PENDING) {
    373     read_in_progress_ = true;
    374     SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
    375   } else {
    376     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
    377   }
    378   return false;
    379 }
    380 
    381 void URLRequestFtpJob::HandleAuthNeededResponse() {
    382   GURL origin = request_->url().GetOrigin();
    383 
    384   if (auth_data_.get()) {
    385     if (auth_data_->state == AUTH_STATE_CANCELED) {
    386       NotifyHeadersComplete();
    387       return;
    388     }
    389 
    390     if (ftp_transaction_ && auth_data_->state == AUTH_STATE_HAVE_AUTH)
    391       ftp_auth_cache_->Remove(origin, auth_data_->credentials);
    392   } else {
    393     auth_data_ = new AuthData;
    394   }
    395   auth_data_->state = AUTH_STATE_NEED_AUTH;
    396 
    397   FtpAuthCache::Entry* cached_auth = NULL;
    398   if (ftp_transaction_ && ftp_transaction_->GetResponseInfo()->needs_auth)
    399     cached_auth = ftp_auth_cache_->Lookup(origin);
    400   if (cached_auth) {
    401     // Retry using cached auth data.
    402     SetAuth(cached_auth->credentials);
    403   } else {
    404     // Prompt for a username/password.
    405     NotifyHeadersComplete();
    406   }
    407 }
    408 
    409 }  // namespace net
    410