// 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 "content/browser/download/download_resource_handler.h"

#include <string>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "content/browser/byte_stream.h"
#include "content/browser/download/download_create_info.h"
#include "content/browser/download/download_interrupt_reasons_impl.h"
#include "content/browser/download/download_manager_impl.h"
#include "content/browser/download/download_request_handle.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/loader/resource_controller.h"
#include "content/browser/loader/resource_dispatcher_host_impl.h"
#include "content/browser/loader/resource_request_info_impl.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_interrupt_reasons.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/browser_side_navigation_policy.h"
#include "content/public/common/resource_response.h"

namespace content {

struct DownloadResourceHandler::DownloadTabInfo {
  GURL tab_url;
  GURL tab_referrer_url;
};

namespace {

// Static function in order to prevent any accidental accesses to
// DownloadResourceHandler members from the UI thread.
static void StartOnUIThread(
    std::unique_ptr<DownloadCreateInfo> info,
    std::unique_ptr<DownloadResourceHandler::DownloadTabInfo> tab_info,
    std::unique_ptr<ByteStreamReader> stream,
    int render_process_id,
    int render_frame_id,
    int frame_tree_node_id,
    const DownloadUrlParameters::OnStartedCallback& started_cb) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  RenderFrameHost* frame_host =
      RenderFrameHost::FromID(render_process_id, render_frame_id);

  // PlzNavigate: navigations don't have associated RenderFrameHosts. Get the
  // SiteInstance from the FrameTreeNode.
  if (!frame_host && IsBrowserSideNavigationEnabled()) {
    FrameTreeNode* frame_tree_node =
        FrameTreeNode::GloballyFindByID(frame_tree_node_id);
    if (frame_tree_node)
      frame_host = frame_tree_node->current_frame_host();
  }

  DownloadManager* download_manager =
      info->request_handle->GetDownloadManager();
  if (!download_manager || !frame_host) {
    // NULL in unittests or if the page closed right after starting the
    // download.
    if (!started_cb.is_null())
      started_cb.Run(nullptr, DOWNLOAD_INTERRUPT_REASON_USER_CANCELED);

    if (stream)
      BrowserThread::DeleteSoon(BrowserThread::FILE, FROM_HERE,
                                stream.release());
    return;
  }

  info->tab_url = tab_info->tab_url;
  info->tab_referrer_url = tab_info->tab_referrer_url;
  info->site_url = frame_host->GetSiteInstance()->GetSiteURL();

  download_manager->StartDownload(std::move(info), std::move(stream),
                                  started_cb);
}

void InitializeDownloadTabInfoOnUIThread(
    const DownloadRequestHandle& request_handle,
    DownloadResourceHandler::DownloadTabInfo* tab_info) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  WebContents* web_contents = request_handle.GetWebContents();
  if (web_contents) {
    NavigationEntry* entry = web_contents->GetController().GetVisibleEntry();
    if (entry) {
      tab_info->tab_url = entry->GetURL();
      tab_info->tab_referrer_url = entry->GetReferrer().url;
    }
  }
}

void DeleteOnUIThread(
    std::unique_ptr<DownloadResourceHandler::DownloadTabInfo> tab_info) {}

}  // namespace

DownloadResourceHandler::DownloadResourceHandler(net::URLRequest* request)
    : ResourceHandler(request),
      tab_info_(new DownloadTabInfo()),
      core_(request, this, false) {
  // Do UI thread initialization for tab_info_ asap after
  // DownloadResourceHandler creation since the tab could be navigated
  // before StartOnUIThread gets called.  This is safe because deletion
  // will occur via PostTask() as well, which will serialized behind this
  // PostTask()
  const ResourceRequestInfoImpl* request_info = GetRequestInfo();
  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(
          &InitializeDownloadTabInfoOnUIThread,
          DownloadRequestHandle(AsWeakPtr(),
                                request_info->GetWebContentsGetterForRequest()),
          tab_info_.get()));
}

DownloadResourceHandler::~DownloadResourceHandler() {
  if (tab_info_) {
    BrowserThread::PostTask(
        BrowserThread::UI, FROM_HERE,
        base::Bind(&DeleteOnUIThread, base::Passed(&tab_info_)));
  }
}

// static
std::unique_ptr<ResourceHandler> DownloadResourceHandler::Create(
    net::URLRequest* request) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  std::unique_ptr<ResourceHandler> handler(
      new DownloadResourceHandler(request));
  return handler;
}

void DownloadResourceHandler::OnRequestRedirected(
    const net::RedirectInfo& redirect_info,
    ResourceResponse* response,
    std::unique_ptr<ResourceController> controller) {
  if (core_.OnRequestRedirected()) {
    controller->Resume();
  } else {
    controller->Cancel();
  }
}

// Send the download creation information to the download thread.
void DownloadResourceHandler::OnResponseStarted(
    ResourceResponse* response,
    std::unique_ptr<ResourceController> controller) {
  // The MIME type in ResourceResponse is the product of
  // MimeTypeResourceHandler.
  if (core_.OnResponseStarted(response->head.mime_type)) {
    controller->Resume();
  } else {
    controller->Cancel();
  }
}

void DownloadResourceHandler::OnWillStart(
    const GURL& url,
    std::unique_ptr<ResourceController> controller) {
  controller->Resume();
}

// Create a new buffer, which will be handed to the download thread for file
// writing and deletion.
void DownloadResourceHandler::OnWillRead(
    scoped_refptr<net::IOBuffer>* buf,
    int* buf_size,
    std::unique_ptr<ResourceController> controller) {
  if (!core_.OnWillRead(buf, buf_size)) {
    controller->Cancel();
    return;
  }

  controller->Resume();
}

// Pass the buffer to the download file writer.
void DownloadResourceHandler::OnReadCompleted(
    int bytes_read,
    std::unique_ptr<ResourceController> controller) {
  DCHECK(!has_controller());

  bool defer = false;
  if (!core_.OnReadCompleted(bytes_read, &defer)) {
    controller->Cancel();
    return;
  }

  if (defer) {
    HoldController(std::move(controller));
  } else {
    controller->Resume();
  }
}

void DownloadResourceHandler::OnResponseCompleted(
    const net::URLRequestStatus& status,
    std::unique_ptr<ResourceController> controller) {
  core_.OnResponseCompleted(status);
  controller->Resume();
}

void DownloadResourceHandler::OnDataDownloaded(int bytes_downloaded) {
  NOTREACHED();
}

void DownloadResourceHandler::PauseRequest() {
  core_.PauseRequest();
}

void DownloadResourceHandler::ResumeRequest() {
  core_.ResumeRequest();
}

void DownloadResourceHandler::OnStart(
    std::unique_ptr<DownloadCreateInfo> create_info,
    std::unique_ptr<ByteStreamReader> stream_reader,
    const DownloadUrlParameters::OnStartedCallback& callback) {
  // If the user cancels the download, then don't call start. Instead ignore the
  // download entirely.
  if (create_info->result == DOWNLOAD_INTERRUPT_REASON_USER_CANCELED &&
      create_info->download_id == DownloadItem::kInvalidId) {
    if (!callback.is_null())
      BrowserThread::PostTask(
          BrowserThread::UI, FROM_HERE,
          base::Bind(callback, nullptr, create_info->result));
    return;
  }

  const ResourceRequestInfoImpl* request_info = GetRequestInfo();
  create_info->has_user_gesture = request_info->HasUserGesture();
  create_info->transition_type = request_info->GetPageTransition();

  create_info->request_handle.reset(new DownloadRequestHandle(
      AsWeakPtr(), request_info->GetWebContentsGetterForRequest()));

  int render_process_id = -1;
  int render_frame_id = -1;
  request_info->GetAssociatedRenderFrame(&render_process_id, &render_frame_id);

  BrowserThread::PostTask(
      BrowserThread::UI, FROM_HERE,
      base::Bind(&StartOnUIThread, base::Passed(&create_info),
                 base::Passed(&tab_info_), base::Passed(&stream_reader),
                 render_process_id, render_frame_id,
                 request_info->frame_tree_node_id(), callback));
}

void DownloadResourceHandler::OnReadyToRead() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  Resume();
}

void DownloadResourceHandler::CancelRequest() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  const ResourceRequestInfoImpl* info = GetRequestInfo();
  ResourceDispatcherHostImpl::Get()->CancelRequest(
      info->GetChildID(),
      info->GetRequestID());
  // This object has been deleted.
}

std::string DownloadResourceHandler::DebugString() const {
  const ResourceRequestInfoImpl* info = GetRequestInfo();
  return base::StringPrintf("{"
                            " url_ = " "\"%s\""
                            " info = {"
                            " child_id = " "%d"
                            " request_id = " "%d"
                            " route_id = " "%d"
                            " }"
                            " }",
                            request() ?
                                request()->url().spec().c_str() :
                                "<NULL request>",
                            info->GetChildID(),
                            info->GetRequestID(),
                            info->GetRouteID());
}

}  // namespace content
