// 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 "chrome/browser/prerender/prerender_resource_handler.h"

#include "chrome/browser/net/chrome_url_request_context.h"
#include "content/common/resource_response.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"

namespace prerender {

namespace {

bool ShouldPrerenderURL(const GURL& url) {
  if (!url.is_valid())
    return false;
  if (!url.SchemeIs("http")) {
    RecordFinalStatus(FINAL_STATUS_HTTPS);
    return false;
  }
  return true;
}

bool ValidateAliasURLs(const std::vector<GURL>& urls) {
  for (std::vector<GURL>::const_iterator it = urls.begin();
       it != urls.end();
       ++it) {
    if (!ShouldPrerenderURL(*it))
      return false;
  }
  return true;
}

bool ShouldPrerender(const ResourceResponse* response) {
  if (!response)
    return false;
  const ResourceResponseHead& rrh = response->response_head;
  if (!rrh.headers)
    return false;
  if (rrh.mime_type != "text/html")
    return false;
  if (rrh.headers->response_code() != 200)
    return false;
  return true;
}

}  // namespace

PrerenderResourceHandler* PrerenderResourceHandler::MaybeCreate(
    const net::URLRequest& request,
    ChromeURLRequestContext* context,
    ResourceHandler* next_handler,
    bool is_from_prerender,
    int child_id,
    int route_id) {
  if (!context || !context->prerender_manager())
    return NULL;
  if (!(request.load_flags() & net::LOAD_PREFETCH))
    return NULL;
  if (!ShouldPrerenderURL(request.url()))
    return NULL;
  if (request.method() != "GET")
    return NULL;

  return new PrerenderResourceHandler(request,
                                      next_handler,
                                      context->prerender_manager(),
                                      is_from_prerender,
                                      child_id,
                                      route_id);
}

PrerenderResourceHandler::PrerenderResourceHandler(
    const net::URLRequest& request,
    ResourceHandler* next_handler,
    PrerenderManager* prerender_manager,
    bool make_pending,
    int child_id,
    int route_id)
    : next_handler_(next_handler),
      prerender_manager_(prerender_manager),
      ALLOW_THIS_IN_INITIALIZER_LIST(
          prerender_callback_(NewCallback(
              this, &PrerenderResourceHandler::StartPrerender))),
      request_(request),
      child_id_(child_id),
      route_id_(route_id),
      make_pending_(make_pending) {
  DCHECK(next_handler);
  DCHECK(prerender_manager);
}

PrerenderResourceHandler::PrerenderResourceHandler(
    const net::URLRequest& request,
    ResourceHandler* next_handler,
    PrerenderCallback* callback)
    : next_handler_(next_handler),
      prerender_callback_(callback),
      request_(request) {
  DCHECK(next_handler);
  DCHECK(callback);
}

PrerenderResourceHandler::~PrerenderResourceHandler() {
}

bool PrerenderResourceHandler::OnUploadProgress(int request_id,
                                                uint64 position,
                                                uint64 size) {
  return next_handler_->OnUploadProgress(request_id, position, size);
}

bool PrerenderResourceHandler::OnRequestRedirected(int request_id,
                                                   const GURL& url,
                                                   ResourceResponse* response,
                                                   bool* defer) {
  bool will_redirect = next_handler_->OnRequestRedirected(
      request_id, url, response, defer);
  if (will_redirect) {
    if (!ShouldPrerenderURL(url))
      return false;
    alias_urls_.push_back(url);
    url_ = url;
  }
  return will_redirect;
}

bool PrerenderResourceHandler::OnResponseStarted(int request_id,
                                                 ResourceResponse* response) {
  if (ShouldPrerender(response)) {
    DCHECK(ValidateAliasURLs(alias_urls_));
    BrowserThread::PostTask(
        BrowserThread::UI,
        FROM_HERE,
        NewRunnableMethod(
            this,
            &PrerenderResourceHandler::RunCallbackFromUIThread,
            std::make_pair(child_id_, route_id_),
            url_,
            alias_urls_,
            GURL(request_.referrer()),
            make_pending_));
  }
  return next_handler_->OnResponseStarted(request_id, response);
}

bool PrerenderResourceHandler::OnWillStart(int request_id,
                                           const GURL& url,
                                           bool* defer) {
  bool will_start = next_handler_->OnWillStart(request_id, url, defer);
  if (will_start) {
    if (!ShouldPrerenderURL(url))
      return false;
    alias_urls_.push_back(url);
    url_ = url;
  }
  return will_start;
}

bool PrerenderResourceHandler::OnWillRead(int request_id,
                                          net::IOBuffer** buf,
                                          int* buf_size,
                                          int min_size) {
  return next_handler_->OnWillRead(request_id, buf, buf_size, min_size);
}

bool PrerenderResourceHandler::OnReadCompleted(int request_id,
                                               int* bytes_read) {
  return next_handler_->OnReadCompleted(request_id, bytes_read);
}

bool PrerenderResourceHandler::OnResponseCompleted(
    int request_id,
    const net::URLRequestStatus& status,
    const std::string& security_info) {
  return next_handler_->OnResponseCompleted(request_id, status, security_info);
}

void PrerenderResourceHandler::OnRequestClosed() {
  next_handler_->OnRequestClosed();
}

void PrerenderResourceHandler::RunCallbackFromUIThread(
    const std::pair<int, int>& child_route_id_pair,
    const GURL& url,
    const std::vector<GURL>& alias_urls,
    const GURL& referrer,
    bool make_pending) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  prerender_callback_->Run(child_route_id_pair,
                           url, alias_urls, referrer,
                           make_pending);
}

void PrerenderResourceHandler::StartPrerender(
    const std::pair<int, int>& child_route_id_pair,
    const GURL& url,
    const std::vector<GURL>& alias_urls,
    const GURL& referrer,
    bool make_pending) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  if (!prerender_manager_->is_enabled())
    return;
  if (make_pending) {
    prerender_manager_->AddPendingPreload(child_route_id_pair,
                                          url, alias_urls, referrer);
  } else {
    prerender_manager_->AddPreload(url, alias_urls, referrer);
  }
}

}  // namespace prerender