// Copyright 2013 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/renderer_host/pepper/pepper_file_system_browser_host.h"

#include "base/bind.h"
#include "base/callback.h"
#include "content/browser/renderer_host/pepper/pepper_file_io_host.h"
#include "content/browser/renderer_host/pepper/quota_reservation.h"
#include "content/common/pepper_file_util.h"
#include "content/public/browser/browser_ppapi_host.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/pepper_plugin_info.h"
#include "net/base/mime_util.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/host/dispatch_host_message.h"
#include "ppapi/host/ppapi_host.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/shared_impl/file_system_util.h"
#include "ppapi/shared_impl/file_type_conversion.h"
#include "storage/browser/fileapi/file_system_operation_runner.h"
#include "storage/browser/fileapi/isolated_context.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/common/fileapi/file_system_util.h"

namespace content {

namespace {

// This is the minimum amount of quota we reserve per file system.
const int64_t kMinimumQuotaReservationSize = 1024 * 1024;  // 1 MB

scoped_refptr<storage::FileSystemContext> GetFileSystemContextFromRenderId(
    int render_process_id) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  RenderProcessHost* host = RenderProcessHost::FromID(render_process_id);
  if (!host)
    return nullptr;
  StoragePartition* storage_partition = host->GetStoragePartition();
  if (!storage_partition)
    return nullptr;
  return storage_partition->GetFileSystemContext();
}

}  // namespace

PepperFileSystemBrowserHost::PepperFileSystemBrowserHost(BrowserPpapiHost* host,
                                                         PP_Instance instance,
                                                         PP_Resource resource,
                                                         PP_FileSystemType type)
    : ResourceHost(host->GetPpapiHost(), instance, resource),
      browser_ppapi_host_(host),
      type_(type),
      called_open_(false),
      opened_(false),
      file_system_context_(nullptr),
      reserved_quota_(0),
      reserving_quota_(false),
      weak_factory_(this) {}

PepperFileSystemBrowserHost::~PepperFileSystemBrowserHost() {
  // If |files_| is not empty, the plugin failed to close some files. It must
  // have crashed.
  if (!files_.empty()) {
    file_system_context_->default_file_task_runner()->PostTask(
        FROM_HERE,
        base::BindOnce(&QuotaReservation::OnClientCrash, quota_reservation_));
  }

  // All FileRefs and FileIOs that reference us must have been destroyed. Cancel
  // all pending file system operations.
  if (file_system_operation_runner_)
    file_system_operation_runner_->Shutdown();
}

void PepperFileSystemBrowserHost::OpenExisting(const GURL& root_url,
                                               const base::Closure& callback) {
  root_url_ = root_url;
  int render_process_id = 0;
  int unused;
  if (!browser_ppapi_host_->GetRenderFrameIDsForInstance(
          pp_instance(), &render_process_id, &unused)) {
    NOTREACHED();
  }
  called_open_ = true;
  // Get the file system context asynchronously, and then complete the Open
  // operation by calling |callback|.
  BrowserThread::PostTaskAndReplyWithResult(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(&GetFileSystemContextFromRenderId, render_process_id),
      base::Bind(&PepperFileSystemBrowserHost::OpenExistingFileSystem,
                 weak_factory_.GetWeakPtr(),
                 callback));
}

int32_t PepperFileSystemBrowserHost::OnResourceMessageReceived(
    const IPC::Message& msg,
    ppapi::host::HostMessageContext* context) {
  PPAPI_BEGIN_MESSAGE_MAP(PepperFileSystemBrowserHost, msg)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileSystem_Open,
                                      OnHostMsgOpen)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(
        PpapiHostMsg_FileSystem_InitIsolatedFileSystem,
        OnHostMsgInitIsolatedFileSystem)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileSystem_ReserveQuota,
                                      OnHostMsgReserveQuota)
  PPAPI_END_MESSAGE_MAP()
  return PP_ERROR_FAILED;
}

bool PepperFileSystemBrowserHost::IsFileSystemHost() { return true; }

void PepperFileSystemBrowserHost::OpenQuotaFile(
    PepperFileIOHost* file_io_host,
    const storage::FileSystemURL& url,
    const OpenQuotaFileCallback& callback) {
  int32_t id = file_io_host->pp_resource();
  std::pair<FileMap::iterator, bool> insert_result =
      files_.insert(std::make_pair(id, file_io_host));
  if (insert_result.second) {
    base::PostTaskAndReplyWithResult(
        file_system_context_->default_file_task_runner(),
        FROM_HERE,
        base::Bind(&QuotaReservation::OpenFile, quota_reservation_, id, url),
        callback);
  } else {
    NOTREACHED();
  }
}

void PepperFileSystemBrowserHost::CloseQuotaFile(
    PepperFileIOHost* file_io_host,
    const ppapi::FileGrowth& file_growth) {
  int32_t id = file_io_host->pp_resource();
  FileMap::iterator it = files_.find(id);
  if (it != files_.end()) {
    files_.erase(it);
  } else {
    NOTREACHED();
    return;
  }

  file_system_context_->default_file_task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&QuotaReservation::CloseFile,
                                quota_reservation_, id, file_growth));
}

int32_t PepperFileSystemBrowserHost::OnHostMsgOpen(
    ppapi::host::HostMessageContext* context,
    int64_t /* unused */) {
  // TODO(raymes): The file system size is now unused by FileSystemDispatcher.
  // Figure out why. Why is the file system size signed?

  // Not allow multiple opens.
  if (called_open_)
    return PP_ERROR_INPROGRESS;
  called_open_ = true;

  storage::FileSystemType file_system_type =
      PepperFileSystemTypeToFileSystemType(type_);
  if (file_system_type == storage::kFileSystemTypeUnknown)
    return PP_ERROR_FAILED;

  int render_process_id = 0;
  int unused;
  if (!browser_ppapi_host_->GetRenderFrameIDsForInstance(
          pp_instance(), &render_process_id, &unused)) {
    return PP_ERROR_FAILED;
  }

  BrowserThread::PostTaskAndReplyWithResult(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(&GetFileSystemContextFromRenderId, render_process_id),
      base::Bind(&PepperFileSystemBrowserHost::OpenFileSystem,
                 weak_factory_.GetWeakPtr(),
                 context->MakeReplyMessageContext(),
                 file_system_type));
  return PP_OK_COMPLETIONPENDING;
}

void PepperFileSystemBrowserHost::OpenExistingFileSystem(
    const base::Closure& callback,
    scoped_refptr<storage::FileSystemContext> file_system_context) {
  if (file_system_context.get()) {
    opened_ = true;
  } else {
    // If there is no file system context, we log a warning and continue with an
    // invalid resource (which will produce errors when used), since we have no
    // way to communicate the error to the caller.
    LOG(WARNING) << "Could not retrieve file system context.";
  }
  SetFileSystemContext(file_system_context);

  if (ShouldCreateQuotaReservation())
    CreateQuotaReservation(callback);
  else
    callback.Run();
}

void PepperFileSystemBrowserHost::OpenFileSystem(
    ppapi::host::ReplyMessageContext reply_context,
    storage::FileSystemType file_system_type,
    scoped_refptr<storage::FileSystemContext> file_system_context) {
  if (!file_system_context.get()) {
    OpenFileSystemComplete(
        reply_context, GURL(), std::string(), base::File::FILE_ERROR_FAILED);
    return;
  }

  SetFileSystemContext(file_system_context);

  GURL origin =
      browser_ppapi_host_->GetDocumentURLForInstance(pp_instance()).GetOrigin();
  file_system_context_->OpenFileSystem(
      origin, file_system_type, storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
      base::BindOnce(&PepperFileSystemBrowserHost::OpenFileSystemComplete,
                     weak_factory_.GetWeakPtr(), reply_context));
}

void PepperFileSystemBrowserHost::OpenFileSystemComplete(
    ppapi::host::ReplyMessageContext reply_context,
    const GURL& root,
    const std::string& /* unused */,
    base::File::Error error) {
  int32_t pp_error = ppapi::FileErrorToPepperError(error);
  if (pp_error == PP_OK) {
    opened_ = true;
    root_url_ = root;

    if (ShouldCreateQuotaReservation()) {
      CreateQuotaReservation(
          base::Bind(&PepperFileSystemBrowserHost::SendReplyForFileSystem,
                     weak_factory_.GetWeakPtr(),
                     reply_context,
                     static_cast<int32_t>(PP_OK)));
      return;
    }
  }
  SendReplyForFileSystem(reply_context, pp_error);
}

void PepperFileSystemBrowserHost::OpenIsolatedFileSystem(
    ppapi::host::ReplyMessageContext reply_context,
    const std::string& fsid,
    PP_IsolatedFileSystemType_Private type,
    scoped_refptr<storage::FileSystemContext> file_system_context) {
  if (!file_system_context.get()) {
    SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_FAILED);
    return;
  }
  SetFileSystemContext(file_system_context);

  root_url_ = GURL(storage::GetIsolatedFileSystemRootURIString(
      browser_ppapi_host_->GetDocumentURLForInstance(pp_instance()).GetOrigin(),
      fsid,
      ppapi::IsolatedFileSystemTypeToRootName(type)));
  if (!root_url_.is_valid()) {
    SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_FAILED);
    return;
  }

  switch (type) {
    case PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_CRX:
      opened_ = true;
      SendReplyForIsolatedFileSystem(reply_context, fsid, PP_OK);
      return;
    case PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_PLUGINPRIVATE:
      OpenPluginPrivateFileSystem(reply_context, fsid, file_system_context_);
      return;
    default:
      NOTREACHED();
      SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_BADARGUMENT);
      return;
  }
}

void PepperFileSystemBrowserHost::OpenPluginPrivateFileSystem(
    ppapi::host::ReplyMessageContext reply_context,
    const std::string& fsid,
    scoped_refptr<storage::FileSystemContext> file_system_context) {
  GURL origin =
      browser_ppapi_host_->GetDocumentURLForInstance(pp_instance()).GetOrigin();
  if (!origin.is_valid()) {
    SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_FAILED);
    return;
  }

  const std::string& plugin_id = GeneratePluginId(GetPluginMimeType());
  if (plugin_id.empty()) {
    SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_BADARGUMENT);
    return;
  }

  file_system_context->OpenPluginPrivateFileSystem(
      origin, storage::kFileSystemTypePluginPrivate, fsid, plugin_id,
      storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
      base::BindOnce(
          &PepperFileSystemBrowserHost::OpenPluginPrivateFileSystemComplete,
          weak_factory_.GetWeakPtr(), reply_context, fsid));
}

void PepperFileSystemBrowserHost::OpenPluginPrivateFileSystemComplete(
    ppapi::host::ReplyMessageContext reply_context,
    const std::string& fsid,
    base::File::Error error) {
  int32_t pp_error = ppapi::FileErrorToPepperError(error);
  if (pp_error == PP_OK)
    opened_ = true;
  SendReplyForIsolatedFileSystem(reply_context, fsid, pp_error);
}

int32_t PepperFileSystemBrowserHost::OnHostMsgInitIsolatedFileSystem(
    ppapi::host::HostMessageContext* context,
    const std::string& fsid,
    PP_IsolatedFileSystemType_Private type) {
  // Do not allow multiple opens.
  if (called_open_)
    return PP_ERROR_INPROGRESS;
  called_open_ = true;

  // Do a sanity check.
  if (!storage::ValidateIsolatedFileSystemId(fsid))
    return PP_ERROR_BADARGUMENT;

  int render_process_id = 0;
  int unused;
  if (!browser_ppapi_host_->GetRenderFrameIDsForInstance(
          pp_instance(), &render_process_id, &unused)) {
    storage::IsolatedContext::GetInstance()->RevokeFileSystem(fsid);
    return PP_ERROR_FAILED;
  }

  root_url_ = GURL(storage::GetIsolatedFileSystemRootURIString(
      browser_ppapi_host_->GetDocumentURLForInstance(pp_instance()).GetOrigin(),
      fsid,
      ppapi::IsolatedFileSystemTypeToRootName(type)));

  BrowserThread::PostTaskAndReplyWithResult(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(&GetFileSystemContextFromRenderId, render_process_id),
      base::Bind(&PepperFileSystemBrowserHost::OpenIsolatedFileSystem,
                 weak_factory_.GetWeakPtr(),
                 context->MakeReplyMessageContext(),
                 fsid,
                 type));
  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperFileSystemBrowserHost::OnHostMsgReserveQuota(
    ppapi::host::HostMessageContext* context,
    int64_t amount,
    const ppapi::FileGrowthMap& file_growths) {
  DCHECK(ChecksQuota());
  DCHECK_GT(amount, 0);

  if (reserving_quota_)
    return PP_ERROR_INPROGRESS;
  reserving_quota_ = true;

  int64_t reservation_amount =
      std::max<int64_t>(kMinimumQuotaReservationSize, amount);
  file_system_context_->default_file_task_runner()->PostTask(
      FROM_HERE,
      base::BindOnce(&QuotaReservation::ReserveQuota, quota_reservation_,
                     reservation_amount, file_growths,
                     base::Bind(&PepperFileSystemBrowserHost::GotReservedQuota,
                                weak_factory_.GetWeakPtr(),
                                context->MakeReplyMessageContext())));

  return PP_OK_COMPLETIONPENDING;
}

void PepperFileSystemBrowserHost::SendReplyForFileSystem(
    ppapi::host::ReplyMessageContext reply_context,
    int32_t pp_error) {
  reply_context.params.set_result(pp_error);
  host()->SendReply(reply_context, PpapiPluginMsg_FileSystem_OpenReply());
}

void PepperFileSystemBrowserHost::SendReplyForIsolatedFileSystem(
    ppapi::host::ReplyMessageContext reply_context,
    const std::string& fsid,
    int32_t error) {
  if (error != PP_OK)
    storage::IsolatedContext::GetInstance()->RevokeFileSystem(fsid);
  reply_context.params.set_result(error);
  host()->SendReply(reply_context,
                    PpapiPluginMsg_FileSystem_InitIsolatedFileSystemReply());
}

void PepperFileSystemBrowserHost::SetFileSystemContext(
    scoped_refptr<storage::FileSystemContext> file_system_context) {
  file_system_context_ = file_system_context;
  if (type_ != PP_FILESYSTEMTYPE_EXTERNAL || root_url_.is_valid()) {
    file_system_operation_runner_ =
        file_system_context_->CreateFileSystemOperationRunner();
  }
}

bool PepperFileSystemBrowserHost::ShouldCreateQuotaReservation() const {
  // Some file system types don't have quota.
  if (!ppapi::FileSystemTypeHasQuota(type_))
    return false;

  // For file system types with quota, some origins have unlimited storage.
  storage::QuotaManagerProxy* quota_manager_proxy =
      file_system_context_->quota_manager_proxy();
  CHECK(quota_manager_proxy);
  CHECK(quota_manager_proxy->quota_manager());
  storage::FileSystemType file_system_type =
      PepperFileSystemTypeToFileSystemType(type_);
  return !quota_manager_proxy->quota_manager()->IsStorageUnlimited(
      root_url_.GetOrigin(),
      storage::FileSystemTypeToQuotaStorageType(file_system_type));
}

void PepperFileSystemBrowserHost::CreateQuotaReservation(
    const base::Closure& callback) {
  DCHECK(root_url_.is_valid());
  base::PostTaskAndReplyWithResult(
      file_system_context_->default_file_task_runner(),
      FROM_HERE,
      base::Bind(&QuotaReservation::Create,
                 file_system_context_,
                 root_url_.GetOrigin(),
                 PepperFileSystemTypeToFileSystemType(type_)),
      base::Bind(&PepperFileSystemBrowserHost::GotQuotaReservation,
                 weak_factory_.GetWeakPtr(),
                 callback));
}

void PepperFileSystemBrowserHost::GotQuotaReservation(
    const base::Closure& callback,
    scoped_refptr<QuotaReservation> quota_reservation) {
  quota_reservation_ = quota_reservation;
  callback.Run();
}

void PepperFileSystemBrowserHost::GotReservedQuota(
    ppapi::host::ReplyMessageContext reply_context,
    int64_t amount,
    const ppapi::FileSizeMap& file_sizes) {
  DCHECK(reserving_quota_);
  reserving_quota_ = false;
  reserved_quota_ = amount;

  reply_context.params.set_result(PP_OK);
  host()->SendReply(
      reply_context,
      PpapiPluginMsg_FileSystem_ReserveQuotaReply(amount, file_sizes));
}

std::string PepperFileSystemBrowserHost::GetPluginMimeType() const {
  base::FilePath plugin_path = browser_ppapi_host_->GetPluginPath();
  PepperPluginInfo* info =
      PluginService::GetInstance()->GetRegisteredPpapiPluginInfo(plugin_path);
  if (!info || info->mime_types.empty())
    return std::string();
  // Use the first element in |info->mime_types| even if several elements exist.
  return info->mime_types[0].mime_type;
}

std::string PepperFileSystemBrowserHost::GeneratePluginId(
    const std::string& mime_type) const {
  // TODO(nhiroki): This function is very specialized for specific plugins (MIME
  // types).  If we bring this API to stable, we might have to make it more
  // general.

  std::string top_level_type;
  std::string subtype;
  if (!net::ParseMimeTypeWithoutParameter(
          mime_type, &top_level_type, &subtype) ||
      !net::IsValidTopLevelMimeType(top_level_type))
    return std::string();

  // Replace a slash used for type/subtype separator with an underscore.
  std::string output = top_level_type + "_" + subtype;

  // Verify |output| contains only alphabets, digits, or "._-".
  for (std::string::const_iterator it = output.begin(); it != output.end();
       ++it) {
    if (!base::IsAsciiAlpha(*it) && !base::IsAsciiDigit(*it) &&
        *it != '.' && *it != '_' && *it != '-') {
      LOG(WARNING) << "Failed to generate a plugin id.";
      return std::string();
    }
  }
  return output;
}

}  // namespace content
