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       weak_factory_(this),
     38       ftp_transaction_factory_(ftp_transaction_factory),
     39       ftp_auth_cache_(ftp_auth_cache) {
     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         &proxy_info_,
    106         base::Bind(&URLRequestFtpJob::OnResolveProxyComplete,
    107                    base::Unretained(this)),
    108         &pac_request_,
    109         request_->net_log());
    110 
    111     if (rv == ERR_IO_PENDING)
    112       return;
    113   }
    114   OnResolveProxyComplete(rv);
    115 }
    116 
    117 void URLRequestFtpJob::Kill() {
    118   if (ftp_transaction_)
    119     ftp_transaction_.reset();
    120   if (http_transaction_)
    121     http_transaction_.reset();
    122   URLRequestJob::Kill();
    123   weak_factory_.InvalidateWeakPtrs();
    124 }
    125 
    126 void URLRequestFtpJob::OnResolveProxyComplete(int result) {
    127   pac_request_ = NULL;
    128 
    129   if (result != OK) {
    130     OnStartCompletedAsync(result);
    131     return;
    132   }
    133 
    134   // Remove unsupported proxies from the list.
    135   proxy_info_.RemoveProxiesWithoutScheme(
    136       ProxyServer::SCHEME_DIRECT |
    137       ProxyServer::SCHEME_HTTP |
    138       ProxyServer::SCHEME_HTTPS);
    139 
    140   // TODO(phajdan.jr): Implement proxy fallback, http://crbug.com/171495 .
    141   if (proxy_info_.is_direct())
    142     StartFtpTransaction();
    143   else if (proxy_info_.is_http() || proxy_info_.is_https())
    144     StartHttpTransaction();
    145   else
    146     OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES);
    147 }
    148 
    149 void URLRequestFtpJob::StartFtpTransaction() {
    150   // Create a transaction.
    151   DCHECK(!ftp_transaction_);
    152 
    153   ftp_request_info_.url = request_->url();
    154   ftp_transaction_.reset(ftp_transaction_factory_->CreateTransaction());
    155 
    156   // No matter what, we want to report our status as IO pending since we will
    157   // be notifying our consumer asynchronously via OnStartCompleted.
    158   SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
    159   int rv;
    160   if (ftp_transaction_) {
    161     rv = ftp_transaction_->Start(
    162         &ftp_request_info_,
    163         base::Bind(&URLRequestFtpJob::OnStartCompleted,
    164                    base::Unretained(this)),
    165         request_->net_log());
    166     if (rv == ERR_IO_PENDING)
    167       return;
    168   } else {
    169     rv = ERR_FAILED;
    170   }
    171   // The transaction started synchronously, but we need to notify the
    172   // URLRequest delegate via the message loop.
    173   OnStartCompletedAsync(rv);
    174 }
    175 
    176 void URLRequestFtpJob::StartHttpTransaction() {
    177   // Create a transaction.
    178   DCHECK(!http_transaction_);
    179 
    180   // Do not cache FTP responses sent through HTTP proxy.
    181   request_->set_load_flags(request_->load_flags() |
    182                            LOAD_DISABLE_CACHE |
    183                            LOAD_DO_NOT_SAVE_COOKIES |
    184                            LOAD_DO_NOT_SEND_COOKIES);
    185 
    186   http_request_info_.url = request_->url();
    187   http_request_info_.method = request_->method();
    188   http_request_info_.load_flags = request_->load_flags();
    189   http_request_info_.request_id = request_->identifier();
    190 
    191   int rv = request_->context()->http_transaction_factory()->CreateTransaction(
    192       priority_, &http_transaction_, NULL);
    193   if (rv == OK) {
    194     rv = http_transaction_->Start(
    195         &http_request_info_,
    196         base::Bind(&URLRequestFtpJob::OnStartCompleted,
    197                    base::Unretained(this)),
    198         request_->net_log());
    199     if (rv == ERR_IO_PENDING)
    200       return;
    201   }
    202   // The transaction started synchronously, but we need to notify the
    203   // URLRequest delegate via the message loop.
    204   OnStartCompletedAsync(rv);
    205 }
    206 
    207 void URLRequestFtpJob::OnStartCompleted(int result) {
    208   // Clear the IO_PENDING status
    209   SetStatus(URLRequestStatus());
    210 
    211   // Note that ftp_transaction_ may be NULL due to a creation failure.
    212   if (ftp_transaction_) {
    213     // FTP obviously doesn't have HTTP Content-Length header. We have to pass
    214     // the content size information manually.
    215     set_expected_content_size(
    216         ftp_transaction_->GetResponseInfo()->expected_content_size);
    217   }
    218 
    219   if (result == OK) {
    220     if (http_transaction_) {
    221       http_response_info_ = http_transaction_->GetResponseInfo();
    222 
    223       if (http_response_info_->headers->response_code() == 401 ||
    224           http_response_info_->headers->response_code() == 407) {
    225         HandleAuthNeededResponse();
    226         return;
    227       }
    228     }
    229     NotifyHeadersComplete();
    230   } else if (ftp_transaction_ &&
    231              ftp_transaction_->GetResponseInfo()->needs_auth) {
    232     HandleAuthNeededResponse();
    233     return;
    234   } else {
    235     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
    236   }
    237 }
    238 
    239 void URLRequestFtpJob::OnStartCompletedAsync(int result) {
    240   base::MessageLoop::current()->PostTask(
    241       FROM_HERE,
    242       base::Bind(&URLRequestFtpJob::OnStartCompleted,
    243                  weak_factory_.GetWeakPtr(), result));
    244 }
    245 
    246 void URLRequestFtpJob::OnReadCompleted(int result) {
    247   read_in_progress_ = false;
    248   if (result == 0) {
    249     NotifyDone(URLRequestStatus());
    250   } else if (result < 0) {
    251     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
    252   } else {
    253     // Clear the IO_PENDING status
    254     SetStatus(URLRequestStatus());
    255   }
    256   NotifyReadComplete(result);
    257 }
    258 
    259 void URLRequestFtpJob::RestartTransactionWithAuth() {
    260   DCHECK(auth_data_.get() && auth_data_->state == AUTH_STATE_HAVE_AUTH);
    261 
    262   // No matter what, we want to report our status as IO pending since we will
    263   // be notifying our consumer asynchronously via OnStartCompleted.
    264   SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
    265 
    266   int rv;
    267   if (proxy_info_.is_direct()) {
    268     rv = ftp_transaction_->RestartWithAuth(
    269         auth_data_->credentials,
    270         base::Bind(&URLRequestFtpJob::OnStartCompleted,
    271                    base::Unretained(this)));
    272   } else {
    273     rv = http_transaction_->RestartWithAuth(
    274         auth_data_->credentials,
    275         base::Bind(&URLRequestFtpJob::OnStartCompleted,
    276                    base::Unretained(this)));
    277   }
    278   if (rv == ERR_IO_PENDING)
    279     return;
    280 
    281   OnStartCompletedAsync(rv);
    282 }
    283 
    284 LoadState URLRequestFtpJob::GetLoadState() const {
    285   if (proxy_info_.is_direct()) {
    286     return ftp_transaction_ ?
    287         ftp_transaction_->GetLoadState() : LOAD_STATE_IDLE;
    288   } else {
    289     return http_transaction_ ?
    290         http_transaction_->GetLoadState() : LOAD_STATE_IDLE;
    291   }
    292 }
    293 
    294 bool URLRequestFtpJob::NeedsAuth() {
    295   return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH;
    296 }
    297 
    298 void URLRequestFtpJob::GetAuthChallengeInfo(
    299     scoped_refptr<AuthChallengeInfo>* result) {
    300   DCHECK(NeedsAuth());
    301 
    302   if (http_response_info_) {
    303     *result = http_response_info_->auth_challenge;
    304     return;
    305   }
    306 
    307   scoped_refptr<AuthChallengeInfo> auth_info(new AuthChallengeInfo);
    308   auth_info->is_proxy = false;
    309   auth_info->challenger = HostPortPair::FromURL(request_->url());
    310   // scheme and realm are kept empty.
    311   DCHECK(auth_info->scheme.empty());
    312   DCHECK(auth_info->realm.empty());
    313   result->swap(auth_info);
    314 }
    315 
    316 void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) {
    317   DCHECK(ftp_transaction_ || http_transaction_);
    318   DCHECK(NeedsAuth());
    319 
    320   auth_data_->state = AUTH_STATE_HAVE_AUTH;
    321   auth_data_->credentials = credentials;
    322 
    323   if (ftp_transaction_) {
    324     ftp_auth_cache_->Add(request_->url().GetOrigin(),
    325                          auth_data_->credentials);
    326   }
    327 
    328   RestartTransactionWithAuth();
    329 }
    330 
    331 void URLRequestFtpJob::CancelAuth() {
    332   DCHECK(ftp_transaction_ || http_transaction_);
    333   DCHECK(NeedsAuth());
    334 
    335   auth_data_->state = AUTH_STATE_CANCELED;
    336 
    337   // Once the auth is cancelled, we proceed with the request as though
    338   // there were no auth.  Schedule this for later so that we don't cause
    339   // any recursing into the caller as a result of this call.
    340   OnStartCompletedAsync(OK);
    341 }
    342 
    343 UploadProgress URLRequestFtpJob::GetUploadProgress() const {
    344   return UploadProgress();
    345 }
    346 
    347 bool URLRequestFtpJob::ReadRawData(IOBuffer* buf,
    348                                    int buf_size,
    349                                    int *bytes_read) {
    350   DCHECK_NE(buf_size, 0);
    351   DCHECK(bytes_read);
    352   DCHECK(!read_in_progress_);
    353 
    354   int rv;
    355   if (proxy_info_.is_direct()) {
    356     rv = ftp_transaction_->Read(buf, buf_size,
    357                                 base::Bind(&URLRequestFtpJob::OnReadCompleted,
    358                                            base::Unretained(this)));
    359   } else {
    360     rv = http_transaction_->Read(buf, buf_size,
    361                                  base::Bind(&URLRequestFtpJob::OnReadCompleted,
    362                                             base::Unretained(this)));
    363   }
    364 
    365   if (rv >= 0) {
    366     *bytes_read = rv;
    367     return true;
    368   }
    369 
    370   if (rv == ERR_IO_PENDING) {
    371     read_in_progress_ = true;
    372     SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
    373   } else {
    374     NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
    375   }
    376   return false;
    377 }
    378 
    379 void URLRequestFtpJob::HandleAuthNeededResponse() {
    380   GURL origin = request_->url().GetOrigin();
    381 
    382   if (auth_data_.get()) {
    383     if (auth_data_->state == AUTH_STATE_CANCELED) {
    384       NotifyHeadersComplete();
    385       return;
    386     }
    387 
    388     if (ftp_transaction_ && auth_data_->state == AUTH_STATE_HAVE_AUTH)
    389       ftp_auth_cache_->Remove(origin, auth_data_->credentials);
    390   } else {
    391     auth_data_ = new AuthData;
    392   }
    393   auth_data_->state = AUTH_STATE_NEED_AUTH;
    394 
    395   FtpAuthCache::Entry* cached_auth = NULL;
    396   if (ftp_transaction_ && ftp_transaction_->GetResponseInfo()->needs_auth)
    397     cached_auth = ftp_auth_cache_->Lookup(origin);
    398   if (cached_auth) {
    399     // Retry using cached auth data.
    400     SetAuth(cached_auth->credentials);
    401   } else {
    402     // Prompt for a username/password.
    403     NotifyHeadersComplete();
    404   }
    405 }
    406 
    407 }  // namespace net
    408