// Copyright (c) 2011 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.h"

#include "base/compiler_specific.h"
#include "base/memory/singleton.h"
#include "base/message_loop.h"
#include "base/metrics/stats_counters.h"
#include "base/synchronization/lock.h"
#include "net/base/host_port_pair.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/net_log.h"
#include "net/base/network_delegate.h"
#include "net/base/ssl_cert_request_info.h"
#include "net/base/upload_data.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_error_job.h"
#include "net/url_request/url_request_job.h"
#include "net/url_request/url_request_job_manager.h"
#include "net/url_request/url_request_netlog_params.h"
#include "net/url_request/url_request_redirect_job.h"

using base::Time;
using std::string;

namespace net {

namespace {

// Max number of http redirects to follow.  Same number as gecko.
const int kMaxRedirects = 20;

// Discard headers which have meaning in POST (Content-Length, Content-Type,
// Origin).
void StripPostSpecificHeaders(HttpRequestHeaders* headers) {
  // These are headers that may be attached to a POST.
  headers->RemoveHeader(HttpRequestHeaders::kContentLength);
  headers->RemoveHeader(HttpRequestHeaders::kContentType);
  headers->RemoveHeader(HttpRequestHeaders::kOrigin);
}

// This counter keeps track of the identifiers used for URL requests so far.
// 0 is reserved to represent an invalid ID.
uint64 g_next_url_request_identifier = 1;

// This lock protects g_next_url_request_identifier.
base::Lock g_next_url_request_identifier_lock;

// Returns an prior unused identifier for URL requests.
uint64 GenerateURLRequestIdentifier() {
  base::AutoLock lock(g_next_url_request_identifier_lock);
  return g_next_url_request_identifier++;
}

}  // namespace

///////////////////////////////////////////////////////////////////////////////
// URLRequest::Interceptor

URLRequestJob* URLRequest::Interceptor::MaybeInterceptRedirect(
    URLRequest* request,
    const GURL& location) {
  return NULL;
}

URLRequestJob* URLRequest::Interceptor::MaybeInterceptResponse(
    URLRequest* request) {
  return NULL;
}

///////////////////////////////////////////////////////////////////////////////
// URLRequest::Delegate

void URLRequest::Delegate::OnReceivedRedirect(URLRequest* request,
                                              const GURL& new_url,
                                              bool* defer_redirect) {
}

void URLRequest::Delegate::OnAuthRequired(URLRequest* request,
                                          AuthChallengeInfo* auth_info) {
  request->CancelAuth();
}

void URLRequest::Delegate::OnCertificateRequested(
    URLRequest* request,
    SSLCertRequestInfo* cert_request_info) {
  request->ContinueWithCertificate(NULL);
}

void URLRequest::Delegate::OnSSLCertificateError(URLRequest* request,
                                                 int cert_error,
                                                 X509Certificate* cert) {
  request->Cancel();
}

void URLRequest::Delegate::OnGetCookies(URLRequest* request,
                                        bool blocked_by_policy) {
}

void URLRequest::Delegate::OnSetCookie(URLRequest* request,
                                       const std::string& cookie_line,
                                       const CookieOptions& options,
                                       bool blocked_by_policy) {
}

///////////////////////////////////////////////////////////////////////////////
// URLRequest

URLRequest::URLRequest(const GURL& url, Delegate* delegate)
    : url_chain_(1, url),
      method_("GET"),
      load_flags_(LOAD_NORMAL),
      delegate_(delegate),
      is_pending_(false),
      redirect_limit_(kMaxRedirects),
      final_upload_progress_(0),
      priority_(LOWEST),
      identifier_(GenerateURLRequestIdentifier()),
      ALLOW_THIS_IN_INITIALIZER_LIST(
          before_request_callback_(this, &URLRequest::BeforeRequestComplete)) {
  SIMPLE_STATS_COUNTER("URLRequestCount");

  // Sanity check out environment.
  DCHECK(MessageLoop::current()) <<
      "The current MessageLoop must exist";
  DCHECK_EQ(MessageLoop::TYPE_IO, MessageLoop::current()->type()) <<
      "The current MessageLoop must be TYPE_IO";
}

URLRequest::~URLRequest() {
  if (context_ && context_->network_delegate())
    context_->network_delegate()->NotifyURLRequestDestroyed(this);

  Cancel();

  if (job_)
    OrphanJob();

  set_context(NULL);
}

// static
URLRequest::ProtocolFactory* URLRequest::RegisterProtocolFactory(
    const string& scheme, ProtocolFactory* factory) {
  return URLRequestJobManager::GetInstance()->RegisterProtocolFactory(scheme,
                                                                      factory);
}

// static
void URLRequest::RegisterRequestInterceptor(Interceptor* interceptor) {
  URLRequestJobManager::GetInstance()->RegisterRequestInterceptor(interceptor);
}

// static
void URLRequest::UnregisterRequestInterceptor(Interceptor* interceptor) {
  URLRequestJobManager::GetInstance()->UnregisterRequestInterceptor(
      interceptor);
}

void URLRequest::AppendBytesToUpload(const char* bytes, int bytes_len) {
  DCHECK(bytes_len > 0 && bytes);
  if (!upload_)
    upload_ = new UploadData();
  upload_->AppendBytes(bytes, bytes_len);
}

void URLRequest::AppendFileRangeToUpload(
    const FilePath& file_path,
    uint64 offset,
    uint64 length,
    const base::Time& expected_modification_time) {
  DCHECK(file_path.value().length() > 0 && length > 0);
  if (!upload_)
    upload_ = new UploadData();
  upload_->AppendFileRange(file_path, offset, length,
                           expected_modification_time);
}

void URLRequest::EnableChunkedUpload() {
  DCHECK(!upload_ || upload_->is_chunked());
  if (!upload_) {
    upload_ = new UploadData();
    upload_->set_is_chunked(true);
  }
}

void URLRequest::AppendChunkToUpload(const char* bytes,
                                     int bytes_len,
                                     bool is_last_chunk) {
  DCHECK(upload_);
  DCHECK(upload_->is_chunked());
  DCHECK_GT(bytes_len, 0);
  upload_->AppendChunk(bytes, bytes_len, is_last_chunk);
}

void URLRequest::set_upload(UploadData* upload) {
  upload_ = upload;
}

// Get the upload data directly.
UploadData* URLRequest::get_upload() {
  return upload_.get();
}

bool URLRequest::has_upload() const {
  return upload_ != NULL;
}

void URLRequest::SetExtraRequestHeaderById(int id, const string& value,
                                           bool overwrite) {
  DCHECK(!is_pending_);
  NOTREACHED() << "implement me!";
}

void URLRequest::SetExtraRequestHeaderByName(const string& name,
                                             const string& value,
                                             bool overwrite) {
  DCHECK(!is_pending_);
  if (overwrite) {
    extra_request_headers_.SetHeader(name, value);
  } else {
    extra_request_headers_.SetHeaderIfMissing(name, value);
  }
}

void URLRequest::SetExtraRequestHeaders(
    const HttpRequestHeaders& headers) {
  DCHECK(!is_pending_);
  extra_request_headers_ = headers;

  // NOTE: This method will likely become non-trivial once the other setters
  // for request headers are implemented.
}

LoadState URLRequest::GetLoadState() const {
  return job_ ? job_->GetLoadState() : LOAD_STATE_IDLE;
}

uint64 URLRequest::GetUploadProgress() const {
  if (!job_) {
    // We haven't started or the request was cancelled
    return 0;
  }
  if (final_upload_progress_) {
    // The first job completed and none of the subsequent series of
    // GETs when following redirects will upload anything, so we return the
    // cached results from the initial job, the POST.
    return final_upload_progress_;
  }
  return job_->GetUploadProgress();
}

void URLRequest::GetResponseHeaderById(int id, string* value) {
  DCHECK(job_);
  NOTREACHED() << "implement me!";
}

void URLRequest::GetResponseHeaderByName(const string& name, string* value) {
  DCHECK(value);
  if (response_info_.headers) {
    response_info_.headers->GetNormalizedHeader(name, value);
  } else {
    value->clear();
  }
}

void URLRequest::GetAllResponseHeaders(string* headers) {
  DCHECK(headers);
  if (response_info_.headers) {
    response_info_.headers->GetNormalizedHeaders(headers);
  } else {
    headers->clear();
  }
}

HostPortPair URLRequest::GetSocketAddress() const {
  DCHECK(job_);
  return job_->GetSocketAddress();
}

HttpResponseHeaders* URLRequest::response_headers() const {
  return response_info_.headers.get();
}

bool URLRequest::GetResponseCookies(ResponseCookies* cookies) {
  DCHECK(job_);
  return job_->GetResponseCookies(cookies);
}

void URLRequest::GetMimeType(string* mime_type) {
  DCHECK(job_);
  job_->GetMimeType(mime_type);
}

void URLRequest::GetCharset(string* charset) {
  DCHECK(job_);
  job_->GetCharset(charset);
}

int URLRequest::GetResponseCode() {
  DCHECK(job_);
  return job_->GetResponseCode();
}

// static
bool URLRequest::IsHandledProtocol(const std::string& scheme) {
  return URLRequestJobManager::GetInstance()->SupportsScheme(scheme);
}

// static
bool URLRequest::IsHandledURL(const GURL& url) {
  if (!url.is_valid()) {
    // We handle error cases.
    return true;
  }

  return IsHandledProtocol(url.scheme());
}

// static
void URLRequest::AllowFileAccess() {
  URLRequestJobManager::GetInstance()->set_enable_file_access(true);
}

// static
bool URLRequest::IsFileAccessAllowed() {
  return URLRequestJobManager::GetInstance()->enable_file_access();
}


void URLRequest::set_first_party_for_cookies(
    const GURL& first_party_for_cookies) {
  first_party_for_cookies_ = first_party_for_cookies;
}

void URLRequest::set_method(const std::string& method) {
  DCHECK(!is_pending_);
  method_ = method;
}

void URLRequest::set_referrer(const std::string& referrer) {
  DCHECK(!is_pending_);
  referrer_ = referrer;
}

GURL URLRequest::GetSanitizedReferrer() const {
  GURL ret(referrer());

  // Ensure that we do not send username and password fields in the referrer.
  if (ret.has_username() || ret.has_password()) {
    GURL::Replacements referrer_mods;
    referrer_mods.ClearUsername();
    referrer_mods.ClearPassword();
    ret = ret.ReplaceComponents(referrer_mods);
  }

  return ret;
}

void URLRequest::Start() {
  response_info_.request_time = Time::Now();

  // Only notify the delegate for the initial request.
  if (context_ && context_->network_delegate()) {
    if (context_->network_delegate()->NotifyBeforeURLRequest(
            this, &before_request_callback_, &delegate_redirect_url_) ==
            net::ERR_IO_PENDING) {
      net_log_.BeginEvent(NetLog::TYPE_URL_REQUEST_BLOCKED_ON_EXTENSION, NULL);
      return;  // paused
    }
  }

  StartInternal();
}

///////////////////////////////////////////////////////////////////////////////

void URLRequest::BeforeRequestComplete(int error) {
  DCHECK(!job_);
  DCHECK_NE(ERR_IO_PENDING, error);

  net_log_.EndEvent(NetLog::TYPE_URL_REQUEST_BLOCKED_ON_EXTENSION, NULL);
  if (error != OK) {
    StartJob(new URLRequestErrorJob(this, error));
  } else if (!delegate_redirect_url_.is_empty()) {
    GURL new_url;
    new_url.Swap(&delegate_redirect_url_);
    StartJob(new URLRequestRedirectJob(this, new_url));
  } else {
    StartInternal();
  }
}

void URLRequest::StartInternal() {
  StartJob(URLRequestJobManager::GetInstance()->CreateJob(this));
}

void URLRequest::StartJob(URLRequestJob* job) {
  DCHECK(!is_pending_);
  DCHECK(!job_);

  net_log_.BeginEvent(
      NetLog::TYPE_URL_REQUEST_START_JOB,
      make_scoped_refptr(new URLRequestStartEventParameters(
          url(), method_, load_flags_, priority_)));

  job_ = job;
  job_->SetExtraRequestHeaders(extra_request_headers_);

  if (upload_.get())
    job_->SetUpload(upload_.get());

  is_pending_ = true;

  response_info_.was_cached = false;

  // Don't allow errors to be sent from within Start().
  // TODO(brettw) this may cause NotifyDone to be sent synchronously,
  // we probably don't want this: they should be sent asynchronously so
  // the caller does not get reentered.
  job_->Start();
}

void URLRequest::Restart() {
  // Should only be called if the original job didn't make any progress.
  DCHECK(job_ && !job_->has_response_started());
  RestartWithJob(URLRequestJobManager::GetInstance()->CreateJob(this));
}

void URLRequest::RestartWithJob(URLRequestJob *job) {
  DCHECK(job->request() == this);
  PrepareToRestart();
  StartJob(job);
}

void URLRequest::Cancel() {
  DoCancel(ERR_ABORTED, SSLInfo());
}

void URLRequest::SimulateError(int os_error) {
  DoCancel(os_error, SSLInfo());
}

void URLRequest::SimulateSSLError(int os_error, const SSLInfo& ssl_info) {
  // This should only be called on a started request.
  if (!is_pending_ || !job_ || job_->has_response_started()) {
    NOTREACHED();
    return;
  }
  DoCancel(os_error, ssl_info);
}

void URLRequest::DoCancel(int os_error, const SSLInfo& ssl_info) {
  DCHECK(os_error < 0);

  // If the URL request already has an error status, then canceling is a no-op.
  // Plus, we don't want to change the error status once it has been set.
  if (status_.is_success()) {
    status_.set_status(URLRequestStatus::CANCELED);
    status_.set_os_error(os_error);
    response_info_.ssl_info = ssl_info;
  }

  // There's nothing to do if we are not waiting on a Job.
  if (!is_pending_ || !job_)
    return;

  job_->Kill();

  // The Job will call our NotifyDone method asynchronously.  This is done so
  // that the Delegate implementation can call Cancel without having to worry
  // about being called recursively.
}

bool URLRequest::Read(IOBuffer* dest, int dest_size, int* bytes_read) {
  DCHECK(job_);
  DCHECK(bytes_read);
  DCHECK(!job_->is_done());
  *bytes_read = 0;

  if (dest_size == 0) {
    // Caller is not too bright.  I guess we've done what they asked.
    return true;
  }

  // Once the request fails or is cancelled, read will just return 0 bytes
  // to indicate end of stream.
  if (!status_.is_success()) {
    return true;
  }

  return job_->Read(dest, dest_size, bytes_read);
}

void URLRequest::StopCaching() {
  DCHECK(job_);
  job_->StopCaching();
}

void URLRequest::ReceivedRedirect(const GURL& location, bool* defer_redirect) {
  URLRequestJob* job =
      URLRequestJobManager::GetInstance()->MaybeInterceptRedirect(this,
                                                                  location);
  if (job) {
    RestartWithJob(job);
  } else if (delegate_) {
    delegate_->OnReceivedRedirect(this, location, defer_redirect);
  }
}

void URLRequest::ResponseStarted() {
  scoped_refptr<NetLog::EventParameters> params;
  if (!status_.is_success())
    params = new NetLogIntegerParameter("net_error", status_.os_error());
  net_log_.EndEvent(NetLog::TYPE_URL_REQUEST_START_JOB, params);

  URLRequestJob* job =
      URLRequestJobManager::GetInstance()->MaybeInterceptResponse(this);
  if (job) {
    RestartWithJob(job);
  } else {
    if (context_ && context_->network_delegate())
      context_->network_delegate()->NotifyResponseStarted(this);
    if (delegate_)
      delegate_->OnResponseStarted(this);
  }
}

void URLRequest::FollowDeferredRedirect() {
  CHECK(job_);
  CHECK(status_.is_success());

  job_->FollowDeferredRedirect();
}

void URLRequest::SetAuth(const string16& username, const string16& password) {
  DCHECK(job_);
  DCHECK(job_->NeedsAuth());

  job_->SetAuth(username, password);
}

void URLRequest::CancelAuth() {
  DCHECK(job_);
  DCHECK(job_->NeedsAuth());

  job_->CancelAuth();
}

void URLRequest::ContinueWithCertificate(X509Certificate* client_cert) {
  DCHECK(job_);

  job_->ContinueWithCertificate(client_cert);
}

void URLRequest::ContinueDespiteLastError() {
  DCHECK(job_);

  job_->ContinueDespiteLastError();
}

void URLRequest::PrepareToRestart() {
  DCHECK(job_);

  // Close the current URL_REQUEST_START_JOB, since we will be starting a new
  // one.
  net_log_.EndEvent(NetLog::TYPE_URL_REQUEST_START_JOB, NULL);

  OrphanJob();

  response_info_ = HttpResponseInfo();
  response_info_.request_time = Time::Now();
  status_ = URLRequestStatus();
  is_pending_ = false;
}

void URLRequest::OrphanJob() {
  job_->Kill();
  job_->DetachRequest();  // ensures that the job will not call us again
  job_ = NULL;
}

int URLRequest::Redirect(const GURL& location, int http_status_code) {
  if (net_log_.IsLoggingAllEvents()) {
    net_log_.AddEvent(
        NetLog::TYPE_URL_REQUEST_REDIRECTED,
        make_scoped_refptr(new NetLogStringParameter(
            "location", location.possibly_invalid_spec())));
  }
  if (redirect_limit_ <= 0) {
    DVLOG(1) << "disallowing redirect: exceeds limit";
    return ERR_TOO_MANY_REDIRECTS;
  }

  if (!location.is_valid())
    return ERR_INVALID_URL;

  if (!job_->IsSafeRedirect(location)) {
    DVLOG(1) << "disallowing redirect: unsafe protocol";
    return ERR_UNSAFE_REDIRECT;
  }

  bool strip_post_specific_headers = false;
  if (http_status_code != 307) {
    // NOTE: Even though RFC 2616 says to preserve the request method when
    // following a 302 redirect, normal browsers don't do that.  Instead, they
    // all convert a POST into a GET in response to a 302 and so shall we.  For
    // 307 redirects, browsers preserve the method.  The RFC says to prompt the
    // user to confirm the generation of a new POST request, but IE omits this
    // prompt and so shall we.
    strip_post_specific_headers = method_ == "POST";
    method_ = "GET";
    upload_ = NULL;
  }

  // Suppress the referrer if we're redirecting out of https.
  if (GURL(referrer_).SchemeIsSecure() && !location.SchemeIsSecure())
    referrer_.clear();

  url_chain_.push_back(location);
  --redirect_limit_;

  if (strip_post_specific_headers) {
    // If being switched from POST to GET, must remove headers that were
    // specific to the POST and don't have meaning in GET. For example
    // the inclusion of a multipart Content-Type header in GET can cause
    // problems with some servers:
    // http://code.google.com/p/chromium/issues/detail?id=843
    StripPostSpecificHeaders(&extra_request_headers_);
  }

  if (!final_upload_progress_)
    final_upload_progress_ = job_->GetUploadProgress();

  PrepareToRestart();
  StartInternal();
  return OK;
}

URLRequestContext* URLRequest::context() const {
  return context_.get();
}

void URLRequest::set_context(URLRequestContext* context) {
  scoped_refptr<URLRequestContext> prev_context = context_;

  context_ = context;

  // If the context this request belongs to has changed, update the tracker.
  if (prev_context != context) {
    net_log_.EndEvent(NetLog::TYPE_REQUEST_ALIVE, NULL);
    net_log_ = BoundNetLog();

    if (context) {
      net_log_ = BoundNetLog::Make(context->net_log(),
                                   NetLog::SOURCE_URL_REQUEST);
      net_log_.BeginEvent(NetLog::TYPE_REQUEST_ALIVE, NULL);
    }
  }
}

int64 URLRequest::GetExpectedContentSize() const {
  int64 expected_content_size = -1;
  if (job_)
    expected_content_size = job_->expected_content_size();

  return expected_content_size;
}

URLRequest::UserData* URLRequest::GetUserData(const void* key) const {
  UserDataMap::const_iterator found = user_data_.find(key);
  if (found != user_data_.end())
    return found->second.get();
  return NULL;
}

void URLRequest::SetUserData(const void* key, UserData* data) {
  user_data_[key] = linked_ptr<UserData>(data);
}

}  // namespace net