// 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/renderer_host/pepper/pepper_flash_file_message_filter.h"

#include <utility>

#include "base/bind.h"
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/task_scheduler/post_task.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/renderer_host/pepper/pepper_security_helper.h"
#include "content/public/browser/browser_ppapi_host.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_constants.h"
#include "ipc/ipc_platform_file.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/host/dispatch_host_message.h"
#include "ppapi/host/host_message_context.h"
#include "ppapi/host/ppapi_host.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/shared_impl/file_path.h"
#include "ppapi/shared_impl/file_type_conversion.h"

namespace content {

namespace {

bool CanRead(int process_id, const base::FilePath& path) {
  return ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(process_id,
                                                                    path);
}

bool CanCreateReadWrite(int process_id, const base::FilePath& path) {
  return ChildProcessSecurityPolicyImpl::GetInstance()->CanCreateReadWriteFile(
      process_id, path);
}

}  // namespace

PepperFlashFileMessageFilter::PepperFlashFileMessageFilter(
    PP_Instance instance,
    BrowserPpapiHost* host)
    : plugin_process_(host->GetPluginProcess().Duplicate()) {
  int unused;
  host->GetRenderFrameIDsForInstance(instance, &render_process_id_, &unused);
  base::FilePath profile_data_directory = host->GetProfileDataDirectory();
  std::string plugin_name = host->GetPluginName();

  if (profile_data_directory.empty() || plugin_name.empty()) {
    // These are used to construct the path. If they are not set it means we
    // will construct a bad path and could provide access to the wrong files.
    // In this case, |plugin_data_directory_| will remain unset and
    // |ValidateAndConvertPepperFilePath| will fail.
    DLOG(ERROR) << "Pepper Flash file access attemped with no data path";
  } else {
    plugin_data_directory_ = GetDataDirName(profile_data_directory).Append(
        base::FilePath::FromUTF8Unsafe(plugin_name));
  }
}

PepperFlashFileMessageFilter::~PepperFlashFileMessageFilter() {}

// static
base::FilePath PepperFlashFileMessageFilter::GetDataDirName(
    const base::FilePath& profile_path) {
  return profile_path.Append(kPepperDataDirname);
}

scoped_refptr<base::TaskRunner>
PepperFlashFileMessageFilter::OverrideTaskRunnerForMessage(
    const IPC::Message& msg) {
  // The blocking pool provides a pool of threads to run file
  // operations, instead of a single thread which might require
  // queuing time.  Since these messages are synchronous as sent from
  // the plugin, the sending thread cannot send a new message until
  // this one returns, so there is no need to sequence tasks here.  If
  // the plugin has multiple threads, it cannot make assumptions about
  // ordering of IPC message sends, so it cannot make assumptions
  // about ordering of operations caused by those IPC messages.
  return scoped_refptr<base::TaskRunner>(
      base::CreateSequencedTaskRunnerWithTraits(
          {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
           base::TaskShutdownBehavior::BLOCK_SHUTDOWN}));
}

int32_t PepperFlashFileMessageFilter::OnResourceMessageReceived(
    const IPC::Message& msg,
    ppapi::host::HostMessageContext* context) {
  PPAPI_BEGIN_MESSAGE_MAP(PepperFlashFileMessageFilter, msg)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashFile_OpenFile,
                                      OnOpenFile)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashFile_RenameFile,
                                      OnRenameFile)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashFile_DeleteFileOrDir,
                                      OnDeleteFileOrDir)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashFile_CreateDir,
                                      OnCreateDir)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashFile_QueryFile,
                                      OnQueryFile)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FlashFile_GetDirContents,
                                      OnGetDirContents)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(
        PpapiHostMsg_FlashFile_CreateTemporaryFile, OnCreateTemporaryFile)
  PPAPI_END_MESSAGE_MAP()
  return PP_ERROR_FAILED;
}

int32_t PepperFlashFileMessageFilter::OnOpenFile(
    ppapi::host::HostMessageContext* context,
    const ppapi::PepperFilePath& path,
    int pp_open_flags) {
  base::FilePath full_path = ValidateAndConvertPepperFilePath(
      path, base::Bind(&CanOpenWithPepperFlags, pp_open_flags));
  if (full_path.empty()) {
    return ppapi::FileErrorToPepperError(base::File::FILE_ERROR_ACCESS_DENIED);
  }

  int platform_file_flags = 0;
  if (!ppapi::PepperFileOpenFlagsToPlatformFileFlags(pp_open_flags,
                                                     &platform_file_flags)) {
    return base::File::FILE_ERROR_FAILED;
  }

  base::File file(full_path, platform_file_flags);
  if (!file.IsValid()) {
    return ppapi::FileErrorToPepperError(file.error_details());
  }

  // Make sure we didn't try to open a directory: directory fd shouldn't be
  // passed to untrusted processes because they open security holes.
  base::File::Info info;
  if (!file.GetInfo(&info) || info.is_directory) {
    // When in doubt, throw it out.
    return ppapi::FileErrorToPepperError(base::File::FILE_ERROR_ACCESS_DENIED);
  }

  IPC::PlatformFileForTransit transit_file =
      IPC::TakePlatformFileForTransit(std::move(file));
  ppapi::host::ReplyMessageContext reply_context =
      context->MakeReplyMessageContext();
  reply_context.params.AppendHandle(ppapi::proxy::SerializedHandle(
      ppapi::proxy::SerializedHandle::FILE, transit_file));
  SendReply(reply_context, IPC::Message());
  return PP_OK_COMPLETIONPENDING;
}

int32_t PepperFlashFileMessageFilter::OnRenameFile(
    ppapi::host::HostMessageContext* context,
    const ppapi::PepperFilePath& from_path,
    const ppapi::PepperFilePath& to_path) {
  base::FilePath from_full_path = ValidateAndConvertPepperFilePath(
      from_path, base::Bind(&CanCreateReadWrite));
  base::FilePath to_full_path = ValidateAndConvertPepperFilePath(
      to_path, base::Bind(&CanCreateReadWrite));
  if (from_full_path.empty() || to_full_path.empty()) {
    return ppapi::FileErrorToPepperError(base::File::FILE_ERROR_ACCESS_DENIED);
  }

  bool result = base::Move(from_full_path, to_full_path);
  return ppapi::FileErrorToPepperError(
      result ? base::File::FILE_OK : base::File::FILE_ERROR_ACCESS_DENIED);
}

int32_t PepperFlashFileMessageFilter::OnDeleteFileOrDir(
    ppapi::host::HostMessageContext* context,
    const ppapi::PepperFilePath& path,
    bool recursive) {
  base::FilePath full_path =
      ValidateAndConvertPepperFilePath(path, base::Bind(&CanCreateReadWrite));
  if (full_path.empty()) {
    return ppapi::FileErrorToPepperError(base::File::FILE_ERROR_ACCESS_DENIED);
  }

  bool result = base::DeleteFile(full_path, recursive);
  return ppapi::FileErrorToPepperError(
      result ? base::File::FILE_OK : base::File::FILE_ERROR_ACCESS_DENIED);
}
int32_t PepperFlashFileMessageFilter::OnCreateDir(
    ppapi::host::HostMessageContext* context,
    const ppapi::PepperFilePath& path) {
  base::FilePath full_path =
      ValidateAndConvertPepperFilePath(path, base::Bind(&CanCreateReadWrite));
  if (full_path.empty()) {
    return ppapi::FileErrorToPepperError(base::File::FILE_ERROR_ACCESS_DENIED);
  }

  bool result = base::CreateDirectory(full_path);
  return ppapi::FileErrorToPepperError(
      result ? base::File::FILE_OK : base::File::FILE_ERROR_ACCESS_DENIED);
}

int32_t PepperFlashFileMessageFilter::OnQueryFile(
    ppapi::host::HostMessageContext* context,
    const ppapi::PepperFilePath& path) {
  base::FilePath full_path =
      ValidateAndConvertPepperFilePath(path, base::Bind(&CanRead));
  if (full_path.empty()) {
    return ppapi::FileErrorToPepperError(base::File::FILE_ERROR_ACCESS_DENIED);
  }

  base::File::Info info;
  bool result = base::GetFileInfo(full_path, &info);
  context->reply_msg = PpapiPluginMsg_FlashFile_QueryFileReply(info);
  return ppapi::FileErrorToPepperError(
      result ? base::File::FILE_OK : base::File::FILE_ERROR_ACCESS_DENIED);
}

int32_t PepperFlashFileMessageFilter::OnGetDirContents(
    ppapi::host::HostMessageContext* context,
    const ppapi::PepperFilePath& path) {
  base::FilePath full_path =
      ValidateAndConvertPepperFilePath(path, base::Bind(&CanRead));
  if (full_path.empty()) {
    return ppapi::FileErrorToPepperError(base::File::FILE_ERROR_ACCESS_DENIED);
  }

  ppapi::DirContents contents;
  base::FileEnumerator enumerator(full_path,
                                  false,
                                  base::FileEnumerator::FILES |
                                      base::FileEnumerator::DIRECTORIES |
                                      base::FileEnumerator::INCLUDE_DOT_DOT);

  while (!enumerator.Next().empty()) {
    base::FileEnumerator::FileInfo info = enumerator.GetInfo();
    ppapi::DirEntry entry = {info.GetName(), info.IsDirectory()};
    contents.push_back(entry);
  }

  context->reply_msg = PpapiPluginMsg_FlashFile_GetDirContentsReply(contents);
  return PP_OK;
}

int32_t PepperFlashFileMessageFilter::OnCreateTemporaryFile(
    ppapi::host::HostMessageContext* context) {
  ppapi::PepperFilePath dir_path(ppapi::PepperFilePath::DOMAIN_MODULE_LOCAL,
                                 base::FilePath());
  base::FilePath validated_dir_path = ValidateAndConvertPepperFilePath(
      dir_path, base::Bind(&CanCreateReadWrite));
  if (validated_dir_path.empty() ||
      (!base::DirectoryExists(validated_dir_path) &&
       !base::CreateDirectory(validated_dir_path))) {
    return ppapi::FileErrorToPepperError(base::File::FILE_ERROR_ACCESS_DENIED);
  }

  base::FilePath file_path;
  if (!base::CreateTemporaryFileInDir(validated_dir_path, &file_path)) {
    return ppapi::FileErrorToPepperError(base::File::FILE_ERROR_FAILED);
  }

  base::File file(file_path,
                  base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_READ |
                      base::File::FLAG_WRITE | base::File::FLAG_TEMPORARY |
                      base::File::FLAG_DELETE_ON_CLOSE);

  if (!file.IsValid())
    return ppapi::FileErrorToPepperError(file.error_details());

  IPC::PlatformFileForTransit transit_file =
      IPC::TakePlatformFileForTransit(std::move(file));
  ppapi::host::ReplyMessageContext reply_context =
      context->MakeReplyMessageContext();
  reply_context.params.AppendHandle(ppapi::proxy::SerializedHandle(
      ppapi::proxy::SerializedHandle::FILE, transit_file));
  SendReply(reply_context, IPC::Message());
  return PP_OK_COMPLETIONPENDING;
}

base::FilePath PepperFlashFileMessageFilter::ValidateAndConvertPepperFilePath(
    const ppapi::PepperFilePath& pepper_path,
    const CheckPermissionsCallback& check_permissions_callback) const {
  base::FilePath file_path;  // Empty path returned on error.
  switch (pepper_path.domain()) {
    case ppapi::PepperFilePath::DOMAIN_ABSOLUTE:
      if (pepper_path.path().IsAbsolute() &&
          check_permissions_callback.Run(render_process_id_,
                                         pepper_path.path()))
        file_path = pepper_path.path();
      break;
    case ppapi::PepperFilePath::DOMAIN_MODULE_LOCAL:
      // This filter provides the module name portion of the path to prevent
      // plugins from accessing each other's data.
      if (!plugin_data_directory_.empty() && !pepper_path.path().IsAbsolute() &&
          !pepper_path.path().ReferencesParent())
        file_path = plugin_data_directory_.Append(pepper_path.path());
      break;
    default:
      NOTREACHED();
      break;
  }
  return file_path;
}

}  // namespace content
