chromium/chrome/browser/extensions/api/file_browser_handler/file_browser_handler_flow_lacros.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/api/file_browser_handler/file_browser_handler_flow_lacros.h"

#include <memory>
#include <set>
#include <utility>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/values.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/api/file_handlers/app_file_handler_util.h"
#include "extensions/browser/api/file_handlers/directory_util.h"
#include "extensions/browser/entry_info.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/granted_file_entry.h"
#include "extensions/browser/lazy_context_id.h"
#include "extensions/browser/lazy_context_task_queue.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handlers/background_info.h"

namespace extensions {

namespace {

using extensions::Extension;
using extensions::GrantedFileEntry;

using extensions::app_file_handler_util::CreateEntryInfos;
using extensions::app_file_handler_util::CreateFileEntry;
using extensions::app_file_handler_util::PrepareFilesForWritableApp;

// This class prepares file entries for fileBrowserHandler, and dispatches the
// data to JavaScript code of the specified |extension|..
class FileBrowserHandlerExecutorFlow {
 public:
  // |done| is called with final results after Execute() runs.
  FileBrowserHandlerExecutorFlow(FileBrowserHandlerFlowFinishedCallback done,
                                 Profile* profile,
                                 const Extension* extension,
                                 const std::string& action_id,
                                 std::vector<base::FilePath>&& entry_paths,
                                 std::vector<std::string>&& mime_types);

  FileBrowserHandlerExecutorFlow(const FileBrowserHandlerExecutorFlow&) =
      delete;
  FileBrowserHandlerExecutorFlow& operator=(
      const FileBrowserHandlerExecutorFlow&) = delete;

  // Main entry point to start the execution flow.
  void Execute();

 private:
  // This object is responsible for deleting itself.
  virtual ~FileBrowserHandlerExecutorFlow();

  void OnAreDirectoriesCollected(
      std::unique_ptr<std::set<base::FilePath>> directory_paths);
  void OnFilesValid(std::unique_ptr<std::set<base::FilePath>> directory_paths);
  void OnFilesInvalid(const base::FilePath& /*error_path*/);
  void PrepareToLaunch();
  void GrantAccessToFilesAndLaunch(
      std::unique_ptr<extensions::LazyContextTaskQueue::ContextInfo>
          context_info);

  // All flows must converge here, which deallocates the instance.
  void Finish(bool success);

  FileBrowserHandlerFlowFinishedCallback done_;

  raw_ptr<Profile> profile_;
  scoped_refptr<const Extension> extension_;

  // Inputs owned by the class.
  const std::string action_id_;
  const std::vector<base::FilePath> entry_paths_;
  const std::vector<std::string> mime_types_;

  extensions::app_file_handler_util::IsDirectoryCollector
      is_directory_collector_;

  std::vector<extensions::EntryInfo> entries_;
};

FileBrowserHandlerExecutorFlow::FileBrowserHandlerExecutorFlow(
    FileBrowserHandlerFlowFinishedCallback done,
    Profile* profile,
    const Extension* extension,
    const std::string& action_id,
    std::vector<base::FilePath>&& entry_paths,
    std::vector<std::string>&& mime_types)
    : done_(std::move(done)),
      profile_(profile),
      extension_(extension),
      action_id_(action_id),      // Copies.
      entry_paths_(entry_paths),  // Takes ownership.
      mime_types_(mime_types),    // Takes ownership.
      is_directory_collector_(profile) {}

FileBrowserHandlerExecutorFlow::~FileBrowserHandlerExecutorFlow() = default;

void FileBrowserHandlerExecutorFlow::Execute() {
  // Forbid calling undeclared handlers.
  if (!FileBrowserHandler::FindForActionId(extension_.get(), action_id_)) {
    Finish(false);  // Action ID not found.
    return;
  }

  // This imposes the assumption that ExecuteFileBrowserHandlerFlow() is invoked
  // by a nontrivial fileBrowserHandler request. Hereafter, if the provided
  // files turn out to be invalid (e.g., due to permission or race conditions
  // involving file changes), it's still possible for the extension to execute
  // with an empty file list.
  if (entry_paths_.empty()) {
    Finish(false);  // File list empty.
    return;
  }

#if DCHECK_IS_ON()
  for (const auto& entry_path : entry_paths_) {
    DCHECK(entry_path.IsAbsolute());
  }
#endif  // DCHECK_IS_ON()

  is_directory_collector_.CollectForEntriesPaths(
      entry_paths_,
      base::BindOnce(&FileBrowserHandlerExecutorFlow::OnAreDirectoriesCollected,
                     base::Unretained(this)));
}

void FileBrowserHandlerExecutorFlow::OnAreDirectoriesCollected(
    std::unique_ptr<std::set<base::FilePath>> directory_paths) {
  const FileBrowserHandler* action =
      FileBrowserHandler::FindForActionId(extension_.get(), action_id_);
  if (action->CanWrite()) {
    std::set<base::FilePath>* const directory_paths_ptr = directory_paths.get();
    PrepareFilesForWritableApp(
        entry_paths_, profile_, *directory_paths_ptr,
        base::BindOnce(&FileBrowserHandlerExecutorFlow::OnFilesValid,
                       base::Unretained(this), std::move(directory_paths)),
        base::BindOnce(&FileBrowserHandlerExecutorFlow::OnFilesInvalid,
                       base::Unretained(this)));
  } else {
    OnFilesValid(std::move(directory_paths));
  }
}

void FileBrowserHandlerExecutorFlow::OnFilesValid(
    std::unique_ptr<std::set<base::FilePath>> directory_paths) {
  entries_ = CreateEntryInfos(entry_paths_, mime_types_, *directory_paths);
  // |entry_paths_| and |mime_types_| should not be used from this point on.
  PrepareToLaunch();
}

void FileBrowserHandlerExecutorFlow::OnFilesInvalid(
    const base::FilePath& /*error_path*/) {
  DCHECK(entries_.empty());
  // |entry_paths_| and |mime_types_| should not be used from this point on.
  PrepareToLaunch();
}

void FileBrowserHandlerExecutorFlow::PrepareToLaunch() {
  // Access needs to be granted to the file for the process associated with
  // the extension. To do this the ExtensionHost is needed. This might not be
  // available, or it might be in the process of being unloaded, in which case
  // the lazy background task queue is used to load the extension and then
  // call back to us.
  const auto context_id =
      extensions::LazyContextId::ForExtension(profile_, extension_.get());
  CHECK(context_id.IsForBackgroundPage());
  extensions::LazyContextTaskQueue* task_queue = context_id.GetTaskQueue();

  if (task_queue->ShouldEnqueueTask(profile_, extension_.get())) {
    task_queue->AddPendingTask(
        context_id,
        base::BindOnce(
            &FileBrowserHandlerExecutorFlow::GrantAccessToFilesAndLaunch,
            base::Unretained(this)));
    return;
  }

  extensions::ProcessManager* manager =
      extensions::ProcessManager::Get(profile_);
  extensions::ExtensionHost* extension_host =
      manager->GetBackgroundHostForExtension(extension_->id());

  if (extension_host) {
    GrantAccessToFilesAndLaunch(
        std::make_unique<extensions::LazyContextTaskQueue::ContextInfo>(
            extension_host));
  } else {
    Finish(false);  // No background page available.
  }
}

void FileBrowserHandlerExecutorFlow::GrantAccessToFilesAndLaunch(
    std::unique_ptr<extensions::LazyContextTaskQueue::ContextInfo>
        context_info) {
  if (!context_info) {
    Finish(false);  // Failed to start app.
    return;
  }

  int handler_pid = context_info->render_process_host->GetID();
  if (handler_pid <= 0) {
    Finish(false);  // No app available.
    return;
  }

  extensions::EventRouter* router = extensions::EventRouter::Get(profile_);
  if (!router) {
    Finish(false);  // Could not send task to app.
    return;
  }

  base::Value::List event_args;
  event_args.Append(action_id_);
  base::Value::Dict details;
  // Creates file_entries, which will be converted into FileEntry or
  // DirectoryEntry instances by fileBrowserHandler JS massager.
  base::Value::List file_entries;
  file_entries.reserve(entries_.size());
  for (const auto& entry : entries_) {
    GrantedFileEntry granted_file_entry = CreateFileEntry(
        profile_, extension_.get(), context_info->render_process_host->GetID(),
        entry.path, entry.is_directory);
    base::Value::Dict item;
    item.Set("fileSystemId", granted_file_entry.filesystem_id);
    item.Set("baseName", granted_file_entry.registered_name);
    item.Set("mimeType", entry.mime_type);
    item.Set("entryId", granted_file_entry.id);
    item.Set("isDirectory", entry.is_directory);
    file_entries.Append(std::move(item));
  }
  details.Set("entries", std::move(file_entries));
  event_args.Append(std::move(details));

  auto event = std::make_unique<extensions::Event>(
      extensions::events::FILE_BROWSER_HANDLER_ON_EXECUTE,
      "fileBrowserHandler.onExecute", std::move(event_args), profile_);
  router->DispatchEventToExtension(extension_->id(), std::move(event));

  Finish(true);  // Success.
}

void FileBrowserHandlerExecutorFlow::Finish(bool success) {
  if (done_) {
    std::move(done_).Run(success);
  }

  delete this;
}

}  // namespace

void ExecuteFileBrowserHandlerFlow(
    Profile* profile,
    const Extension* extension,
    const std::string& action_id,
    std::vector<base::FilePath>&& entry_paths,
    std::vector<std::string>&& mime_types,
    FileBrowserHandlerFlowFinishedCallback done) {
  DCHECK_EQ(entry_paths.size(), mime_types.size());

  // The executor object will self-delete on completion.
  (new FileBrowserHandlerExecutorFlow(std::move(done), profile, extension,
                                      action_id, std::move(entry_paths),
                                      std::move(mime_types)))
      ->Execute();
}

}  // namespace extensions