root/net/url_request/url_request_ftp_job.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ftp_auth_cache_
  2. IsSafeRedirect
  3. GetMimeType
  4. GetResponseInfo
  5. GetSocketAddress
  6. SetPriority
  7. Start
  8. Kill
  9. OnResolveProxyComplete
  10. StartFtpTransaction
  11. StartHttpTransaction
  12. OnStartCompleted
  13. OnStartCompletedAsync
  14. OnReadCompleted
  15. RestartTransactionWithAuth
  16. GetLoadState
  17. NeedsAuth
  18. GetAuthChallengeInfo
  19. SetAuth
  20. CancelAuth
  21. GetUploadProgress
  22. ReadRawData
  23. HandleAuthNeededResponse

// 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/message_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "net/base/auth.h"
#include "net/base/host_port_pair.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/ftp/ftp_auth_cache.h"
#include "net/ftp/ftp_response_info.h"
#include "net/ftp/ftp_transaction_factory.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_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),
      priority_(DEFAULT_PRIORITY),
      proxy_service_(request_->context()->proxy_service()),
      pac_request_(NULL),
      http_response_info_(NULL),
      read_in_progress_(false),
      weak_factory_(this),
      ftp_transaction_factory_(ftp_transaction_factory),
      ftp_auth_cache_(ftp_auth_cache) {
  DCHECK(proxy_service_);
  DCHECK(ftp_transaction_factory);
  DCHECK(ftp_auth_cache);
}

URLRequestFtpJob::~URLRequestFtpJob() {
  if (pac_request_)
    proxy_service_->CancelPacRequest(pac_request_);
}

bool URLRequestFtpJob::IsSafeRedirect(const GURL& location) {
  // Disallow all redirects.
  return false;
}

bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const {
  if (proxy_info_.is_direct()) {
    if (ftp_transaction_->GetResponseInfo()->is_directory_listing) {
      *mime_type = "text/vnd.chromium.ftp-dir";
      return true;
    }
  } else {
    // No special handling of MIME type is needed. As opposed to direct FTP
    // transaction, we do not get a raw directory listing to parse.
    return http_transaction_->GetResponseInfo()->
        headers->GetMimeType(mime_type);
  }
  return false;
}

void URLRequestFtpJob::GetResponseInfo(HttpResponseInfo* info) {
  if (http_response_info_)
    *info = *http_response_info_;
}

HostPortPair URLRequestFtpJob::GetSocketAddress() const {
  if (proxy_info_.is_direct()) {
    if (!ftp_transaction_)
      return HostPortPair();
    return ftp_transaction_->GetResponseInfo()->socket_address;
  } else {
    if (!http_transaction_)
      return HostPortPair();
    return http_transaction_->GetResponseInfo()->socket_address;
  }
}

void URLRequestFtpJob::SetPriority(RequestPriority priority) {
  priority_ = priority;
  if (http_transaction_)
    http_transaction_->SetPriority(priority);
}

void URLRequestFtpJob::Start() {
  DCHECK(!pac_request_);
  DCHECK(!ftp_transaction_);
  DCHECK(!http_transaction_);

  int rv = OK;
  if (request_->load_flags() & LOAD_BYPASS_PROXY) {
    proxy_info_.UseDirect();
  } else {
    DCHECK_EQ(request_->context()->proxy_service(), proxy_service_);
    rv = proxy_service_->ResolveProxy(
        request_->url(),
        &proxy_info_,
        base::Bind(&URLRequestFtpJob::OnResolveProxyComplete,
                   base::Unretained(this)),
        &pac_request_,
        request_->net_log());

    if (rv == ERR_IO_PENDING)
      return;
  }
  OnResolveProxyComplete(rv);
}

void URLRequestFtpJob::Kill() {
  if (ftp_transaction_)
    ftp_transaction_.reset();
  if (http_transaction_)
    http_transaction_.reset();
  URLRequestJob::Kill();
  weak_factory_.InvalidateWeakPtrs();
}

void URLRequestFtpJob::OnResolveProxyComplete(int result) {
  pac_request_ = NULL;

  if (result != OK) {
    OnStartCompletedAsync(result);
    return;
  }

  // Remove unsupported proxies from the list.
  proxy_info_.RemoveProxiesWithoutScheme(
      ProxyServer::SCHEME_DIRECT |
      ProxyServer::SCHEME_HTTP |
      ProxyServer::SCHEME_HTTPS);

  // TODO(phajdan.jr): Implement proxy fallback, http://crbug.com/171495 .
  if (proxy_info_.is_direct())
    StartFtpTransaction();
  else if (proxy_info_.is_http() || proxy_info_.is_https())
    StartHttpTransaction();
  else
    OnStartCompletedAsync(ERR_NO_SUPPORTED_PROXIES);
}

void URLRequestFtpJob::StartFtpTransaction() {
  // Create a transaction.
  DCHECK(!ftp_transaction_);

  ftp_request_info_.url = request_->url();
  ftp_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 (ftp_transaction_) {
    rv = ftp_transaction_->Start(
        &ftp_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.
  OnStartCompletedAsync(rv);
}

void URLRequestFtpJob::StartHttpTransaction() {
  // Create a transaction.
  DCHECK(!http_transaction_);

  // Do not cache FTP responses sent through HTTP proxy.
  request_->SetLoadFlags(request_->load_flags() |
                         LOAD_DISABLE_CACHE |
                         LOAD_DO_NOT_SAVE_COOKIES |
                         LOAD_DO_NOT_SEND_COOKIES);

  http_request_info_.url = request_->url();
  http_request_info_.method = request_->method();
  http_request_info_.load_flags = request_->load_flags();

  int rv = request_->context()->http_transaction_factory()->CreateTransaction(
      priority_, &http_transaction_);
  if (rv == OK) {
    rv = http_transaction_->Start(
        &http_request_info_,
        base::Bind(&URLRequestFtpJob::OnStartCompleted,
                  base::Unretained(this)),
        request_->net_log());
    if (rv == ERR_IO_PENDING)
      return;
  }
  // The transaction started synchronously, but we need to notify the
  // URLRequest delegate via the message loop.
  OnStartCompletedAsync(rv);
}

void URLRequestFtpJob::OnStartCompleted(int result) {
  // Clear the IO_PENDING status
  SetStatus(URLRequestStatus());

  // Note that ftp_transaction_ may be NULL due to a creation failure.
  if (ftp_transaction_) {
    // FTP obviously doesn't have HTTP Content-Length header. We have to pass
    // the content size information manually.
    set_expected_content_size(
        ftp_transaction_->GetResponseInfo()->expected_content_size);
  }

  if (result == OK) {
    if (http_transaction_) {
      http_response_info_ = http_transaction_->GetResponseInfo();

      if (http_response_info_->headers->response_code() == 401 ||
          http_response_info_->headers->response_code() == 407) {
        HandleAuthNeededResponse();
        return;
      }
    }
    NotifyHeadersComplete();
  } else if (ftp_transaction_ &&
             ftp_transaction_->GetResponseInfo()->needs_auth) {
    HandleAuthNeededResponse();
    return;
  } else {
    NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
  }
}

void URLRequestFtpJob::OnStartCompletedAsync(int result) {
  base::MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&URLRequestFtpJob::OnStartCompleted,
                 weak_factory_.GetWeakPtr(), 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(auth_data_.get() && auth_data_->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;
  if (proxy_info_.is_direct()) {
    rv = ftp_transaction_->RestartWithAuth(
        auth_data_->credentials,
        base::Bind(&URLRequestFtpJob::OnStartCompleted,
                   base::Unretained(this)));
  } else {
    rv = http_transaction_->RestartWithAuth(
        auth_data_->credentials,
        base::Bind(&URLRequestFtpJob::OnStartCompleted,
                   base::Unretained(this)));
  }
  if (rv == ERR_IO_PENDING)
    return;

  OnStartCompletedAsync(rv);
}

LoadState URLRequestFtpJob::GetLoadState() const {
  if (proxy_info_.is_direct()) {
    return ftp_transaction_ ?
        ftp_transaction_->GetLoadState() : LOAD_STATE_IDLE;
  } else {
    return http_transaction_ ?
        http_transaction_->GetLoadState() : LOAD_STATE_IDLE;
  }
}

bool URLRequestFtpJob::NeedsAuth() {
  return auth_data_.get() && auth_data_->state == AUTH_STATE_NEED_AUTH;
}

void URLRequestFtpJob::GetAuthChallengeInfo(
    scoped_refptr<AuthChallengeInfo>* result) {
  DCHECK(NeedsAuth());

  if (http_response_info_) {
    *result = http_response_info_->auth_challenge;
    return;
  }

  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(ftp_transaction_ || http_transaction_);
  DCHECK(NeedsAuth());

  auth_data_->state = AUTH_STATE_HAVE_AUTH;
  auth_data_->credentials = credentials;

  if (ftp_transaction_) {
    ftp_auth_cache_->Add(request_->url().GetOrigin(),
                         auth_data_->credentials);
  }

  RestartTransactionWithAuth();
}

void URLRequestFtpJob::CancelAuth() {
  DCHECK(ftp_transaction_ || http_transaction_);
  DCHECK(NeedsAuth());

  auth_data_->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.
  OnStartCompletedAsync(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;
  if (proxy_info_.is_direct()) {
    rv = ftp_transaction_->Read(buf, buf_size,
                                base::Bind(&URLRequestFtpJob::OnReadCompleted,
                                           base::Unretained(this)));
  } else {
    rv = http_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;
}

void URLRequestFtpJob::HandleAuthNeededResponse() {
  GURL origin = request_->url().GetOrigin();

  if (auth_data_.get()) {
    if (auth_data_->state == AUTH_STATE_CANCELED) {
      NotifyHeadersComplete();
      return;
    }

    if (ftp_transaction_ && auth_data_->state == AUTH_STATE_HAVE_AUTH)
      ftp_auth_cache_->Remove(origin, auth_data_->credentials);
  } else {
    auth_data_ = new AuthData;
  }
  auth_data_->state = AUTH_STATE_NEED_AUTH;

  FtpAuthCache::Entry* cached_auth = NULL;
  if (ftp_transaction_ && ftp_transaction_->GetResponseInfo()->needs_auth)
    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();
  }
}

}  // namespace net

/* [<][>][^][v][top][bottom][index][help] */