// Copyright 2014 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/devtools/protocol/network_handler.h"

#include <stddef.h>

#include "base/containers/hash_tables.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/cert_store.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_client.h"
#include "net/cert/x509_cert_types.h"
#include "net/cert/x509_certificate.h"
#include "net/cookies/cookie_store.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"

namespace content {
namespace devtools {
namespace network {

using CookieListCallback = net::CookieStore::GetCookieListCallback;

namespace {

net::URLRequestContext* GetRequestContextOnIO(
    ResourceContext* resource_context,
    net::URLRequestContextGetter* context_getter,
    const GURL& url) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  net::URLRequestContext* context =
      GetContentClient()->browser()->OverrideRequestContextForURL(
          url, resource_context);
  if (!context)
    context = context_getter->GetURLRequestContext();
  return context;
}

void GotCookiesForURLOnIO(
    const CookieListCallback& callback,
    const net::CookieList& cookie_list) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  BrowserThread::PostTask(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(callback, cookie_list));
}

void GetCookiesForURLOnIO(
    ResourceContext* resource_context,
    net::URLRequestContextGetter* context_getter,
    const GURL& url,
    const CookieListCallback& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  net::URLRequestContext* request_context =
      GetRequestContextOnIO(resource_context, context_getter, url);
  request_context->cookie_store()->GetAllCookiesForURLAsync(
      url, base::Bind(&GotCookiesForURLOnIO, callback));
}

void GetCookiesForURLOnUI(
    ResourceContext* resource_context,
    net::URLRequestContextGetter* context_getter,
    const GURL& url,
    const CookieListCallback& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  BrowserThread::PostTask(
      BrowserThread::IO,
      FROM_HERE,
      base::Bind(&GetCookiesForURLOnIO,
                 base::Unretained(resource_context),
                 base::Unretained(context_getter),
                 url,
                 callback));
}

void DeletedCookieOnIO(const base::Closure& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  BrowserThread::PostTask(
      BrowserThread::UI,
      FROM_HERE,
      callback);
}

void DeleteCookieOnIO(
    ResourceContext* resource_context,
    net::URLRequestContextGetter* context_getter,
    const GURL& url,
    const std::string& cookie_name,
    const base::Closure& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);
  net::URLRequestContext* request_context =
      GetRequestContextOnIO(resource_context, context_getter, url);
  request_context->cookie_store()->DeleteCookieAsync(
      url, cookie_name, base::Bind(&DeletedCookieOnIO, callback));
}

void DeleteCookieOnUI(
    ResourceContext* resource_context,
    net::URLRequestContextGetter* context_getter,
    const GURL& url,
    const std::string& cookie_name,
    const base::Closure& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  BrowserThread::PostTask(
      BrowserThread::IO,
      FROM_HERE,
      base::Bind(&DeleteCookieOnIO,
                 base::Unretained(resource_context),
                 base::Unretained(context_getter),
                 url,
                 cookie_name,
                 callback));
}

class GetCookiesCommand {
 public:
  explicit GetCookiesCommand(
      RenderFrameHostImpl* frame_host,
      const CookieListCallback& callback)
      : callback_(callback),
        request_count_(0) {
    CookieListCallback got_cookies_callback = base::Bind(
        &GetCookiesCommand::GotCookiesForURL, base::Unretained(this));
    BrowserContext* browser_context =
        frame_host->GetSiteInstance()->GetBrowserContext();

    std::queue<FrameTreeNode*> queue;
    queue.push(frame_host->frame_tree_node());
    while (!queue.empty()) {
      FrameTreeNode* node = queue.front();
      queue.pop();

      // Only traverse nodes with the same local root.
      if (node->current_frame_host()->IsCrossProcessSubframe())
        continue;
      int process_id = node->current_frame_host()->GetProcess()->GetID();
      ++request_count_;
      GetCookiesForURLOnUI(
          browser_context->GetResourceContext(),
          browser_context->GetRequestContextForRenderProcess(process_id),
          node->current_url(),
          got_cookies_callback);

      for (size_t i = 0; i < node->child_count(); ++i)
        queue.push(node->child_at(i));
    }
  }

 private:
  void GotCookiesForURL(const net::CookieList& cookie_list) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    for (const net::CanonicalCookie& cookie : cookie_list) {
      std::string key = base::StringPrintf(
          "%s::%s::%s::%d",
          cookie.Name().c_str(),
          cookie.Domain().c_str(),
          cookie.Path().c_str(),
          cookie.IsSecure());
      cookies_[key] = cookie;
    }
    --request_count_;
    if (!request_count_) {
      net::CookieList list;
      list.reserve(cookies_.size());
      for (const auto& pair : cookies_)
        list.push_back(pair.second);
      callback_.Run(list);
      delete this;
    }
  }

  CookieListCallback callback_;
  int request_count_;
  base::hash_map<std::string, net::CanonicalCookie> cookies_;
};

}  // namespace

typedef DevToolsProtocolClient::Response Response;

NetworkHandler::NetworkHandler() : host_(nullptr), weak_factory_(this) {
}

NetworkHandler::~NetworkHandler() {
}

void NetworkHandler::SetRenderFrameHost(RenderFrameHostImpl* host) {
  host_ = host;
}

void NetworkHandler::SetClient(scoped_ptr<Client> client) {
  client_.swap(client);
}

Response NetworkHandler::ClearBrowserCache() {
  if (host_)
    GetContentClient()->browser()->ClearCache(host_);
  return Response::OK();
}

Response NetworkHandler::ClearBrowserCookies() {
  if (host_)
    GetContentClient()->browser()->ClearCookies(host_);
  return Response::OK();
}

Response NetworkHandler::GetCookies(DevToolsCommandId command_id) {
  if (!host_)
    return Response::InternalError("Could not connect to view");
  new GetCookiesCommand(
      host_,
      base::Bind(&NetworkHandler::SendGetCookiesResponse,
                 weak_factory_.GetWeakPtr(),
                 command_id));
  return Response::OK();
}

void NetworkHandler::SendGetCookiesResponse(
    DevToolsCommandId command_id,
    const net::CookieList& cookie_list) {
  std::vector<scoped_refptr<Cookie>> cookies;
  for (size_t i = 0; i < cookie_list.size(); ++i) {
    const net::CanonicalCookie& cookie = cookie_list[i];
    cookies.push_back(Cookie::Create()
         ->set_name(cookie.Name())
         ->set_value(cookie.Value())
         ->set_domain(cookie.Domain())
         ->set_path(cookie.Path())
         ->set_expires(cookie.ExpiryDate().ToDoubleT() * 1000)
         ->set_size(cookie.Name().length() + cookie.Value().length())
         ->set_http_only(cookie.IsHttpOnly())
         ->set_secure(cookie.IsSecure())
         ->set_session(!cookie.IsPersistent()));
  }
  client_->SendGetCookiesResponse(command_id,
      GetCookiesResponse::Create()->set_cookies(cookies));
}

Response NetworkHandler::DeleteCookie(
    DevToolsCommandId command_id,
    const std::string& cookie_name,
    const std::string& url) {
  if (!host_)
    return Response::InternalError("Could not connect to view");
  BrowserContext* browser_context =
      host_->GetSiteInstance()->GetBrowserContext();
  int process_id = host_->GetProcess()->GetID();
  DeleteCookieOnUI(
      browser_context->GetResourceContext(),
      browser_context->GetRequestContextForRenderProcess(process_id),
      GURL(url),
      cookie_name,
      base::Bind(&NetworkHandler::SendDeleteCookieResponse,
                 weak_factory_.GetWeakPtr(),
                 command_id));
  return Response::OK();
}

void NetworkHandler::SendDeleteCookieResponse(DevToolsCommandId command_id) {
  client_->SendDeleteCookieResponse(command_id,
      DeleteCookieResponse::Create());
}


Response NetworkHandler::CanEmulateNetworkConditions(bool* result) {
  *result = false;
  return Response::OK();
}

Response NetworkHandler::EmulateNetworkConditions(bool offline,
                                                  double latency,
                                                  double download_throughput,
                                                  double upload_throughput) {
  return Response::FallThrough();
}

Response NetworkHandler::GetCertificateDetails(
    int certificate_id,
    scoped_refptr<CertificateDetails>* result) {
  scoped_refptr<net::X509Certificate> cert;
  content::CertStore* cert_store = CertStore::GetInstance();
  cert_store->RetrieveCert(certificate_id, &cert);
  if (!cert.get())
    return Response::InvalidParams("certificateId");

  std::string name(cert->subject().GetDisplayName());
  std::string issuer(cert->issuer().GetDisplayName());
  base::Time valid_from = cert->valid_start();
  base::Time valid_to = cert->valid_expiry();

  std::vector<std::string> dns_names;
  std::vector<std::string> ip_addrs;
  cert->GetSubjectAltName(&dns_names, &ip_addrs);

  *result = CertificateDetails::Create()
                    ->set_subject(CertificateSubject::Create()
                                  ->set_name(name)
                                  ->set_san_dns_names(dns_names)
                                  ->set_san_ip_addresses(ip_addrs))
                    ->set_issuer(issuer)
                    ->set_valid_from(valid_from.ToDoubleT())
                    ->set_valid_to(valid_to.ToDoubleT());
  return Response::OK();
}

Response NetworkHandler::ShowCertificateViewer(int certificate_id) {
  if (!host_)
    return Response::InternalError("Could not connect to view");
  WebContents* web_contents = WebContents::FromRenderFrameHost(host_);
  web_contents->GetDelegate()->ShowCertificateViewerInDevTools(
      web_contents, certificate_id);
  return Response::OK();
}

}  // namespace network
}  // namespace devtools
}  // namespace content
